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
@@ -0,0 +1,45 @@
1
+ RDL.type_params 'RDL::Type::SingletonType', [:t], :satisfies?
2
+
3
+ RDL.type 'RDL::Type::SingletonType', :initialize, "(x) -> self<x>", wrap: false, effect: [:+, :+]
4
+ RDL.type 'RDL::Type::SingletonType', :val, "() -> t", wrap: false, effect: [:+, :+]
5
+ RDL.type 'RDL::Type::SingletonType', :nominal, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+]
6
+
7
+ RDL.type 'RDL::Type::NominalType', :initialize, "(Class or String) -> self", wrap: false, effect: [:+, :+]
8
+ RDL.type 'RDL::Type::NominalType', :klass, "() -> Class", wrap: false, effect: [:+, :+]
9
+ RDL.type 'RDL::Type::NominalType', :name, "() -> String", wrap: false, effect: [:+, :+]
10
+
11
+ RDL.type 'RDL::Type::GenericType', :initialize, "(RDL::Type::Type, *RDL::Type::Type) -> self", wrap: false, effect: [:+, :+]
12
+ RDL.type 'RDL::Type::GenericType', :params, "() -> Array<RDL::Type::Type>", wrap: false, effect: [:+, :+]
13
+ RDL.type 'RDL::Type::GenericType', :base, "() -> RDL::Type::NominalType", wrap: false, effect: [:+, :+]
14
+
15
+ RDL.type 'RDL::Type::UnionType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+]
16
+ RDL.type 'RDL::Type::UnionType', :canonical, "() -> RDL::Type::Type", wrap: false, effect: [:+, :+]
17
+ RDL.type 'RDL::Type::UnionType', :types, "() -> Array<RDL::Type::Type>", wrap: false, effect: [:+, :+]
18
+
19
+ RDL.type 'RDL::Type::TupleType', :initialize, "(*RDL::Type::Type) -> self", wrap: false, effect: [:+, :+]
20
+ RDL.type 'RDL::Type::TupleType', :params, "() -> Array<RDL::Type::Type>", wrap: false, effect: [:+, :+]
21
+ RDL.type 'RDL::Type::TupleType', :promote, "(?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+]
22
+ RDL.type 'RDL::Type::TupleType', :promote!, "(?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+]
23
+ RDL.type 'RDL::Type::TupleType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+]
24
+
25
+ RDL.type 'RDL::Type::FiniteHashType', :elts, "() -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:+, :+]
26
+ RDL.type 'RDL::Type::FiniteHashType', :elts=, "(Hash<%any, RDL::Type::Type>) -> Hash<%any, RDL::Type::Type>", wrap: false, effect: [:-, :+]
27
+ RDL.type 'RDL::Type::FiniteHashType', :promote, "(?%any, ?RDL::Type::Type) -> RDL::Type::GenericType", wrap: false, effect: [:+, :+]
28
+ RDL.type 'RDL::Type::FiniteHashType', :promote!, "(?%any, ?RDL::Type::Type) -> %bool", wrap: false, effect: [:-, :+]
29
+ RDL.type 'RDL::Type::FiniteHashType', :initialize, "(Hash<%any, RDL::Type::Type> or {}, ?RDL::Type::Type) -> self", wrap: false, effect: [:+, :+]
30
+ RDL.type 'RDL::Type::FiniteHashType', :check_bounds, "(?%bool) -> %bool", wrap: false, effect: [:+, :+]
31
+
32
+ RDL.type 'RDL::Type::OptionalType', :initialize, "(RDL::Type::Type) -> self", wrap: false, effect: [:+, :+]
33
+
34
+ RDL.type 'RDL::Type::VarargType', :initialize, '(RDL::Type::Type) -> self', wrap: false, effect: [:+, :+]
35
+
36
+ RDL.type 'RDL::Type::VarType', :initialize, "(String) -> self", wrap: false, effect: [:+, :+]
37
+
38
+ RDL.type "RDL::Type::Type", 'self.leq', "(RDL::Type::Type, RDL::Type::Type) -> %bool", wrap: false, effect: [:+, :+]
39
+
40
+ RDL.type 'RDL::Globals', 'self.parser', "() -> RDL::Type::Parser", wrap: false, effect: [:+, :+]
41
+ RDL.type 'RDL::Globals', 'self.types', "() -> Hash<Symbol, RDL::Type::Type>", wrap: false, effect: [:+, :+]
42
+ RDL.type 'RDL::Type::Parser', :scan_str, "(String) -> RDL::Type::Type", wrap: false, effect: [:+, :+]
43
+ RDL.type 'RDL::Config', 'self.instance', "() -> RDL::Config", wrap: false, effect: [:+, :+]
44
+ RDL.type 'RDL::Config', 'weak_update_promote', "() -> %bool", wrap: false, effect: [:+, :+]
45
+ RDL.type "Object", '__getobj__', "() -> self", wrap: false, effect: [:+, :+] ## needed due to type casting
@@ -1,6 +1,6 @@
1
1
  module RDL::Type
