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
@@ -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
+