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
@@ -41,6 +41,14 @@ module RDL::Type
41
41
  return AnnotatedArgType.new(@name, @type.instantiate(inst))
42
42
  end
43
43
 
44
+ def widen
45
+ return AnnotatedArgType.new(@name, @type.widen)
46
+ end
47
+
48
+ def copy
49
+ return AnnotatedArgType.new(@name, @type.copy)
50
+ end
51
+
44
52
  def optional?
45
53
  return type.optional?
46
54
  end
@@ -0,0 +1,73 @@
1
+ require 'pp'
2
+
3
+ module RDL::Type
4
+ class AstNode < Type
5
+ attr_reader :op
6
+ attr_accessor :val, :children, :parent
7
+
8
+ def initialize(op, val)
9
+ @op = op
10
+ @val = val
11
+ @children = []
12
+ @parent = nil
13
+ end
14
+
15
+ def root
16
+ unless self.parent
17
+ self
18
+ else
19
+ root(self.parent)
20
+ end
21
+ end
22
+
23
+ def insert(child)
24
+ raise "AstNode expected" unless child.is_a? AstNode
25
+ @children << child
26
+ end
27
+
28
+ def find_all(op)
29
+ @children.find_all { |obj| obj.op == op }
30
+ end
31
+
32
+ def find_one(op)
33
+ results = self.find_all(op)
34
+ raise "One node expected" unless results.size < 2
35
+ results[0]
36
+ end
37
+
38
+ def ==(other)
39
+ return false if other.nil?
40
+ other = other.canonical
41
+ return (other.instance_of? self.class) && (other.val.equal? @val)
42
+ end
43
+
44
+ alias eql? ==
45
+
46
+ def match(other)
47
+ other = other.canonical
48
+ other = other.type if other.instance_of? AnnotatedArgType
49
+ return true if other.instance_of? WildQuery
50
+ return self == other
51
+ end
52
+
53
+ def hash # :nodoc:
54
+ return @val.hash
55
+ end
56
+
57
+ def to_s
58
+ self.pretty_inspect
59
+ end
60
+
61
+ def <=(other)
62
+ return Type.leq(self, other)
63
+ end
64
+
65
+ def member?(obj, *args)
66
+ raise "member? on AstNode called"
67
+ end
68
+
69
+ def instantiate(inst)
70
+ return self
71
+ end
72
+ end
73
+ end
@@ -45,6 +45,14 @@ module RDL::Type
45
45
  return self
46
46
  end
47
47
 
48
+ def widen
49
+ return self
50
+ end
51
+
52
+ def copy
53
+ self
54
+ end
55
+
48
56
  def hash
49
57
  13
50
58
  end
