rdl 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -6
  4. data/CHANGES.md +29 -0
  5. data/README.md +94 -26
  6. data/lib/rdl/boot.rb +82 -41
  7. data/lib/rdl/boot_rails.rb +5 -0
  8. data/lib/rdl/config.rb +9 -1
  9. data/lib/rdl/query.rb +2 -2
  10. data/lib/rdl/typecheck.rb +972 -225
  11. data/lib/rdl/types/annotated_arg.rb +8 -0
  12. data/lib/rdl/types/ast_node.rb +73 -0
  13. data/lib/rdl/types/bot.rb +8 -0
  14. data/lib/rdl/types/bound_arg.rb +63 -0
  15. data/lib/rdl/types/computed.rb +48 -0
  16. data/lib/rdl/types/dependent_arg.rb +9 -0
  17. data/lib/rdl/types/dynamic.rb +61 -0
  18. data/lib/rdl/types/finite_hash.rb +54 -9
  19. data/lib/rdl/types/generic.rb +33 -0
  20. data/lib/rdl/types/intersection.rb +8 -0
  21. data/lib/rdl/types/lexer.rex +6 -1
  22. data/lib/rdl/types/lexer.rex.rb +13 -1
  23. data/lib/rdl/types/method.rb +14 -0
  24. data/lib/rdl/types/nominal.rb +8 -0
  25. data/lib/rdl/types/non_null.rb +8 -0
  26. data/lib/rdl/types/optional.rb +8 -0
  27. data/lib/rdl/types/parser.racc +31 -5
  28. data/lib/rdl/types/parser.tab.rb +540 -302
  29. data/lib/rdl/types/rdl_types.rb +45 -0
  30. data/lib/rdl/types/singleton.rb +14 -1
  31. data/lib/rdl/types/string.rb +104 -0
  32. data/lib/rdl/types/structural.rb +8 -0
  33. data/lib/rdl/types/top.rb +8 -0
  34. data/lib/rdl/types/tuple.rb +32 -8
  35. data/lib/rdl/types/type.rb +54 -11
  36. data/lib/rdl/types/union.rb +41 -2
  37. data/lib/rdl/types/var.rb +10 -0
  38. data/lib/rdl/types/vararg.rb +8 -0
  39. data/lib/rdl/util.rb +13 -10
  40. data/lib/rdl/wrap.rb +271 -27
  41. data/lib/rdl_disable.rb +16 -2
  42. data/lib/types/active_record.rb +1 -0
  43. data/lib/types/core/array.rb +442 -23
  44. data/lib/types/core/basic_object.rb +3 -3
  45. data/lib/types/core/bigdecimal.rb +5 -0
  46. data/lib/types/core/class.rb +2 -0
  47. data/lib/types/core/dir.rb +3 -3
  48. data/lib/types/core/enumerable.rb +4 -4
  49. data/lib/types/core/enumerator.rb +1 -1
  50. data/lib/types/core/file.rb +4 -4
  51. data/lib/types/core/float.rb +203 -0
  52. data/lib/types/core/hash.rb +390 -15
  53. data/lib/types/core/integer.rb +223 -10
  54. data/lib/types/core/io.rb +2 -2
  55. data/lib/types/core/kernel.rb +8 -5
  56. data/lib/types/core/marshal.rb +3 -0
  57. data/lib/types/core/module.rb +3 -3
  58. data/lib/types/core/numeric.rb +0 -2
  59. data/lib/types/core/object.rb +5 -5
  60. data/lib/types/core/pathname.rb +2 -2
  61. data/lib/types/core/process.rb +1 -3
  62. data/lib/types/core/range.rb +1 -1
  63. data/lib/types/core/regexp.rb +2 -2
  64. data/lib/types/core/set.rb +1 -1
  65. data/lib/types/core/string.rb +408 -16
  66. data/lib/types/core/symbol.rb +3 -3
  67. data/lib/types/core/time.rb +1 -1
  68. data/lib/types/core/uri.rb +13 -13
  69. data/lib/types/rails/_helpers.rb +7 -1
  70. data/lib/types/rails/action_controller/mime_responds.rb +2 -0
  71. data/lib/types/rails/active_record/associations.rb +42 -30
  72. data/lib/types/rails/active_record/comp_types.rb +637 -0
  73. data/lib/types/rails/active_record/finder_methods.rb +1 -1
  74. data/lib/types/rails/active_record/model_schema.rb +28 -16
  75. data/lib/types/rails/active_record/relation.rb +5 -3
  76. data/lib/types/rails/active_record/sql-strings.rb +166 -0
  77. data/lib/types/rails/string.rb +1 -1
  78. data/lib/types/sequel.rb +1 -0
  79. data/lib/types/sequel/comp_types.rb +581 -0
  80. data/rdl.gemspec +5 -4
  81. data/test/test_alias.rb +4 -0
  82. data/test/test_array_types.rb +244 -0
  83. data/test/test_bound_types.rb +80 -0
  84. data/test/test_contract.rb +4 -0
  85. data/test/test_dsl.rb +5 -0
  86. data/test/test_dyn_comptype_checks.rb +206 -0
  87. data/test/test_generic.rb +21 -20
  88. data/test/test_hash_types.rb +322 -0
  89. data/test/test_intersection.rb +1 -0
  90. data/test/test_le.rb +29 -4
  91. data/test/test_member.rb +3 -1
  92. data/test/test_parser.rb +5 -0
  93. data/test/test_query.rb +1 -0
  94. data/test/test_rdl.rb +63 -28
  95. data/test/test_rdl_type.rb +4 -0
  96. data/test/test_string_types.rb +102 -0
  97. data/test/test_type_contract.rb +59 -37
  98. data/test/test_typecheck.rb +480 -75
  99. data/test/test_types.rb +17 -0
  100. data/test/test_wrap.rb +5 -0
  101. metadata +35 -5
  102. data/lib/types/rails/active_record/schema_types.rb +0 -51
