rubybreaker 0.0.1
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/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
|
+
|