@@ -0,0 +1,63 @@
1
+ require_relative 'type'
2
+
3
+ module RDL::Type
4
+ class BoundArgType < Type
5
+ attr_reader :name
6
+ attr_reader :type
7
+
8
+ # Note: Named argument types aren't hashconsed.
9
+
10
+ def initialize(name, type)
11
+ @name = name
12
+ @type = type
13
+ raise RuntimeError, "Attempt to create bound type with non-type" unless type.is_a? Type
14
+ raise RuntimeError, "Attempt to create doubly annotated type" if (type.is_a?(BoundArgType) || type.is_a?(AnnotatedArgType))
15
+ raise RuntimeError, "Cannot create bound type with optional type" if type.is_a? OptionalType
16
+ raise RuntimeError, "Cannot create bound type with variable argument type" if type.is_a? VarargType
17
+ super()
18
+ end
19
+
20
+ def to_s
21
+ return "#{@name}<::#{@type.to_s}"
22
+ end
23
+
24
+ def ==(other) # :nodoc:
25
+ return false if other.nil?
26
+ other = other.canonical
27
+ return (other.instance_of? BoundArgType) && (other.name == @name) && (other.type == @type)
28
+ end
29
+
30
+ alias eql? ==
31
+
32
+ # doesn't have a match method - queries shouldn't have annotations in them
33
+
34
+ def hash # :nodoc:
35
+ return (57 + @name.hash) * @type.hash
36
+ end
37
+
38
+ def member?(obj, *args)
39
+ @type.member?(obj, *args)
40
+ end
41
+
42
+ def instantiate(inst)
43
+ return BoundArgType.new(@name, @type.instantiate(inst))
44
+ end
45
+
46
+ def widen
47
+ return BoundArgType.new(@name, @type.widen)
48
+ end
49
+
50
+ def copy
51
+ return BoundArgType.new(@name, @type.copy)
52
+ end
53
+
54
+ def optional?
55
+ return type.optional?
56
+ end
57
+
58
+ def vararg?
59
+ return type.vararg?
60
+ end
61
+ end
62
+ end
63
+
@@ -0,0 +1,48 @@
1
+ require_relative 'type'
2
+
3
+ module RDL::Type
4
+ class ComputedType < Type
5
+ attr_reader :code
6
+
7
+ def initialize(code)
8
+ @code = code
9
+ super()
10
+ end
11
+
12
+ def compute(bind)
13
+ res = bind.eval(@code)
14
+ raise RuntimeError, "Expected ComputedType to evaluate to type, instead got #{res}." unless res.is_a?(Type)
15
+ res
16
+ end
17
+
18
+ def to_s
19
+ "``#{@code}``"
20
+ end
21
+
22
+ ### TODO:: Figure out how to fill in the below methods.
23
+ ### I believe a ComputedType will always be evaluated to
24
+ ### another RDL type before any of these methods would be called.
25
+ ### Need to think about this though.
26
+
27
+ def instantiate(inst)
28
+ @inst = inst
29
+ self
30
+ end
31
+
32
+ def widen
33
+ self
34
+ end
35
+
36
+ def <=(other)
37
+ ## TODO
38
+ end
39
+
40
+ def ==(other)
41
+ ## TODO
42
+ end
43
+
44
+ alias eql? ==
45
+
46
+
47
+ end
48
+ end
@@ -52,5 +52,14 @@ module RDL::Type
52
52
  def instantiate(inst)
53
53
  return DependentArgType.new(@name, @type.instantiate(inst), @predicate)
54
54
  end
55
+
56
+ def widen
57
+ return DependentArgType.new(@name, @type.widen, @predicate)
58
+ end
59
+
60
+ def copy
61
+ return DependentArgType.new(@name, @type.copy, @predicate)
62
+ end
63
+
55
64
  end
56
65
  end
@@ -0,0 +1,61 @@
1
+ module RDL::Type
2
+ class DynamicType < Type
3
+ @@cache = nil
4
+
5
+ attr_reader :block
6
+
7
+ class << self
8
+ alias :__new__ :new
9
+ end
10
+
11
+ def self.new
12
+ @@cache = DynamicType.__new__ unless @@cache
13
+ return @@cache
14
+ end
15
+
16
+ def initialize
17
+ super
18
+ end
19
+
20
+ def to_s
21
+ "%dyn"
22
+ end
23
+
24
+ def ==(other)
25
+ return false if other.nil?
26
+ other = other.canonical
27
+ other.instance_of? DynamicType
28
+ end
29
+
30
+ alias eql? ==
31
+
32
+ def match(other)
33
+ other = other.canonical
34
+ other = other.type if other.instance_of? AnnotatedArgType
35
+ return true if other.instance_of? WildQuery
36
+ return self == other
37
+ end
38
+
39
+ def <=(other)
40
+ return Type.leq(self, other)
41
+ end
42
+
43
+ def member?(obj, *args)
44
+ t = RDL::Util.rdl_type obj
45
+ return t <= self if t
46
+ true
47
+ end
48
+
49
+ def instantiate(inst)
50
+ return self
51
+ end
52
+
53
+ def copy
54
+ return self
55
+ end
56
+
57
+ def hash
58
+ 16
59
+ end
60
+ end
61
+ end
@@ -6,7 +6,7 @@ module RDL::Type
6
6
  # Finite hashes can also have a "rest" type (okay, they're not exactly finite in this case...)