2
2
  class SingletonType < Type
3
- attr_reader :val
3
+ attr_accessor :val
4
4
  attr_reader :nominal
5
5
 
6
6
  @@cache = {}
@@ -59,11 +59,24 @@ module RDL::Type
59
59
  def member?(obj, *args)
60
60
  t = RDL::Util.rdl_type obj
61
61
  return t <= self if t
62
+ return true if obj.nil?
62
63
  obj.equal?(@val)
63
64
  end
64
65
 
65
66
  def instantiate(inst)
66
67
  return self
67
68
  end
69
+
70
+ def widen
71
+ return self
72
+ end
73
+
74
+ def copy
75
+ return self
76
+ end
77
+
78
+ def satisfies?
79
+ yield(val)
80
+ end
68
81
  end
69
82
  end
@@ -0,0 +1,104 @@
1
+ module RDL::Type
2
+ # A specialized precise type for Strings
3
+ class PreciseStringType < Type
4
+ attr_accessor :vals
5
+ attr_accessor :interp, :ubounds, :lbounds
6
+
7
+ ## @vals will be an array. For non-interpolated strings, it will only have one element, the string itself.
8
+ ## For interpolated strings, it will include strings and RDL types, in order, where the types represent
9
+ ## the types of the interpolated portions, and the strings represent the surrounding string parts.
10
+ def initialize(*vals)
11
+ @interp = false
12
+ vals.each { |v|
13
+ case v
14
+ when String
15
+ ## do nothing
16
+ when Type
17
+ ## interpolated string
18
+ @interp = true
19
+ else
20
+ raise RuntimeError, "Attempt to create precise string type with non-string or non-type value #{v}" unless vals.all? { |val| val.is_a?(Type) || val.is_a?(String) }
21
+ end
22
+ }
23
+ vals = [vals.join] if !@interp && vals.size > 1 ## if all elements of `vals` are strings, join them into one
24
+
25
+ @vals = vals
26
+ @ubounds = []
27
+ @lbounds = []
28
+ @promoted = false
29
+ @cant_promote = false
30
+ super()
31
+ end
32
+
33
+ def canonical
34
+ return RDL::Globals.types[:string] if @promoted
35
+ return self
36
+ end
37
+
38
+ def to_s
39
+ return RDL::Globals.types[:string].to_s if @promoted
40
+ printed_vals = @vals.map { |v|
41
+ case v
42
+ when String
43
+ "'"+v+"'"
44
+ when Type
45
+ #'#{' + v.to_s.gsub('"',"'") + '}'
46
+ return "String"
47
+ end
48
+ }
49
+ return printed_vals.join
50
+ end
51
+
52
+ def ==(other)
53
+ return false if other.nil?
54
+ return RDL::Globals.types[:string] == other if @promoted
55
+ other = other.canonical
56
+ return (other.instance_of? PreciseStringType) && (other.vals == @vals)
57
+ end
58
+
59
+ def member?(obj, *args)
60
+ return false unless obj.is_a?(String)
61
+ raise "Checking membership of PreciseStringType not currently supported for interpolated strings." unless @vals.all? { |v| v.is_a?(String) }
62
+ return (@vals.join == obj)
63
+ end
64
+
65
+ def promote!
66
+ return false if @cant_promote
67
+ @promoted = true
68
+ check_bounds
69
+ end
70
+
71
+ def check_bounds
72
+ return (@lbounds.all? { |lbound| lbound <= self }) && (@ubounds.all? { |ubound| self <= ubound } )
73
+ end
74
+
75
+ def cant_promote!
76
+ raise RuntimeError, "already promoted!" if @promoted
77
+ @cant_promote = true
78
+ end
79
+
80
+ def <=(other, no_constraint=false)
81
+ return Type.leq(self, other, no_constraint: no_constraint)
82
+ end
83
+
84
+ def instantiate(inst)
85
+ return RDL::Globals.types[:string] if @promoted
86
+ @vals.map! { |v| if v.is_a?(Type) then v.instantiate(inst) else v end }
87
+ self
88
+ end
89
+
90
+ def widen
91
+ @vals.map! { |v| if v.is_a?(Type) then v.widen else v end }
92
+ self
93
+ end
94
+
95
+ def copy
96
+ return PreciseStringType.new(*@vals.map { |v| if v.is_a?(String) then v.clone else v.copy end })
97
+ end
98
+
99
+ def hash
100
+ 99 * @vals.hash
101
+ end
102
+
103
+ end
104
+ end
@@ -47,6 +47,14 @@ module RDL::Type
47
47
  StructuralType.new(Hash[*@methods.each_pair.map { |m, t| [m, t.instantiate(inst)] }.flatten])
