rdl 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +7 -6
- data/CHANGES.md +29 -0
- data/README.md +94 -26
- data/lib/rdl/boot.rb +82 -41
- data/lib/rdl/boot_rails.rb +5 -0
- data/lib/rdl/config.rb +9 -1
- data/lib/rdl/query.rb +2 -2
- data/lib/rdl/typecheck.rb +972 -225
- data/lib/rdl/types/annotated_arg.rb +8 -0
- data/lib/rdl/types/ast_node.rb +73 -0
- data/lib/rdl/types/bot.rb +8 -0
- data/lib/rdl/types/bound_arg.rb +63 -0
- data/lib/rdl/types/computed.rb +48 -0
- data/lib/rdl/types/dependent_arg.rb +9 -0
- data/lib/rdl/types/dynamic.rb +61 -0
- data/lib/rdl/types/finite_hash.rb +54 -9
- data/lib/rdl/types/generic.rb +33 -0
- data/lib/rdl/types/intersection.rb +8 -0
- data/lib/rdl/types/lexer.rex +6 -1
- data/lib/rdl/types/lexer.rex.rb +13 -1
- data/lib/rdl/types/method.rb +14 -0
- data/lib/rdl/types/nominal.rb +8 -0
- data/lib/rdl/types/non_null.rb +8 -0
- data/lib/rdl/types/optional.rb +8 -0
- data/lib/rdl/types/parser.racc +31 -5
- data/lib/rdl/types/parser.tab.rb +540 -302
- data/lib/rdl/types/rdl_types.rb +45 -0
- data/lib/rdl/types/singleton.rb +14 -1
- data/lib/rdl/types/string.rb +104 -0
- data/lib/rdl/types/structural.rb +8 -0
- data/lib/rdl/types/top.rb +8 -0
- data/lib/rdl/types/tuple.rb +32 -8
- data/lib/rdl/types/type.rb +54 -11
- data/lib/rdl/types/union.rb +41 -2
- data/lib/rdl/types/var.rb +10 -0
- data/lib/rdl/types/vararg.rb +8 -0
- data/lib/rdl/util.rb +13 -10
- data/lib/rdl/wrap.rb +271 -27
- data/lib/rdl_disable.rb +16 -2
- data/lib/types/active_record.rb +1 -0
- data/lib/types/core/array.rb +442 -23
- data/lib/types/core/basic_object.rb +3 -3
- data/lib/types/core/bigdecimal.rb +5 -0
- data/lib/types/core/class.rb +2 -0
- data/lib/types/core/dir.rb +3 -3
- data/lib/types/core/enumerable.rb +4 -4
- data/lib/types/core/enumerator.rb +1 -1
- data/lib/types/core/file.rb +4 -4
- data/lib/types/core/float.rb +203 -0
- data/lib/types/core/hash.rb +390 -15
- data/lib/types/core/integer.rb +223 -10
- data/lib/types/core/io.rb +2 -2
- data/lib/types/core/kernel.rb +8 -5
- data/lib/types/core/marshal.rb +3 -0
- data/lib/types/core/module.rb +3 -3
- data/lib/types/core/numeric.rb +0 -2
- data/lib/types/core/object.rb +5 -5
- data/lib/types/core/pathname.rb +2 -2
- data/lib/types/core/process.rb +1 -3
- data/lib/types/core/range.rb +1 -1
- data/lib/types/core/regexp.rb +2 -2
- data/lib/types/core/set.rb +1 -1
- data/lib/types/core/string.rb +408 -16
- data/lib/types/core/symbol.rb +3 -3
- data/lib/types/core/time.rb +1 -1
- data/lib/types/core/uri.rb +13 -13
- data/lib/types/rails/_helpers.rb +7 -1
- data/lib/types/rails/action_controller/mime_responds.rb +2 -0
- data/lib/types/rails/active_record/associations.rb +42 -30
- data/lib/types/rails/active_record/comp_types.rb +637 -0
- data/lib/types/rails/active_record/finder_methods.rb +1 -1
- data/lib/types/rails/active_record/model_schema.rb +28 -16
- data/lib/types/rails/active_record/relation.rb +5 -3
- data/lib/types/rails/active_record/sql-strings.rb +166 -0
- data/lib/types/rails/string.rb +1 -1
- data/lib/types/sequel.rb +1 -0
- data/lib/types/sequel/comp_types.rb +581 -0
- data/rdl.gemspec +5 -4
- data/test/test_alias.rb +4 -0
- data/test/test_array_types.rb +244 -0
- data/test/test_bound_types.rb +80 -0
- data/test/test_contract.rb +4 -0
- data/test/test_dsl.rb +5 -0
- data/test/test_dyn_comptype_checks.rb +206 -0
- data/test/test_generic.rb +21 -20
- data/test/test_hash_types.rb +322 -0
- data/test/test_intersection.rb +1 -0
- data/test/test_le.rb +29 -4
- data/test/test_member.rb +3 -1
- data/test/test_parser.rb +5 -0
- data/test/test_query.rb +1 -0
- data/test/test_rdl.rb +63 -28
- data/test/test_rdl_type.rb +4 -0
- data/test/test_string_types.rb +102 -0
- data/test/test_type_contract.rb +59 -37
- data/test/test_typecheck.rb +480 -75
- data/test/test_types.rb +17 -0
- data/test/test_wrap.rb +5 -0
- metadata +35 -5
- data/lib/types/rails/active_record/schema_types.rb +0 -51
data/lib/rdl/boot_rails.rb
CHANGED
@@ -4,6 +4,11 @@ if Rails.env.development? || Rails.env.test?
|
|
4
4
|
|
5
5
|
require_relative "../types/rails/_helpers.rb" # load type aliases first
|
6
6
|
Dir[File.dirname(__FILE__) + "/../types/rails/**/*.rb"].each { |f| require f }
|
7
|
+
class RDLRailtie < ::Rails::Railtie
|
8
|
+
config.after_initialize do
|
9
|
+
RDL.load_rails_schema
|
10
|
+
end
|
11
|
+
end
|
7
12
|
elsif Rails.env.production?
|
8
13
|
require 'rdl_disable'
|
9
14
|
class ActionController::Base
|
data/lib/rdl/config.rb
CHANGED
@@ -6,7 +6,8 @@ class RDL::Config
|
|
6
6
|
attr_accessor :nowrap
|
7
7
|
attr_accessor :gather_stats
|
8
8
|
attr_reader :report # writer is custom defined
|
9
|
-
attr_accessor :
|
9
|
+
attr_accessor :weak_update_promote, :widen_bound, :promote_widen, :use_comp_types, :check_comp_types
|
10
|
+
attr_accessor :type_defaults, :pre_defaults, :post_defaults, :rerun_comp_types, :assume_dyn_type
|
10
11
|
|
11
12
|
def initialize
|
12
13
|
@nowrap = Set.new # Set of symbols
|
@@ -14,9 +15,16 @@ class RDL::Config
|
|
14
15
|
@report = false # if this is enabled by default, modify @at_exit_installed
|
15
16
|
@guess_types = [] # same as above
|
16
17
|
@at_exit_installed = false
|
18
|
+
@weak_update_promote = false
|
19
|
+
@promote_widen = false
|
17
20
|
@type_defaults = { wrap: true, typecheck: false }
|
18
21
|
@pre_defaults = { wrap: true }
|
19
22
|
@post_defaults = { wrap: true }
|
23
|
+
@assume_dyn_type = false
|
24
|
+
@widen_bound = 5
|
25
|
+
@use_comp_types = true
|
26
|
+
@check_comp_types = false ## this for dynamically checking that the result of a computed type still holds
|
27
|
+
@rerun_comp_types = false ## this is for dynamically checking that a type computation still evaluates to the same thing as it did at type checking time
|
20
28
|
end
|
21
29
|
|
22
30
|
def report=(val)
|
data/lib/rdl/query.rb
CHANGED
@@ -71,7 +71,7 @@ module RDL
|
|
71
71
|
|
72
72
|
def self.query(q)
|
73
73
|
RDL::Globals.contract_switch.off {
|
74
|
-
if q =~ /^[A-Z]\w*(#|\.)([a-z_]\w*(!|\?|=)?|!|~|\+|\*\*|-|\*|\/|%|<<|>>|&|\||\^|<|<=|=>|>|==|===|!=|=~|!~|<=>|\[\]|\[\]=)$/
|
74
|
+
if q =~ /^[A-Z]\w*(::[A-Z]\w*)*(#|\.)([a-z_]\w*(!|\?|=)?|!|~|\+|\*\*|-|\*|\/|%|<<|>>|&|\||\^|<|<=|=>|>|==|===|!=|=~|!~|<=>|\[\]|\[\]=)$/
|
75
75
|
typs = RDL::Query.method_query(q)
|
76
76
|
if typs.nil? then
|
77
77
|
puts "No types for #{q}"
|
@@ -80,7 +80,7 @@ module RDL
|
|
80
80
|
puts "#{q}: #{t}"
|
81
81
|
}
|
82
82
|
end
|
83
|
-
elsif q =~ /^[A-Z]\w*$/
|
83
|
+
elsif q =~ /^[A-Z]\w*(::[A-Z]\w*)*$/
|
84
84
|
typs = RDL::Query.class_query(q)
|
85
85
|
if typs.nil? then
|
86
86
|
puts "No method types for #{q}"
|
data/lib/rdl/typecheck.rb
CHANGED
@@ -126,6 +126,11 @@ module RDL::Typecheck
|
|
126
126
|
else
|
127
127
|
typ = RDL::Type::UnionType.new(first_typ, *rest.map { |other| ((other.has_key? var) && other[var]) || RDL::Globals.types[:nil] })
|
128
128
|
typ = typ.canonical
|
129
|
+
if typ.instance_of?(RDL::Type::UnionType)
|
130
|
+
sings = 0
|
131
|
+
typ.types.each { |t| sings = sings + 1 if t.instance_of?(RDL::Type::SingletonType) }
|
132
|
+
typ = typ.widen if sings > RDL::Config.instance.widen_bound
|
133
|
+
end
|
129
134
|
env.env[var] = {type: typ, fixed: false}
|
130
135
|
end
|
131
136
|
}
|
@@ -208,10 +213,17 @@ module RDL::Typecheck
|
|
208
213
|
return ast
|
209
214
|
end
|
210
215
|
|
211
|
-
def self.typecheck(klass, meth)
|
216
|
+
def self.typecheck(klass, meth, ast=nil, types = nil, effects = nil)
|
212
217
|
@cur_meth = [klass, meth]
|
213
|
-
ast = get_ast(klass, meth)
|
214
|
-
types = RDL::Globals.info.get(klass, meth, :type)
|
218
|
+
ast = get_ast(klass, meth) unless ast
|
219
|
+
types = RDL::Globals.info.get(klass, meth, :type) unless types
|
220
|
+
effects = RDL::Globals.info.get(klass, meth, :effect) unless effects
|
221
|
+
if effects.empty? || effects[0] == nil
|
222
|
+
effect = nil
|
223
|
+
else
|
224
|
+
effect = [:+, :+]
|
225
|
+
effects.each { |e| effect = effect_union(effect, e) unless e.nil? } ## being very lazy about this right now, conservatively taking the union of all effects if there are multiple ones
|
226
|
+
end
|
215
227
|
raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == []
|
216
228
|
|
217
229
|
if ast.type == :def
|
@@ -234,24 +246,110 @@ module RDL::Typecheck
|
|
234
246
|
# initialize method must always return "self" or GenericType where base is "self"
|
235
247
|
error :bad_initialize_type, [], ast unless ((type.ret.is_a?(RDL::Type::VarType) && type.ret.name == :self) || (type.ret.is_a?(RDL::Type::GenericType) && type.ret.base.is_a?(RDL::Type::VarType) && type.ret.base.name == :self))
|
236
248
|
end
|
249
|
+
raise RuntimeError, "Type checking of methods with computed types is not currently supported." unless (type.args + [type.ret]).all? { |t| !t.instance_of?(RDL::Type::ComputedType) }
|
237
250
|
inst = {self: self_type}
|
238
251
|
type = type.instantiate inst
|
239
|
-
_, targs = args_hash({}, Env.new, type, args, ast, 'method')
|
252
|
+
_, targs = args_hash({}, Env.new(:self => self_type), type, args, ast, 'method')
|
240
253
|
targs[:self] = self_type
|
241
|
-
scope = { tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types }
|
254
|
+
scope = { tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types, eff: effect }
|
242
255
|
begin
|
243
256
|
old_captured = scope[:captured].dup
|
244
257
|
if body.nil?
|
245
258
|
body_type = RDL::Globals.types[:nil]
|
246
259
|
else
|
247
|
-
|
260
|
+
targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this
|
261
|
+
_, body_type, body_effect = tc(scope, Env.new(targs_dup.merge(scope[:captured])), body)
|
248
262
|
end
|
263
|
+
old_captured, scope[:captured] = widen_scopes(old_captured, scope[:captured])
|
249
264
|
end until old_captured == scope[:captured]
|
250
265
|
error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || meth == :initialize || body_type <= type.ret
|
266
|
+
error :bad_effect, [body_effect, effect], body unless body.nil? || effect.nil? || effect_leq(body_effect, effect)
|
251
267
|
}
|
268
|
+
if RDL::Config.instance.check_comp_types
|
269
|
+
new_meth = WrapCall.rewrite(ast) # rewrite ast to insert dynamic checks
|
270
|
+
RDL::Util.silent_warnings { RDL::Util.to_class(klass).class_eval(new_meth) } # redefine method in the same class
|
271
|
+
end
|
252
272
|
RDL::Globals.info.set(klass, meth, :typechecked, true)
|
253
273
|
end
|
254
274
|
|
275
|
+
def self.effect_leq(e1, e2)
|
276
|
+
raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) }
|
277
|
+
p1, t1 = e1
|
278
|
+
p2, t2 = e2
|
279
|
+
case p1 #:+ always okay
|
280
|
+
when :~
|
281
|
+
return false if p2 == :+
|
282
|
+
when :-
|
283
|
+
return false if p2 == :+# || p2 == :~ going to treat this as okay, like a type cast
|
284
|
+
end
|
285
|
+
case t1 #:+ always okay
|
286
|
+
when :-
|
287
|
+
return false if t2 == :+
|
288
|
+
end
|
289
|
+
return true
|
290
|
+
end
|
291
|
+
|
292
|
+
def self.effect_union(e1, e2)
|
293
|
+
raise "Unexpected effect #{e1} or #{e2}" unless (e1+e2).all? { |e| [:+, :-, :~].include?(e) }#{ |e| e.is_a?(Symbol) }
|
294
|
+
p1, t1 = e1
|
295
|
+
p2, t2 = e2
|
296
|
+
pret = tret = nil
|
297
|
+
case p1
|
298
|
+
when :+
|
299
|
+
pret = p2
|
300
|
+
when :~
|
301
|
+
if p2 == :- then pret = :- else pret = :~ end
|
302
|
+
else
|
303
|
+
pret = :-
|
304
|
+
end
|
305
|
+
case t1
|
306
|
+
when :+
|
307
|
+
tret = t2
|
308
|
+
else
|
309
|
+
tret = :-
|
310
|
+
end
|
311
|
+
[pret, tret]
|
312
|
+
end
|
313
|
+
|
314
|
+
### TODO: clean up below. Should probably incorporate it into `targs.merge` call in `self.typecheck`.
|
315
|
+
def self.widen_scopes(h1, h2)
|
316
|
+
h1new = {}
|
317
|
+
h2new = {}
|
318
|
+
[[h1, h1new], [h2, h2new]].each { |hash, newhash|
|
319
|
+
hash.each { |k, v|
|
320
|
+
case v
|
321
|
+
when RDL::Type::TupleType
|
322
|
+
if v.params.size > 50
|
323
|
+
newhash[k] = v.promote
|
324
|
+
else
|
325
|
+
newhash[k] = v
|
326
|
+
end
|
327
|
+
when RDL::Type::FiniteHashType
|
328
|
+
if v.elts.size > 50
|
329
|
+
newhash[k] = v.promote
|
330
|
+
else
|
331
|
+
newhash[k] = v
|
332
|
+
end
|
333
|
+
when RDL::Type::PreciseStringType
|
334
|
+
if v.vals.size > 50 || (v.vals.size == 1 && v.vals[0].size > 50)
|
335
|
+
newhash[k] = RDL::Globals.types[:string]
|
336
|
+
else
|
337
|
+
newhash[k] = v
|
338
|
+
end
|
339
|
+
when RDL::Type::UnionType
|
340
|
+
if v.types.size > 50
|
341
|
+
newhash[k] = v.widen
|
342
|
+
else
|
343
|
+
newhash[k] = v
|
344
|
+
end
|
345
|
+
else
|
346
|
+
newhash[k] = v
|
347
|
+
end
|
348
|
+
}
|
349
|
+
}
|
350
|
+
[h1new, h2new]
|
351
|
+
end
|
352
|
+
|
255
353
|
# [+ scope +] is used to typecheck default values for optional arguments
|
256
354
|
# [+ env +] is used to typecheck default values for optional arguments
|
257
355
|
# [+ type +] is a MethodType
|
@@ -267,10 +365,12 @@ module RDL::Typecheck
|
|
267
365
|
args.children.each { |arg|
|
268
366
|
error :type_args_fewer, [kind, kind], arg if tpos >= type.args.length && arg.type != :blockarg # blocks could be called with yield
|
269
367
|
targ = type.args[tpos]
|
368
|
+
(if (targ.is_a?(RDL::Type::AnnotatedArgType) || targ.is_a?(RDL::Type::DependentArgType) || targ.is_a?(RDL::Type::BoundArgType)) then targ = targ.type end)
|
270
369
|
if arg.type == :arg
|
271
370
|
error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg if targ.optional?
|
272
371
|
error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg if targ.vararg?
|
273
372
|
targs[arg.children[0]] = targ
|
373
|
+
env = env.merge(Env.new(arg.children[0] => targ))
|
274
374
|
tpos += 1
|
275
375
|
elsif arg.type == :optarg
|
276
376
|
error :type_arg_kind_mismatch, [kind, 'vararg', 'optional'], arg if targ.vararg?
|
@@ -278,6 +378,7 @@ module RDL::Typecheck
|
|
278
378
|
env, default_type = tc(scope, env, arg.children[1])
|
279
379
|
error :optional_default_type, [default_type, targ.type], arg.children[1] unless default_type <= targ.type
|
280
380
|
targs[arg.children[0]] = targ.type
|
381
|
+
env = env.merge(Env.new(arg.children[0] => targ.type))
|
281
382
|
tpos += 1
|
282
383
|
elsif arg.type == :restarg
|
283
384
|
error :type_arg_kind_mismatch, [kind, 'optional', 'vararg'], arg if targ.optional?
|
@@ -292,6 +393,7 @@ module RDL::Typecheck
|
|
292
393
|
error :type_args_kw_mismatch, [kind, 'optional', kw, 'required'], arg if tkw.is_a? RDL::Type::OptionalType
|
293
394
|
kw_args_matched << kw
|
294
395
|
targs[kw] = tkw
|
396
|
+
env = env.merge(Env.new(kw => tkw))
|
295
397
|
elsif arg.type == :kwoptarg
|
296
398
|
error :type_args_no_kws, [kind], arg unless targ.is_a?(RDL::Type::FiniteHashType)
|
297
399
|
kw = arg.children[0]
|
@@ -302,6 +404,7 @@ module RDL::Typecheck
|
|
302
404
|
error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless default_type <= tkw.type
|
303
405
|
kw_args_matched << kw
|
304
406
|
targs[kw] = tkw.type
|
407
|
+
env = env.merge(Env.new(kw => tkw.type))
|
305
408
|
elsif arg.type == :kwrestarg
|
306
409
|
error :type_args_no_kws, [kind], e unless targ.is_a?(RDL::Type::FiniteHashType)
|
307
410
|
error :type_args_no_kw_rest, [kind], arg if targ.rest.nil?
|
@@ -320,7 +423,9 @@ module RDL::Typecheck
|
|
320
423
|
error :type_args_kw_more, [kind, rest.map { |s| s.to_s }.join(", "), kind], ast unless rest.empty?
|
321
424
|
error :type_args_kw_rest, [kind], ast unless kw_rest_matched || type.args[tpos].rest.nil?
|
322
425
|
else
|
323
|
-
|
426
|
+
unless (type.args.length == 1) && (type.args[0].is_a?(RDL::Type::OptionalType) || type.args[0].is_a?(RDL::Type::VarargType)) && args.children.empty?
|
427
|
+
error :type_args_more, [kind, kind], (if args.children.empty? then ast else args end) if (type.args.length != tpos)
|
428
|
+
end
|
324
429
|
end
|
325
430
|
return [env, targs]
|
326
431
|
end
|
@@ -350,39 +455,55 @@ module RDL::Typecheck
|
|
350
455
|
# [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables
|
351
456
|
# [+ env +] is the (local variable) Env
|
352
457
|
# [+ e +] is the expression to type check
|
353
|
-
# Returns [env', t], where env' is the type environment at the end of the expression
|
458
|
+
# Returns [env', t, eff], where env' is the type environment at the end of the expression
|
354
459
|
# and t is the type of the expression. t is always canonical.
|
355
460
|
def self.tc(scope, env, e)
|
356
461
|
case e.type
|
357
462
|
when :nil
|
358
|
-
[env, RDL::Globals.types[:nil]]
|
463
|
+
[env, RDL::Globals.types[:nil], [:+, :+]]
|
359
464
|
when :true
|
360
|
-
[env, RDL::Globals.types[:true]]
|
465
|
+
[env, RDL::Globals.types[:true], [:+, :+]]
|
361
466
|
when :false
|
362
|
-
[env, RDL::Globals.types[:false]]
|
363
|
-
when :
|
364
|
-
[env, RDL::Type::
|
467
|
+
[env, RDL::Globals.types[:false], [:+, :+]]
|
468
|
+
when :str, :string
|
469
|
+
[env, RDL::Type::PreciseStringType.new(e.children[0]), [:+, :+]]
|
470
|
+
when :complex, :rational # constants
|
471
|
+
[env, RDL::Type::NominalType.new(e.children[0].class), [:+, :+]]
|
365
472
|
when :int, :float, :sym # singletons
|
366
|
-
[env, RDL::Type::SingletonType.new(e.children[0])]
|
473
|
+
[env, RDL::Type::SingletonType.new(e.children[0]), [:+, :+]]
|
367
474
|
when :dstr, :xstr # string (or execute-string) with interpolation
|
475
|
+
effi = [:+, :+]
|
476
|
+
prec_str = []
|
368
477
|
envi = env
|
369
|
-
e.children.each { |ei|
|
370
|
-
|
478
|
+
e.children.each { |ei|
|
479
|
+
envi, ti, eff_new = tc(scope, envi, ei)
|
480
|
+
effi = effect_union(effi, eff_new)
|
481
|
+
if ei.type == :str || ei.type == :string
|
482
|
+
## for strings, just append the string itself
|
483
|
+
prec_str << ei.children[0]
|
484
|
+
else
|
485
|
+
## for interpolated part, append the interpolated part
|
486
|
+
prec_str << (if ti.is_a?(RDL::Type::SingletonType) then ti.val.to_s else ti end)
|
487
|
+
end
|
488
|
+
}
|
489
|
+
[envi, RDL::Type::PreciseStringType.new(*prec_str), effi]
|
371
490
|
when :dsym # symbol with interpolation
|
372
491
|
envi = env
|
373
492
|
e.children.each { |ei| envi, _ = tc(scope, envi, ei) }
|
374
|
-
[envi, RDL::Globals.types[:symbol]]
|
493
|
+
[envi, RDL::Globals.types[:symbol], [:+, :+]]
|
375
494
|
when :regexp
|
376
495
|
envi = env
|
377
496
|
e.children.each { |ei| envi, _ = tc(scope, envi, ei) unless ei.type == :regopt }
|
378
|
-
[envi, RDL::Globals.types[:regexp]]
|
497
|
+
[envi, RDL::Globals.types[:regexp], [:+, :+]]
|
379
498
|
when :array
|
380
499
|
envi = env
|
381
500
|
tis = []
|
382
501
|
is_array = false
|
502
|
+
effi = [:+, :+]
|
383
503
|
e.children.each { |ei|
|
384
504
|
if ei.type == :splat
|
385
|
-
envi, ti = tc(scope, envi, ei.children[0]);
|
505
|
+
envi, ti, new_eff = tc(scope, envi, ei.children[0]);
|
506
|
+
effi = effect_union(effi, new_eff)
|
386
507
|
if ti.is_a? RDL::Type::TupleType
|
387
508
|
ti.cant_promote! # must remain a tuple
|
388
509
|
tis.concat(ti.params)
|
@@ -407,30 +528,35 @@ module RDL::Typecheck
|
|
407
528
|
tis << ti # splat does nothing
|
408
529
|
end
|
409
530
|
else
|
410
|
-
envi, ti = tc(scope, envi, ei);
|
531
|
+
envi, ti, new_eff = tc(scope, envi, ei);
|
532
|
+
effi = effect_union(effi, new_eff)
|
411
533
|
tis << ti
|
412
534
|
end
|
413
535
|
}
|
414
536
|
if is_array
|
415
|
-
[envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical)]
|
537
|
+
[envi, RDL::Type::GenericType.new(RDL::Globals.types[:array], RDL::Type::UnionType.new(*tis).canonical), effi]
|
416
538
|
else
|
417
|
-
[envi, RDL::Type::TupleType.new(*tis)]
|
539
|
+
[envi, RDL::Type::TupleType.new(*tis), effi]
|
418
540
|
end
|
419
541
|
when :hash
|
420
542
|
envi = env
|
421
543
|
tlefts = []
|
422
544
|
trights = []
|
423
545
|
is_fh = true
|
546
|
+
effi = [:+, :+]
|
424
547
|
e.children.each { |p|
|
425
548
|
# each child is a pair
|
426
549
|
if p.type == :pair
|
427
|
-
envi, tleft = tc(scope, envi, p.children[0])
|
550
|
+
envi, tleft, effl = tc(scope, envi, p.children[0])
|
428
551
|
tlefts << tleft
|
429
|
-
|
552
|
+
effi = effect_union(effi, effl)
|
553
|
+
envi, tright, effr = tc(scope, envi, p.children[1])
|
430
554
|
trights << tright
|
555
|
+
effi = effect_union(effi, effr)
|
431
556
|
is_fh = false unless tleft.is_a?(RDL::Type::SingletonType)
|
432
557
|
elsif p.type == :kwsplat
|
433
|
-
envi, tkwsplat = tc(scope, envi, p.children[0])
|
558
|
+
envi, tkwsplat, new_eff = tc(scope, envi, p.children[0])
|
559
|
+
effi = effect_union(effi, new_eff)
|
434
560
|
if tkwsplat.is_a? RDL::Type::FiniteHashType
|
435
561
|
tkwsplat.cant_promote! # must remain finite hash
|
436
562
|
tlefts.concat(tkwsplat.elts.keys.map { |k| RDL::Type::SingletonType.new(k) })
|
@@ -449,41 +575,46 @@ module RDL::Typecheck
|
|
449
575
|
if is_fh
|
450
576
|
# keys are all symbols
|
451
577
|
fh = tlefts.map { |t| t.val }.zip(trights).to_h
|
452
|
-
[envi, RDL::Type::FiniteHashType.new(fh, nil)]
|
578
|
+
[envi, RDL::Type::FiniteHashType.new(fh, nil), effi]
|
453
579
|
else
|
454
580
|
tleft = RDL::Type::UnionType.new(*tlefts)
|
455
581
|
tright = RDL::Type::UnionType.new(*trights)
|
456
|
-
[envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright)]
|
582
|
+
[envi, RDL::Type::GenericType.new(RDL::Globals.types[:hash], tleft, tright), effi]
|
457
583
|
end
|
458
584
|
#TODO test!
|
459
585
|
# when :kwsplat # TODO!
|
460
586
|
when :irange, :erange
|
461
|
-
env1, t1 = tc(scope, env, e.children[0])
|
462
|
-
env2, t2 = tc(scope, env1, e.children[1])
|
587
|
+
env1, t1, eff1 = tc(scope, env, e.children[0])
|
588
|
+
env2, t2, eff2 = tc(scope, env1, e.children[1])
|
463
589
|
# promote singleton types to nominal types; safe since Ranges are immutable
|
464
590
|
t1 = RDL::Type::NominalType.new(t1.val.class) if t1.is_a? RDL::Type::SingletonType
|
465
591
|
t2 = RDL::Type::NominalType.new(t2.val.class) if t2.is_a? RDL::Type::SingletonType
|
466
592
|
error :nonmatching_range_type, [t1, t2], e unless t1 <= t2 || t2 <= t1
|
467
|
-
[env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1)]
|
593
|
+
[env2, RDL::Type::GenericType.new(RDL::Globals.types[:range], t1), effect_union(eff1, eff2)]
|
468
594
|
when :self
|
469
|
-
[env, env[:self]]
|
595
|
+
[env, env[:self], [:+, :+]]
|
470
596
|
when :lvar, :ivar, :cvar, :gvar
|
471
|
-
|
597
|
+
if e.type == :lvar then eff = [:+, :+] else eff = [:-, :+] end
|
598
|
+
tc_var(scope, env, e.type, e.children[0], e) + [eff]
|
472
599
|
when :lvasgn, :ivasgn, :cvasgn, :gvasgn
|
600
|
+
if e.type == :lvasgn || @cur_meth[1] == :initialize then eff = [:+, :+] else eff = [:-, :+] end
|
473
601
|
x = e.children[0]
|
474
602
|
# if local var, lhs is bound to nil before assignment is executed! only matters in type checking for locals
|
475
603
|
env = env.bind(x, RDL::Globals.types[:nil]) if ((e.type == :lvasgn) && (not (env.has_key? x)))
|
476
|
-
envright, tright = tc(scope, env, e.children[1])
|
477
|
-
tc_vasgn(scope, envright, e.type, x, tright, e)
|
604
|
+
envright, tright, effright = tc(scope, env, e.children[1])
|
605
|
+
tc_vasgn(scope, envright, e.type, x, tright, e)+[effect_union(eff, effright)]
|
478
606
|
when :masgn
|
479
607
|
# (masgn (mlhs (Xvasgn var-name) ... (Xvasgn var-name)) rhs)
|
608
|
+
effi = [:+, :+]
|
480
609
|
e.children[0].children.each { |asgn|
|
610
|
+
effi = effect_union(effi, [:-, :+]) if asgn.type != :lvasgn && @cur_meth != :initialize
|
481
611
|
next unless asgn.type == :lvasgn
|
482
612
|
x = e.children[0]
|
483
613
|
env = env.bind(x, RDL::Globals.types[:nil]) if (not (env.has_key? x)) # see lvasgn
|
484
614
|
# Note don't need to check outer_env here because will be checked by tc_vasgn below
|
485
615
|
}
|
486
|
-
envi, tright = tc(scope, env, e.children[1])
|
616
|
+
envi, tright, effright = tc(scope, env, e.children[1])
|
617
|
+
effi = effect_union(effi, effright)
|
487
618
|
lhs = e.children[0].children
|
488
619
|
if tright.is_a? RDL::Type::TupleType
|
489
620
|
tright.cant_promote! # must always remain a tuple because of the way type checking currently works
|
@@ -504,13 +635,13 @@ module RDL::Typecheck
|
|
504
635
|
}
|
505
636
|
splat = lhs[splat_ind]
|
506
637
|
envi, _ = tc_vasgn(scope, envi, splat.children[0].type, splat.children[0].children[0], RDL::Type::TupleType.new(*rhs), splat)
|
507
|
-
[envi, tright]
|
638
|
+
[envi, tright, effi]
|
508
639
|
else
|
509
640
|
error :masgn_num, [rhs.length, lhs.length], e unless lhs.length == rhs.length
|
510
641
|
lhs.zip(rhs).each { |left, right|
|
511
642
|
envi, _ = tc_vasgn(scope, envi, left.type, left.children[0], right, left)
|
512
643
|
}
|
513
|
-
[envi, tright]
|
644
|
+
[envi, tright, effi]
|
514
645
|
end
|
515
646
|
elsif (tright.is_a? RDL::Type::GenericType) && (tright.base == RDL::Globals.types[:array])
|
516
647
|
tasgn = tright.params[0]
|
@@ -521,53 +652,84 @@ module RDL::Typecheck
|
|
521
652
|
envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn)
|
522
653
|
end
|
523
654
|
}
|
524
|
-
[envi, tright]
|
655
|
+
[envi, tright, effi]
|
656
|
+
elsif (tright.is_a? RDL::Type::DynamicType)
|
657
|
+
tasgn = tright
|
658
|
+
lhs.each { |asgn|
|
659
|
+
if asgn.type == :splat
|
660
|
+
envi, _ = tc_vasgn(scope, envi, asgn.children[0].type, asgn.children[0].children[0], tright, asgn)
|
661
|
+
else
|
662
|
+
envi, _ = tc_vasgn(scope, envi, asgn.type, asgn.children[0], tasgn, asgn)
|
663
|
+
end
|
664
|
+
}
|
665
|
+
[env, tright, effi]
|
525
666
|
else
|
526
667
|
error :masgn_bad_rhs, [tright], e.children[1]
|
527
668
|
end
|
528
669
|
when :op_asgn
|
670
|
+
effi = [:+, :+]
|
529
671
|
if e.children[0].type == :send
|
530
672
|
# (op-asgn (send recv meth) :op operand)
|
531
673
|
meth = e.children[0].children[1]
|
532
|
-
envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv
|
674
|
+
envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv
|
675
|
+
effi = effect_union(effi, effleft)
|
533
676
|
elargs = e.children[0].children[2]
|
534
677
|
|
535
678
|
if elargs
|
536
|
-
envleft, elargs = tc(scope, envleft, elargs)
|
679
|
+
envleft, elargs, effleft = tc(scope, envleft, elargs)
|
680
|
+
effi = effect_union(effi, effleft)
|
537
681
|
largs = [elargs]
|
538
682
|
else
|
539
683
|
largs = []
|
540
684
|
end
|
541
|
-
|
542
|
-
|
543
|
-
envoperand, troperand = tc(scope, envleft, e.children[2]) # operand
|
544
|
-
|
545
|
-
|
685
|
+
tloperand, lopeff = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth()
|
686
|
+
effi = effect_union(effi, lopeff)
|
687
|
+
envoperand, troperand, effoperand = tc(scope, envleft, e.children[2]) # operand
|
688
|
+
effi = effect_union(effi, effoperand)
|
689
|
+
tright, effright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand)
|
690
|
+
effi = effect_union(effi, effright)
|
546
691
|
tright = largs.push(tright) if largs
|
547
692
|
mutation_meth = (meth.to_s + '=').to_sym
|
548
|
-
tres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e) # call recv.meth=(recvt.meth().op(operand))
|
549
|
-
|
693
|
+
tres, effres = tc_send(scope, envoperand, trecv, mutation_meth, tright, nil, e, true) # call recv.meth=(recvt.meth().op(operand))
|
694
|
+
effi = effect_union(effi, effres)
|
695
|
+
[envoperand, tres, effi]
|
550
696
|
else
|
551
697
|
# (op-asgn (Xvasgn var-name) :op operand)
|
552
698
|
x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_vasgn below
|
553
699
|
env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn
|
700
|
+
effi = effect_union(effi, [:-, :+]) if e.children[0].type != :lvasgn
|
554
701
|
envi, trecv = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to
|
555
|
-
envright, tright = tc(scope, envi, e.children[2]) # operand
|
556
|
-
|
557
|
-
|
702
|
+
envright, tright, effright = tc(scope, envi, e.children[2]) # operand
|
703
|
+
effi = effect_union(effi, effright)
|
704
|
+
trhs, effrhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e)
|
705
|
+
effi = effect_union(effrhs, effi)
|
706
|
+
tc_vasgn(scope, envright, e.children[0].type, x, trhs, e) + [effi]
|
558
707
|
end
|
559
708
|
when :and_asgn, :or_asgn
|
560
709
|
# very similar logic to op_asgn
|
710
|
+
effi = [:+, :+]
|
561
711
|
if e.children[0].type == :send
|
562
712
|
meth = e.children[0].children[1]
|
563
|
-
envleft, trecv = tc(scope, env, e.children[0].children[0]) # recv
|
564
|
-
|
565
|
-
|
713
|
+
envleft, trecv, effleft = tc(scope, env, e.children[0].children[0]) # recv
|
714
|
+
effi = effect_union(effi, effleft)
|
715
|
+
elargs = e.children[0].children[2]
|
716
|
+
if elargs
|
717
|
+
envleft, elargs, eleff = tc(scope, envleft, elargs)
|
718
|
+
effi = effect_union(effi, eleff)
|
719
|
+
largs = [elargs]
|
720
|
+
else
|
721
|
+
largs = []
|
722
|
+
end
|
723
|
+
tleft, effleft = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth()
|
724
|
+
effi = effect_union(effi, effleft)
|
725
|
+
envright, tright, effright = tc(scope, envleft, e.children[1]) # operand
|
726
|
+
effi = effect_union(effi, effright)
|
566
727
|
else
|
567
728
|
x = e.children[0].children[0] # Note don't need to check outer_env here because will be checked by tc_var below
|
568
729
|
env = env.bind(x, RDL::Globals.types[:nil]) if ((e.children[0].type == :lvasgn) && (not (env.has_key? x))) # see :lvasgn
|
569
730
|
envleft, tleft = tc_var(scope, env, @@asgn_to_var[e.children[0].type], x, e.children[0]) # var being assigned to
|
570
|
-
envright, tright = tc(scope, envleft, e.children[1])
|
731
|
+
envright, tright, effright = tc(scope, envleft, e.children[1])
|
732
|
+
effi = effect_union(effi, effright)
|
571
733
|
end
|
572
734
|
envi, trhs = (if tleft.is_a? RDL::Type::SingletonType
|
573
735
|
if e.type == :and_asgn
|
@@ -580,77 +742,37 @@ module RDL::Typecheck
|
|
580
742
|
end)
|
581
743
|
if e.children[0].type == :send
|
582
744
|
mutation_meth = (meth.to_s + '=').to_sym
|
583
|
-
|
584
|
-
|
745
|
+
rhs_array = [*largs, trhs]
|
746
|
+
tres, effres = tc_send(scope, envi, trecv, mutation_meth, rhs_array, nil, e)
|
747
|
+
effi = effect_union(effi, effres)
|
748
|
+
[envi, tres, effi]
|
585
749
|
else
|
586
|
-
tc_vasgn(scope, envi, e.children[0].type, x, trhs, e)
|
750
|
+
tc_vasgn(scope, envi, e.children[0].type, x, trhs, e) + [effi]
|
587
751
|
end
|
588
752
|
when :nth_ref, :back_ref
|
589
|
-
[env, RDL::Globals.types[:string]]
|
753
|
+
[env, RDL::Globals.types[:string], [:+, :+]]
|
590
754
|
when :const
|
591
|
-
c =
|
592
|
-
if e.children[0].nil?
|
593
|
-
case env[:self]
|
594
|
-
when RDL::Type::SingletonType
|
595
|
-
sclass = env[:self].val
|
596
|
-
when RDL::Type::NominalType
|
597
|
-
sclass = env[:self].klass
|
598
|
-
else
|
599
|
-
raise Exception, "unsupported env[self]=#{env[:self]}"
|
600
|
-
end
|
601
|
-
c1_str = RDL::Util.to_class_str(e.children[1])
|
602
|
-
self_klass_str = RDL::Util.to_class_str(sclass)
|
603
|
-
if self_klass_str.end_with?('::' + c1_str)
|
604
|
-
i = self_klass_str.rindex('::' + c1_str)
|
605
|
-
pc = RDL::Util.to_class self_klass_str[0..i-1]
|
606
|
-
c = pc.const_get(e.children[1])
|
607
|
-
else
|
608
|
-
if self_klass_str['::']
|
609
|
-
i = self_klass_str.rindex('::')
|
610
|
-
sclass = RDL::Util.to_class self_klass_str[0..i-1]
|
611
|
-
end
|
612
|
-
c = sclass.const_get(e.children[1])
|
613
|
-
end
|
614
|
-
elsif e.children[0].type == :cbase
|
615
|
-
raise "const cbase not implemented yet" # TODO!
|
616
|
-
elsif e.children[0].type == :lvar
|
617
|
-
raise "const lvar not implemented yet" # TODO!
|
618
|
-
elsif e.children[0].type == :const
|
619
|
-
if env[:self]
|
620
|
-
if env[:self].is_a?(RDL::Type::SingletonType)
|
621
|
-
ic = env[:self].val
|
622
|
-
else
|
623
|
-
ic = env[:self].class
|
624
|
-
end
|
625
|
-
else
|
626
|
-
ic = Object
|
627
|
-
end
|
628
|
-
c = get_leaves(e).inject(ic) {|m, c2| m.const_get(c2)}
|
629
|
-
else
|
630
|
-
raise "const other not implemented yet"
|
631
|
-
end
|
755
|
+
c = find_constant(env, e)
|
632
756
|
case c
|
633
|
-
when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class
|
634
|
-
[env, RDL::Type::SingletonType.new(c)]
|
635
|
-
when Module
|
636
|
-
t = RDL::Type::SingletonType.new(const_get(e.children[1]))
|
637
|
-
[env, t]
|
757
|
+
when TrueClass, FalseClass, Complex, Rational, Integer, Float, Symbol, Class, Module
|
758
|
+
[env, RDL::Type::SingletonType.new(c), [:+, :+]]
|
638
759
|
else
|
639
|
-
[env, RDL::Type::NominalType.new(
|
760
|
+
[env, RDL::Type::NominalType.new(c.class), [:+, :+]]
|
640
761
|
end
|
641
762
|
when :defined?
|
642
763
|
# do not type check subexpression, since it may not be type correct, e.g., undefined variable
|
643
|
-
[env, RDL::Globals.types[:string]]
|
764
|
+
[env, RDL::Globals.types[:string], [:+, :+]]
|
644
765
|
when :send, :csend
|
645
766
|
# children[0] = receiver; if nil, receiver is self
|
646
767
|
# children[1] = method name, a symbol
|
647
768
|
# children [2..] = actual args
|
648
|
-
return tc_var_type(scope, env, e) if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type
|
649
|
-
return tc_type_cast(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil?
|
650
|
-
return tc_note_type(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :rdl_note_type
|
651
|
-
return tc_instantiate!(scope, env, e) if is_RDL(e.children[0]) && e.children[1] == :instantiate!
|
769
|
+
return tc_var_type(scope, env, e) + [[:+, :+]] if (e.children[0].nil? || is_RDL(e.children[0])) && e.children[1] == :var_type
|
770
|
+
return tc_type_cast(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :type_cast && scope[:block].nil? ## TODO: could be more precise with effects here, punting for now
|
771
|
+
return tc_note_type(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :rdl_note_type
|
772
|
+
return tc_instantiate!(scope, env, e) + [[:+, :+]] if is_RDL(e.children[0]) && e.children[1] == :instantiate!
|
652
773
|
envi = env
|
653
774
|
tactuals = []
|
775
|
+
eff = [:+, :+]
|
654
776
|
block = scope[:block]
|
655
777
|
scope_merge(scope, block: nil, break: env, next: env) { |sscope|
|
656
778
|
e.children[2..-1].each { |ei|
|
@@ -667,24 +789,30 @@ module RDL::Typecheck
|
|
667
789
|
raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block]
|
668
790
|
envi, ti = tc(sscope, envi, ei.children[0])
|
669
791
|
# convert using to_proc if necessary
|
670
|
-
ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType
|
792
|
+
ti, effi = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType
|
793
|
+
eff = effect_union(eff, effi)
|
671
794
|
block = [ti, ei]
|
672
795
|
else
|
673
|
-
envi, ti = tc(sscope, envi, ei)
|
796
|
+
envi, ti, effi = tc(sscope, envi, ei)
|
797
|
+
eff = effect_union(eff, effi)
|
674
798
|
tactuals << ti
|
675
799
|
end
|
676
800
|
}
|
677
|
-
envi, trecv = if e.children[0].nil? then [envi, envi[:self]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver
|
678
|
-
|
801
|
+
envi, trecv, effrec = if e.children[0].nil? then [envi, envi[:self], [:+, :+]] else tc(sscope, envi, e.children[0]) end # if no receiver, self is receiver
|
802
|
+
eff = effect_union(effrec, eff)
|
803
|
+
tres, effres = tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e)
|
804
|
+
[envi, tres.canonical, effect_union(effres, eff) ]
|
679
805
|
}
|
680
806
|
when :yield
|
807
|
+
## TODO: effects
|
681
808
|
# very similar to send except the callee is the method's block
|
682
809
|
error :no_block, [], e unless scope[:tblock]
|
683
810
|
error :block_block, [], e if scope[:tblock].block
|
684
811
|
scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception
|
685
812
|
envi = env
|
686
813
|
tactuals = []
|
687
|
-
|
814
|
+
eff = [:+, :+]
|
815
|
+
e.children[0..-1].each { |ei| envi, ti, effi = tc(scope, envi, ei); tactuals << ti ; eff = effect_union(effi, eff)}
|
688
816
|
unless tc_arg_types(scope[:tblock], tactuals)
|
689
817
|
msg = <<RUBY
|
690
818
|
Block type: #{scope[:tblock]}
|
@@ -693,7 +821,7 @@ RUBY
|
|
693
821
|
msg.chomp! # remove trailing newline
|
694
822
|
error :block_type_error, [msg], e
|
695
823
|
end
|
696
|
-
[envi, scope[:tblock].ret]
|
824
|
+
[envi, scope[:tblock].ret, eff]
|
697
825
|
# tblock
|
698
826
|
when :block
|
699
827
|
# (block send block-args block-body)
|
@@ -701,16 +829,16 @@ RUBY
|
|
701
829
|
tc(bscope, env, e.children[0])
|
702
830
|
}
|
703
831
|
when :and, :or
|
704
|
-
envleft, tleft = tc(scope, env, e.children[0])
|
705
|
-
envright, tright = tc(scope, envleft, e.children[1])
|
832
|
+
envleft, tleft, effleft = tc(scope, env, e.children[0])
|
833
|
+
envright, tright, effright = tc(scope, envleft, e.children[1])
|
706
834
|
if tleft.is_a? RDL::Type::SingletonType
|
707
835
|
if e.type == :and
|
708
|
-
if tleft.val then [envright, tright] else [envleft, tleft] end
|
836
|
+
if tleft.val then [envright, tright, effright] else [envleft, tleft, effleft] end
|
709
837
|
else # e.type == :or
|
710
|
-
if tleft.val then [envleft, tleft] else [envright, tright] end
|
838
|
+
if tleft.val then [envleft, tleft, effleft] else [envright, tright, effright] end
|
711
839
|
end
|
712
840
|
else
|
713
|
-
[Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical]
|
841
|
+
[Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical, effect_union(effleft, effright)]
|
714
842
|
end
|
715
843
|
# when :not # in latest Ruby, not is a method call that could be redefined, so can't count on its behavior
|
716
844
|
# a1, t1 = tc(scope, a, e.children[0])
|
@@ -720,18 +848,20 @@ RUBY
|
|
720
848
|
# [a1, RDL::Globals.types[:bool]]
|
721
849
|
# end
|
722
850
|
when :if
|
723
|
-
envi, tguard = tc(scope, env, e.children[0]) # guard; any type allowed
|
851
|
+
envi, tguard, effguard = tc(scope, env, e.children[0]) # guard; any type allowed
|
724
852
|
# always type check both sides
|
725
|
-
envleft, tleft = if e.children[1].nil? then [envi, RDL::Globals.types[:nil]] else tc(scope, envi, e.children[1]) end # then
|
726
|
-
envright, tright = if e.children[2].nil? then [envi, RDL::Globals.types[:nil]] else tc(scope, envi, e.children[2]) end # else
|
853
|
+
envleft, tleft, effleft = if e.children[1].nil? then [envi, RDL::Globals.types[:nil], [:+, :+]] else tc(scope, envi, e.children[1]) end # then
|
854
|
+
envright, tright, effright = if e.children[2].nil? then [envi, RDL::Globals.types[:nil], [:+, :+]] else tc(scope, envi, e.children[2]) end # else
|
727
855
|
if tguard.is_a? RDL::Type::SingletonType
|
728
|
-
if tguard.val then [envleft, tleft] else [envright, tright] end
|
856
|
+
if tguard.val then [envleft, tleft, effleft] else [envright, tright, effright] end
|
729
857
|
else
|
730
|
-
|
858
|
+
eff = effect_union(effguard, effect_union(effleft, effright))
|
859
|
+
[Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical, eff]
|
731
860
|
end
|
732
861
|
when :case
|
733
862
|
envi = env
|
734
|
-
envi, tcontrol = tc(scope, envi, e.children[0]) unless e.children[0].nil? # the control expression, which make be nil
|
863
|
+
envi, tcontrol, effcontrol = tc(scope, envi, e.children[0]) unless e.children[0].nil? # the control expression, which make be nil
|
864
|
+
effi = effcontrol ? effcontrol : [:+, :+]
|
735
865
|
# for each guard, invoke guard === control expr, then possibly do body, possibly short-circuiting arbitrary later stuff
|
736
866
|
tbodies = []
|
737
867
|
envbodies = []
|
@@ -740,7 +870,8 @@ RUBY
|
|
740
870
|
envguards = []
|
741
871
|
tguards = []
|
742
872
|
wclause.children[0..-2].each { |guard| # first wclause.length-1 children are the guards
|
743
|
-
envi, tguard = tc(scope, envi, guard) # guard type can be anything
|
873
|
+
envi, tguard, effguard = tc(scope, envi, guard) # guard type can be anything
|
874
|
+
effi = effect_union(effi, effguard)
|
744
875
|
tguards << tguard
|
745
876
|
tc_send(scope, envi, tguard, :===, [tcontrol], nil, guard) unless tcontrol.nil?
|
746
877
|
envguards << envi
|
@@ -773,7 +904,8 @@ RUBY
|
|
773
904
|
envbody = initial_env
|
774
905
|
tbody = RDL::Globals.types[:nil]
|
775
906
|
else
|
776
|
-
envbody, tbody = tc(scope, initial_env, wclause.children[-1]) # last wclause child is body
|
907
|
+
envbody, tbody, effbody = tc(scope, initial_env, wclause.children[-1]) # last wclause child is body
|
908
|
+
effi = effect_union(effi, effbody)
|
777
909
|
end
|
778
910
|
|
779
911
|
tbodies << tbody
|
@@ -784,53 +916,61 @@ RUBY
|
|
784
916
|
envbodies << envi
|
785
917
|
else
|
786
918
|
# there is an else clause
|
787
|
-
envelse, telse = tc(scope, envi, e.children[-1])
|
919
|
+
envelse, telse, effelse = tc(scope, envi, e.children[-1])
|
920
|
+
effi = effect_union(effi, effelse)
|
788
921
|
tbodies << telse
|
789
922
|
envbodies << envelse
|
790
923
|
end
|
791
|
-
return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical]
|
924
|
+
return [Env.join(e, *envbodies), RDL::Type::UnionType.new(*tbodies).canonical, effi]
|
792
925
|
when :while, :until
|
793
926
|
# break: loop exit, i.e., right after loop guard; may take argument
|
794
927
|
# next: before loop guard; argument not allowed
|
795
928
|
# retry: not allowed
|
796
929
|
# redo: after loop guard, which is same as break
|
797
|
-
env_break, _ = tc(scope, env, e.children[0]) # guard can have any type, may exit after checking guard
|
930
|
+
env_break, _, effi = tc(scope, env, e.children[0]) # guard can have any type, may exit after checking guard
|
798
931
|
scope_merge(scope, break: env_break, tbreak: RDL::Globals.types[:nil], next: env, redo: env_break) { |lscope|
|
799
932
|
begin
|
800
933
|
old_break = lscope[:break]
|
801
934
|
old_next = lscope[:next]
|
802
935
|
old_tbreak = lscope[:tbreak]
|
803
936
|
if e.children[1]
|
804
|
-
env_body, _ = tc(lscope, lscope[:break], e.children[1]) # loop runs
|
937
|
+
env_body, _, eff_body = tc(lscope, lscope[:break], e.children[1]) # loop runs
|
938
|
+
effi = effect_union(effi, eff_body)
|
805
939
|
lscope[:next] = Env.join(e, lscope[:next], env_body)
|
806
940
|
end
|
807
|
-
env_guard, _ = tc(lscope, lscope[:next], e.children[0]) # then guard runs
|
941
|
+
env_guard, _, eff_guard = tc(lscope, lscope[:next], e.children[0]) # then guard runs
|
942
|
+
effi = effect_union(eff_guard, effi)
|
808
943
|
lscope[:break] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:redo], env_guard)
|
809
944
|
end until old_break == lscope[:break] && old_next == lscope[:next] && old_tbreak == lscope[:tbreak]
|
810
|
-
|
945
|
+
eff = effect_union(effi, [:+, :-]) ## conservative approximation
|
946
|
+
[lscope[:break], lscope[:tbreak].canonical, eff]
|
811
947
|
}
|
812
948
|
when :while_post, :until_post
|
813
949
|
# break: loop exit; note may exit loop before hitting guard once; maybe take argument
|
814
950
|
# next: before loop guard; argument not allowed
|
815
951
|
# retry: not allowed
|
816
952
|
# redo: beginning of body, which is same as after guard, i.e., same as break
|
953
|
+
effi = [:+, :-] ## conservative approximation
|
817
954
|
scope_merge(scope, break: nil, tbreak: RDL::Globals.types[:nil], next: nil, redo: nil) { |lscope|
|
818
955
|
if e.children[1]
|
819
|
-
env_body, _ = tc(lscope, env, e.children[1])
|
956
|
+
env_body, _, eff_body = tc(lscope, env, e.children[1])
|
957
|
+
effi = effect_union(effi, eff_body)
|
820
958
|
lscope[:next] = Env.join(e, lscope[:next], env_body)
|
821
959
|
end
|
822
960
|
begin
|
823
961
|
old_break = lscope[:break]
|
824
962
|
old_next = lscope[:next]
|
825
963
|
old_tbreak = lscope[:tbreak]
|
826
|
-
env_guard, _ = tc(lscope, lscope[:next], e.children[0])
|
964
|
+
env_guard, _, eff_guard = tc(lscope, lscope[:next], e.children[0])
|
965
|
+
effi = effect_union(effi, eff_guard)
|
827
966
|
lscope[:break] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:redo], env_guard)
|
828
967
|
if e.children[1]
|
829
|
-
env_body, _ = tc(lscope, lscope[:break], e.children[1])
|
968
|
+
env_body, _, eff_body = tc(lscope, lscope[:break], e.children[1])
|
969
|
+
effi = effect_union(effi, eff_body)
|
830
970
|
lscope[:next] = Env.join(e, lscope[:next], env_body)
|
831
971
|
end
|
832
972
|
end until old_break == lscope[:break] && old_next == lscope[:next] && old_tbreak == lscope[:tbreak]
|
833
|
-
[lscope[:break], lscope[:tbreak].canonical]
|
973
|
+
[lscope[:break], lscope[:tbreak].canonical, effi]
|
834
974
|
}
|
835
975
|
when :for
|
836
976
|
# (for (lvasgn var) collection body)
|
@@ -841,19 +981,33 @@ RUBY
|
|
841
981
|
raise RuntimeError, "Loop variable #{e.children[0]} in for unsupported" unless e.children[0].type == :lvasgn
|
842
982
|
# TODO: mlhs in e.children[0]
|
843
983
|
x = e.children[0].children[0] # loop variable
|
844
|
-
|
984
|
+
effi = [:+, :-]
|
985
|
+
envi, tcollect, effcoll = tc(scope, env, e.children[1]) # collection to iterate through
|
986
|
+
effi = effect_union(effcoll, effi)
|
845
987
|
teaches = nil
|
846
988
|
tcollect = tcollect.canonical
|
847
989
|
case tcollect
|
848
990
|
when RDL::Type::NominalType
|
849
|
-
|
850
|
-
|
991
|
+
self_klass = tcollect.klass
|
992
|
+
teaches, eeaches = lookup(scope, tcollect.name, :each, e.children[1])
|
993
|
+
teaches = filter_comp_types(teaches, RDL::Config.instance.use_comp_types)
|
994
|
+
when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType, RDL::Type::PreciseStringType
|
851
995
|
unless tcollect.is_a? RDL::Type::GenericType
|
852
|
-
error :tuple_finite_hash_promote, (if tcollect.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e.children[1] unless tcollect.promote!
|
996
|
+
error :tuple_finite_hash_promote, (if tcollect.is_a? RDL::Type::TupleType then ['tuple', 'Array'] elsif tcollect.is_a? RDL::Type::PreciseStringType then ['precise string', 'String'] else ['finite hash', 'Hash'] end), e.children[1] unless tcollect.promote!
|
853
997
|
tcollect = tcollect.canonical
|
854
998
|
end
|
855
|
-
|
999
|
+
self_klass = tcollect.base.klass
|
1000
|
+
teaches, eeaches = lookup(scope, tcollect.base.name, :each, e.children[1])
|
1001
|
+
teaches = filter_comp_types(teaches, RDL::Config.instance.use_comp_types)
|
856
1002
|
inst = tcollect.to_inst.merge(self: tcollect)
|
1003
|
+
teaches = teaches.map { |typ|
|
1004
|
+
block_types = (if typ.block then typ.block.args + [typ.block.ret] else [] end)
|
1005
|
+
if (typ.args+[typ.ret]+block_types).all? { |t| !t.instance_of?(RDL::Type::ComputedType) }
|
1006
|
+
typ
|
1007
|
+
else
|
1008
|
+
compute_types(typ, self_klass, tcollect, [])
|
1009
|
+
end
|
1010
|
+
}
|
857
1011
|
teaches = teaches.map { |typ| typ.instantiate(inst) }
|
858
1012
|
else
|
859
1013
|
error :for_collection, [tcollect], e.children[1]
|
@@ -882,42 +1036,47 @@ RUBY
|
|
882
1036
|
old_tnext = lscope[:tnext]
|
883
1037
|
if e.children[2]
|
884
1038
|
lscope[:break] = lscope[:break].bind(x, lscope[:tnext])
|
885
|
-
env_body, _ = tc(lscope, lscope[:break], e.children[2])
|
1039
|
+
env_body, _, eff_body = tc(lscope, lscope[:break], e.children[2])
|
1040
|
+
effi = effect_union(effi, eff_body)
|
886
1041
|
lscope[:break] = lscope[:next] = lscope[:redo] = Env.join(e, lscope[:break], lscope[:next], lscope[:redo], env_body)
|
887
1042
|
end
|
888
1043
|
end until old_break == lscope[:break] && old_tbreak == lscope[:tbreak] && old_tnext == lscope[:tnext]
|
889
|
-
[lscope[:break], lscope[:tbreak].canonical]
|
1044
|
+
[lscope[:break], lscope[:tbreak].canonical, [:-, :-]] ## going very conservative on this one
|
890
1045
|
}
|
891
1046
|
when :break, :redo, :next, :retry
|
892
1047
|
error :kw_not_allowed, [e.type], e unless scope.has_key? e.type
|
1048
|
+
effi = [:+, :-] ## conservative approximation
|
893
1049
|
if e.children[0]
|
894
1050
|
tkw_name = ('t' + e.type.to_s).to_sym
|
895
1051
|
error :kw_arg_not_allowed, [e.type], e unless scope.has_key? tkw_name
|
896
|
-
env, tkw = tc(scope, env, e.children[0])
|
1052
|
+
env, tkw, eff = tc(scope, env, e.children[0])
|
1053
|
+
effi = effect_union(eff, effi)
|
897
1054
|
scope[tkw_name] = RDL::Type::UnionType.new(scope[tkw_name], tkw)
|
898
1055
|
end
|
899
1056
|
scope[e.type] = Env.join(e, scope[e.type], env)
|
900
|
-
[env, RDL::Globals.types[:bot]]
|
1057
|
+
[env, RDL::Globals.types[:bot], effi]
|
901
1058
|
when :return
|
902
1059
|
# TODO return in lambda returns from lambda and not outer scope
|
903
1060
|
if e.children[0]
|
904
|
-
env1, t1 = tc(scope, env, e.children[0])
|
1061
|
+
env1, t1, effi = tc(scope, env, e.children[0])
|
905
1062
|
else
|
906
|
-
env1, t1 = [env, RDL::Globals.types[:nil]]
|
1063
|
+
env1, t1, effi = [env, RDL::Globals.types[:nil], [:+, :+]]
|
907
1064
|
end
|
908
1065
|
error :bad_return_type, [t1.to_s, scope[:tret]], e unless t1 <= scope[:tret]
|
909
|
-
[
|
1066
|
+
error :bad_effect, [effi, scope[:eff]], e unless (scope[:eff].nil? || effect_leq(effi, scope[:eff]))
|
1067
|
+
[env1, RDL::Globals.types[:bot], effi] # return is a void value expression
|
910
1068
|
when :begin, :kwbegin # sequencing
|
911
1069
|
envi = env
|
912
1070
|
ti = nil
|
913
|
-
|
914
|
-
|
1071
|
+
effi = [:+, :+]
|
1072
|
+
e.children.each { |ei| envi, ti, eff_new = tc(scope, envi, ei) ; effi = effect_union(effi, eff_new) }
|
1073
|
+
[envi, ti, effi]
|
915
1074
|
when :ensure
|
916
1075
|
# (ensure main-body ensure-body)
|
917
1076
|
# TODO exception control flow from main-body, vars initialized to nil
|
918
|
-
env_body, tbody = tc(scope, env, e.children[0])
|
919
|
-
env_ensure, _ = tc(scope, env_body, e.children[1])
|
920
|
-
[env_ensure, tbody] # value of ensure not returned
|
1077
|
+
env_body, tbody, eff1 = tc(scope, env, e.children[0])
|
1078
|
+
env_ensure, _, eff2 = tc(scope, env_body, e.children[1])
|
1079
|
+
[env_ensure, tbody, effect_union(eff1, eff2)] # value of ensure not returned
|
921
1080
|
when :rescue
|
922
1081
|
# (rescue main-body resbody1 resbody2 ... (else else-body))
|
923
1082
|
# resbodyi, else optional
|
@@ -925,35 +1084,41 @@ RUBY
|
|
925
1084
|
# is raised during main-body's execution before those varibles are assigned to.
|
926
1085
|
# similarly, local variables assigned in resbody will be initialized to nil even if the resbody
|
927
1086
|
# is never triggered
|
1087
|
+
effi = [:+, :+]
|
928
1088
|
scope_merge(scope, retry: env, exn: nil) { |rscope|
|
929
1089
|
begin
|
930
1090
|
old_retry = rscope[:retry]
|
931
|
-
env_body, tbody = tc(rscope, rscope[:retry], e.children[0])
|
1091
|
+
env_body, tbody, eff_body = tc(rscope, rscope[:retry], e.children[0])
|
1092
|
+
effi = effect_union(effi, eff_body)
|
932
1093
|
tres = [tbody] # note throw away inferred types from previous iterations---should be okay since should be monotonic
|
933
1094
|
env_res = [env_body]
|
934
1095
|
if rscope[:exn]
|
935
1096
|
e.children[1..-2].each { |resbody|
|
936
|
-
env_resbody, tresbody = tc(rscope, rscope[:exn], resbody)
|
1097
|
+
env_resbody, tresbody, eff_resbody = tc(rscope, rscope[:exn], resbody)
|
1098
|
+
effi = effect_union(eff_resbody, effi)
|
937
1099
|
tres << tresbody
|
938
1100
|
env_res << env_resbody
|
939
1101
|
}
|
940
1102
|
if e.children[-1]
|
941
|
-
env_else, telse = tc(rscope, rscope[:exn], e.children[-1])
|
1103
|
+
env_else, telse, eff_else = tc(rscope, rscope[:exn], e.children[-1])
|
1104
|
+
effi = effect_union(effi, eff_else)
|
942
1105
|
tres << telse
|
943
1106
|
env_res << env_else
|
944
1107
|
end
|
945
1108
|
end
|
946
1109
|
end until old_retry == rscope[:retry]
|
947
1110
|
# TODO: variables newly bound in *env_res should be unioned with nil
|
948
|
-
[Env.join(e, *env_res), RDL::Type::UnionType.new(*tres).canonical]
|
1111
|
+
[Env.join(e, *env_res), RDL::Type::UnionType.new(*tres).canonical, effi]
|
949
1112
|
}
|
950
1113
|
when :resbody
|
951
1114
|
# (resbody (array exns) (lvasgn var) rescue-body)
|
952
1115
|
envi = env
|
953
1116
|
texns = []
|
1117
|
+
effi = [:+, :+]
|
954
1118
|
if e.children[0]
|
955
1119
|
e.children[0].children.each { |exn|
|
956
|
-
envi, texn = tc(scope, envi, exn)
|
1120
|
+
envi, texn, eff_new = tc(scope, envi, exn)
|
1121
|
+
effi = effect_union(effi, eff_new)
|
957
1122
|
error :exn_type, [], exn unless texn.is_a?(RDL::Type::SingletonType) && texn.val.is_a?(Class)
|
958
1123
|
texns << RDL::Type::NominalType.new(texn.val)
|
959
1124
|
}
|
@@ -963,12 +1128,13 @@ RUBY
|
|
963
1128
|
if e.children[1]
|
964
1129
|
envi, _ = tc_vasgn(scope, envi, :lvasgn, e.children[1].children[0], RDL::Type::UnionType.new(*texns), e.children[1])
|
965
1130
|
end
|
966
|
-
tc(scope, envi, e.children[2])
|
1131
|
+
env_fin, t_fin, eff_fin = tc(scope, envi, e.children[2])
|
1132
|
+
[env_fin, t_fin, effect_union(eff_fin, effi)]
|
967
1133
|
when :super
|
968
1134
|
envi = env
|
969
1135
|
tactuals = []
|
970
1136
|
block = scope[:block]
|
971
|
-
|
1137
|
+
effi = [:+, :+]
|
972
1138
|
if block
|
973
1139
|
raise Exception, 'block in super method with block not supported'
|
974
1140
|
end
|
@@ -976,7 +1142,8 @@ RUBY
|
|
976
1142
|
scope_merge(scope, block: nil, break: env, next: env) { |sscope|
|
977
1143
|
e.children.each { |ei|
|
978
1144
|
if ei.type == :splat
|
979
|
-
envi, ti = tc(sscope, envi, ei.children[0])
|
1145
|
+
envi, ti, eff_new = tc(sscope, envi, ei.children[0])
|
1146
|
+
effi = effect_union(eff_new, effi)
|
980
1147
|
if ti.is_a? RDL::Type::TupleType
|
981
1148
|
tactuals.concat ti.params
|
982
1149
|
elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_array_type
|
@@ -986,18 +1153,22 @@ RUBY
|
|
986
1153
|
end
|
987
1154
|
elsif ei.type == :block_pass
|
988
1155
|
raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block]
|
989
|
-
envi, ti = tc(sscope, envi, ei.children[0])
|
1156
|
+
envi, ti, eff_new = tc(sscope, envi, ei.children[0])
|
1157
|
+
effi = effect_union(eff_new, effi)
|
990
1158
|
# convert using to_proc if necessary
|
991
|
-
ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType
|
1159
|
+
ti, effsend = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType
|
1160
|
+
effi = effect_union(effsend, effi)
|
992
1161
|
block = [ti, ei]
|
993
1162
|
else
|
994
|
-
envi, ti = tc(sscope, envi, ei)
|
1163
|
+
envi, ti, eff_new = tc(sscope, envi, ei)
|
1164
|
+
effi = effect_union(eff_new, effi)
|
995
1165
|
tactuals << ti
|
996
1166
|
end
|
997
1167
|
}
|
998
1168
|
|
999
1169
|
trecv = get_super_owner(envi[:self], @cur_meth[1])
|
1000
|
-
|
1170
|
+
tres, effres = tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e)
|
1171
|
+
[envi, tres.canonical, effect_union(effi, effres)]
|
1001
1172
|
}
|
1002
1173
|
when :zsuper
|
1003
1174
|
envi = env
|
@@ -1018,7 +1189,8 @@ RUBY
|
|
1018
1189
|
|
1019
1190
|
scope_merge(scope, block: nil, break: env, next: env) { |sscope|
|
1020
1191
|
trecv = get_super_owner(envi[:self], @cur_meth[1])
|
1021
|
-
|
1192
|
+
tres, effres = tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e)
|
1193
|
+
[envi, tres.canonical, effres]
|
1022
1194
|
}
|
1023
1195
|
else
|
1024
1196
|
raise RuntimeError, "Expression kind #{e.type} unsupported"
|
@@ -1040,13 +1212,17 @@ RUBY
|
|
1040
1212
|
end
|
1041
1213
|
when :ivar, :cvar, :gvar
|
1042
1214
|
klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self] end)
|
1043
|
-
|
1215
|
+
if RDL::Globals.info.has?(klass, name, :type)
|
1216
|
+
type = RDL::Globals.info.get(klass, name, :type)
|
1217
|
+
elsif RDL::Config.instance.assume_dyn_type
|
1218
|
+
type = RDL::Globals.types[:dyn]
|
1219
|
+
else
|
1044
1220
|
kind_text = (if kind == :ivar then "instance"
|
1045
1221
|
elsif kind == :cvar then "class"
|
1046
1222
|
else "global" end)
|
1047
1223
|
error :untyped_var, [kind_text, name, klass], e
|
1048
1224
|
end
|
1049
|
-
[env,
|
1225
|
+
[env, type.canonical]
|
1050
1226
|
else
|
1051
1227
|
raise RuntimeError, "unknown kind #{kind}"
|
1052
1228
|
end
|
@@ -1055,6 +1231,7 @@ RUBY
|
|
1055
1231
|
# Same arguments as tc_var except
|
1056
1232
|
# [+ tright +] is type of right-hand side
|
1057
1233
|
def self.tc_vasgn(scope, env, kind, name, tright, e)
|
1234
|
+
error :empty_env, [name], e if env.nil?
|
1058
1235
|
case kind
|
1059
1236
|
when :lvasgn
|
1060
1237
|
if ((scope[:captured] && scope[:captured].has_key?(name)) ||
|
@@ -1069,13 +1246,16 @@ RUBY
|
|
1069
1246
|
end
|
1070
1247
|
when :ivasgn, :cvasgn, :gvasgn
|
1071
1248
|
klass = (if kind == :gvasgn then RDL::Util::GLOBAL_NAME else env[:self] end)
|
1072
|
-
|
1249
|
+
if RDL::Globals.info.has?(klass, name, :type)
|
1250
|
+
tleft = RDL::Globals.info.get(klass, name, :type)
|
1251
|
+
elsif RDL::Config.instance.assume_dyn_type
|
1252
|
+
tleft = RDL::Globals.types[:dyn]
|
1253
|
+
else
|
1073
1254
|
kind_text = (if kind == :ivasgn then "instance"
|
1074
1255
|
elsif kind == :cvasgn then "class"
|
1075
1256
|
else "global" end)
|
1076
1257
|
error :untyped_var, [kind_text, name, klass], e
|
1077
1258
|
end
|
1078
|
-
tleft = RDL::Globals.info.get(klass, name, :type)
|
1079
1259
|
error :vasgn_incompat, [tright.to_s, tleft.to_s], e unless tright <= tleft
|
1080
1260
|
[env, tright.canonical]
|
1081
1261
|
when :send
|
@@ -1127,9 +1307,11 @@ RUBY
|
|
1127
1307
|
pair = fh.children[0]
|
1128
1308
|
error :type_cast_format, [], fh unless pair.type == :pair && pair.children[0].type == :sym && pair.children[0].children[0] == :force
|
1129
1309
|
force_arg = pair.children[1]
|
1130
|
-
|
1310
|
+
env, _ = tc(scope, env, force_arg)
|
1131
1311
|
end
|
1132
|
-
[
|
1312
|
+
sub_expr = e.children[2]
|
1313
|
+
env2, _ = tc(scope, env, sub_expr)
|
1314
|
+
[env2, typ]
|
1133
1315
|
end
|
1134
1316
|
|
1135
1317
|
def self.tc_note_type(scope, env, e)
|
@@ -1151,6 +1333,8 @@ RUBY
|
|
1151
1333
|
klass = "Array"
|
1152
1334
|
when RDL::Type::FiniteHashType
|
1153
1335
|
klass = "Hash"
|
1336
|
+
when RDL::Type::PreciseStringType
|
1337
|
+
klass = "String"
|
1154
1338
|
when RDL::Type::SingletonType
|
1155
1339
|
klass = if obj_typ.val.is_a?(Class) then obj_typ.val.to_s else obj_typ.val.class.to_s end
|
1156
1340
|
else
|
@@ -1202,26 +1386,37 @@ RUBY
|
|
1202
1386
|
# [+ tactuals +] are the actual arguments
|
1203
1387
|
# [+ block +] is a pair of expressions [block-args, block-body], from the block AST node OR [block-type, block-arg-AST-node]
|
1204
1388
|
# [+ e +] is the expression at which location to report an error
|
1205
|
-
|
1389
|
+
# [+ op_asgn +] is a bool telling us that we are type checking the mutation method for an op_asgn node. used for ast rewriting.
|
1390
|
+
def self.tc_send(scope, env, trecvs, meth, tactuals, block, e, op_asgn=false)
|
1206
1391
|
scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception
|
1207
1392
|
|
1208
1393
|
# convert trecvs to array containing all receiver types
|
1209
1394
|
trecvs = trecvs.canonical
|
1210
|
-
trecvs = if trecvs.is_a? RDL::Type::UnionType then trecvs.types else [trecvs] end
|
1395
|
+
trecvs = if trecvs.is_a? RDL::Type::UnionType then union = true; trecvs.types else union = false; [trecvs] end
|
1211
1396
|
|
1212
1397
|
trets = []
|
1398
|
+
eff = [:+, :+]
|
1213
1399
|
trecvs.each { |trecv|
|
1214
|
-
|
1400
|
+
ts, es = tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union)
|
1401
|
+
if es.nil? || (es.all? { |effect| effect.nil? }) ## could be multiple, because every time e is called, nil is added to effects
|
1402
|
+
## should probably change default effect to be [:-, :-], but for now I want it like this,
|
1403
|
+
## so I can easily see when a method has been used and its effect set to the default.
|
1404
|
+
#puts "Going to assume method #{meth} for receiver #{trecv} has effect [:-, :-]."
|
1405
|
+
eff = [:-, :-]
|
1406
|
+
else
|
1407
|
+
es.each { |effect| eff = effect_union(eff, effect) unless effect.nil? }
|
1408
|
+
end
|
1409
|
+
trets.concat(ts)
|
1215
1410
|
}
|
1216
|
-
trets.map! {|t| t.is_a?(RDL::Type::AnnotatedArgType) ? t.type : t}
|
1217
|
-
return RDL::Type::UnionType.new(*trets)
|
1411
|
+
trets.map! {|t| (t.is_a?(RDL::Type::AnnotatedArgType) || t.is_a?(RDL::Type::BoundArgType)) ? t.type : t}
|
1412
|
+
return [RDL::Type::UnionType.new(*trets), eff]
|
1218
1413
|
end
|
1219
1414
|
|
1220
1415
|
# Like tc_send but trecv should never be a union type
|
1221
1416
|
# Returns array of possible return types, or throws exception if there are none
|
1222
|
-
def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e)
|
1223
|
-
return tc_send_class(trecv, e) if (meth == :class) && (tactuals.empty?)
|
1224
|
-
|
1417
|
+
def self.tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e, op_asgn, union)
|
1418
|
+
return [tc_send_class(trecv, e), [[:+, :+]]] if (meth == :class) && (tactuals.empty?)
|
1419
|
+
ts = [] # Array<MethodType>, i.e., an intersection types
|
1225
1420
|
case trecv
|
1226
1421
|
when RDL::Type::SingletonType
|
1227
1422
|
if trecv.val.is_a? Class or trecv.val.is_a? Module
|
@@ -1234,11 +1429,11 @@ RUBY
|
|
1234
1429
|
trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s)
|
1235
1430
|
self_inst = trecv
|
1236
1431
|
end
|
1237
|
-
ts = lookup(scope, trecv_lookup, meth_lookup, e)
|
1432
|
+
ts, es = lookup(scope, trecv_lookup, meth_lookup, e)
|
1238
1433
|
ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined
|
1239
1434
|
error :no_singleton_method_type, [trecv.val, meth], e unless ts
|
1240
1435
|
inst = {self: self_inst}
|
1241
|
-
|
1436
|
+
self_klass = trecv.val
|
1242
1437
|
elsif trecv.val.is_a?(Symbol) && meth == :to_proc
|
1243
1438
|
# Symbol#to_proc on a singleton symbol type produces a Proc for the method of the same name
|
1244
1439
|
if env[:self].is_a?(RDL::Type::NominalType)
|
@@ -1246,68 +1441,174 @@ RUBY
|
|
1246
1441
|
else # SingletonType(class)
|
1247
1442
|
klass = env[:self].val
|
1248
1443
|
end
|
1249
|
-
ts = lookup(scope, klass.to_s, trecv.val, e)
|
1444
|
+
ts, es = lookup(scope, klass.to_s, trecv.val, e)
|
1250
1445
|
error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil?
|
1251
|
-
return ts
|
1446
|
+
return [ts, nil] ## TODO: not sure what to do hear about effect
|
1252
1447
|
else
|
1253
1448
|
klass = trecv.val.class.to_s
|
1254
|
-
ts = lookup(scope, klass, meth, e)
|
1449
|
+
ts, es = lookup(scope, klass, meth, e)
|
1255
1450
|
error :no_instance_method_type, [klass, meth], e unless ts
|
1256
1451
|
inst = {self: trecv}
|
1257
|
-
|
1452
|
+
self_klass = trecv.val.class
|
1258
1453
|
end
|
1454
|
+
when RDL::Type::AstNode
|
1455
|
+
meth_lookup = meth
|
1456
|
+
trecv_lookup = RDL::Util.add_singleton_marker(trecv.val.to_s)
|
1457
|
+
self_inst = trecv
|
1458
|
+
ts, es = lookup(scope, trecv_lookup, meth_lookup, e)
|
1459
|
+
ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined
|
1460
|
+
error :no_singleton_method_type, [trecv.val, meth], e unless ts
|
1461
|
+
inst = {self: self_inst}
|
1462
|
+
self_klass = trecv.val
|
1463
|
+
ts = ts.map { |t| t.instantiate(inst) }
|
1259
1464
|
when RDL::Type::NominalType
|
1260
|
-
ts = lookup(scope, trecv.name, meth, e)
|
1465
|
+
ts, es = lookup(scope, trecv.name, meth, e)
|
1261
1466
|
error :no_instance_method_type, [trecv.name, meth], e unless ts
|
1262
1467
|
inst = {self: trecv}
|
1263
|
-
|
1264
|
-
when RDL::Type::GenericType
|
1265
|
-
|
1266
|
-
error :tuple_finite_hash_promote, (if trecv.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e unless trecv.promote!
|
1267
|
-
trecv = trecv.canonical
|
1268
|
-
end
|
1269
|
-
ts = lookup(scope, trecv.base.name, meth, e)
|
1468
|
+
self_klass = RDL::Util.to_class(trecv.name)
|
1469
|
+
when RDL::Type::GenericType
|
1470
|
+
ts, es = lookup(scope, trecv.base.name, meth, e)
|
1270
1471
|
error :no_instance_method_type, [trecv.base.name, meth], e unless ts
|
1271
1472
|
inst = trecv.to_inst.merge(self: trecv)
|
1272
|
-
|
1473
|
+
self_klass = RDL::Util.to_class(trecv.base.name)
|
1474
|
+
when RDL::Type::TupleType
|
1475
|
+
if RDL::Config.instance.use_comp_types
|
1476
|
+
ts, es = lookup(scope, "Array", meth, e)
|
1477
|
+
error :no_instance_method_type, ["Array", meth], e unless ts
|
1478
|
+
#inst = trecv.to_inst.merge(self: trecv)
|
1479
|
+
inst = { self: trecv }
|
1480
|
+
self_klass = Array
|
1481
|
+
else
|
1482
|
+
## need to promote in this case
|
1483
|
+
error :tuple_finite_hash_promote, ['tuple', 'Array'], e unless trecv.promote!
|
1484
|
+
trecv = trecv.canonical
|
1485
|
+
ts, es = lookup(scope, trecv.base.name, meth, e)
|
1486
|
+
error :no_instance_method_type, [trecv.base.name, meth], e unless ts
|
1487
|
+
inst = trecv.to_inst.merge(self: trecv)
|
1488
|
+
self_klass = RDL::Util.to_class(trecv.base.name)
|
1489
|
+
end
|
1490
|
+
when RDL::Type::FiniteHashType
|
1491
|
+
if RDL::Config.instance.use_comp_types
|
1492
|
+
ts, es = lookup(scope, "Hash", meth, e)
|
1493
|
+
error :no_instance_method_type, ["Hash", meth], e unless ts
|
1494
|
+
#inst = trecv.to_inst.merge(self: trecv)
|
1495
|
+
inst = { self: trecv }
|
1496
|
+
self_klass = Hash
|
1497
|
+
else
|
1498
|
+
## need to promote in this case
|
1499
|
+
error :tuple_finite_hash_promote, ['finite hash', 'Hash'], e unless trecv.promote!
|
1500
|
+
trecv = trecv.canonical
|
1501
|
+
ts, es = lookup(scope, trecv.base.name, meth, e)
|
1502
|
+
error :no_instance_method_type, [trecv.base.name, meth], e unless ts
|
1503
|
+
inst = trecv.to_inst.merge(self: trecv)
|
1504
|
+
self_klass = RDL::Util.to_class(trecv.base.name)
|
1505
|
+
end
|
1506
|
+
when RDL::Type::PreciseStringType
|
1507
|
+
if RDL::Config.instance.use_comp_types
|
1508
|
+
ts, es = lookup(scope, "String", meth, e)
|
1509
|
+
error :no_instance_method_type, ["String", meth], e unless ts
|
1510
|
+
inst = { self: trecv }
|
1511
|
+
self_klass = String
|
1512
|
+
else
|
1513
|
+
## need to promote in this case
|
1514
|
+
error :tuple_finite_hash_promote, ['precise string type', 'String'], e unless trecv.promote!
|
1515
|
+
trecv = trecv.canonical
|
1516
|
+
ts, es = lookup(scope, trecv.name, meth, e)
|
1517
|
+
error :no_instance_method_type, [trecv.name, meth], e unless ts
|
1518
|
+
inst = trecv.to_inst.merge(self: trecv)
|
1519
|
+
self_klass = RDL::Util.to_class(trecv.name)
|
1520
|
+
end
|
1273
1521
|
when RDL::Type::VarType
|
1274
1522
|
error :recv_var_type, [trecv], e
|
1275
1523
|
when RDL::Type::MethodType
|
1276
1524
|
if meth == :call
|
1277
1525
|
# Special case - invokes the Proc
|
1278
|
-
|
1526
|
+
ts = [trecv]
|
1279
1527
|
else
|
1280
1528
|
# treat as Proc
|
1281
|
-
tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e)
|
1529
|
+
tc_send_one_recv(scope, env, RDL::Globals.types[:proc], meth, tactuals, block, e, op_asgn, union)
|
1282
1530
|
end
|
1531
|
+
when RDL::Type::DynamicType
|
1532
|
+
return [[trecv]]
|
1283
1533
|
else
|
1284
1534
|
raise RuntimeError, "receiver type #{trecv} not supported yet, meth=#{meth}"
|
1285
1535
|
end
|
1286
1536
|
|
1287
1537
|
trets = [] # all possible return types
|
1288
1538
|
# there might be more than one return type because multiple cases of an intersection type might match
|
1289
|
-
|
1539
|
+
tmeth_names = [] ## necessary for more precise error messages with ComputedTypes
|
1290
1540
|
# for ALL of the expanded lists of actuals...
|
1541
|
+
if RDL::Config.instance.use_comp_types
|
1542
|
+
ts = filter_comp_types(ts, true)
|
1543
|
+
else
|
1544
|
+
ts = filter_comp_types(ts, false)
|
1545
|
+
error :no_non_dep_types, [trecv, meth], e unless !ts.empty?
|
1546
|
+
end
|
1291
1547
|
RDL::Type.expand_product(tactuals).each { |tactuals_expanded|
|
1292
1548
|
# AT LEAST ONE of the possible intesection arms must match
|
1293
1549
|
trets_tmp = []
|
1294
|
-
|
1295
|
-
|
1550
|
+
ts.each_with_index { |tmeth, ind| # MethodType
|
1551
|
+
comp_type = false
|
1552
|
+
if tmeth.is_a? RDL::Type::DynamicType
|
1553
|
+
trets_tmp << RDL::Type::DynamicType.new
|
1554
|
+
elsif ((tmeth.block && block) || (tmeth.block.nil? && block.nil?))
|
1555
|
+
if trecv.is_a?(RDL::Type::FiniteHashType) && trecv.the_hash
|
1556
|
+
trecv = trecv.canonical
|
1557
|
+
inst = trecv.to_inst.merge(self: trecv)
|
1558
|
+
end
|
1559
|
+
block_types = (if tmeth.block then tmeth.block.args + [tmeth.block.ret] else [] end)
|
1560
|
+
unless (tmeth.args+[tmeth.ret]+block_types).all? { |t| !t.instance_of?(RDL::Type::ComputedType) }
|
1561
|
+
tmeth_old = tmeth
|
1562
|
+
trecv_old = trecv.copy
|
1563
|
+
targs_old = tactuals_expanded.map { |t| t.copy }
|
1564
|
+
binds = tc_bind_arg_types(tmeth, tactuals_expanded)
|
1565
|
+
#binds = {} if binds.nil?
|
1566
|
+
tmeth = tmeth_res = compute_types(tmeth, self_klass, trecv, tactuals_expanded, binds) unless binds.nil?
|
1567
|
+
comp_type = true
|
1568
|
+
end
|
1569
|
+
tmeth = tmeth.instantiate(inst) if inst
|
1570
|
+
tmeth_names << tmeth
|
1296
1571
|
tmeth_inst = tc_arg_types(tmeth, tactuals_expanded)
|
1297
1572
|
if tmeth_inst
|
1298
|
-
tc_block(scope, env, tmeth.block, block, tmeth_inst) if block
|
1573
|
+
effblock = tc_block(scope, env, tmeth.block, block, tmeth_inst) if block
|
1574
|
+
if es
|
1575
|
+
es = es.map { |es_effect| if es_effect.nil? then es_effect else es_effect.clone end }
|
1576
|
+
es.each { |es_effect| ## expecting just one effect per method right now. can clean this up later.
|
1577
|
+
if !es_effect.nil? && (es_effect[1] == :blockdep || es_effect[0] == :blockdep)
|
1578
|
+
raise "Got block-dependent effect, but no block." unless block && effblock
|
1579
|
+
if effblock[0] == :+ or effblock[0] == :~
|
1580
|
+
es_effect[1] = :+
|
1581
|
+
es_effect[0] = :+
|
1582
|
+
elsif effblock[0] == :-
|
1583
|
+
es_effect[1] = :-
|
1584
|
+
es_effect[0] = :-
|
1585
|
+
else
|
1586
|
+
raise "unexpected effect #{effblock[0]}"
|
1587
|
+
end
|
1588
|
+
end
|
1589
|
+
}
|
1590
|
+
end
|
1299
1591
|
if trecv.is_a?(RDL::Type::SingletonType) && meth == :new
|
1300
1592
|
init_typ = RDL::Type::NominalType.new(trecv.val)
|
1301
1593
|
if (tmeth.ret.instance_of?(RDL::Type::GenericType))
|
1302
1594
|
error :bad_initialize_type, [], e unless (tmeth.ret.base == init_typ)
|
1303
|
-
elsif (tmeth.ret.instance_of?(RDL::Type::AnnotatedArgType) || tmeth.ret.instance_of?(RDL::Type::DependentArgType))
|
1595
|
+
elsif (tmeth.ret.instance_of?(RDL::Type::AnnotatedArgType) || tmeth.ret.instance_of?(RDL::Type::DependentArgType) || tmeth.ret.instance_of?(RDL::Type::BoundArgType))
|
1304
1596
|
error :bad_initialize_type, [], e unless (tmeth.ret.type == init_typ)
|
1305
1597
|
else
|
1306
1598
|
error :bad_initialize_type, [], e unless (tmeth.ret == init_typ)
|
1307
1599
|
end
|
1308
1600
|
trets_tmp << init_typ
|
1309
1601
|
else
|
1310
|
-
trets_tmp << tmeth.ret.instantiate(tmeth_inst) # found a match for this subunion; add its return type to trets_tmp
|
1602
|
+
trets_tmp << (tmeth.ret.instantiate(tmeth_inst)) # found a match for this subunion; add its return type to trets_tmp
|
1603
|
+
if comp_type && RDL::Config.instance.check_comp_types && !union
|
1604
|
+
if (e.type == :op_asgn) && op_asgn
|
1605
|
+
## Hacky trick here. Because the ast `e` is used twice when type checking an op_asgn,
|
1606
|
+
## in one of the cases we will use the object_id of its object_id to get two different mappings.
|
1607
|
+
RDL::Globals.comp_type_map[e.object_id.object_id] = [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, (binds || {})]
|
1608
|
+
else
|
1609
|
+
RDL::Globals.comp_type_map[e.object_id] = [tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, (binds || {})]
|
1610
|
+
end
|
1611
|
+
end
|
1311
1612
|
end
|
1312
1613
|
end
|
1313
1614
|
end
|
@@ -1323,7 +1624,7 @@ RUBY
|
|
1323
1624
|
if trets.empty? # no possible matching call
|
1324
1625
|
msg = <<RUBY
|
1325
1626
|
Method type:
|
1326
|
-
#{
|
1627
|
+
#{ tmeth_names.map { |ti| " " + ti.to_s }.join("\n") }
|
1327
1628
|
Actual arg type#{tactuals.size > 1 ? "s" : ""}:
|
1328
1629
|
(#{tactuals.map { |ti| ti.to_s }.join(', ')}) #{if block then '{ block }' end}
|
1329
1630
|
RUBY
|
@@ -1332,7 +1633,7 @@ RUBY
|
|
1332
1633
|
:initialize
|
1333
1634
|
elsif trecv.is_a? RDL::Type::SingletonType
|
1334
1635
|
trecv.val.class.to_s
|
1335
|
-
elsif
|
1636
|
+
elsif [RDL::Type::NominalType, RDL::Type::GenericType, RDL::Type::FiniteHashType, RDL::Type::TupleType, RDL::Type::AstNode, RDL::Type::PreciseStringType].any? { |t| trecv.is_a? t }
|
1336
1637
|
trecv.to_s
|
1337
1638
|
elsif trecv.is_a?(RDL::Type::MethodType)
|
1338
1639
|
'Proc'
|
@@ -1342,7 +1643,51 @@ RUBY
|
|
1342
1643
|
error :arg_type_single_receiver_error, [name, meth, msg], e
|
1343
1644
|
end
|
1344
1645
|
# TODO: issue warning if trets.size > 1 ?
|
1345
|
-
return trets
|
1646
|
+
return [trets, es]
|
1647
|
+
end
|
1648
|
+
|
1649
|
+
# Evaluates any ComputedTypes in a method type
|
1650
|
+
# [+ tmeth +] is a MethodType for which we want to evaluate ComputedType args or return
|
1651
|
+
# [+ self_klass +] is the class of the receiver to the method call
|
1652
|
+
# [+ trecv +] is the type of the receiver to the method call
|
1653
|
+
# [+ tactuals +] is a list Array<Type> of types of the input to a method call
|
1654
|
+
# [+ binds +] is a Hash<Symbol, Type> mapping bound type names to the corresponding actual type.
|
1655
|
+
# Returns a new MethodType where all ComputedTypes in tmeth have been evaluated
|
1656
|
+
def self.compute_types(tmeth, self_klass, trecv, tactuals, binds={})
|
1657
|
+
bind = nil
|
1658
|
+
self_klass.class_eval { bind = binding() }
|
1659
|
+
bind.local_variable_set(:trec, trecv)
|
1660
|
+
bind.local_variable_set(:targs, tactuals)
|
1661
|
+
binds.each { |name, t| bind.local_variable_set(name, t) }
|
1662
|
+
new_args = []
|
1663
|
+
tmeth.args.each { |targ|
|
1664
|
+
case targ
|
1665
|
+
when RDL::Type::ComputedType
|
1666
|
+
new_args << targ.compute(bind)
|
1667
|
+
when RDL::Type::BoundArgType
|
1668
|
+
if targ.type.instance_of?(RDL::Type::ComputedType)
|
1669
|
+
new_args << targ.type.compute(bind)
|
1670
|
+
else
|
1671
|
+
new_args << targ
|
1672
|
+
end
|
1673
|
+
else
|
1674
|
+
new_args << targ
|
1675
|
+
end
|
1676
|
+
}
|
1677
|
+
case tmeth.ret
|
1678
|
+
when RDL::Type::ComputedType
|
1679
|
+
new_ret = tmeth.ret.compute(bind)
|
1680
|
+
when RDL::Type::BoundArgType
|
1681
|
+
if targ.type.instance_of?(RDL::Type::ComputedType)
|
1682
|
+
new_ret << targ.type.compute(bind)
|
1683
|
+
else
|
1684
|
+
new_ret << targ
|
1685
|
+
end
|
1686
|
+
else
|
1687
|
+
new_ret = tmeth.ret
|
1688
|
+
end
|
1689
|
+
new_block = compute_types(tmeth.block, self_klass, trecv, tactuals, binds) if tmeth.block
|
1690
|
+
RDL::Type::MethodType.new(new_args, new_block, new_ret)
|
1346
1691
|
end
|
1347
1692
|
|
1348
1693
|
def self.tc_send_class(trecv, e)
|
@@ -1363,6 +1708,8 @@ RUBY
|
|
1363
1708
|
[RDL::Type::SingletonType.new(Array)]
|
1364
1709
|
when RDL::Type::FiniteHashType
|
1365
1710
|
[RDL::Type::SingletonType.new(Hash)]
|
1711
|
+
when RDL::Type::PreciseStringType
|
1712
|
+
[RDL::Type::SingletonType.new(String)]
|
1366
1713
|
when RDL::Type::VarType
|
1367
1714
|
error :recv_var_type, [trecv], e
|
1368
1715
|
when RDL::Type::MethodType
|
@@ -1387,7 +1734,7 @@ RUBY
|
|
1387
1734
|
end
|
1388
1735
|
next if formal >= tformals.size # Too many actuals to match
|
1389
1736
|
t = tformals[formal]
|
1390
|
-
if t.instance_of? RDL::Type::
|
1737
|
+
if t.instance_of?(RDL::Type::AnnotatedArgType) || t.instance_of?(RDL::Type::BoundArgType)
|
1391
1738
|
t = t.type
|
1392
1739
|
end
|
1393
1740
|
case t
|
@@ -1429,6 +1776,84 @@ RUBY
|
|
1429
1776
|
return nil
|
1430
1777
|
end
|
1431
1778
|
|
1779
|
+
|
1780
|
+
# [+ tmeth +] is MethodType
|
1781
|
+
# [+ actuals +] is Array<Type> containing the actual argument types
|
1782
|
+
# return binding of BoundArgType names to the corresponding actual type
|
1783
|
+
# Very similar to MethodType#pre_cond?
|
1784
|
+
def self.tc_bind_arg_types(tmeth, tactuals)
|
1785
|
+
states = [[0, 0, Hash.new, Hash.new]] # position in tmeth, position in tactuals, inst of free vars in tmeth
|
1786
|
+
tformals = tmeth.args
|
1787
|
+
until states.empty?
|
1788
|
+
formal, actual, inst, binds = states.pop
|
1789
|
+
inst = inst.dup # avoid aliasing insts in different states since Type.leq mutates inst arg
|
1790
|
+
if formal == tformals.size && actual == tactuals.size # Matched everything
|
1791
|
+
return binds
|
1792
|
+
end
|
1793
|
+
next if formal >= tformals.size # Too many actuals to match
|
1794
|
+
t = tformals[formal]
|
1795
|
+
if t.instance_of? RDL::Type::AnnotatedArgType
|
1796
|
+
t = t.type
|
1797
|
+
end
|
1798
|
+
case t
|
1799
|
+
when RDL::Type::OptionalType
|
1800
|
+
t = t.type
|
1801
|
+
if actual == tactuals.size
|
1802
|
+
states << [formal+1, actual, inst, binds] # skip over optinal formal
|
1803
|
+
elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false)
|
1804
|
+
states << [formal+1, actual+1, inst, binds] # match
|
1805
|
+
states << [formal+1, actual, inst, binds] # skip
|
1806
|
+
else
|
1807
|
+
states << [formal+1, actual, inst, binds] # types don't match; must skip this formal
|
1808
|
+
end
|
1809
|
+
when RDL::Type::VarargType
|
1810
|
+
if actual == tactuals.size
|
1811
|
+
states << [formal+1, actual, inst, binds] # skip to allow empty vararg at end
|
1812
|
+
elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false)
|
1813
|
+
states << [formal, actual+1, inst, binds] # match, more varargs coming
|
1814
|
+
states << [formal+1, actual+1, inst, binds] # match, no more varargs
|
1815
|
+
states << [formal+1, actual, inst, binds] # skip over even though matches
|
1816
|
+
elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false) &&
|
1817
|
+
RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true)
|
1818
|
+
states << [formal+1, actual+1, inst, binds] # match, no more varargs; no other choices!
|
1819
|
+
else
|
1820
|
+
states << [formal+1, actual, inst, binds] # doesn't match, must skip
|
1821
|
+
end
|
1822
|
+
when RDL::Type::ComputedType
|
1823
|
+
## arbitrarily count this as a match, we only care about binding names
|
1824
|
+
## treat this same as VarargType but without call to leq
|
1825
|
+
#states << [formal+1, actual+1, inst, binds]
|
1826
|
+
if actual == tactuals.size
|
1827
|
+
states << [formal+1, actual, inst, binds] # skip to allow empty vararg at end
|
1828
|
+
elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType)))
|
1829
|
+
states << [formal, actual+1, inst, binds] # match, more varargs coming
|
1830
|
+
states << [formal+1, actual+1, inst, binds] # match, no more varargs
|
1831
|
+
states << [formal+1, actual, inst, binds] # skip over even though matches
|
1832
|
+
elsif tactuals[actual].is_a?(RDL::Type::VarargType)
|
1833
|
+
states << [formal+1, actual+1, inst, binds] # match, no more varargs; no other choices!
|
1834
|
+
else
|
1835
|
+
states << [formal+1, actual, inst, binds] # doesn't match, must skip
|
1836
|
+
end
|
1837
|
+
else
|
1838
|
+
if actual == tactuals.size
|
1839
|
+
next unless t.instance_of? RDL::Type::FiniteHashType
|
1840
|
+
if @@empty_hash_type <= t
|
1841
|
+
states << [formal+1, actual, inst, binds]
|
1842
|
+
end
|
1843
|
+
elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) #&& RDL::Type::Type.leq(tactuals[actual], t, inst, false)
|
1844
|
+
if t.is_a?(RDL::Type::BoundArgType)
|
1845
|
+
binds[t.name.to_sym] = tactuals[actual]
|
1846
|
+
t = t.type
|
1847
|
+
end
|
1848
|
+
states << [formal+1, actual+1, inst, binds] if (t.is_a?(RDL::Type::ComputedType) || RDL::Type::Type.leq(tactuals[actual], t, inst, false))# match!
|
1849
|
+
# no else case; if there is no match, this is a dead end
|
1850
|
+
end
|
1851
|
+
end
|
1852
|
+
end
|
1853
|
+
return nil
|
1854
|
+
end
|
1855
|
+
|
1856
|
+
|
1432
1857
|
# [+ tblock +] is the type of the block (a MethodType)
|
1433
1858
|
# [+ block +] is a pair [block-args, block-body] from the block AST node OR [block-type, block-arg-AST-node]
|
1434
1859
|
# returns if the block matches type tblock
|
@@ -1446,10 +1871,12 @@ RUBY
|
|
1446
1871
|
env, targs = args_hash(scope, env, tblock, args, block, 'block')
|
1447
1872
|
scope_merge(scope, outer_env: env) { |bscope|
|
1448
1873
|
# note: okay if outer_env shadows, since nested scope will include outer scope by next line
|
1449
|
-
|
1450
|
-
|
1874
|
+
targs_dup = Hash[targs.map { |k, t| [k, t.copy] }] ## args can be mutated in method body. duplicate to avoid this. TODO: check on this
|
1875
|
+
env = env.merge(Env.new(targs_dup))
|
1876
|
+
_, body_type, eff = if body.nil? then [nil, RDL::Globals.types[:nil], [:+, :+]] else tc(bscope, env.merge(Env.new(targs)), body) end
|
1451
1877
|
error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false)
|
1452
1878
|
#
|
1879
|
+
eff
|
1453
1880
|
}
|
1454
1881
|
end
|
1455
1882
|
end
|
@@ -1469,30 +1896,38 @@ RUBY
|
|
1469
1896
|
# return array of all matching types from context_types, if any
|
1470
1897
|
ts = []
|
1471
1898
|
scope[:context_types].each { |ctk, ctm, ctt| ts << ctt if ctk.to_s == klass && ctm == name }
|
1472
|
-
return ts unless ts.empty?
|
1899
|
+
return [ts, [[:-, :-]]] unless ts.empty? ## not sure what to do about effects here, so just going to be super conservative
|
1473
1900
|
end
|
1474
1901
|
if scope[:context_types]
|
1475
1902
|
scope[:context_types].each { |k, m, t|
|
1476
|
-
return t if k == klass && m = name
|
1903
|
+
return [t, [[:-, :-]]] if k == klass && m = name ## not sure what to do about effects here, so just going to be super conservative
|
1477
1904
|
}
|
1478
1905
|
end
|
1479
1906
|
t = RDL::Globals.info.get_with_aliases(klass, name, :type)
|
1480
|
-
|
1907
|
+
e = RDL::Globals.info.get_with_aliases(klass, name, :effect)
|
1908
|
+
return [t, e] if t # simplest case, no need to walk inheritance hierarchy
|
1481
1909
|
the_klass = RDL::Util.to_class(klass)
|
1482
|
-
is_singleton = RDL::Util.has_singleton_marker(
|
1483
|
-
included =
|
1910
|
+
is_singleton = RDL::Util.has_singleton_marker(klass)
|
1911
|
+
included = RDL::Util.to_class(klass.gsub("[s]", "")).included_modules
|
1484
1912
|
the_klass.ancestors[1..-1].each { |ancestor|
|
1485
1913
|
# assumes ancestors is proper order to walk hierarchy
|
1486
1914
|
# included modules' instance methods get added as instance methods, so can't be in singleton class
|
1487
|
-
next if (ancestor.instance_of? Module) && (included.member? ancestor) && is_singleton
|
1915
|
+
next if (ancestor.instance_of? Module) && (included.member? ancestor) && is_singleton && !(ancestor == Kernel)
|
1488
1916
|
# extended (i.e., not included) modules' instance methods get added as singleton methods, so can't be in class
|
1489
1917
|
next if (ancestor.instance_of? Module) && (not (included.member? ancestor)) && (not is_singleton)
|
1490
|
-
|
1491
|
-
|
1918
|
+
if is_singleton #&& !ancestor.instance_of?(Module)
|
1919
|
+
anc_lookup = get_singleton_name(ancestor.to_s)
|
1920
|
+
else
|
1921
|
+
anc_lookup = ancestor.to_s
|
1922
|
+
end
|
1923
|
+
tancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :type)
|
1924
|
+
eancestor = RDL::Globals.info.get_with_aliases(anc_lookup, name, :effect)
|
1925
|
+
return [tancestor, eancestor] if tancestor
|
1492
1926
|
# special caes: Kernel's singleton methods are *also* added when included?!
|
1493
1927
|
if ancestor == Kernel
|
1494
1928
|
tancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :type)
|
1495
|
-
|
1929
|
+
eancestor = RDL::Globals.info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :effect)
|
1930
|
+
return [tancestor, eancestor] if tancestor
|
1496
1931
|
end
|
1497
1932
|
if ancestor.instance_methods(false).member?(name)
|
1498
1933
|
if RDL::Util.has_singleton_marker klass
|
@@ -1500,11 +1935,130 @@ RUBY
|
|
1500
1935
|
klass = '(singleton) ' + klass
|
1501
1936
|
end
|
1502
1937
|
|
1503
|
-
return nil if the_klass.to_s.start_with?('#<Class:') and name
|
1504
|
-
error :missing_ancestor_type, [ancestor, klass, name], e
|
1938
|
+
return nil if the_klass.to_s.start_with?('#<Class:') and name == :new
|
1505
1939
|
end
|
1506
1940
|
}
|
1507
|
-
|
1941
|
+
|
1942
|
+
if RDL::Config.instance.assume_dyn_type
|
1943
|
+
# method is nil when it isn't found? maybe log something here or raise exception
|
1944
|
+
method = the_klass.instance_method(name) rescue nil
|
1945
|
+
if method
|
1946
|
+
arity = method.arity
|
1947
|
+
has_varargs = false
|
1948
|
+
if arity < 0
|
1949
|
+
has_varargs = true
|
1950
|
+
arity = -arity - 1
|
1951
|
+
end
|
1952
|
+
args = arity.times.map { RDL::Globals.types[:dyn] }
|
1953
|
+
args << RDL::Type::VarargType.new(RDL::Globals.types[:dyn]) if has_varargs
|
1954
|
+
else
|
1955
|
+
args = [RDL::Type::VarargType.new(RDL::Globals.types[:dyn])]
|
1956
|
+
end
|
1957
|
+
|
1958
|
+
ret = RDL::Globals.types[:dyn]
|
1959
|
+
ret = RDL::Type::NominalType.new(the_klass) if name == :initialize
|
1960
|
+
|
1961
|
+
return [[RDL::Type::MethodType.new(args, nil, ret)]]
|
1962
|
+
else
|
1963
|
+
return nil
|
1964
|
+
end
|
1965
|
+
end
|
1966
|
+
|
1967
|
+
def self.filter_comp_types(ts, use_dep_types)
|
1968
|
+
return nil unless ts
|
1969
|
+
dep_ts = []
|
1970
|
+
non_dep_ts = []
|
1971
|
+
ts.each { |typ|
|
1972
|
+
case typ
|
1973
|
+
when RDL::Type::MethodType
|
1974
|
+
block_types = (if typ.block then typ.block.args + [typ.block.ret] else [] end)
|
1975
|
+
typs = typ.args + block_types + [typ.ret]
|
1976
|
+
if typs.any? { |t| t.is_a?(RDL::Type::ComputedType) || (t.is_a?(RDL::Type::BoundArgType) && t.type.is_a?(RDL::Type::ComputedType)) }
|
1977
|
+
dep_ts << typ
|
1978
|
+
else
|
1979
|
+
non_dep_ts << typ
|
1980
|
+
end
|
1981
|
+
else
|
1982
|
+
raise "Expected method type."
|
1983
|
+
end
|
1984
|
+
}
|
1985
|
+
if !use_dep_types || dep_ts.empty?
|
1986
|
+
return non_dep_ts ## if not using dependent types, or if none exist, return non-dependent types
|
1987
|
+
else
|
1988
|
+
return dep_ts ## if using dependent types and some exist, then *only* return dependent types
|
1989
|
+
end
|
1990
|
+
end
|
1991
|
+
|
1992
|
+
def self.get_singleton_name(name)
|
1993
|
+
/#<Class:(.+)>/ =~ name
|
1994
|
+
return name unless $1 ### possible to get no match for extended modules, or class Class, Module, ..., BasicObject
|
1995
|
+
new_name = RDL::Util.add_singleton_marker($1)
|
1996
|
+
new_name
|
1997
|
+
end
|
1998
|
+
|
1999
|
+
def self.find_constant(env, e)
|
2000
|
+
# https://cirw.in/blog/constant-lookup.html
|
2001
|
+
# First look in Module.nesting for a lexically scoped variable
|
2002
|
+
if @cur_meth
|
2003
|
+
if (RDL::Util.has_singleton_marker(@cur_meth[0]))
|
2004
|
+
klass = RDL::Util.to_class(RDL::Util.remove_singleton_marker(@cur_meth[0]))
|
2005
|
+
mod_inst = false
|
2006
|
+
else
|
2007
|
+
klass = RDL::Util.to_class(@cur_meth[0])
|
2008
|
+
if klass.instance_of?(Module)
|
2009
|
+
mod_inst = true
|
2010
|
+
else
|
2011
|
+
mod_inst = false
|
2012
|
+
klass = klass.allocate
|
2013
|
+
end
|
2014
|
+
end
|
2015
|
+
if RDL::Wrap.wrapped?(@cur_meth[0], @cur_meth[1])
|
2016
|
+
meth_name = RDL::Wrap.wrapped_name(@cur_meth[0], @cur_meth[1])
|
2017
|
+
else
|
2018
|
+
meth_name = @cur_meth[1]
|
2019
|
+
end
|
2020
|
+
if mod_inst ## TODO: Is there a better way to do this? Module method bindings are made at runtime, so not sure.
|
2021
|
+
nesting = klass.module_eval('Module.nesting')
|
2022
|
+
else
|
2023
|
+
method = klass.method(meth_name)
|
2024
|
+
nesting = method.to_proc.binding.eval('Module.nesting')
|
2025
|
+
end
|
2026
|
+
nesting.each do |ic|
|
2027
|
+
c = get_leaves(e).inject(ic) {|m, c2| m && m.const_defined?(c2, false) && m.const_get(c2, false)}
|
2028
|
+
# My first time using ruby's stupid return-from-block correctly
|
2029
|
+
return c if c
|
2030
|
+
end
|
2031
|
+
end
|
2032
|
+
|
2033
|
+
# Check the ancestors
|
2034
|
+
if e.children[0].nil?
|
2035
|
+
case env[:self]
|
2036
|
+
when RDL::Type::SingletonType
|
2037
|
+
ic = env[:self].val
|
2038
|
+
when RDL::Type::NominalType
|
2039
|
+
ic = env[:self].klass
|
2040
|
+
else
|
2041
|
+
raise Exception, "unsupported env[self]=#{env[:self]}"
|
2042
|
+
end
|
2043
|
+
c = get_leaves(e).inject(ic) {|m, c2| m.const_get(c2)}
|
2044
|
+
elsif e.children[0].type == :cbase
|
2045
|
+
raise "const cbase not implemented yet" # TODO!
|
2046
|
+
elsif e.children[0].type == :lvar
|
2047
|
+
raise "const lvar not implemented yet" # TODO!
|
2048
|
+
elsif e.children[0].type == :const
|
2049
|
+
if env[:self]
|
2050
|
+
if env[:self].is_a?(RDL::Type::SingletonType)
|
2051
|
+
ic = env[:self].val
|
2052
|
+
else
|
2053
|
+
ic = env[:self].klass
|
2054
|
+
end
|
2055
|
+
else
|
2056
|
+
ic = Object
|
2057
|
+
end
|
2058
|
+
c = get_leaves(e).inject(ic) {|m, c2| m.const_get(c2)}
|
2059
|
+
else
|
2060
|
+
raise "const other not implemented yet"
|
2061
|
+
end
|
1508
2062
|
end
|
1509
2063
|
end
|
1510
2064
|
|
@@ -1517,6 +2071,7 @@ class Diagnostic < Parser::Diagnostic
|
|
1517
2071
|
|
1518
2072
|
RDL_MESSAGES = {
|
1519
2073
|
bad_return_type: "got type `%s' where return type `%s' expected",
|
2074
|
+
bad_effect: "got effect `%s' where effect `%s' expected",
|
1520
2075
|
bad_inst_type: "instantiate! called on object of type `%s' where Generic Type was expected",
|
1521
2076
|
inst_not_param: "instantiate! receiver is of class `%s' which is not parameterized",
|
1522
2077
|
inst_num_args: "instantiate! expecting `%s' type parameters, got `%s' parameters",
|
@@ -1541,7 +2096,6 @@ class Diagnostic < Parser::Diagnostic
|
|
1541
2096
|
no_block: "attempt to call yield in method not declared to take a block argument",
|
1542
2097
|
block_block: "can't call yield on a block expecting another block argument",
|
1543
2098
|
block_type_error: "argument type error for block\n%s",
|
1544
|
-
missing_ancestor_type: "ancestor `%s' of `%s' has method `%s' but no type for it",
|
1545
2099
|
type_cast_format: "type_cast must be called as `type_cast obj, type-string' or `type_cast obj, type-string, force: expr'",
|
1546
2100
|
instantiate_format: "instantiate! must be called as `instantiate! type*' or `instantiate! type*, check: bool' where type is a string, symbol, or class for static type checking.",
|
1547
2101
|
var_type_format: "var_type must be called as `var_type :var-name, type-string'",
|
@@ -1569,5 +2123,198 @@ class Diagnostic < Parser::Diagnostic
|
|
1569
2123
|
non_block_block_arg: "block argument should have a block type but instead has type `%s'",
|
1570
2124
|
proc_block_arg_type: "block argument is a Proc; can't tell if it matches expected type `%s'",
|
1571
2125
|
no_type_for_symbol: "can't find type for method corresponding to `%s.to_proc'",
|
2126
|
+
no_non_dep_types: "no non-dependent types for receiver %s in call to method %s",
|
2127
|
+
empty_env: "for some reason, environment is nil when type checking assignment to variable %s.",
|
1572
2128
|
}
|
1573
2129
|
end
|
2130
|
+
|
2131
|
+
class Object
|
2132
|
+
|
2133
|
+
## Method to replace dependently typed methods, and insert dynamic checks of types.
|
2134
|
+
## This method will check that given args satisfy given type, run the original method,
|
2135
|
+
## then check that the returned value satisfies the returned type, and finally return that value.
|
2136
|
+
## [+ __rdl_meth +] is a Symbol naming the method being replaced.
|
2137
|
+
## [+ node_id +] is an Integer representing the object_id of the relevant AST node to be looked up in the comp_type_map.
|
2138
|
+
## [+ *args +], [+ &block +] are the original arguments and blocked passed in a method call.
|
2139
|
+
## returns whatever is returned by calling the given method with the given args and block.
|
2140
|
+
def __rdl_dyn_type_check(__rdl_meth, node_id, *args, &block)
|
2141
|
+
tmeth, tmeth_old, tmeth_res, self_klass, trecv_old, targs_old, binds = RDL::Globals.comp_type_map[node_id]
|
2142
|
+
raise RuntimeError, "Could not find cached type-level computation results for method #{__rdl_meth}." unless tmeth
|
2143
|
+
if RDL::Config.instance.rerun_comp_types
|
2144
|
+
tmeth_new = RDL::Typecheck.compute_types(tmeth_old, self_klass, trecv_old, targs_old, binds)
|
2145
|
+
unless tmeth_new == tmeth_res
|
2146
|
+
raise RDL::Type::TypeError, "Type-level computation evaluated to different result from type checking time for class #{self_klass} method #{__rdl_meth}.\n Got #{tmeth_res} the first time, but #{tmeth_new} the second time."
|
2147
|
+
end
|
2148
|
+
end
|
2149
|
+
bind = binding
|
2150
|
+
inst = nil
|
2151
|
+
inst = @__rdl_type.to_inst if ((defined? @__rdl_type) && @__rdl_type.is_a?(RDL::Type::GenericType))
|
2152
|
+
klass = self.class.to_s
|
2153
|
+
inst = Hash[RDL::Globals.type_params[klass][0].zip []] if (not(inst) && RDL::Globals.type_params[klass])
|
2154
|
+
inst = {} if not inst
|
2155
|
+
|
2156
|
+
matches, args, _, bind = RDL::Type::MethodType.check_arg_types("#{__rdl_meth}", self, bind, [tmeth], inst, *args, &block)
|
2157
|
+
|
2158
|
+
ret = self.send(__rdl_meth, *args, &block)
|
2159
|
+
|
2160
|
+
if matches
|
2161
|
+
ret = RDL::Type::MethodType.check_ret_types(self, "#{__rdl_meth}", [tmeth], inst, matches, ret, bind, *args, &block) unless __rdl_meth == :initialize
|
2162
|
+
end
|
2163
|
+
|
2164
|
+
return ret
|
2165
|
+
end
|
2166
|
+
|
2167
|
+
end
|
2168
|
+
|
2169
|
+
module Parser
|
2170
|
+
module Source
|
2171
|
+
class TreeRewriter
|
2172
|
+
## Had to add some methods to the parser. Specifically, wanted to use `replace` for not just method being
|
2173
|
+
## called, but allso for its receiver and args. Doing so requires aligning the `range` being replaced
|
2174
|
+
## with the `buffer` containing the string that is being rewritten, in a way that the Parser did not support.
|
2175
|
+
def align_replace(range, offset, content)
|
2176
|
+
align_combine(range, offset, replacement: content)
|
2177
|
+
end
|
2178
|
+
|
2179
|
+
def align_combine(range, offset, attributes)
|
2180
|
+
if range.length > @source_buffer.source.size ## these are expected to be equal since buffer should be created from range source.
|
2181
|
+
raise IndexError, "The range #{range} is outside the bounds of the source of size #{@source_buffer.source.size}"
|
2182
|
+
end
|
2183
|
+
dummy_range = Parser::Source::Range.new(@source_buffer, range.begin_pos - offset, range.end_pos - offset)
|
2184
|
+
action = TreeRewriter::Action.new(dummy_range, @enforcer, attributes)
|
2185
|
+
@action_root = @action_root.combine(action)
|
2186
|
+
self
|
2187
|
+
end
|
2188
|
+
|
2189
|
+
end
|
2190
|
+
end
|
2191
|
+
end
|
2192
|
+
|
2193
|
+
module Parser
|
2194
|
+
class TreeRewriter < Parser::AST::Processor
|
2195
|
+
|
2196
|
+
def align_replace(range, offset, content)
|
2197
|
+
@source_rewriter.align_replace(range, offset, content)
|
2198
|
+
end
|
2199
|
+
end
|
2200
|
+
end
|
2201
|
+
|
2202
|
+
|
2203
|
+
|
2204
|
+
class WrapCall < Parser::TreeRewriter
|
2205
|
+
|
2206
|
+
def on_send(node)
|
2207
|
+
rec_ast = node.children[0]
|
2208
|
+
rec_code = WrapCall.rewrite(rec_ast)+"." if rec_ast.is_a?(AST::Node) ## receiver is nil, or it gets rewritten
|
2209
|
+
args_code = node.children[2..-1].map { |n| WrapCall.rewrite(n) if n.is_a?(AST::Node) }
|
2210
|
+
args_code = args_code.empty? ? nil : ","+args_code.join(",") ## no args, or args get rewritten
|
2211
|
+
unless node.children[1] == :__rdl_dyn_type_check ## I don't believe this check is necessary, but at one point I had this issue so I'm leaving it in
|
2212
|
+
if RDL::Globals.comp_type_map[node.object_id] ## Only do this if a call is associated with a type in the map. Otherwise, it may be a call to a non-dependently typed method.
|
2213
|
+
align_replace(node.location.expression, @offset, "#{rec_code}__rdl_dyn_type_check(:#{node.children[1]}, #{node.object_id} #{args_code})")
|
2214
|
+
end
|
2215
|
+
end
|
2216
|
+
end
|
2217
|
+
|
2218
|
+
def on_op_asgn(node)
|
2219
|
+
if node.children[0].type == :send
|
2220
|
+
rec_ast = node.children[0].children[0]
|
2221
|
+
rec_code = WrapCall.rewrite(rec_ast) + "." if rec_ast.is_a?(AST::Node)
|
2222
|
+
|
2223
|
+
rec_meth_ast = node.children[0]
|
2224
|
+
rec_meth_code = WrapCall.rewrite(rec_meth_ast)
|
2225
|
+
|
2226
|
+
elargs_ast = node.children[0].children[2]
|
2227
|
+
elargs_code = WrapCall.rewrite(elargs_ast)
|
2228
|
+
|
2229
|
+
rhs_ast = node.children[2]
|
2230
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2231
|
+
|
2232
|
+
op_meth = node.children[1]
|
2233
|
+
mutation_meth = node.children[0].children[1].to_s + "="
|
2234
|
+
|
2235
|
+
if RDL::Globals.comp_type_map[node.object_id]
|
2236
|
+
op_code = "#{rec_meth_code}.__rdl_dyn_type_check(:#{op_meth}, #{node.object_id}, #{rhs_code})"
|
2237
|
+
else
|
2238
|
+
op_code = "#{rec_meth_code}.send(:#{op_meth}, #{rhs_code})"
|
2239
|
+
end
|
2240
|
+
|
2241
|
+
if RDL::Globals.comp_type_map[node.object_id.object_id]
|
2242
|
+
align_replace(node.location.expression, @offset, "#{rec_code}__rdl_dyn_type_check(:#{mutation_meth}, #{node.object_id.object_id}, #{elargs_code}, #{op_code})")
|
2243
|
+
end
|
2244
|
+
else
|
2245
|
+
lhs = node.location.name.source
|
2246
|
+
meth = node.children[1]
|
2247
|
+
rhs_ast = node.children[2]
|
2248
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2249
|
+
if RDL::Globals.comp_type_map[node.object_id]
|
2250
|
+
align_replace(node.location.expression, @offset, "#{lhs} = #{lhs}.__rdl_dyn_type_check(:#{meth}, #{node.object_id}, #{rhs_code})")
|
2251
|
+
end
|
2252
|
+
end
|
2253
|
+
end
|
2254
|
+
|
2255
|
+
def on_or_asgn(node)
|
2256
|
+
if node.children[0].type == :send
|
2257
|
+
rec_ast = node.children[0].children[0]
|
2258
|
+
rec_code = WrapCall.rewrite(rec_ast)+"." if rec_ast.is_a?(AST::Node)
|
2259
|
+
|
2260
|
+
rec_meth_ast = node.children[0]
|
2261
|
+
rec_meth_code = WrapCall.rewrite(rec_meth_ast)
|
2262
|
+
|
2263
|
+
elargs_ast = node.children[0].children[2]
|
2264
|
+
elargs_code = WrapCall.rewrite(elargs_ast)
|
2265
|
+
|
2266
|
+
rhs_ast = node.children[1]
|
2267
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2268
|
+
|
2269
|
+
mutation_meth = node.children[0].children[1].to_s + "="
|
2270
|
+
|
2271
|
+
if RDL::Globals.comp_type_map[node.object_id]
|
2272
|
+
align_replace(node.location.expression, @offset, "#{rec_code}__rdl_dyn_type_check(:#{mutation_meth}, #{node.object_id}, #{elargs_code}, #{rec_meth_code} || #{rhs_code})")
|
2273
|
+
end
|
2274
|
+
else
|
2275
|
+
lhs = node.location.name.source
|
2276
|
+
rhs_ast = node.children[1]
|
2277
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2278
|
+
align_replace(node.location.expression, @offset, "#{lhs} = #{lhs} || #{rhs_code}")
|
2279
|
+
end
|
2280
|
+
end
|
2281
|
+
|
2282
|
+
def on_and_asgn(node)
|
2283
|
+
if node.children[0].type == :send
|
2284
|
+
rec_ast = node.children[0].children[0]
|
2285
|
+
rec_code = WrapCall.rewrite(rec_ast)+"." if rec_ast.is_a?(AST::Node)
|
2286
|
+
|
2287
|
+
rec_meth_ast = node.children[0]
|
2288
|
+
rec_meth_code = WrapCall.rewrite(rec_meth_ast)
|
2289
|
+
|
2290
|
+
elargs_ast = node.children[0].children[2]
|
2291
|
+
elargs_code = WrapCall.rewrite(elargs_ast)
|
2292
|
+
|
2293
|
+
rhs_ast = node.children[1]
|
2294
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2295
|
+
|
2296
|
+
mutation_meth = node.children[0].children[1].to_s + "="
|
2297
|
+
|
2298
|
+
if RDL::Globals.comp_type_map[node.object_id]
|
2299
|
+
align_replace(node.location.expression, @offset, "#{rec_code}__rdl_dyn_type_check(:#{mutation_meth}, #{node.object_id}, #{elargs_code}, #{rec_meth_code} && #{rhs_code})")
|
2300
|
+
end
|
2301
|
+
else
|
2302
|
+
lhs = node.location.name.source
|
2303
|
+
rhs_ast = node.children[1]
|
2304
|
+
rhs_code = WrapCall.rewrite(rhs_ast)
|
2305
|
+
align_replace(node.location.expression, @offset, "#{lhs} = #{lhs} && #{rhs_code}")
|
2306
|
+
end
|
2307
|
+
end
|
2308
|
+
|
2309
|
+
|
2310
|
+
def initialize(offset)
|
2311
|
+
@offset = offset
|
2312
|
+
end
|
2313
|
+
|
2314
|
+
def self.rewrite(ast)
|
2315
|
+
rewriter = WrapCall.new(ast.location.expression.begin_pos)
|
2316
|
+
buffer = Parser::Source::Buffer.new("(ast)")
|
2317
|
+
buffer.source = ast.location.expression.source
|
2318
|
+
rewriter.rewrite(buffer, ast)
|
2319
|
+
end
|
2320
|
+
end
|