7
7
  # which is treated as a hash from Symbol to the type.
8
8
  class FiniteHashType < Type
9
- attr_reader :elts
9
+ attr_accessor :elts
10
10
  attr_reader :rest
11
11
  attr_reader :the_hash # either nil or hash type if self has been promoted to hash
12
12
  attr_accessor :ubounds # upper bounds this tuple has been compared with using <=
@@ -58,18 +58,42 @@ module RDL::Type
58
58
  @elts.all? { |k, v| (other.elts.has_key? k) && (v.match(other.elts[k]))})
59
59
  end
60
60
 
61
- def promote!
61
+ def promote(key=nil, value=nil)
62
62
  return false if @cant_promote
63
63
  # TODO look at key types
64
- domain_type = UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) }))
65
- range_type = UnionType.new(*@elts.values)
64
+ if key
65
+ domain_type = UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) }), key)
66
+ else
67
+ domain_type = UnionType.new(*(@elts.keys.map { |k| NominalType.new(k.class) }))
68
+ end
69
+ if value
70
+ range_type = UnionType.new(*@elts.values, value)
71
+ else
72
+ range_type = UnionType.new(*@elts.values)
73
+ end
66
74
  if @rest
67
75
  domain_type = UnionType.new(domain_type, RDL::Globals.types[:symbol])
68
76
  range_type = UnionType.new(range_type, @rest)
69
77
  end
70
- @the_hash = GenericType.new(RDL::Globals.types[:hash], domain_type, range_type)
78
+ if RDL::Config.instance.promote_widen
79
+ case range_type
80
+ when RDL::Type::SingletonType
81
+ range_type = range_type.nominal if range_type.val
82
+ when RDL::Type::UnionType
83
+ range_type = range_type.widen
84
+ end
85
+ end
86
+ return GenericType.new(RDL::Globals.types[:hash], domain_type.canonical, range_type.canonical)
87
+ end
88
+
89
+ ### [+ key +] is type to add to promoted key types
90
+ ### [+ value +] is type to add to promoted value types
91
+ def promote!(key=nil, value=nil)
92
+ hash = promote(key, value)
93
+ return hash if !hash
94
+ @the_hash = hash
71
95
  # same logic as Tuple
72
- return (@lbounds.all? { |lbound| lbound <= self }) && (@ubounds.all? { |ubound| self <= ubound })
96
+ return check_bounds
73
97
  end
74
98
 
75
99
  def cant_promote!
@@ -77,8 +101,12 @@ module RDL::Type
77
101
  @cant_promote = true
78
102
  end
79
103
 
80
- def <=(other)
81
- return Type.leq(self, other)
104
+ def check_bounds(no_promote=false)
105
+ return (@lbounds.all? { |lbound| lbound.<=(self, no_promote) }) && (@ubounds.all? { |ubound| self.<=(ubound, no_promote) })
106
+ end
107
+
108
+ def <=(other, no_constraint=false)
109
+ return Type.leq(self, other, no_constraint: no_constraint)
82
110
  end
83
111
 
84
112
  def member?(obj, *args)
@@ -87,6 +115,7 @@ module RDL::Type
87
115
  return t <= self if t
88
116
  right_elts = @elts.clone # shallow copy
89
117
 
118
+ return true if obj.nil?
90
119
  return false unless obj.instance_of? Hash
91
120
 
92
121
  # Check that every mapping in obj exists in @map and matches the type
@@ -111,7 +140,23 @@ module RDL::Type
111
140
 
112
141
  def instantiate(inst)
113
142
  return @the_hash.instantiate(inst) if @the_hash
