rubybreaker 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS +7 -0
- data/LICENSE +26 -0
- data/README.md +403 -0
- data/Rakefile +90 -0
- data/TODO +30 -0
- data/bin/gen_stub_rubylib +64 -0
- data/bin/rubybreaker +67 -0
- data/lib/rubybreaker/context.rb +122 -0
- data/lib/rubybreaker/debug.rb +48 -0
- data/lib/rubybreaker/error.rb +59 -0
- data/lib/rubybreaker/rubylib/core.rb +2316 -0
- data/lib/rubybreaker/rubylib.rb +3 -0
- data/lib/rubybreaker/runtime/inspector.rb +57 -0
- data/lib/rubybreaker/runtime/monitor.rb +235 -0
- data/lib/rubybreaker/runtime/object_wrapper.rb +77 -0
- data/lib/rubybreaker/runtime/overrides.rb +42 -0
- data/lib/rubybreaker/runtime/pluggable.rb +57 -0
- data/lib/rubybreaker/runtime/type_placeholder.rb +27 -0
- data/lib/rubybreaker/runtime/type_system.rb +228 -0
- data/lib/rubybreaker/runtime/typesig_parser.rb +45 -0
- data/lib/rubybreaker/runtime.rb +103 -0
- data/lib/rubybreaker/test/testcase.rb +39 -0
- data/lib/rubybreaker/test.rb +1 -0
- data/lib/rubybreaker/type/type.rb +241 -0
- data/lib/rubybreaker/type/type_comparer.rb +143 -0
- data/lib/rubybreaker/type/type_grammar.treetop +285 -0
- data/lib/rubybreaker/type/type_unparser.rb +142 -0
- data/lib/rubybreaker/type.rb +2 -0
- data/lib/rubybreaker/typing/rubytype.rb +47 -0
- data/lib/rubybreaker/typing/subtyping.rb +480 -0
- data/lib/rubybreaker/typing.rb +3 -0
- data/lib/rubybreaker/util.rb +31 -0
- data/lib/rubybreaker.rb +193 -0
- data/test/integrated/tc_method_missing.rb +30 -0
- data/test/integrated/tc_simple1.rb +77 -0
- data/test/runtime/tc_obj_wrapper.rb +73 -0
- data/test/runtime/tc_typesig_parser.rb +33 -0
- data/test/ts_integrated.rb +4 -0
- data/test/ts_runtime.rb +5 -0
- data/test/ts_type.rb +5 -0
- data/test/ts_typing.rb +4 -0
- data/test/type/tc_comparer.rb +211 -0
- data/test/type/tc_parser.rb +219 -0
- data/test/type/tc_unparser.rb +276 -0
- data/test/typing/tc_rubytype.rb +63 -0
- data/test/typing/tc_typing.rb +219 -0
- data/webpage/footer.html +5 -0
- data/webpage/generated_toc.js +319 -0
- data/webpage/header.html +14 -0
- data/webpage/images/logo.png +0 -0
- data/webpage/index.html +439 -0
- data/webpage/rubybreaker.css +53 -0
- metadata +119 -0
@@ -0,0 +1,480 @@
|
|
1
|
+
#--
|
2
|
+
# This file is the mechanism behind the default type system of RubyBreaker.
|
3
|
+
# Typing works as follows:
|
4
|
+
#
|
5
|
+
# In RubyBreaker, there are two *kinds* of modules (classes)--Broken and
|
6
|
+
# non-Broken. The former refers to modules that already have type
|
7
|
+
# information, and the latter refers to ones without prior type information.
|
8
|
+
# Although it might be optional as an add-on, RubyBreaker does not generate
|
9
|
+
# any constraint graph to resolve subtype relations. This is the main
|
10
|
+
# difference between Rubydust and RubyBreaker.
|
11
|
+
#
|
12
|
+
# If a module is not Broken but is Breakable (i.e., monitored), then it is
|
13
|
+
# treated as non-Broken. At the end of the execution, the module will be
|
14
|
+
# Broken--i.e., its type information is now revealed. If the user wishes to
|
15
|
+
# use the result of the analysis, this type information will be documented
|
16
|
+
# and the module itself can be used as Broken in future execution.
|
17
|
+
#
|
18
|
+
# Consider the following examples:
|
19
|
+
#
|
20
|
+
# class Numeric
|
21
|
+
# typesig("+(numeric) -> numeric")
|
22
|
+
# ...
|
23
|
+
# end
|
24
|
+
#
|
25
|
+
# class A
|
26
|
+
# include RubyBreaker::Breakable
|
27
|
+
# def m(x,y)
|
28
|
+
# return x+y
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# a = A.new
|
33
|
+
# assert_equal(3,a.m(1,2))
|
34
|
+
#
|
35
|
+
# Type of A#m will be "m(fixnum[+],numeric) -> numeric". Note that the type
|
36
|
+
# signature for Numeric#+ is given by RubyBreaker in base.rb. The first
|
37
|
+
# argument of A#m gets fixnum[+] since x is a Numeric and was invoked +
|
38
|
+
# method. The second argument simply gets Numeric since no method was
|
39
|
+
# invoked on the object but + method takes a Numeric.
|
40
|
+
#
|
41
|
+
# The basis of RubyBreaker typing is object subtyping. It is object typing
|
42
|
+
# because RubyBreaker supports duck types and fusion types where individual
|
43
|
+
# methods can be specified in the type.
|
44
|
+
#
|
45
|
+
|
46
|
+
require_relative "rubytype"
|
47
|
+
require_relative "../type"
|
48
|
+
|
49
|
+
module RubyBreaker
|
50
|
+
|
51
|
+
# This module contains subtyping logic used in RubyBreaker. See
|
52
|
+
# _rubytype.rb_ for logic related to subclassing which is directly related
|
53
|
+
# to pure Ruby types.
|
54
|
+
module Typing
|
55
|
+
|
56
|
+
include TypeDefs
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Thie method checks if the module has all the methods specified in
|
61
|
+
# meths array
|
62
|
+
def self.module_has_methods?(mod, meths)
|
63
|
+
has_all = true
|
64
|
+
mod_meths = mod.instance_methods
|
65
|
+
meths.each do |m|
|
66
|
+
if !mod_meths.include?(m)
|
67
|
+
has_all = false
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
return has_all
|
72
|
+
end
|
73
|
+
|
74
|
+
# This method checks if the duck type has all the methods specified in
|
75
|
+
# meths array
|
76
|
+
def self.duck_has_methods?(duck, meths)
|
77
|
+
has_all = true
|
78
|
+
meths.each do |m|
|
79
|
+
if !duck.meth_names.include?(m)
|
80
|
+
has_all = false
|
81
|
+
break
|
82
|
+
end
|
83
|
+
end
|
84
|
+
return has_all
|
85
|
+
end
|
86
|
+
|
87
|
+
# This method checks if a duck type (LHS) is a subtype of the other
|
88
|
+
# type. There are a few cases to consider:
|
89
|
+
#
|
90
|
+
# If RHS is a nominal type, every method in RHS must exist in LHS.
|
91
|
+
# This is because the subtype has the same number of or more methods
|
92
|
+
# than supertype.
|
93
|
+
#
|
94
|
+
# If RHS is a duck type OR a fusion type, every method in RHS must
|
95
|
+
# exist in LHS.
|
96
|
+
#
|
97
|
+
# If RHS is an "or" type, LHS must be a subtype of one of RHS'es inner
|
98
|
+
# types.
|
99
|
+
#
|
100
|
+
# All other cases would not satisfy this subtyping relationship.
|
101
|
+
#
|
102
|
+
def self.duck_subtype_rel?(lhs,rhs)
|
103
|
+
return false unless lhs.kind_of?(DuckType)
|
104
|
+
if rhs.instance_of?(NominalType) # Don't include self type
|
105
|
+
# Ok, this is unlikely but we still do a check
|
106
|
+
is_subtype = self.duck_has_methods?(lhs, rhs.mod.instance_methods)
|
107
|
+
elsif rhs.kind_of?(DuckType) # duck type and fusion type
|
108
|
+
is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
|
109
|
+
elsif rhs.instance_of?(OrType)
|
110
|
+
is_subtype = false
|
111
|
+
rhs.types.each {|rhs_inner_t|
|
112
|
+
# Only one has to be in subtype relation
|
113
|
+
if self.duck_subtype_rel?(lhs,rhs_inner_t)
|
114
|
+
is_subtype = true
|
115
|
+
break
|
116
|
+
end
|
117
|
+
}
|
118
|
+
else
|
119
|
+
is_subtype = false
|
120
|
+
end
|
121
|
+
return is_subtype
|
122
|
+
end
|
123
|
+
|
124
|
+
# Procedure subtype relation check is for both methods and blocks. A
|
125
|
+
# typical "method" subtype is satified if
|
126
|
+
#
|
127
|
+
# - Each argument in RHS is a subtype of its counterpart in LHS. This
|
128
|
+
# includes the block argument. It's called contra-variance.
|
129
|
+
# - The return in LHS is a subtype of its counterpart in RHS. It's
|
130
|
+
# called co-variance.
|
131
|
+
#
|
132
|
+
# Here is the explanation to why we do this:
|
133
|
+
#
|
134
|
+
# Assume m1 <: m2 (i.e, m1 is a subtype of m2)
|
135
|
+
#
|
136
|
+
# Then, any place where m2 is used, m1 should be able to replace m2.
|
137
|
+
# In other words, m1's arguments should accept more types than m2's
|
138
|
+
# arguments. On the other hand, m1's return should be more restrictive
|
139
|
+
# than m2's return to make sure it would work after the call. There it
|
140
|
+
# is, you just received a mini type systems class without paying
|
141
|
+
# thousands of dollars.
|
142
|
+
#
|
143
|
+
# This method also takes care of optional argument and variable length
|
144
|
+
# argument. This method is the ONLY one that should handle those types.
|
145
|
+
#
|
146
|
+
def self.proc_subtype_rel?(lhs, rhs)
|
147
|
+
|
148
|
+
# use kind_of? because method_subtype_rel? calls this method
|
149
|
+
return false unless lhs.kind_of?(BlockType) & rhs.kind_of?(BlockType)
|
150
|
+
|
151
|
+
is_subtype = true
|
152
|
+
|
153
|
+
if lhs.arg_types.length != rhs.arg_types.length
|
154
|
+
is_subtype = false
|
155
|
+
# elsif !self.subtype_rel?(rhs.blk_type, lhs.blk_type)
|
156
|
+
# is_subtype = false
|
157
|
+
elsif !self.subtype_rel?(lhs.ret_type, rhs.ret_type)
|
158
|
+
is_subtype = false
|
159
|
+
else
|
160
|
+
|
161
|
+
# Subtyping with optional type and variable length type works as
|
162
|
+
# follows:
|
163
|
+
#
|
164
|
+
# (x?) <: (x) since optional argument can replace original
|
165
|
+
# argument
|
166
|
+
#
|
167
|
+
# (x*) <: (x) for the same reason
|
168
|
+
#
|
169
|
+
# (x*) <: (x?) since LHS can have more arguments
|
170
|
+
#
|
171
|
+
|
172
|
+
is_subtype = true
|
173
|
+
# check arguments
|
174
|
+
rhs.arg_types.each_with_index {|rhs_arg,i|
|
175
|
+
lhs_arg = lhs.arg_types[i]
|
176
|
+
if lhs_arg.kind_of?(OptionalType)
|
177
|
+
lhs_arg = lhs_arg.type
|
178
|
+
if rhs_arg.kind_of?(OptionalType)
|
179
|
+
rhs_arg = rhs_arg.type
|
180
|
+
elsif rhs_arg.kind_of?(VarLengthType)
|
181
|
+
is_subtype = false
|
182
|
+
break
|
183
|
+
end
|
184
|
+
elsif lhs_arg.kind_of?(VarLengthType)
|
185
|
+
lhs_arg = lhs_arg.type
|
186
|
+
if rhs_arg.kind_of?(OptionalType) ||
|
187
|
+
rhs_arg.kind_of?(VarLengthType)
|
188
|
+
rhs_arg = rhs_arg.type
|
189
|
+
end
|
190
|
+
end
|
191
|
+
if !self.subtype_rel?(rhs_arg, lhs_arg)
|
192
|
+
is_subtype = false
|
193
|
+
break
|
194
|
+
end
|
195
|
+
}
|
196
|
+
end
|
197
|
+
return is_subtype
|
198
|
+
end
|
199
|
+
|
200
|
+
# This method checks the subtype relation between two method types.
|
201
|
+
# There are several cases to consider:
|
202
|
+
#
|
203
|
+
# Case 1: If RHS is a MethodListType, we give up. It is actually
|
204
|
+
# difficult to figure out the subtype relation in this case.
|
205
|
+
# Case 2: If LHS is a MethodListType, only one of them has to be in
|
206
|
+
# subtype relation to RHS.
|
207
|
+
# Case 3: If both are Methods, then do a typical method subtype
|
208
|
+
# comparison.
|
209
|
+
#
|
210
|
+
# Note: See methodtypelist_subtype_rel? if LHS is a MethodListType.
|
211
|
+
#
|
212
|
+
def self.method_subtype_rel?(lhs,rhs)
|
213
|
+
is_subtype = true
|
214
|
+
# First check if LHS and RHS are either a method type or a method list
|
215
|
+
# type. Otherwise, return false.
|
216
|
+
if (!lhs.instance_of?(MethodType) && !lhs.instance_of?(MethodListType)) ||
|
217
|
+
(!rhs.instance_of?(MethodType) && !rhs.instance_of?(MethodListType))
|
218
|
+
is_subtype = false
|
219
|
+
elsif lhs.instance_of?(MethodListType)
|
220
|
+
# Every method type in LHS must be a subtype of RHS
|
221
|
+
is_subtype = true
|
222
|
+
lhs.types.each {|type|
|
223
|
+
if !self.method_subtype_rel?(type,rhs)
|
224
|
+
is_subtype = false
|
225
|
+
break
|
226
|
+
end
|
227
|
+
}
|
228
|
+
elsif rhs.instance_of?(MethodListType)
|
229
|
+
# One of the RHS has to be a supertype
|
230
|
+
is_subtype = false
|
231
|
+
rhs.types.each {|type|
|
232
|
+
if self.method_subtype_rel?(lhs,type)
|
233
|
+
is_subtype = true
|
234
|
+
break
|
235
|
+
end
|
236
|
+
}
|
237
|
+
else # ok, both are MethodType
|
238
|
+
# Remember the contra-variance in arguments, co-variance in return
|
239
|
+
if lhs.meth_name != rhs.meth_name
|
240
|
+
is_subtype = false
|
241
|
+
else
|
242
|
+
# re-use block subtype relation check
|
243
|
+
is_subtype = self.proc_subtype_rel?(lhs, rhs)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
return is_subtype
|
247
|
+
end
|
248
|
+
|
249
|
+
# This method checks if each method in the subtype is indeed a subtype
|
250
|
+
# of the counterpart in the supertype.
|
251
|
+
def self.methods_subtype_rel?(sub_meth_types, super_meth_types)
|
252
|
+
# wider means, a fewer number of methods
|
253
|
+
return false unless sub_meth_types.size <= super_meth_types
|
254
|
+
is_subtype = true
|
255
|
+
sub_meth_types.each_pair { |meth_name, sub_meth_type|
|
256
|
+
super_meth_type = super_meth_types[meth_name]
|
257
|
+
if super_meth_type == nil ||
|
258
|
+
!method_subtype_rel?(sub_meth_type, super_meth_type)
|
259
|
+
is_subtype = false
|
260
|
+
break
|
261
|
+
end
|
262
|
+
}
|
263
|
+
return is_subtype
|
264
|
+
end
|
265
|
+
|
266
|
+
# This method determines if the fusion type (LHS) is a subtype of the
|
267
|
+
# other type (RHS). There are many cases to consider:
|
268
|
+
#
|
269
|
+
# If LHS is Broken, there are several cases to consider:
|
270
|
+
#
|
271
|
+
# If RHS is a nominal type, there are a few cases to consider:
|
272
|
+
#
|
273
|
+
# If LHS is a subclass of RHS, then there is a subtype relation
|
274
|
+
#
|
275
|
+
# If RHS is Broken, each method in RHS must be a supertype of the
|
276
|
+
# counterpart in LHS.
|
277
|
+
#
|
278
|
+
# Otherwise, each in LHS must exist in RHS.
|
279
|
+
#
|
280
|
+
# If RHS is a fusion type, ther eare a few cases to consider:
|
281
|
+
#
|
282
|
+
# If LHS is a subclass of RHS, then LHS is a subtype
|
283
|
+
#
|
284
|
+
# If RHS is Broken, each method in RHS must be a supertype of the
|
285
|
+
# counterpart in LHS.
|
286
|
+
#
|
287
|
+
# Otherwise, every method in RHS must exist in LHS.
|
288
|
+
#
|
289
|
+
# If RHS is a duck type, then every method in RHS must exist in LHS.
|
290
|
+
#
|
291
|
+
# Otherwise, there is no subtype relation.
|
292
|
+
#
|
293
|
+
# If LHS is not Broken, subtyping satifies only when LHS has all
|
294
|
+
# methods in RHS
|
295
|
+
#
|
296
|
+
def self.fusion_subtype_rel?(lhs,rhs)
|
297
|
+
return false unless lhs.kind_of?(FusionType)
|
298
|
+
if lhs.mod.kind_of?(Broken)
|
299
|
+
if rhs.instance_of?(NominalType) # don't include self type
|
300
|
+
if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
|
301
|
+
is_subtype = true
|
302
|
+
elsif rhs.mod.kind_of?(Broken)
|
303
|
+
# then do a type check for each method
|
304
|
+
lhs_meths = Inspect.inspect_all(lhs.mod)
|
305
|
+
rhs_meths = Inspect.inspect_all(rhs.mod)
|
306
|
+
is_subtype = self.methods_subtype_rel?(lhs_meths,rhs_meths)
|
307
|
+
else
|
308
|
+
# if not, the only possible way is if lhs has all the rhs' methods
|
309
|
+
is_subtype = self.duck_has_methods?(lhs, rhs.mod.instance_methods)
|
310
|
+
end
|
311
|
+
elsif rhs.instance_of?(FusionType)
|
312
|
+
if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
|
313
|
+
is_subtype = true
|
314
|
+
elsif rhs.mod.kind_of?(Broken)
|
315
|
+
# then do a type check for each method
|
316
|
+
lhs_meths = Inspect.inspect_all(lhs.mod)
|
317
|
+
rhs_meths = Inspect.inspect_meths(rhs.mod, lhs.meths.keys)
|
318
|
+
is_subtype = self.methods_subtype_rel?(lhs_meths,rhs_meths)
|
319
|
+
else
|
320
|
+
is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
|
321
|
+
end
|
322
|
+
elsif rhs.instance_of?(DuckType)
|
323
|
+
is_subtype = self.duck_has_methods?(lhs, rhs.meth_names)
|
324
|
+
else
|
325
|
+
is_subtype = false
|
326
|
+
end
|
327
|
+
else
|
328
|
+
# lhs is not broken, so only thing we can do is method check
|
329
|
+
is_subtype = self.duck_subtype_rel?(lhs, rhs)
|
330
|
+
end
|
331
|
+
return is_subtype
|
332
|
+
end
|
333
|
+
|
334
|
+
# Self type works exactly like nominal type except that, if RHS is a
|
335
|
+
# self type, then only self type is allowed in LHS.
|
336
|
+
#
|
337
|
+
# For example, consider you are inside Fixnum
|
338
|
+
#
|
339
|
+
# self <: Fixnum
|
340
|
+
# self <: Numeric
|
341
|
+
# self <: Object
|
342
|
+
# self <: self
|
343
|
+
#
|
344
|
+
# but,
|
345
|
+
#
|
346
|
+
# Fixnum !<: self
|
347
|
+
#
|
348
|
+
def self.self_subtype_rel?(lhs,rhs)
|
349
|
+
self.nominal_subtype_rel?(lhs,rhs)
|
350
|
+
end
|
351
|
+
|
352
|
+
# This method checks the subtype relation when LHS is a nominal type.
|
353
|
+
# There are several cases to consider:
|
354
|
+
#
|
355
|
+
# my_class <: your_class is true only if MyClass and YourClass have
|
356
|
+
# (Ruby) subclass relationship.
|
357
|
+
#
|
358
|
+
# my_class <: a[foo] is true if
|
359
|
+
#
|
360
|
+
# (1) MyClass is a subtype of A or
|
361
|
+
# (2) Both MyClass and A are Broken and every method in MyClass is a
|
362
|
+
# subtype of the counterpart method in A. (This should be rare).
|
363
|
+
# Or,
|
364
|
+
# (3) A are not Broken and MyClass has all methods of A.
|
365
|
+
#
|
366
|
+
#
|
367
|
+
def self.nominal_subtype_rel?(lhs,rhs)
|
368
|
+
return false unless lhs.kind_of?(NominalType) # Self type is a nominal type
|
369
|
+
if rhs.instance_of?(SelfType)
|
370
|
+
is_subtype = lhs.instance_of?(SelfType)
|
371
|
+
elsif rhs.instance_of?(NominalType) # don't include self type
|
372
|
+
is_subtype = RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
|
373
|
+
elsif rhs.instance_of?(FusionType)
|
374
|
+
# If RHS is a superclass or included module then true
|
375
|
+
# If both LHS and RHS are Broken, do a subtype check on each method
|
376
|
+
# If only RHS is broken, look at each method's type
|
377
|
+
# If RHS is not broken, sorry no subtype relationship
|
378
|
+
if RubyTypeUtils.subclass_rel?(lhs.mod, rhs.mod)
|
379
|
+
is_subtype = true
|
380
|
+
elsif lhs.kind_of?(Broken) && rhs.kind_of?(Broken)
|
381
|
+
is_subtype = true
|
382
|
+
lhs_methods = lhs.mod.instance_methods
|
383
|
+
rhs.meth_names.each {|m|
|
384
|
+
lhs_m = Inspector.inspect_meth(lhs, m)
|
385
|
+
rhs_m = Inspector.inspect_meth(rhs, m)
|
386
|
+
if !meth_subtype_rel?(lhs_m, rhs_m)
|
387
|
+
is_subtype = false
|
388
|
+
break
|
389
|
+
end
|
390
|
+
}
|
391
|
+
else
|
392
|
+
is_subtype = self.module_has_methods?(lhs.mod, rhs.meth_names)
|
393
|
+
end
|
394
|
+
elsif rhs.instance_of?(DuckType)
|
395
|
+
# Do simple method existence check since there is no nominal type to
|
396
|
+
# inspect.
|
397
|
+
is_subtype = self.module_has_methods?(lhs.mod, rhs.meth_names)
|
398
|
+
else
|
399
|
+
# No other possibility of subtype relation
|
400
|
+
is_subtype = false
|
401
|
+
end
|
402
|
+
return is_subtype
|
403
|
+
end
|
404
|
+
|
405
|
+
# If OrType is on the LHS, the only possible subtype relation is if each
|
406
|
+
# child type in OrType is a subtype of RHS. For example,
|
407
|
+
#
|
408
|
+
# numeric or string <: object
|
409
|
+
#
|
410
|
+
# is true only if numeric is subype of object and string is subtype of
|
411
|
+
# object.
|
412
|
+
#
|
413
|
+
def self.or_subtype_rel?(lhs,rhs)
|
414
|
+
return false if !lhs.kind_of?(OrType)
|
415
|
+
is_subtype = true
|
416
|
+
# Each type in LHS has to be a subtype of RHS
|
417
|
+
lhs.types.each do |t|
|
418
|
+
if !self.subtype_rel?(t,rhs)
|
419
|
+
is_subtype = false
|
420
|
+
break
|
421
|
+
end
|
422
|
+
end
|
423
|
+
return is_subtype
|
424
|
+
end
|
425
|
+
|
426
|
+
public
|
427
|
+
|
428
|
+
# This method determines if one type is a subtype of another. This check
|
429
|
+
# is for RubyBreaker defined types. See _TypeDefs_ module for more
|
430
|
+
# detail.
|
431
|
+
#
|
432
|
+
# lhs:: The allegedly "subtype"
|
433
|
+
# rhs:: The allegedly "supertype"
|
434
|
+
#
|
435
|
+
def self.subtype_rel?(lhs, rhs)
|
436
|
+
|
437
|
+
# Don't even bother if they are same object or syntactically
|
438
|
+
# equivalent. NOTE: would this really help improve performance???
|
439
|
+
return true if (lhs.equal?(rhs) || lhs.eql?(rhs))
|
440
|
+
|
441
|
+
# Break down the cases by what LHS is.
|
442
|
+
is_subtype = false
|
443
|
+
if lhs.instance_of?(NilType)
|
444
|
+
is_subtype = rhs.instance_of?(NilType)
|
445
|
+
elsif lhs.instance_of?(AnyType)
|
446
|
+
is_subtype = true
|
447
|
+
elsif lhs.instance_of?(SelfType)
|
448
|
+
is_subtype = self.self_subtype_rel?(lhs,rhs)
|
449
|
+
elsif lhs.instance_of?(NominalType)
|
450
|
+
is_subtype = self.nominal_subtype_rel?(lhs,rhs)
|
451
|
+
elsif lhs.instance_of?(FusionType)
|
452
|
+
is_subtype = self.fusion_subtype_rel?(lhs,rhs)
|
453
|
+
elsif lhs.instance_of?(DuckType)
|
454
|
+
is_subtype = self.duck_subtype_rel?(lhs,rhs)
|
455
|
+
elsif lhs.instance_of?(MethodType)
|
456
|
+
is_subtype = self.method_subtype_rel?(lhs,rhs)
|
457
|
+
elsif lhs.instance_of?(OrType)
|
458
|
+
is_subtype = self.or_subtype_rel?(lhs,rhs)
|
459
|
+
elsif lhs.instance_of?(BlockType)
|
460
|
+
is_subtype = self.proc_subtype_rel?(lhs,rhs)
|
461
|
+
elsif lhs.instance_of?(MethodListType)
|
462
|
+
is_subtype = self.method_subtype_rel?(lhs,rhs)
|
463
|
+
end
|
464
|
+
return is_subtype
|
465
|
+
end
|
466
|
+
|
467
|
+
end
|
468
|
+
|
469
|
+
#--
|
470
|
+
# Reopening the class to allow subtyping more accessible.
|
471
|
+
class TypeDefs::Type
|
472
|
+
|
473
|
+
# This is a shorthand for calling Typing.subtype_rel?
|
474
|
+
def subtype_of?(rhs)
|
475
|
+
return Typing.subtype_rel?(self,rhs)
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
end
|
480
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
#--
|
2
|
+
# This file contains utility functions that are useful for RubyBreaker. They
|
3
|
+
# may have been taken from other sources.
|
4
|
+
|
5
|
+
module RubyBreaker
|
6
|
+
|
7
|
+
module Utilities
|
8
|
+
|
9
|
+
# File activesupport/lib/active_support/inflector/methods.rb, line 48
|
10
|
+
def self.underscore(camel_cased_word)
|
11
|
+
word = camel_cased_word.to_s.dup
|
12
|
+
word.gsub!(/::/, '/')
|
13
|
+
word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
|
14
|
+
word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
|
15
|
+
word.tr!("-", "_")
|
16
|
+
word.downcase!
|
17
|
+
word
|
18
|
+
end
|
19
|
+
|
20
|
+
# File activesupport/lib/active_support/inflector/methods.rb
|
21
|
+
def self.camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true)
|
22
|
+
if first_letter_in_uppercase
|
23
|
+
lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
|
24
|
+
else
|
25
|
+
lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
data/lib/rubybreaker.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
#--
|
2
|
+
# This library dynamically profiles and resolves the type information
|
3
|
+
# observed at runtime and generates type annotation at the end. It can be
|
4
|
+
# run as either a stand-alone script or as a Ruby library.
|
5
|
+
|
6
|
+
require_relative "rubybreaker/runtime"
|
7
|
+
require_relative "rubybreaker/test"
|
8
|
+
|
9
|
+
# RubyBreaker is a dynamic instrumentation and monitoring tool that
|
10
|
+
# documents type information automatically.
|
11
|
+
module RubyBreaker
|
12
|
+
|
13
|
+
# This constant contains the copyright information.
|
14
|
+
COPYRIGHT = "Copyright (c) 2012 Jong-hoon (David) An. All Rights Reserved."
|
15
|
+
|
16
|
+
# Options for RubyBreaker
|
17
|
+
OPTIONS = {
|
18
|
+
:debug => false, # in debug mode?
|
19
|
+
:verbose => true, # in verbose mode?
|
20
|
+
:mode => :lib, # bin or lib?
|
21
|
+
:io_file => nil, # generate input/output other than default?
|
22
|
+
:append => true, # append to the input file (if there is)?
|
23
|
+
:stdout => true, # also display on the screen?
|
24
|
+
:rubylib => true, # include core ruby library documentation?
|
25
|
+
}
|
26
|
+
|
27
|
+
# Broken takes higher precedence than Breakable. Once a module is
|
28
|
+
# "declared" to be Broken, it cannot be Breakable.
|
29
|
+
#
|
30
|
+
# TODO: In future, there will be a module to support both states.
|
31
|
+
|
32
|
+
# This array lists modules/classes that will be monitored.
|
33
|
+
BREAKABLE = []
|
34
|
+
|
35
|
+
# This array lists modules/classes that are actually instrumented with a
|
36
|
+
# monitor.
|
37
|
+
INSTALLED = []
|
38
|
+
|
39
|
+
# This array lists "broken" classes--i.e., with type signatures
|
40
|
+
BROKEN = []
|
41
|
+
|
42
|
+
# This array lists monitored modules/classes that are outputed.
|
43
|
+
DOCUMENTED = []
|
44
|
+
|
45
|
+
# Extension used by RubyBreaker for output/input
|
46
|
+
EXTENSION = "rubybreaker"
|
47
|
+
|
48
|
+
# This module has a set of entry points to the program and misc. methods
|
49
|
+
# for running RubyBreaker.
|
50
|
+
module Main
|
51
|
+
|
52
|
+
include TypeDefs
|
53
|
+
include Runtime
|
54
|
+
|
55
|
+
public
|
56
|
+
|
57
|
+
# This method is the trigger point to install a monitor in each
|
58
|
+
# module/class.
|
59
|
+
def self.setup()
|
60
|
+
|
61
|
+
BREAKABLE.each do |m|
|
62
|
+
# Avoid already installed module or now Broken module. Remember,
|
63
|
+
# once a module is a declared to be Broken, it wins. Broken modules
|
64
|
+
# cannot be Breakable!
|
65
|
+
unless INSTALLED.include?(m) || BROKEN.include?(m)
|
66
|
+
MonitorInstaller.install_module_monitor(m,true)
|
67
|
+
INSTALLED << m
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# At the end, we generate an output of the type information.
|
72
|
+
at_exit do
|
73
|
+
self.output
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
# Reads the input file if specified or exists
|
79
|
+
def self.input()
|
80
|
+
return unless OPTIONS[:io_file] && File.exist?(OPTIONS[:io_file])
|
81
|
+
eval "load \"#{OPTIONS[:io_file]}\"", TOPLEVEL_BINDING
|
82
|
+
end
|
83
|
+
|
84
|
+
# This method will generate the output
|
85
|
+
def self.output()
|
86
|
+
|
87
|
+
io_exist = OPTIONS[:io_file] && File.exist?(OPTIONS[:io_file])
|
88
|
+
|
89
|
+
str = ""
|
90
|
+
pp = PrettyPrint.new(str)
|
91
|
+
|
92
|
+
# Document each module that was monitored
|
93
|
+
INSTALLED.each do |mod|
|
94
|
+
|
95
|
+
# Skip it if we already have seen it
|
96
|
+
next if DOCUMENTED.include?(mod)
|
97
|
+
DOCUMENTED << mod
|
98
|
+
|
99
|
+
pp.text("class #{mod.to_s}")
|
100
|
+
pp.nest(2) do
|
101
|
+
pp.breakable("")
|
102
|
+
pp.text("include RubyBreaker::Broken")
|
103
|
+
meth_type_map = Inspector.inspect_all(mod)
|
104
|
+
meth_type_map.each { |meth_name, meth_type|
|
105
|
+
case meth_type
|
106
|
+
when MethodType
|
107
|
+
pp.breakable()
|
108
|
+
pp.text("typesig(\"")
|
109
|
+
TypeUnparser.unparse_pp(pp,meth_type)
|
110
|
+
pp.text("\")")
|
111
|
+
when MethodListType
|
112
|
+
meth_type.types.each { |real_meth_type|
|
113
|
+
pp.breakable()
|
114
|
+
pp.text("typesig(\"")
|
115
|
+
TypeUnparser.unparse_pp(pp,real_meth_type)
|
116
|
+
pp.text("\")")
|
117
|
+
}
|
118
|
+
else
|
119
|
+
# Can't happen
|
120
|
+
end
|
121
|
+
}
|
122
|
+
end
|
123
|
+
pp.breakable()
|
124
|
+
pp.text("end")
|
125
|
+
pp.breakable()
|
126
|
+
end
|
127
|
+
pp.flush
|
128
|
+
|
129
|
+
# First, display the result on the stdout if set
|
130
|
+
puts str if OPTIONS[:stdout]
|
131
|
+
|
132
|
+
# If this was a library mode run, exit now.
|
133
|
+
return if OPTIONS[:mode] == :lib
|
134
|
+
|
135
|
+
# Append the result to the input file (or create a new file)
|
136
|
+
open(OPTIONS[:io_file],"a") do |f|
|
137
|
+
unless io_exist
|
138
|
+
f.puts "# This file is auto-generated by RubyBreaker"
|
139
|
+
# f.puts "require \"rubybreaker\""
|
140
|
+
end
|
141
|
+
f.puts str
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
# This method will run the input file
|
147
|
+
def self.run()
|
148
|
+
|
149
|
+
# First, take care of the program file.
|
150
|
+
|
151
|
+
argv0 = ARGV[0]
|
152
|
+
prog_file = argv0
|
153
|
+
prog_file = File.expand_path(prog_file)
|
154
|
+
|
155
|
+
# It is ok to omit .rb extension. So try to see if prog_file.rb exists
|
156
|
+
if !File.exist?(prog_file) && !File.extname(prog_file) == ".rb"
|
157
|
+
prog_file = "#{prog_file}.rb"
|
158
|
+
end
|
159
|
+
|
160
|
+
if !File.exist?(prog_file)
|
161
|
+
puts "ERROR: '#{argv0}' is an invalid file."
|
162
|
+
exit(1)
|
163
|
+
end
|
164
|
+
|
165
|
+
# Then, input/output file if specified
|
166
|
+
if !OPTIONS[:io_file] || OPTIONS[:io_file].empty?
|
167
|
+
OPTIONS[:io_file] = "#{File.basename(argv0, ".rb")}.#{EXTENSION}"
|
168
|
+
end
|
169
|
+
OPTIONS[:io_file] = File.absolute_path(OPTIONS[:io_file])
|
170
|
+
|
171
|
+
if OPTIONS[:rubylib]
|
172
|
+
# Load the core library type documentation
|
173
|
+
eval("require \"#{File.dirname(__FILE__)}/rubybreaker/rubylib\"", TOPLEVEL_BINDING)
|
174
|
+
end
|
175
|
+
|
176
|
+
# Read the input file first (as it might contain type documentation
|
177
|
+
# already)
|
178
|
+
Main.input()
|
179
|
+
|
180
|
+
# Finally, require the program file! Let it run! Wheeee!
|
181
|
+
eval "require '#{prog_file}'", TOPLEVEL_BINDING
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
|
187
|
+
# Just redirecting
|
188
|
+
def self.monitor()
|
189
|
+
Main.setup()
|
190
|
+
end
|
191
|
+
|
192
|
+
end
|
193
|
+
|