rdl 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (102) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -0
  3. data/.travis.yml +7 -6
  4. data/CHANGES.md +29 -0
  5. data/README.md +94 -26
  6. data/lib/rdl/boot.rb +82 -41
  7. data/lib/rdl/boot_rails.rb +5 -0
  8. data/lib/rdl/config.rb +9 -1
  9. data/lib/rdl/query.rb +2 -2
  10. data/lib/rdl/typecheck.rb +972 -225
  11. data/lib/rdl/types/annotated_arg.rb +8 -0
  12. data/lib/rdl/types/ast_node.rb +73 -0
  13. data/lib/rdl/types/bot.rb +8 -0
  14. data/lib/rdl/types/bound_arg.rb +63 -0
  15. data/lib/rdl/types/computed.rb +48 -0
  16. data/lib/rdl/types/dependent_arg.rb +9 -0
  17. data/lib/rdl/types/dynamic.rb +61 -0
  18. data/lib/rdl/types/finite_hash.rb +54 -9
  19. data/lib/rdl/types/generic.rb +33 -0
  20. data/lib/rdl/types/intersection.rb +8 -0
  21. data/lib/rdl/types/lexer.rex +6 -1
  22. data/lib/rdl/types/lexer.rex.rb +13 -1
  23. data/lib/rdl/types/method.rb +14 -0
  24. data/lib/rdl/types/nominal.rb +8 -0
  25. data/lib/rdl/types/non_null.rb +8 -0
  26. data/lib/rdl/types/optional.rb +8 -0
  27. data/lib/rdl/types/parser.racc +31 -5
  28. data/lib/rdl/types/parser.tab.rb +540 -302
  29. data/lib/rdl/types/rdl_types.rb +45 -0
  30. data/lib/rdl/types/singleton.rb +14 -1
  31. data/lib/rdl/types/string.rb +104 -0
  32. data/lib/rdl/types/structural.rb +8 -0
  33. data/lib/rdl/types/top.rb +8 -0
  34. data/lib/rdl/types/tuple.rb +32 -8
  35. data/lib/rdl/types/type.rb +54 -11
  36. data/lib/rdl/types/union.rb +41 -2
  37. data/lib/rdl/types/var.rb +10 -0
  38. data/lib/rdl/types/vararg.rb +8 -0
  39. data/lib/rdl/util.rb +13 -10
  40. data/lib/rdl/wrap.rb +271 -27
  41. data/lib/rdl_disable.rb +16 -2
  42. data/lib/types/active_record.rb +1 -0
  43. data/lib/types/core/array.rb +442 -23
  44. data/lib/types/core/basic_object.rb +3 -3
  45. data/lib/types/core/bigdecimal.rb +5 -0
  46. data/lib/types/core/class.rb +2 -0
  47. data/lib/types/core/dir.rb +3 -3
  48. data/lib/types/core/enumerable.rb +4 -4
  49. data/lib/types/core/enumerator.rb +1 -1
  50. data/lib/types/core/file.rb +4 -4
  51. data/lib/types/core/float.rb +203 -0
  52. data/lib/types/core/hash.rb +390 -15
  53. data/lib/types/core/integer.rb +223 -10
  54. data/lib/types/core/io.rb +2 -2
  55. data/lib/types/core/kernel.rb +8 -5
  56. data/lib/types/core/marshal.rb +3 -0
  57. data/lib/types/core/module.rb +3 -3
  58. data/lib/types/core/numeric.rb +0 -2
  59. data/lib/types/core/object.rb +5 -5
  60. data/lib/types/core/pathname.rb +2 -2
  61. data/lib/types/core/process.rb +1 -3
  62. data/lib/types/core/range.rb +1 -1
  63. data/lib/types/core/regexp.rb +2 -2
  64. data/lib/types/core/set.rb +1 -1
  65. data/lib/types/core/string.rb +408 -16
  66. data/lib/types/core/symbol.rb +3 -3
  67. data/lib/types/core/time.rb +1 -1
  68. data/lib/types/core/uri.rb +13 -13
  69. data/lib/types/rails/_helpers.rb +7 -1
  70. data/lib/types/rails/action_controller/mime_responds.rb +2 -0
  71. data/lib/types/rails/active_record/associations.rb +42 -30
  72. data/lib/types/rails/active_record/comp_types.rb +637 -0
  73. data/lib/types/rails/active_record/finder_methods.rb +1 -1
  74. data/lib/types/rails/active_record/model_schema.rb +28 -16
  75. data/lib/types/rails/active_record/relation.rb +5 -3
  76. data/lib/types/rails/active_record/sql-strings.rb +166 -0
  77. data/lib/types/rails/string.rb +1 -1
  78. data/lib/types/sequel.rb +1 -0
  79. data/lib/types/sequel/comp_types.rb +581 -0
  80. data/rdl.gemspec +5 -4
  81. data/test/test_alias.rb +4 -0
  82. data/test/test_array_types.rb +244 -0
  83. data/test/test_bound_types.rb +80 -0
  84. data/test/test_contract.rb +4 -0
  85. data/test/test_dsl.rb +5 -0
  86. data/test/test_dyn_comptype_checks.rb +206 -0
  87. data/test/test_generic.rb +21 -20
  88. data/test/test_hash_types.rb +322 -0
  89. data/test/test_intersection.rb +1 -0
  90. data/test/test_le.rb +29 -4
  91. data/test/test_member.rb +3 -1
  92. data/test/test_parser.rb +5 -0
  93. data/test/test_query.rb +1 -0
  94. data/test/test_rdl.rb +63 -28
  95. data/test/test_rdl_type.rb +4 -0
  96. data/test/test_string_types.rb +102 -0
  97. data/test/test_type_contract.rb +59 -37
  98. data/test/test_typecheck.rb +480 -75
  99. data/test/test_types.rb +17 -0
  100. data/test/test_wrap.rb +5 -0
  101. metadata +35 -5
  102. data/lib/types/rails/active_record/schema_types.rb +0 -51