48
48
  end
49
49
 
50
+ def widen
51
+ StructuralType.new(Hash[*@methods.each_pair.map { |m, t| [m, t.widen] }.flatten])
52
+ end
53
+
54
+ def copy
55
+ StructuralType.new(Hash[*@methods.each_pair.map { |m, t| [m, t.copy] }.flatten])
56
+ end
57
+
50
58
  def ==(other) # :nodoc:
51
59
  return false if other.nil?
52
60
  other = other.canonical
@@ -48,6 +48,14 @@ module RDL::Type
48
48
  return self
49
49
  end
50
50
 
51
+ def widen
52
+ return self
53
+ end
54
+
55
+ def copy
56
+ return self
57
+ end
58
+
51
59
  def hash
52
60
  17
53
61
  end
@@ -44,12 +44,23 @@ module RDL::Type
44
44
  return (other.instance_of? TupleType) && (@params.length == other.params.length) && (@params.zip(other.params).all? { |t,o| t.match(o) })
45
45
  end
46
46
 
47
- def promote!
47
+ def promote(t=nil)
48
48
  return false if @cant_promote
49
- @array = GenericType.new(RDL::Globals.types[:array], UnionType.new(*@params))
50
- # note since we promoted this, lbounds and ubounds will be ignored in future constraints, which
51
- # is good because otherwise we'd get infinite loops
52
- return (@lbounds.all? { |lbound| lbound <= self }) && (@ubounds.all? { |ubound| self <= ubound })
49
+ param = UnionType.new(*@params, t)
50
+ param = param.widen if RDL::Config.instance.promote_widen
51
+ GenericType.new(RDL::Globals.types[:array], param)
52
+ end
53
+
54
+ ### TODO: similar question as in tuple types. Should [1,2,3] be promoted to Array<1 or 2 or 3> or Array<Integer>
55
+ def promote!(t=nil)
56
+ array = promote(t)
57
+ return false if !array
58
+ @array = array
59
+ check_bounds
60
+ end
61
+
62
+ def check_bounds(no_promote=false)
63
+ return (@lbounds.all? { |lbound| lbound.<=(self, no_promote )}) && (@ubounds.all? { |ubound| self.<=(ubound, no_promote) })
53
64
  end
54
65
 
55
66
  def cant_promote!
@@ -57,8 +68,8 @@ module RDL::Type
57
68
  @cant_promote = true
58
69
  end
59
70
 
60
- def <=(other)
61
- return Type.leq(self, other)
71
+ def <=(other, no_constraint=false)
72
+ return Type.leq(self, other, no_constraint: no_constraint)
62
73
  end
63
74
 
64
75
  def member?(obj, *args)
@@ -71,7 +82,20 @@ module RDL::Type
71
82
 
72
83
  def instantiate(inst)
73
84
  return @array.instantiate(inst) if @array
74
- return TupleType.new(*@params.map { |t| t.instantiate(inst) })
85
+ #return TupleType.new(*@params.map { |t| t.instantiate(inst) })
86
+ @params.map! { |t| t.instantiate(inst) }
87
+ self
88
+ end
89
+
90
+ def widen
91
+ return @array.widen if @array
92
+ #return TupleType.new(*@params.map { |t| t.widen })
93
+ @params.map! { |t| t.widen }
94
+ self
95
+ end
96
+
97
+ def copy
98
+ return TupleType.new(*@params.map { |t| t.copy })
75
99
  end
76
100
 
77
101
  def hash
@@ -32,10 +32,11 @@ module RDL::Type
32
32
  # [+ other +] is a Type
33
33
  # [+ inst +] is a Hash<Symbol, Type> representing an instantiation
34
34
  # [+ ileft +] is a %bool
