rdl 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|