rdl 2.1.0 → 2.2.0

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