114
- return FiniteHashType.new(Hash[@elts.map { |k, t| [k, t.instantiate(inst)] }], (if @rest then @rest.instantiate(inst) end))
143
+ #return FiniteHashType.new(Hash[@elts.map { |k, t| [k, t.instantiate(inst)] }], (if @rest then @rest.instantiate(inst) end))
144
+ @elts = Hash[@elts.map { |k, t| [k, t.instantiate(inst)] }]
145
+ @rest = @rest.instantiate(inst) if @rest
146
+ self
147
+ end
148
+
149
+ def widen
150
+ return @the_hash.widen if @the_hash
151
+ #return FiniteHashType.new(Hash[@elts.map { |k, t| [k, t.widen] }], (if @rest then @rest.widen end))
152
+ @elts = Hash[@elts.map { |k, t| [k, t.widen] }]
153
+ @rest = @rest.widen if @rest
154
+ self
155
+ end
156
+
157
+ def copy
158
+ rest = @rest.copy if @rest
159
+ return FiniteHashType.new(Hash[@elts.map { |k,t| [k, t.copy] }], rest)
115
160
  end
116
161
 
117
162
  def hash
@@ -40,6 +40,22 @@ module RDL::Type
40
40
  end
41
41
 
42
42
  def member?(obj, *args)
43
+ if base.name == "Table"
44
+ return true if obj.class.to_s == "Mocha::Mock" ## mock object class appearing in one of the benchmarks. Not much we can do here.
45
+ return false unless obj.class.ancestors.map { |a| a.to_s}.include?("Sequel::Dataset")#is_a?(Sequel::Dataset) (obj.class.to_s == "Sequel::SQLite::Dataset")
46
+ raise RDL::Type::TypeError, "Expected Table type to be parameterized by finite hash, instead got #{@params}." unless @params[0].is_a?(RDL::Type::FiniteHashType)
47
+ if @params[0].elts[:__all_joined].is_a?(RDL::Type::UnionType) && obj.joined_dataset?
48
+ type_joined_tables = @params[0].elts[:__all_joined].types.map { |t| t.val }
49
+ obj_joined_tables = obj.opts[:from] + obj.opts[:join].map { |t| t.table }
50
+ return (type_joined_tables.sort == obj_joined_tables.sort)
51
+ elsif !@params[0].elts[:__all_joined].is_a?(RDL::Type::TupleType) && !obj.joined_dataset?
52
+ return true
53
+ else
54
+ return false
55
+ end
56
+ elsif base.name == "ActiveRecord_Relation"
57
+ return (obj.class.name == "ActiveRecord::Relation" || obj.class.name == "ActiveRecord::Associations::CollectionProxy" || obj.class.name == "ActiveRecord::AssociationRelation") ## TODO: check for joins?
58
+ end
43
59
  raise "No type parameters defined for #{base.name}" unless RDL::Globals.type_params[base.name]
44
60
  # formals = RDL::Globals.type_params[base.name][0]
45
61
  t = RDL::Util.rdl_type obj
@@ -52,6 +68,14 @@ module RDL::Type
52
68
  GenericType.new(base.instantiate(inst), *params.map { |t| t.instantiate(inst) })
53
69
  end
54
70
 
71
+ def widen
72
+ GenericType.new(base.widen, *params.map { |t| t.widen })
73
+ end
74
+
75
+ def copy
76
+ GenericType.new(base.copy, *params.map { |t| t.copy })
77
+ end
78
+
55
79
  def hash
56
80
  (61 + @base.hash) * @params.hash
57
81
  end
@@ -59,5 +83,14 @@ module RDL::Type
59
83
  def to_inst
60
84
  return RDL::Globals.type_params[base.name][0].zip(@params).to_h
61
85
  end
86
+
87
+ def canonical
88
+ canonicalize!
89
+ return self
90
+ end
91
+
92
+ def canonicalize!
93
+ @params.map! {|p| p.canonical}
94
+ end
62
95
  end
63
96
  end