35
+ # [+ no_constraint +] is a %bool indicating whether or not we should add to tuple/FHT constraints
35
36
  # if inst is nil, returns self <= other
36
37
  # if inst is non-nil and ileft, returns inst(self) <= other, possibly mutating inst to make this true
37
38
  # if inst is non-nil and !ileft, returns self <= inst(other), again possibly mutating inst
38
- def self.leq(left, right, inst=nil, ileft=true)
39
+ def self.leq(left, right, inst=nil, ileft=true, no_constraint: false)
39
40
  left = inst[left.name] if inst && ileft && left.is_a?(VarType) && inst[left.name]
40
41
  right = inst[right.name] if inst && !ileft && right.is_a?(VarType) && inst[right.name]
41
42
  left = left.type if left.is_a? DependentArgType
@@ -49,6 +50,10 @@ module RDL::Type
49
50
  return true if left.is_a? BotType
50
51
  return true if right.is_a? TopType
51
52
 
53
+ # dynamic
54
+ return true if left.is_a? DynamicType
55
+ return true if right.is_a? DynamicType
56
+
52
57
  # type variables
53
58
  begin inst.merge!(left.name => right); return true end if inst && ileft && left.is_a?(VarType)
54
59
  begin inst.merge!(right.name => left); return true end if inst && !ileft && right.is_a?(VarType)
@@ -72,12 +77,24 @@ module RDL::Type
72
77
 
73
78
  # nominal
74
79
  return left.klass.ancestors.member?(right.klass) if left.is_a?(NominalType) && right.is_a?(NominalType)