@@ -57,5 +57,15 @@ module RDL::Type
57
57
  return inst[@name] if inst[@name]
58
58
  return self
59
59
  end
60
+
61
+ def widen
62
+ return inst[@name] if inst[@name]
63
+ return self
64
+ end
65
+
66
+ def copy
67
+ self
68
+ end
69
+
60
70
  end
61
71
  end
@@ -53,6 +53,14 @@ module RDL::Type
53
53
  return VarargType.new(@type.instantiate(inst))
54
54
  end
55
55
 
56
+ def widen
57
+ return VarargType.new(@type.widen)
58
+ end
59
+
60
+ def copy
61
+ return VarargType.new(@type.copy)
62
+ end
63
+
56
64
  def hash # :nodoc:
57
65
  return 59 + @type.hash
58
66
  end
@@ -60,20 +60,15 @@ class RDL::Util
60
60
  begin
61
61
  sk = self.to_class klass
62
62
  msym = method.to_sym
63
- mstr = method.to_s
64
- sk.instance_method msym
65
63
  rescue NameError
66
64
  return false
67
65
  end
68
- klass_str = RDL::Util.to_class_str(klass).hash
69
- if mstr.start_with?('__rdl') and mstr.end_with?('_old_#{klass_str}')
70
- mstr0 = RDL::Wrap.unwrapped_name(klass, mstr)
71
- owner0 = sk.instance_method(mstr0).owner
72
- owner = sk.instance_method(mstr).owner
73
- return false if owner0 != owner
74
- end
75
66
 
76
- true
67
+ return sk.methods.include?(:new) if method == :new
68
+
69
+ sk.public_instance_methods(false).include?(msym) or
70
+ sk.protected_instance_methods(false).include?(msym) or
71
+ sk.private_instance_methods(false).include?(msym)
77
72
  end
78
73
 
79
74
  # Returns the @__rdl_type field of [+obj+]
@@ -93,4 +88,12 @@ class RDL::Util
93
88
  klass + "#" + meth.to_s
94
89
  end
95
90
  end
91
+
92
+ def self.silent_warnings
93
+ old_stderr = $stderr
94
+ $stderr = StringIO.new
95
+ yield
96
+ ensure
97
+ $stderr = old_stderr
98
+ end
96
99
  end
@@ -68,7 +68,13 @@ class RDL::Wrap
68
68
  posts = RDL::Globals.info.get(klass, meth, :post)
69
69
  RDL::Contract::AndContract.check_array(posts, self, ret, *args, &blk) if posts
70
70
  if matches
