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