75
- if left.is_a?(NominalType) && right.is_a?(StructuralType)
80
+ if (left.is_a?(NominalType) || left.is_a?(TupleType) || left.is_a?(FiniteHashType) || left.is_a?(PreciseStringType)) && right.is_a?(StructuralType)
81
+ case left
82
+ when TupleType
83
+ lklass = Array
84
+ when FiniteHashType
85
+ lklass = Hash
86
+ when PreciseStringType
87
+ lklass = String
88
+ else
89
+ lklass = left.klass
90
+ end
76
91
  right.methods.each_pair { |m, t|
77
- return false unless left.klass.method_defined? m
78
- types = RDL::Globals.info.get(left.klass, m, :type)
92
+ return false unless lklass.method_defined? m
93
+ types = RDL::Globals.info.get(lklass, m, :type)
79
94
  if types
80
- return false unless types.all? { |tlm| leq(tlm, t, nil, ileft) }
95
+ non_dep_types = RDL::Typecheck.filter_comp_types(types, false)
96
+ raise "Need non-dependent types for method #{m} of class #{lklass} in order to use a structural type." if non_dep_types.empty?
97
+ return false unless non_dep_types.all? { |tlm| leq(tlm, t, nil, ileft) }
81
98
  # inst above is nil because the method types inside the class and
82
99
  # inside the structural type have an implicit quantifier on them. So
83
100
  # even if we're allowed to instantiate type variables we can't do that
@@ -98,7 +115,7 @@ module RDL::Type
98
115
  # do check here to avoid hiding errors if generic type written
99
116
  # with wrong number of parameters but never checked against
100
117
  # instantiated instances
101
- raise TypeError, "No type parameters defined for #{base.name}" unless formals
118
+ raise TypeError, "No type parameters defined for #{left.base.name}" unless formals
102
119
  return false unless left.base == right.base
103
120
  return variance.zip(left.params, right.params).all? { |v, tl, tr|
104
121
  case v
@@ -116,7 +133,7 @@ module RDL::Type
116
133
  if left.is_a?(GenericType) && right.is_a?(StructuralType)
117
134
  # similar to logic above for leq(NominalType, StructuralType, ...)
118
135
  formals, variance, _ = RDL::Globals.type_params[left.base.name]
119
- raise TypeError, "No type parameters defined for #{base.name}" unless formals
136
+ raise TypeError, "No type parameters defined for #{left.base.name}" unless formals
120
137
  base_inst = Hash[*formals.zip(left.params).flatten] # instantiation for methods in base's class
121
138
  klass = left.base.klass
122
139
  right.methods.each_pair { |meth, t|
@@ -162,8 +179,8 @@ module RDL::Type
162
179
  return false unless left.params.length == right.params.length
163
180
  return false unless left.params.zip(right.params).all? { |lt, rt| leq(lt, rt, inst, ileft) }
164
181
  # subyping check passed
165
- left.ubounds << right
166
- right.lbounds << left
182
+ left.ubounds << right unless no_constraint
183
+ right.lbounds << left unless no_constraint
167
184
  return true
168
185
  end
169
186
  if left.is_a?(TupleType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:array]
@@ -196,8 +213,8 @@ module RDL::Type
196
213
  # If left has optional stuff, right needs to accept it
197
214
  return false unless !(right.rest.nil?) && leq(left.rest, right.rest, inst, ileft)
198
215
  end
199
- left.ubounds << right
200
- right.lbounds << left
216
+ left.ubounds << right unless no_constraint
217
+ right.lbounds << left unless no_constraint
201
218
  return true
202
219
  end
203
220
  if left.is_a?(FiniteHashType) && right.is_a?(GenericType) && right.base == RDL::Globals.types[:hash]
@@ -206,6 +223,32 @@ module RDL::Type
206
223
  return leq(left, right, inst, ileft) # recheck for promoted type
207
224
  end
208
225
 
226
+ ## precise string
227
+ if left.is_a?(PreciseStringType)
228
+ if right.is_a?(PreciseStringType)
229
+ return false if left.vals.size != right.vals.size
230
+ left.vals.each_with_index { |v, i|
231
+ if v.is_a?(String) && right.vals[i].is_a?(String)
232
+ return false unless v == right.vals[i]
233
+ elsif v.is_a?(Type) && right.vals[i].is_a?(Type)
234
+ return false unless v <= right.vals[i]
235
+ else
236
+ return false
237
+ end
238
+ }
239
+ left.ubounds << right unless no_constraint
240
+ right.lbounds << left unless no_constraint
241
+ return true
242
+ elsif right == RDL::Globals.types[:string]
243
+ return false unless left.promote!
244
+ return true
245
+ elsif right.is_a?(NominalType) && String.ancestors.include?(RDL::Util.to_class(right.name))
246
+ ## necessary because of checking agains union types: we don't want to promote! unless it will work
247
+ left.promote!
248
+ return true
249
+ end
250
+ end
251
+
209
252
  return false
210
253
  end
211
254
  end
@@ -14,11 +14,11 @@ module RDL::Type
14
14
  if t.instance_of? UnionType
15
15
  ts.concat t.types
16
16
  else
17
- raise RuntimeError, "Attempt to create union type with non-type" unless t.is_a? Type
17
+ raise RuntimeError, "Attempt to create union type with non-type" unless t.is_a?(Type) || t.nil?
18
18
  raise RuntimeError, "Attempt to create union with optional type" if t.is_a? OptionalType
19
19
  raise RuntimeError, "Attempt to create union with vararg type" if t.is_a? VarargType
20
20
  raise RuntimeError, "Attempt to create union with annotated type" if t.is_a? AnnotatedArgType
21
- ts << t
21
+ ts << t if t
22
22
  end
23
23
  }
24
24
  return ts[0] if ts.size == 1
@@ -114,6 +114,45 @@ module RDL::Type
114
114
  return UnionType.new(*(@types.map { |t| t.instantiate(inst) }))
115
115
  end
116
116
 
117
+ def widen
118
+ return @canonical.widen if @canonical
119
+ sing_types = []
120
+ non_sing_types = []
121
+ @types.each { |t|
122
+ case t
123
+ when SingletonType
124
+ sing_types << t.nominal
125
+ when TupleType
126
+ sing_types << t.promote
127
+ when FiniteHashType
128
+ sing_types << t.promote
129
+ else
130
+ non_sing_types << t
131
+ end
132
+ }
133
+ UnionType.new(*sing_types, *non_sing_types).canonical
134
+ end
135
+
136
+ def copy
137
+ UnionType.new(*@types.map { |t| t.copy })
138
+ end
139
+ =begin
140
+ def self.widened_type(u)
141
+ return u unless u.instance_of?(UnionType)
142
+ nominal_class = nil
143
+ @types.each { |t|
144
+ return u if !t.instance_of?(SingletonType)
145
+ if nominal_class.nil?
146
+ nominal_class = t.nominal ## first type
147
+ elsif nominal_class <= t.nominal
148
+ nominal_class = t.nominal ## update to more general class (or just stay the same)
149
+ else
150
+ return u ## found a case with differing singleton classes, can't widen
151
+ end
152
+ }
153
+ nominal_class
154
+ end
155
+ =end
117
156
  def hash # :nodoc:
118
157
  return @hash
119
158
  end