71
- ret = RDL::Type::MethodType.check_ret_types(self, "#{full_method_name}", types, inst, matches, ret, bind, *args, &blk)
71
+ if meth.to_sym == :initialize
72
+ types.each { |t|
73
+ raise ArgumentError, "Initialize method must be annotated with return type `self` or a generic type where base is `self`. #{full_method_name} has incorrect type." unless ((t.ret.is_a?(RDL::Type::VarType) && t.ret.name == :self) || (t.ret.is_a?(RDL::Type::GenericType) && t.ret.base.is_a?(RDL::Type::VarType) && t.ret.base.name == :self))
74
+ }
75
+ else
76
+ ret = RDL::Type::MethodType.check_ret_types(self, "#{full_method_name}", types, inst, matches, ret, bind, *args, &blk) unless (meth.to_sym == :initialize)
77
+ end
72
78
  end
73
79
  if RDL::Config.instance.guess_types.include?("#{klass_str_without_singleton}".to_sym)
74
80
  RDL::Globals.info.add(klass, meth, :otype, { args: (args.map { |arg| arg.class }), ret: ret.class, block: block_given? })
@@ -151,13 +157,13 @@ RUBY
151
157
  "__rdl_#{meth.to_s}_old_#{klass_str}".to_sym
152
158
  end
153
159
 
154
- def self.unwrapped_name(s)
155
- if not s.start_with?('__rdl_') and s.include?('_old_')
156
- raise Exception, "cannot get unwrapped name for #{s}"
160
+ def self.unwrapped_name(klass, meth_str)
161
+ if not meth_str.start_with?('__rdl_') and meth_str.include?('_old_')
162
+ raise Exception, "cannot get unwrapped name for #{meth_str}"
157
163
  end
158
164
  klass_str = RDL::Util.to_class_str(klass).hash.to_s
159
- s = klass_str.split("_#{klass_str}")
160
- s[6..-5]
165
+ meth_str = meth_str.split("_#{klass_str}")[0]
166
+ meth_str[6..-5]
161
167
  end
162
168
 
163
169
  def self.class_to_string(klass)
@@ -213,6 +219,7 @@ RUBY
213
219
  raise RuntimeError, "Deferred #{kind} contract from class #{prev_klass} being applied in class #{tmp_klass} to #{meth}"
214
220
  end
215
221
  RDL::Globals.info.add(klass, meth, kind, contract)
222
+ RDL::Globals.info.add(klass, meth, :effect, h[:effect]) if h.has_key?(:effect)
216
223
  RDL::Wrap.wrap(klass, meth) if h[:wrap]
217
224
  unless !h.has_key?(:typecheck) || RDL::Globals.info.set(klass, meth, :typecheck, h[:typecheck])
218
225
  raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}"
@@ -317,7 +324,7 @@ module RDL::Annotate
317
324
  # type(klass, meth, type)
318
325
  # type(meth, type)
319
326
  # type(type)
320
- def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil)
327
+ def type(*args, wrap: RDL::Config.instance.type_defaults[:wrap], typecheck: RDL::Config.instance.type_defaults[:typecheck], version: nil, effect: nil)
321
328
  return if version && !(Gem::Requirement.new(version).satisfied_by? Gem.ruby_version)
322
329
  klass, meth, type = begin
323
330
  RDL::Wrap.process_type_args(self, *args)
@@ -331,12 +338,20 @@ module RDL::Annotate
331
338
  err.set_backtrace bt
332
339
  raise err
333
340
  end
341
+ effect[0] = :- if effect && effect[0] == :~ ## For now, treating pure-ish :~ as :-, since we realized it doesn't actually affect termination checking.
342
+ typs = type.args + [type.ret]
343
+ if type.block
344
+ block_type = type.block.is_a?(RDL::Type::OptionalType) ? type.block.type : type.block
345
+ typs = typs + block_type.args + [block_type.ret]
346
+ end
347
+ RDL::Globals.dep_types << [klass, meth, type] if typs.any? { |t| t.is_a?(RDL::Type::ComputedType) || (t.is_a?(RDL::Type::BoundArgType) && t.type.is_a?(RDL::Type::ComputedType)) }
334
348
  if meth
335
349
  # It turns out Ruby core/stdlib don't always follow this convention...