@@ -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
@@ -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 :type_defaults, :pre_defaults, :post_defaults
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)
@@ -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}"
@@ -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
- _, body_type = tc(scope, Env.new(targs.merge(scope[:captured])), body)
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
- error :type_args_more, [kind, kind], (if args.children.empty? then ast else args end) if type.args.length != tpos
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 :complex, :rational, :str, :string # constants
364
- [env, RDL::Type::NominalType.new(e.children[0].class)]
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| envi, _ = tc(scope, envi, ei) }
370
- [envi, RDL::Globals.types[:string]]
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
- envi, tright = tc(scope, envi, p.children[1])
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
- tc_var(scope, env, e.type, e.children[0], e)
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
- tloperand = tc_send(scope, envleft, trecv, meth, largs, nil, e.children[0]) # call recv.meth()
543
- envoperand, troperand = tc(scope, envleft, e.children[2]) # operand
544
- tright = tc_send(scope, envoperand, tloperand, e.children[1], [troperand], nil, e) # recv.meth().op(operand)
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
- [envoperand, tres]
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
- trhs = tc_send(scope, envright, trecv, e.children[1], [tright], nil, e)
557
- tc_vasgn(scope, envright, e.children[0].type, x, trhs, e)
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
- tleft = tc_send(scope, envleft, trecv, meth, [], nil, e.children[0]) # call recv.meth()
565
- envright, tright = tc(scope, envleft, e.children[1]) # operand
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
- tres = tc_send(scope, envi, trecv, mutation_meth, [trhs], nil, e)
584
- [envi, tres]
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 = nil
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(const_get(e.children[1]).class)]
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
- [envi, tc_send(sscope, envi, trecv, e.children[1], tactuals, block, e).canonical]
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
- e.children[0..-1].each { |ei| envi, ti = tc(scope, envi, ei); tactuals << ti }
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
- [Env.join(e, envleft, envright), RDL::Type::UnionType.new(tleft, tright).canonical]
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
- [lscope[:break], lscope[:tbreak].canonical]
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
- envi, tcollect = tc(scope, env, e.children[1]) # collection to iterate through
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
- teaches = lookup(scope, tcollect.name, :each, e.children[1])
850
- when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType
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
- teaches = lookup(scope, tcollect.base.name, :each, e.children[1])
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
- [env1, RDL::Globals.types[:bot]] # return is a void value expression
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
- e.children.each { |ei| envi, ti = tc(scope, envi, ei) }
914
- [envi, ti]
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
- [envi, tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e).canonical]
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
- [envi, tc_send(sscope, envi, trecv, @cur_meth[1], tactuals, block, e).canonical]
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
- unless RDL::Globals.info.has?(klass, name, :type)
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, RDL::Globals.info.get(klass, name, :type).canonical]
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
- unless RDL::Globals.info.has?(klass, name, :type)
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
- env1, _ = tc(scope, env, force_arg)
1310
+ env, _ = tc(scope, env, force_arg)
1131
1311
  end
1132
- [env1, typ]
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
- def self.tc_send(scope, env, trecvs, meth, tactuals, block, e)
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
- trets.concat(tc_send_one_recv(scope, env, trecv, meth, tactuals, block, e))
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
- tmeth_inter = [] # Array<MethodType>, i.e., an intersection types
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
- tmeth_inter = ts.map { |t| t.instantiate(inst) }
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
- tmeth_inter = ts.map { |t| t.instantiate(inst) }
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
- tmeth_inter = ts.map { |t| t.instantiate(inst) }
1264
- when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType
1265
- unless trecv.is_a? RDL::Type::GenericType
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
- tmeth_inter = ts.map { |t| t.instantiate(inst) }
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
- tmeth_inter = [trecv]
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
- tmeth_inter.each { |tmeth| # MethodType
1295
- if ((tmeth.block && block) || (tmeth.block.nil? && block.nil?))
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
- #{ tmeth_inter.map { |ti| " " + ti.to_s }.join("\n") }
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 trecv.is_a?(RDL::Type::NominalType) || trecv.is_a?(RDL::Type::GenericType)
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::AnnotatedArgType
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
- env = env.merge(Env.new(targs))
1450
- _, body_type = if body.nil? then [nil, RDL::Globals.types[:nil]] else tc(bscope, env.merge(Env.new(targs)), body) end
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
- return t if t # simplest case, no need to walk inheritance hierarchy
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(the_klass.to_s)
1483
- included = the_klass.included_modules
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
- tancestor = RDL::Globals.info.get_with_aliases(ancestor.to_s, name, :type)
1491
- return tancestor if tancestor
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
- return tancestor if tancestor
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 ==:new
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
- return nil
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