rubybreaker 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- 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
|