336
350
  # if (meth.to_s[-1] == "?") && (type.ret != RDL::Globals.types[:bool])
337
351
  # warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool"
338
352
  # end
339
353
  RDL::Globals.info.add(klass, meth, :type, type)
354
+ RDL::Globals.info.add(klass, meth, :effect, effect)
340
355
  unless RDL::Globals.info.set(klass, meth, :typecheck, typecheck)
341
356
  raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}"
342
357
  end
@@ -351,29 +366,30 @@ module RDL::Annotate
351
366
  end
352
367
  RDL::Wrap.wrap(klass, meth) if wrap
353
368
  else
354
- if wrap
355
- RDL::Globals.to_wrap << [klass, meth]
356
- if (typecheck && typecheck != :call)
357
- RDL::Globals.to_typecheck[typecheck] = Set.new unless RDL::Globals.to_typecheck[typecheck]
358
- RDL::Globals.to_typecheck[typecheck].add([klass, meth])
359
- end
369
+ RDL::Globals.to_wrap << [klass, meth] if wrap
370
+ if (typecheck && typecheck != :call)
371
+ RDL::Globals.to_typecheck[typecheck] = Set.new unless RDL::Globals.to_typecheck[typecheck]
372
+ RDL::Globals.to_typecheck[typecheck].add([klass, meth])
360
373
  end
361
374
  end
362
375
  end
363
376
  else
364
377
  RDL::Globals.deferred << [klass, :type, type, {wrap: wrap,
365
- typecheck: typecheck}]
378
+ typecheck: typecheck, effect: effect}]
366
379
  end
367
380
  nil
368
381
  end
369
382
 
383
+ def readd_comp_types
384
+ RDL::Globals.dep_types.each { |klass, meth, t| RDL::Globals.info.add(klass, meth, :type, t) unless meth.nil? }
385
+ end
386
+
370
387
  # [+ klass +] is the class containing the variable; self if omitted; ignored for local and global variables
371
388
  # [+ var +] is a symbol or string containing the name of the variable
372
389
  # [+ typ +] is a string containing the type
373
390
  def var_type(klass=self, var, typ)
374
391
  raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/
375
392
  return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker
376
- raise RuntimeError, "Global variables can't be typed in a class" unless klass = self
377
393
  klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/
378
394
  unless RDL::Globals.info.set(klass, var, :type, RDL::Globals.parser.scan_str("#T #{typ}"))
379
395
  raise RuntimeError, "Type already declared for #{var}"
@@ -385,6 +401,7 @@ module RDL::Annotate
385
401
  # [+ args +] is a sequence of symbol, typ. attr_reader is called for each symbol,
386
402
  # and var_type is called to assign the immediately following type to the
387
403
  # attribute named after that symbol.
404
+ # Note these three methods are duplicated in RDLAnnotate
388
405
  def attr_accessor_type(*args)
