rubybreaker 0.0.7 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.md +6 -6
- data/TUTORIAL.md +64 -34
- data/VERSION +1 -1
- data/lib/rubybreaker/debug/error.rb +7 -1
- data/lib/rubybreaker/runtime/monitor.rb +2 -2
- data/lib/rubybreaker/runtime/object_wrapper.rb +32 -1
- data/lib/rubybreaker/runtime/overrides.rb +31 -1
- data/lib/rubybreaker/runtime/type_system.rb +67 -11
- data/test/integrated/tc_checking.rb +35 -5
- data/test/integrated/tc_class_methods.rb +4 -1
- data/test/integrated/tc_original_behavior.rb +68 -0
- data/test/runtime/tc_obj_wrapper.rb +50 -4
- data/test/testtask/tc_testtask.rb +1 -1
- data/test/ts_integrated.rb +3 -1
- data/webpage/images/title.png +0 -0
- data/webpage/index.html +6 -6
- data/webpage/rdoc/Object.html +5 -1
- data/webpage/rdoc/Rake.html +5 -1
- data/webpage/rdoc/Rake/RubyBreakerTestTask.html +5 -1
- data/webpage/rdoc/RubyBreaker.html +5 -1
- data/webpage/rdoc/RubyBreaker/Breakable.html +5 -1
- data/webpage/rdoc/RubyBreaker/Broken.html +5 -1
- data/webpage/rdoc/RubyBreaker/Context.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors/ArgumentTypeError.html +216 -0
- data/webpage/rdoc/RubyBreaker/Errors/{SubtypeFailure.html → ArityError.html} +7 -3
- data/webpage/rdoc/RubyBreaker/Errors/InternalError.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors/InvalidSubtypeCheck.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors/InvalidTypeConstruction.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors/ReturnTypeError.html +216 -0
- data/webpage/rdoc/RubyBreaker/Errors/TypeError.html +5 -1
- data/webpage/rdoc/RubyBreaker/Errors/UserError.html +5 -1
- data/webpage/rdoc/RubyBreaker/ObjectPosition.html +5 -1
- data/webpage/rdoc/RubyBreaker/Position.html +5 -1
- data/webpage/rdoc/RubyBreaker/RDocSupport.html +5 -1
- data/webpage/rdoc/RubyBreaker/RubyTypeUtils.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime.html +10 -1
- data/webpage/rdoc/RubyBreaker/Runtime/Inspector.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/MethodInfo.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/Monitor.html +7 -3
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorInstaller.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/MonitorSwitch.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/ObjectWrapper.html +37 -2
- data/webpage/rdoc/RubyBreaker/Runtime/Pluggable.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigParser.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSigUnparser.html +5 -1
- data/webpage/rdoc/RubyBreaker/Runtime/TypeSystem.html +103 -16
- data/webpage/rdoc/RubyBreaker/TypeComparer.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/AnyType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/BlockType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/DuckType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/FusionType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodListType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/MethodType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/NilType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/NominalType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/OptionalType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/OrType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/SelfType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/Type.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeDefs/VarLengthType.html +5 -1
- data/webpage/rdoc/RubyBreaker/TypeUnparser.html +5 -1
- data/webpage/rdoc/RubyBreaker/Typing.html +5 -1
- data/webpage/rdoc/RubyBreaker/Util.html +5 -1
- data/webpage/rdoc/Test.html +5 -1
- data/webpage/rdoc/Test/Unit.html +5 -1
- data/webpage/rdoc/created.rid +8 -8
- data/webpage/rdoc/index.html +5 -1
- data/webpage/rdoc/js/search_index.js +1 -1
- data/webpage/rdoc/table_of_contents.html +39 -31
- data/webpage/tutorial.html +69 -39
- metadata +8 -5
data/README.md
CHANGED
@@ -3,11 +3,11 @@
|
|
3
3
|
RubyBreaker is a dynamic type documentation tool written in pure Ruby. It
|
4
4
|
provides the framework for dynamically instrumenting a Ruby program to
|
5
5
|
monitor objects during the execution and document the observed type
|
6
|
-
information. In
|
7
|
-
|
8
|
-
|
9
|
-
RubyBreaker is also an executable Ruby code that
|
10
|
-
subsequent analyses.
|
6
|
+
information. In addition, it can perform early dynamic type checking.
|
7
|
+
In other words, RubyBreaker helps Ruby programs "break" out of obscurities
|
8
|
+
and convolutions by documenting the type information. The type
|
9
|
+
documentation generated by RubyBreaker is also an executable Ruby code that
|
10
|
+
can be used as an input to subsequent analyses.
|
11
11
|
|
12
12
|
The primary goal of RubyBreaker is to assign a type signature to every
|
13
13
|
method in selected modules and classes. A type signature is written in the
|
@@ -16,7 +16,7 @@ used in Ruby Core Library Doc. No manual code change is required. Overall,
|
|
16
16
|
this tool should help Ruby programmers document their code more rigorously
|
17
17
|
and effectively.
|
18
18
|
|
19
|
-
Currently, RubyBreaker *
|
19
|
+
Currently, RubyBreaker *does not*
|
20
20
|
|
21
21
|
* Auto-document block arguments (inherent)
|
22
22
|
* Support parametric polymorphic types
|
data/TUTORIAL.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# Tutorial
|
2
2
|
|
3
|
-
This tutorial will describe the basic
|
4
|
-
|
3
|
+
This tutorial will describe the basic usages of the tool and the types
|
4
|
+
supported by RubyBreaker.
|
5
5
|
|
6
6
|
## Usage
|
7
7
|
|
@@ -11,16 +11,36 @@ does require a minimum change in the Rakefile (but no code change in the
|
|
11
11
|
source program) but is better for a long-term maintenance. Regardless of the
|
12
12
|
mode you choose to run, no source code change is required.
|
13
13
|
|
14
|
+
### Running RubyBreaker
|
15
|
+
|
14
16
|
Let us briefly see how RubyBreaker can be run directly as a command-line
|
15
17
|
program to understand the overall concept of the tool. We will explain how
|
16
|
-
to use RubyBreaker in a Rakefile later.
|
18
|
+
to use RubyBreaker in a Rakefile later. The following is the banner of
|
19
|
+
RubyBreaker when running in the command-line mode:
|
20
|
+
|
21
|
+
Usage: rubybreaker.rb [options] prog[.rb]
|
22
|
+
-b, --break MODULES Specify modules/classes to 'break'
|
23
|
+
-c, --check MODULES Specify modules/classes to check
|
24
|
+
-l, --libs LIBRARIES Specify libraries to load
|
25
|
+
--debug Run in debug mode
|
26
|
+
--style STYLE Select type signature style - underscore or camelize
|
27
|
+
--io-file FILE Specify I/O file
|
28
|
+
--save-output Save output to file
|
29
|
+
-s, --[no-]stdout Show output on screen
|
30
|
+
-a, --[no-]append Append output to input file
|
31
|
+
-v, --verbose Show messages in detail
|
32
|
+
-h, --help Show this help text
|
33
|
+
|
34
|
+
#### Example
|
35
|
+
|
36
|
+
Here is an example of running Rubybreaker as a command.
|
17
37
|
|
18
38
|
$ rubybreaker -v -s -l lib.rb -b A,B prog.rb
|
19
39
|
|
20
|
-
|
40
|
+
This runs RubyBreaker in verbose mode (`-v`) and will display
|
21
41
|
the output on the screen (`-s`). Before RubyBreaker runs `prog.rb`, it will
|
22
42
|
import (`-l`) `lib.rb` and _break_ (`-b`) classes `A` and `B`.
|
23
|
-
Here is `lib.rb`:
|
43
|
+
Here is the source of `lib.rb`:
|
24
44
|
|
25
45
|
class A
|
26
46
|
def foo(x)
|
@@ -33,12 +53,12 @@ Here is `lib.rb`:
|
|
33
53
|
end
|
34
54
|
end
|
35
55
|
|
36
|
-
And, `prog.rb` simply imports the library file and executes
|
56
|
+
And, `prog.rb` simply imports the library file and executes `A#foo` with a
|
57
|
+
number:
|
37
58
|
|
38
59
|
require "lib"
|
39
60
|
A.new.foo(1)
|
40
61
|
|
41
|
-
This example will show how `A#foo` method is given a type by RubyBreaker.
|
42
62
|
After running the command shown above, the following output will be
|
43
63
|
generated and displayed on the screen:
|
44
64
|
|
@@ -46,11 +66,12 @@ generated and displayed on the screen:
|
|
46
66
|
typesig("foo(fixnum[to_s]) -> string")
|
47
67
|
end
|
48
68
|
|
69
|
+
This example shows how RubyBreaker documents the type of `A#foo`.
|
49
70
|
Here, the `typesig` method call registers `foo` as a method type that takes
|
50
71
|
an object that has `Fixnum#to_s` method and returns a `String`. This
|
51
72
|
method is made available simply by importing `rubybreaker`. Now, assume
|
52
73
|
that an additional code, `B.new.bar(A.new,1)`, is added at the end of
|
53
|
-
`prog.rb`.
|
74
|
+
`prog.rb`. It generate the following result:
|
54
75
|
|
55
76
|
class A
|
56
77
|
typesig("foo(fixnum[to_s]) -> string")
|
@@ -67,6 +88,20 @@ the program behaves correctly (for those test runs) as intended by the
|
|
67
88
|
programmer. This assumption is not a strong requirement, but is necessary to
|
68
89
|
obtain precise and accurate type information.
|
69
90
|
|
91
|
+
#### Early Dynamic Type Checking
|
92
|
+
|
93
|
+
RubyBreaker gives you an option to run the early dynamic type check. If a
|
94
|
+
method is documented and its module is specified to be _type checked_, then
|
95
|
+
RubyBreaker will verify if the argument types and the return type are
|
96
|
+
appropriate at runtime. To allow this feature in command-line, use `-c`
|
97
|
+
(checking) option:
|
98
|
+
|
99
|
+
rubybreaker -l lib.rb -c A prog.rb
|
100
|
+
|
101
|
+
Here, the library code `lib.rb` will be imported (`-l`) and class `A` will
|
102
|
+
be instrumented for type checking (`-c`) during the execution of `prog.rb`.
|
103
|
+
We will explain how to run type checking in non-command mode.
|
104
|
+
|
70
105
|
### Using Ruby Unit Testing Framework
|
71
106
|
|
72
107
|
Instead of manually inserting the entry point indicator in the source
|
@@ -133,6 +168,27 @@ If this is the route you are taking, there needs no editing of the source
|
|
133
168
|
program whatsoever. This task will take care of instrumenting the specified
|
134
169
|
modules and classes at proper moments.
|
135
170
|
|
171
|
+
#### Early Dynamic Type Checking in Rakefile
|
172
|
+
|
173
|
+
Previously, we explained how to perform type checking in the command mode.
|
174
|
+
You can also specify which modules/classes to type check in the Rakfile. The
|
175
|
+
following shows an example usage of `check` option.
|
176
|
+
|
177
|
+
require "rubybreaker/task"
|
178
|
+
...
|
179
|
+
desc "Run RubyBreaker"
|
180
|
+
Rake::RubyBreakerTestTask.new(:"rubybreaker") do |t|
|
181
|
+
t.libs << "lib"
|
182
|
+
t.test_files = ["test/foo/tc_foo1.rb"]
|
183
|
+
# ...Other test task options..
|
184
|
+
t.rubybreaker_opts << "-v" # run in verbose mode
|
185
|
+
t.break = ["Class1", "Class2", ...] # specify classes to monitor
|
186
|
+
t.check = ["Class3", ....] # specify classes to check
|
187
|
+
end
|
188
|
+
|
189
|
+
Alternatively, you can use `RubyBreaker.check()` to specify classes to type
|
190
|
+
check in the setup code of the test case.
|
191
|
+
|
136
192
|
## Type Annotation
|
137
193
|
|
138
194
|
The annotation language used in RubyBreaker resembles the method
|
@@ -289,29 +345,3 @@ compatibility between the return types and "promote" the method type to a
|
|
289
345
|
method list type by spliting the type signature into two (or more in
|
290
346
|
subsequent "promotions").
|
291
347
|
|
292
|
-
### Early Dynamic Type Checking
|
293
|
-
|
294
|
-
RubyBreaker gives you an option to run the early dynamic type check. If a
|
295
|
-
method is documented and its module is specified to be _type checked_, then
|
296
|
-
RubyBreaker will verify if the argument types and the return type are
|
297
|
-
appropriate at runtime. To allow this feature in command-line, use `-c`
|
298
|
-
(checking) option:
|
299
|
-
|
300
|
-
rubybreaker -l lib.rb -c A prog.rb
|
301
|
-
|
302
|
-
Or use it in Rakefile:
|
303
|
-
|
304
|
-
require "rubybreaker/task"
|
305
|
-
...
|
306
|
-
desc "Run RubyBreaker"
|
307
|
-
Rake::RubyBreakerTestTask.new(:"rubybreaker") do |t|
|
308
|
-
t.libs << "lib"
|
309
|
-
t.test_files = ["test/foo/tc_foo1.rb"]
|
310
|
-
# ...Other test task options..
|
311
|
-
t.rubybreaker_opts << "-v" # run in verbose mode
|
312
|
-
t.break = ["Class1", "Class2", ...] # specify classes to monitor
|
313
|
-
t.check = ["Class3", ....] # specify classes to check
|
314
|
-
end
|
315
|
-
|
316
|
-
Alternatively, you can use `RubyBreaker.check()` to specify classes to type
|
317
|
-
check in the setup code of the test case.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.8
|
@@ -93,7 +93,7 @@ module RubyBreaker
|
|
93
93
|
when :check
|
94
94
|
mm.check_before_method(obj, meth_info)
|
95
95
|
end
|
96
|
-
rescue
|
96
|
+
rescue ::Exception => e
|
97
97
|
# Trap it, turn on the global monitor and then re-raise the
|
98
98
|
# exception
|
99
99
|
GLOBAL_MONITOR_SWITCH.turn_on()
|
@@ -121,7 +121,7 @@ module RubyBreaker
|
|
121
121
|
when :check
|
122
122
|
mm.check_after_method(obj, meth_info)
|
123
123
|
end
|
124
|
-
rescue
|
124
|
+
rescue ::Exception => e
|
125
125
|
# Trap it, turn on the global monitor and then re-raise the
|
126
126
|
# exception
|
127
127
|
GLOBAL_MONITOR_SWITCH.turn_on()
|
@@ -70,14 +70,45 @@ module RubyBreaker
|
|
70
70
|
def method_missing(mname,*args,&blk)
|
71
71
|
::RubyBreaker.log("Method_missing for #{mname}")
|
72
72
|
if GLOBAL_MONITOR_SWITCH.switch
|
73
|
+
|
74
|
+
# Be safe and turn the switch off
|
75
|
+
GLOBAL_MONITOR_SWITCH.turn_off
|
76
|
+
|
73
77
|
# Must handle send method specially (do not track them)
|
74
78
|
if [:"__send__", :send].include?(mname)
|
75
79
|
mname = args[0]
|
76
80
|
args = args[1..-1]
|
77
81
|
end
|
78
82
|
@__rubybreaker_type.add_meth(mname)
|
83
|
+
|
84
|
+
# If self is not subject to breaking, then no need to send the
|
85
|
+
# wrapped arguments. This part is super IMPORTANT. Otherwise many
|
86
|
+
# native code stuff won't work including Numeric#+.
|
87
|
+
obj = self.__rubybreaker_obj
|
88
|
+
is_obj_mod = (obj.class == ::Class or obj.class == ::Module)
|
89
|
+
mod = is_obj_mod ? Runtime.eigen_class(obj) : obj.class
|
90
|
+
|
91
|
+
# Monitor map doesn't exist MEANS it's not being monitored.
|
92
|
+
unless MONITOR_MAP[mod]
|
93
|
+
args.map! do |arg|
|
94
|
+
if arg.respond_to?(WRAPPED_INDICATOR)
|
95
|
+
arg.__rubybreaker_obj
|
96
|
+
else
|
97
|
+
arg
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Turn on the global switch again
|
103
|
+
GLOBAL_MONITOR_SWITCH.turn_on
|
104
|
+
|
105
|
+
# And call the original method
|
79
106
|
retval = @__rubybreaker_obj.send(mname, *args, &blk)
|
80
|
-
|
107
|
+
|
108
|
+
# No need to wrap the object again...if it's wrapped already
|
109
|
+
unless retval.respond_to?(WRAPPED_INDICATOR)
|
110
|
+
retval = ObjectWrapper.new(retval)
|
111
|
+
end
|
81
112
|
else
|
82
113
|
retval = @__rubybreaker_obj.send(mname, *args, &blk)
|
83
114
|
end
|
@@ -19,6 +19,11 @@ module RubyBreaker
|
|
19
19
|
# indicate overridden methods.
|
20
20
|
OVERRIDE_PREFIX = "__rubybreaker"
|
21
21
|
|
22
|
+
# Prohibit these module/class+method from being overriden
|
23
|
+
BLACKLIST = {
|
24
|
+
String => [:to_s, :<<, :concat, :gsub],
|
25
|
+
}
|
26
|
+
|
22
27
|
end
|
23
28
|
|
24
29
|
end
|
@@ -53,8 +58,33 @@ end
|
|
53
58
|
|
54
59
|
end
|
55
60
|
|
61
|
+
[Symbol, Numeric, Fixnum, Float, Integer, Bignum, String].each do |mod|
|
62
|
+
mod.instance_methods(false).each do |meth_name|
|
63
|
+
black_list = RubyBreaker::Runtime::BLACKLIST
|
64
|
+
next if black_list[mod] && black_list[mod].include?(meth_name)
|
65
|
+
alias_name = "#{RubyBreaker::Runtime::OVERRIDE_PREFIX}_#{mod.object_id}" +
|
66
|
+
"_#{meth_name}"
|
67
|
+
mod.module_eval <<-EOS
|
68
|
+
|
69
|
+
alias :"#{alias_name}" :"#{meth_name}"
|
70
|
+
|
71
|
+
def #{meth_name}(*args)
|
72
|
+
args = args.map do |arg|
|
73
|
+
if arg.respond_to?(RubyBreaker::Runtime::WRAPPED_INDICATOR)
|
74
|
+
arg.__rubybreaker_obj
|
75
|
+
else
|
76
|
+
arg
|
77
|
+
end
|
78
|
+
end
|
79
|
+
send(:"#{RubyBreaker::Runtime::OVERRIDE_PREFIX}_#{mod.object_id}_#{meth_name}",*args)
|
80
|
+
end
|
81
|
+
|
82
|
+
EOS
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
56
86
|
# TODO: add more modules here as necessary!
|
57
|
-
[Object,
|
87
|
+
[Object, Enumerable, Array, Hash].each do |mod|
|
58
88
|
|
59
89
|
[:"==", :equal?, :eql?].each do |meth_name|
|
60
90
|
|
@@ -171,11 +171,33 @@ module RubyBreaker
|
|
171
171
|
|
172
172
|
# This method creates the prefix for the type error message.
|
173
173
|
def type_error_msg_prefix(mod, meth_name)
|
174
|
+
# Match eigen class and returns a prefix that is a class method
|
175
|
+
# using dot (.) instead of double colons (::).
|
174
176
|
result = /#<Class:(.+)>/.match("#{mod}")
|
175
177
|
prefix = result ? "#{result[1]}." : "#{mod}#"
|
176
178
|
return "#{prefix}#{meth_name}"
|
177
179
|
end
|
178
180
|
|
181
|
+
# This method performs the arity check.
|
182
|
+
def arity_check(num_of_args, meth_type)
|
183
|
+
arg_types = meth_type.arg_types
|
184
|
+
|
185
|
+
opt = false
|
186
|
+
varlen = false
|
187
|
+
|
188
|
+
arg_types.each_with_index do |arg_type, i|
|
189
|
+
if arg_type.kind_of?(OptionalType)
|
190
|
+
opt = true
|
191
|
+
elsif arg_type.kind_of?(VarLengthType)
|
192
|
+
varlen = true
|
193
|
+
end
|
194
|
+
end
|
195
|
+
check = (opt && num_of_args <= arg_types.size) ||
|
196
|
+
(varlen && num_of_args >= arg_types.size) ||
|
197
|
+
num_of_args == arg_types.size
|
198
|
+
return check
|
199
|
+
end
|
200
|
+
|
179
201
|
public
|
180
202
|
|
181
203
|
# This method is invoked before the original method is executed.
|
@@ -189,19 +211,55 @@ module RubyBreaker
|
|
189
211
|
# Let's take things out of the MethodInfo object
|
190
212
|
meth_name = meth_info.meth_name
|
191
213
|
args = meth_info.args
|
192
|
-
blk = meth_info.blk
|
193
|
-
ret = meth_info.ret
|
214
|
+
# blk = meth_info.blk
|
194
215
|
|
195
216
|
# Get the registered method type for this method
|
196
217
|
meth_type = meth_type_map[meth_name]
|
197
218
|
|
198
|
-
|
199
|
-
|
200
|
-
|
219
|
+
# Do an arity check first.
|
220
|
+
if !arity_check(args.size, meth_type)
|
221
|
+
msg = type_error_msg_prefix(mod, meth_name) +
|
222
|
+
" has an arity of #{meth_type.arg_types.size} " +
|
223
|
+
"but #{args.size} arguments were passed in"
|
224
|
+
raise Errors::ArityError.new(msg)
|
225
|
+
end
|
226
|
+
|
227
|
+
# Remember what the last formal argument type was so that, if it is
|
228
|
+
# a variable length argument type, we use to check the remaining
|
229
|
+
# arguments.
|
230
|
+
last_supertype = nil
|
231
|
+
|
232
|
+
# Check actual arguments up until the last position of the formal
|
233
|
+
# argument type. If the number of the formal arguments is less than
|
234
|
+
# actual arguments, it means the last formal argument is a variable
|
235
|
+
# length. If it's the other way around, there are optional
|
236
|
+
# arguments.
|
237
|
+
meth_type.arg_types.each_with_index do |supertype, i|
|
238
|
+
if supertype.kind_of?(OptionalType) ||
|
239
|
+
supertype.kind_of?(VarLengthType)
|
240
|
+
supertype = supertype.type
|
241
|
+
end
|
242
|
+
last_supertype = supertype
|
243
|
+
break if i >= args.size
|
244
|
+
subtype = NominalType.new(args[i].class)
|
245
|
+
if !subtype.subtype_of?(supertype)
|
201
246
|
msg = type_error_msg_prefix(mod, meth_name) +
|
202
247
|
"'s #{Util.ordinalize(i+1)} argument " +
|
203
|
-
"does not have type #{
|
204
|
-
raise Errors::
|
248
|
+
"does not have type #{supertype.unparse()}."
|
249
|
+
raise Errors::ArgumentTypeError.new(msg)
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# Handle the remaining actual arguments
|
254
|
+
if meth_type.arg_types.size < args.size
|
255
|
+
for i in meth_type.arg_types.size..args.size-1
|
256
|
+
subtype = NominalType.new(args[i].class)
|
257
|
+
if !subtype.subtype_of?(last_supertype)
|
258
|
+
msg = type_error_msg_prefix(mod, meth_name) +
|
259
|
+
"'s #{Util.ordinalize(i+1)} argument " +
|
260
|
+
"does not have type #{last_supertype.unparse()}."
|
261
|
+
raise Errors::ArgumentTypeError.new(msg)
|
262
|
+
end
|
205
263
|
end
|
206
264
|
end
|
207
265
|
|
@@ -217,8 +275,6 @@ module RubyBreaker
|
|
217
275
|
|
218
276
|
# Let's take things out of the MethodInfo object
|
219
277
|
meth_name = meth_info.meth_name
|
220
|
-
args = meth_info.args
|
221
|
-
blk = meth_info.blk
|
222
278
|
ret = meth_info.ret
|
223
279
|
|
224
280
|
# Get the registered method type for this method
|
@@ -228,7 +284,7 @@ module RubyBreaker
|
|
228
284
|
if !meth_type.ret_type.subtype_of?(ret_type)
|
229
285
|
msg = type_error_msg_prefix(mod, meth_name) +
|
230
286
|
" return value does not have type #{ret_type.unparse()}."
|
231
|
-
raise Errors::
|
287
|
+
raise Errors::ReturnTypeError.new(msg)
|
232
288
|
end
|
233
289
|
end
|
234
290
|
|
@@ -251,7 +307,7 @@ module RubyBreaker
|
|
251
307
|
ret = meth_info.ret
|
252
308
|
|
253
309
|
args = args.map do |arg|
|
254
|
-
if arg.kind_of?(TrueClass) || arg.kind_of?(FalseClass)
|
310
|
+
if arg == nil || arg.kind_of?(TrueClass) || arg.kind_of?(FalseClass)
|
255
311
|
# XXX: would overrides resolve this issue?
|
256
312
|
arg
|
257
313
|
else
|
@@ -12,6 +12,9 @@ class IntegratedCheckingTest < Test::Unit::TestCase
|
|
12
12
|
typesig("f2(fixnum[to_s]) -> string")
|
13
13
|
def f2(x); x.to_s end
|
14
14
|
|
15
|
+
typesig("g2([to_s]) -> string")
|
16
|
+
def g2(x); x.to_s end
|
17
|
+
|
15
18
|
typesig("f3(fixnum[foo, to_s]) -> string")
|
16
19
|
def f3(x); x.to_s end
|
17
20
|
|
@@ -21,6 +24,12 @@ class IntegratedCheckingTest < Test::Unit::TestCase
|
|
21
24
|
typesig("f5(fixnum, fixnum) -> fixnum")
|
22
25
|
def f5(x,y); x+y end
|
23
26
|
|
27
|
+
typesig("f6(fixnum, fixnum?, fixnum*) -> fixnum")
|
28
|
+
def f6(x, y=1, *z)
|
29
|
+
z = eval(z.join("+"))
|
30
|
+
x + y + (z ? z : 0)
|
31
|
+
end
|
32
|
+
|
24
33
|
end
|
25
34
|
|
26
35
|
def setup()
|
@@ -32,25 +41,46 @@ class IntegratedCheckingTest < Test::Unit::TestCase
|
|
32
41
|
assert_nothing_thrown do
|
33
42
|
a.f1(2)
|
34
43
|
end
|
35
|
-
assert_raise RubyBreaker::Errors::
|
44
|
+
assert_raise RubyBreaker::Errors::ArgumentTypeError do
|
36
45
|
a.f1("2")
|
37
46
|
end
|
38
47
|
end
|
39
48
|
|
49
|
+
def test_arity()
|
50
|
+
a = A.new
|
51
|
+
assert_raise Errors::ArityError do
|
52
|
+
a.f1(1, 2)
|
53
|
+
end
|
54
|
+
assert_nothing_thrown do
|
55
|
+
a.f6(1, 2)
|
56
|
+
a.f6(1, 2, 3)
|
57
|
+
a.f6(1, 2, 3, 4)
|
58
|
+
a.f6(1, 2, 3, 4, 5)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
40
62
|
def test_duck()
|
63
|
+
a = A.new
|
64
|
+
assert_nothing_thrown do
|
65
|
+
a.g2(2)
|
66
|
+
a.g2("2")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def test_fusion()
|
41
71
|
a = A.new
|
42
72
|
assert_nothing_thrown do
|
43
73
|
a.f2(2)
|
44
74
|
a.f3(2) # This will pass too
|
45
75
|
end
|
46
|
-
assert_raise RubyBreaker::Errors::
|
76
|
+
assert_raise RubyBreaker::Errors::ArgumentTypeError do
|
47
77
|
a.f3("2")
|
48
78
|
end
|
49
79
|
end
|
50
80
|
|
51
81
|
def test_ret()
|
52
82
|
a = A.new
|
53
|
-
assert_raise RubyBreaker::Errors::
|
83
|
+
assert_raise RubyBreaker::Errors::ReturnTypeError do
|
54
84
|
a.f4(2)
|
55
85
|
end
|
56
86
|
end
|
@@ -60,10 +90,10 @@ class IntegratedCheckingTest < Test::Unit::TestCase
|
|
60
90
|
assert_nothing_thrown do
|
61
91
|
a.f5(1, 2)
|
62
92
|
end
|
63
|
-
assert_raise RubyBreaker::Errors::
|
93
|
+
assert_raise RubyBreaker::Errors::ArgumentTypeError do
|
64
94
|
a.f5("1", 2)
|
65
95
|
end
|
66
|
-
assert_raise RubyBreaker::Errors::
|
96
|
+
assert_raise RubyBreaker::Errors::ArgumentTypeError do
|
67
97
|
a.f5(1, "2")
|
68
98
|
end
|
69
99
|
end
|