389
406
  args.each_slice(2) { |name, typ|
390
407
  attr_accessor name
@@ -496,12 +513,40 @@ module RDL::RDLAnnotate
496
513
  define_method :rdl_post, RDL::Annotate.instance_method(:post)
497
514
  define_method :rdl_type, RDL::Annotate.instance_method(:type)
498
515
  define_method :rdl_var_type, RDL::Annotate.instance_method(:var_type)
499
- define_method :rdl_attr_accessor_type, RDL::Annotate.instance_method(:attr_accessor_type)
500
- define_method :rdl_attr_reader_type, RDL::Annotate.instance_method(:attr_reader_type)
501
- define_method :rdl_attr_type, RDL::Annotate.instance_method(:attr_type)
502
- define_method :rdl_attr_writer_type, RDL::Annotate.instance_method(:attr_writer_type)
503
516
  define_method :rdl_alias, RDL::Annotate.instance_method(:rdl_alias)
504
517
  define_method :rdl_type_params, RDL::Annotate.instance_method(:type_params)
518
+
519
+ # Need to duplicate these methods because they need to call rdl_var_type and rdl_type
520
+ # and couldn't figure out how to do instance_method with a partial argument binding
521
+ def rdl_attr_accessor_type(*args)
522
+ args.each_slice(2) { |name, typ|
523
+ attr_accessor name
524
+ rdl_var_type ("@" + name.to_s), typ
525
+ rdl_type name, "() -> #{typ}"
526
+ rdl_type name.to_s + "=", "(#{typ}) -> #{typ}"
527
+ }
528
+ nil
529
+ end
530
+
531
+ def rdl_attr_reader_type(*args)
532
+ args.each_slice(2) { |name, typ|
533
+ attr_reader name
534
+ rdl_var_type ("@" + name.to_s), typ
535
+ rdl_type name, "() -> #{typ}"
536
+ }
537
+ nil
538
+ end
539
+
540
+ alias_method :rdl_attr_type, :rdl_attr_reader_type
541
+
542
+ def rdl_attr_writer_type(*args)
543
+ args.each_slice(2) { |name, typ|
544
+ attr_writer name
545
+ rdl_var_type ("@" + name.to_s), typ
546
+ rdl_type name.to_s + "=", "(#{typ}) -> #{typ}"
547
+ }
548
+ nil
549
+ end
505
550
  end
506
551
 
507
552
  module RDL
@@ -553,6 +598,146 @@ module RDL
553
598
  nil
554
599
  end
555
600
 
601
+ def self.load_sequel_schema(db)
602
+ db.tables.each { |table|
603
+ hash_str = "{ "
604
+ kl_name = table.to_s.camelize.singularize
605
+ db.schema(table).each { |col|
606
+ hash_str << "#{col[0]}: "
607
+ typ = col[1][:type].to_s.camelize
608
+ if typ == "Datetime"
609
+ typ = "DateTime or Time" ## Sequel accepts both
610
+ elsif typ == "Boolean"
611
+ typ = "%bool"
612
+ elsif typ == "Text"
613
+ typ = "String"
614
+ end
615
+ hash_str << "#{typ},"
616
+ RDL.type kl_name, col[0], "() -> #{typ}", wrap: false
617
+ RDL.type kl_name, "#{col[0]}=", "(#{typ}) -> #{typ}", wrap: false
618
+ }
619
+ hash_str.chomp!(",") << " }"
620
+ RDL::Globals.seq_db_schema[table] = RDL::Globals.parser.scan_str "#T #{hash_str}"
621
+ }
622
+ end
623
+
624
+ def self.load_rails_schema
625
+ return unless defined?(Rails)
626
+ ::Rails.application.eager_load! # load Rails app
627
+ models = ActiveRecord::Base.descendants.each { |m|
628
+ begin
629
+ ## load schema for each Rails model
630
+ m.send(:load_schema) unless m.abstract_class?
631
+ rescue
632
+ end }
633
+
634
+ models.each { |model|
635
+ next if model.to_s == "ApplicationRecord"
636
+ next if model.to_s == "GroupManager"
637
+ RDL.nowrap model
638
+ s1 = {}
639
+ model.columns_hash.each { |k, v| t_name = v.type.to_s.camelize
640
+ ## Map SQL column types to the corresponding RDL type
641
+ if t_name == "Boolean"
642
+ t_name = "%bool"
643
+ s1[k] = RDL::Globals.types[:bool]
644
+ elsif t_name == "Datetime"
645
+ t_name = "DateTime or Time"
646
+ s1[k] = RDL::Type::UnionType.new(RDL::Type::NominalType.new(Time), RDL::Type::NominalType.new(DateTime))
647
+ elsif t_name == "Text"
648
+ ## difference between `text` and `string` is in the SQL types they're mapped to, not in Ruby types
649
+ t_name = "String"
650
+ s1[k] = RDL::Globals.types[:string]
651
+ else
652
+ s1[k] = RDL::Type::NominalType.new(t_name)
653
+ end
654
+ RDL.type model, (k+"=").to_sym, "(#{t_name}) -> #{t_name}", wrap: false ## create method type for column setter
655
+ RDL.type model, (k).to_sym, "() -> #{t_name}", wrap: false ## create method type for column getter
656
+ }
657
+ s2 = s1.transform_keys { |k| k.to_sym }
658
+ assoc = {}
659
+ model.reflect_on_all_associations.each { |a|
660
+ ## Generate method types based on associations
661
+ add_ar_assoc(assoc, a.macro, a.name)
662
+ if a.name.to_s.pluralize == a.name.to_s ## plural association
663
+ ## This actually returns an Associations CollectionProxy, which is a descendant of ActiveRecord_Relation (see below actual type). This makes no difference in practice.
664
+ RDL.type model, a.name, "() -> ActiveRecord_Relation<#{a.name.to_s.camelize.singularize}>", wrap: false
665
+ #ActiveRecord_Associations_CollectionProxy<#{a.name.to_s.camelize.singularize}>'
666
+ else
667
+ ## association is singular, we just return an instance of associated class
668
+ RDL.type model, a.name, "() -> #{a.name.to_s.camelize.singularize}", wrap: false
669
+ end
670
+ }
671
+ s2[:__associations] = RDL::Type::FiniteHashType.new(assoc, nil)
672
+ base_name = model.to_s
673
+ base_type = RDL::Type::NominalType.new(model.to_s)
674
+ hash_type = RDL::Type::FiniteHashType.new(s2, nil)
675
+ schema = RDL::Type::GenericType.new(base_type, hash_type)
676
+ RDL::Globals.ar_db_schema[base_name.to_sym] = schema
677
+ }
678
+ end
679
+
680
+ def self.check_type_code
681
+ RDL.config { |config| config.use_comp_types = false }
682
+ count = 1
683
+ #code_type = RDL::Globals.parser.scan_str "(RDL::Type::Type, Array<RDL::Type::Type>) -> RDL::Type::Type"
684
+ RDL::Globals.dep_types.each { |klass, meth, typ|
685
+ klass = RDL::Util.has_singleton_marker(klass) ? RDL::Util.remove_singleton_marker(klass) : klass
686
+ arg_list = "(trec, targs"
687
+ type_list = "(RDL::Type::Type, Array<RDL::Type::Type>"
688
+ (typ.args+[typ.ret]+[typ.block]).each { |t|
689
+ ## First collect all bindings to be used during type checking.
690
+ if (t.is_a?(RDL::Type::BoundArgType))
691
+ arg_list << ", #{t.name}"
692
+ type_list << ", #{t.type.class}"
693
+ end
694
+ }
695
+ arg_list << ")"
696
+ type_list << ")"
697
+ code_type = RDL::Globals.parser.scan_str "#{type_list} -> RDL::Type::Type"
698
+ (typ.args+[typ.ret]+[typ.block]).each { |t|
699
+ if t.is_a?(RDL::Type::ComputedType)
700
+ meth = cleanse_meth_name(meth)
701
+ if klass.to_s.include?("::") ## hacky way around namespace issue
702
+ tmp_meth = "def klass.tc_#{meth}#{count}#{arg_list} #{t.code}; end"
703
+ tmp_eval = "klass = #{klass} ; #{tmp_meth}"
704
+ else
705
+ tmp_meth = tmp_eval = "def #{klass}.tc_#{meth}#{count}#{arg_list} #{t.code}; end"
706
+ end
707
+ eval tmp_eval
708
+ ast = Parser::CurrentRuby.parse tmp_meth
709
+ RDL::Typecheck.typecheck("[s]#{klass}", "tc_#{meth}#{count}".to_sym, ast, [code_type], [[:-, :+]])
710
+ count += 1
711
+ end
712
+ }
713
+ }
714
+ RDL.do_typecheck :type_code
715
+ RDL.config { |config| config.use_comp_types = true }
716
+ true
717
+ end
718
+
719
+ def self.cleanse_meth_name(meth)
720
+ meth = meth.to_s
721
+ meth.gsub!("%", "percent")
722
+ meth.gsub!("&", "ampersand")
723
+ meth.gsub!("*", "asterisk")
724
+ meth.gsub!("+", "plus")
725
+ meth.gsub!("-", "dash")
726
+ meth.gsub!("@", "at")
727
+ meth.gsub!("/", "slash")
728
+ meth.gsub!("<", "lt")
729
+ meth.gsub!(">", "gt")
730
+ meth.gsub!("=", "eq")
731
+ meth.gsub!("[", "lbracket")
732
+ meth.gsub!("]", "rbracket")
733
+ meth.gsub!("^", "carrot")
734
+ meth.gsub!("|", "line")
735
+ meth.gsub!("~", "line")
736
+ meth.gsub!("?", "qmark")
737
+ meth.gsub!("!", "bang")
738
+ meth
739
+ end
740
+
556
741
  # Does nothing at run time
557
742
  def self.note_type(x)
558
743
  return x
@@ -567,7 +752,7 @@ module RDL
567
752
  # Returns a new object that wraps self in a type cast. If force is true this cast is *unchecked*, so use with caution
568
753
  def self.type_cast(obj, typ, force: false)
569
754
  new_typ = if typ.is_a? RDL::Type::Type then typ else RDL::Globals.parser.scan_str "#T #{typ}" end
570
- raise RuntimeError, "type cast error: self not a member of #{new_typ}" unless force || typ.member?(obj)
755
+ raise RuntimeError, "type cast error: self not a member of #{new_typ}" unless force || new_typ.member?(obj)
571
756
  new_obj = SimpleDelegator.new(obj)
572
757
  new_obj.instance_variable_set('@__rdl_type', new_typ)
573
758
  new_obj
@@ -582,7 +767,7 @@ module RDL
582
767
  formals, _, all = RDL::Globals.type_params[klass]
583
768
  raise RuntimeError, "Receiver is of class #{klass}, which is not parameterized" unless formals
584
769
  raise RuntimeError, "Expecting #{formals.size} type parameters, got #{typs.size}" unless formals.size == typs.size
585
- raise RuntimeError, "Instance already has type instantiation" if obj.instance_variable_get(:@__rdl_type)
770
+ raise RuntimeError, "Instance already has type instantiation" if obj.instance_variable_defined?(:@__rdl_type) && obj.instance_variable_get(:@__rdl_type)
586
771
  new_typs = typs.map { |t| if t.is_a? RDL::Type::Type then t else RDL::Globals.parser.scan_str "#T #{t}" end }
587
772
  t = RDL::Type::GenericType.new(RDL::Type::NominalType.new(klass), *new_typs)
588
773
  if check
@@ -614,9 +799,34 @@ module RDL
614
799
  obj.instance_variable_set(:@__rdl_type, nil)
615
800
  obj
616
801
  end
802
+
803
+ private
804
+ def self.add_ar_assoc(hash, aname, aklass)
805
+ kl_type = RDL::Type::SingletonType.new(aklass)
806
+ if hash[aname]
807
+ hash[aname] = RDL::Type::UnionType.new(hash[aname], kl_type)
808
+ else
809
+ hash[aname] = kl_type unless hash[aname]
810
+ end
811
+ hash
812
+ end
813
+ end
814
+
815
+ class Object
816
+ def singleton_method_added(meth)
817
+ klass = self.to_s
818
+ klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
819
+ sklass = RDL::Util.add_singleton_marker(klass)
820
+ RDL::Wrap.do_method_added(self, true, sklass, meth)
821
+ nil
822
+ end
617
823
  end
618
824
 
619
825
  class Module
826
+ define_method :singleton_method_added, Object.instance_method(:singleton_method_added)
827
+
828
+ RDL::Util.silent_warnings {
829
+
620
830
  def method_added(meth)
621
831
  klass = self.to_s
622
832
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
@@ -624,11 +834,45 @@ class Module
624
834
  nil
625
835
  end
626
836
 
627
- def singleton_method_added(meth)
628
- klass = self.to_s
629
- klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
630
- sklass = RDL::Util.add_singleton_marker(klass)
631
- RDL::Wrap.do_method_added(self, true, sklass, meth)
632
- nil
837
+ }
838
+ end
839
+
840
+ class Class
841
+ def ===(x)
842
+ if x.method(:is_a?).owner == SimpleDelegator then super(x.__getobj__) else super(x) end
843
+ end
844
+ end
845
+
846
+ class SimpleDelegator
847
+ ## pass methods through to wrapped object
848
+ ## necessary when type casts are inside type-level code
849
+ def is_a?(c)
850
+ __getobj__.is_a?(c)
851
+ end
852
+
853
+ def instance_of?(c)
854
+ __getobj__.instance_of?(c)
855
+ end
856
+
857
+ def kind_of?(c)
858
+ __getobj__.kind_of?(c)
859
+ end
860
+
861
+ def ===(x)
862
+ __getobj__ === x
633
863
  end
864
+
865
+ def ==(x)
866
+ __getobj__ == x
867
+ end
868
+
869
+ def class
870
+ __getobj__.class
871
+ end
872
+
873
+ def nil?
874
+ __getobj__.nil?
875
+ end
876
+
634
877
  end
878
+