rdl 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +1 -0
- data/.travis.yml +7 -6
- data/CHANGES.md +29 -0
- data/README.md +94 -26
- data/lib/rdl/boot.rb +82 -41
- data/lib/rdl/boot_rails.rb +5 -0
- data/lib/rdl/config.rb +9 -1
- data/lib/rdl/query.rb +2 -2
- data/lib/rdl/typecheck.rb +972 -225
- data/lib/rdl/types/annotated_arg.rb +8 -0
- data/lib/rdl/types/ast_node.rb +73 -0
- data/lib/rdl/types/bot.rb +8 -0
- data/lib/rdl/types/bound_arg.rb +63 -0
- data/lib/rdl/types/computed.rb +48 -0
- data/lib/rdl/types/dependent_arg.rb +9 -0
- data/lib/rdl/types/dynamic.rb +61 -0
- data/lib/rdl/types/finite_hash.rb +54 -9
- data/lib/rdl/types/generic.rb +33 -0
- data/lib/rdl/types/intersection.rb +8 -0
- data/lib/rdl/types/lexer.rex +6 -1
- data/lib/rdl/types/lexer.rex.rb +13 -1
- data/lib/rdl/types/method.rb +14 -0
- data/lib/rdl/types/nominal.rb +8 -0
- data/lib/rdl/types/non_null.rb +8 -0
- data/lib/rdl/types/optional.rb +8 -0
- data/lib/rdl/types/parser.racc +31 -5
- data/lib/rdl/types/parser.tab.rb +540 -302
- data/lib/rdl/types/rdl_types.rb +45 -0
- data/lib/rdl/types/singleton.rb +14 -1
- data/lib/rdl/types/string.rb +104 -0
- data/lib/rdl/types/structural.rb +8 -0
- data/lib/rdl/types/top.rb +8 -0
- data/lib/rdl/types/tuple.rb +32 -8
- data/lib/rdl/types/type.rb +54 -11
- data/lib/rdl/types/union.rb +41 -2
- data/lib/rdl/types/var.rb +10 -0
- data/lib/rdl/types/vararg.rb +8 -0
- data/lib/rdl/util.rb +13 -10
- data/lib/rdl/wrap.rb +271 -27
- data/lib/rdl_disable.rb +16 -2
- data/lib/types/active_record.rb +1 -0
- data/lib/types/core/array.rb +442 -23
- data/lib/types/core/basic_object.rb +3 -3
- data/lib/types/core/bigdecimal.rb +5 -0
- data/lib/types/core/class.rb +2 -0
- data/lib/types/core/dir.rb +3 -3
- data/lib/types/core/enumerable.rb +4 -4
- data/lib/types/core/enumerator.rb +1 -1
- data/lib/types/core/file.rb +4 -4
- data/lib/types/core/float.rb +203 -0
- data/lib/types/core/hash.rb +390 -15
- data/lib/types/core/integer.rb +223 -10
- data/lib/types/core/io.rb +2 -2
- data/lib/types/core/kernel.rb +8 -5
- data/lib/types/core/marshal.rb +3 -0
- data/lib/types/core/module.rb +3 -3
- data/lib/types/core/numeric.rb +0 -2
- data/lib/types/core/object.rb +5 -5
- data/lib/types/core/pathname.rb +2 -2
- data/lib/types/core/process.rb +1 -3
- data/lib/types/core/range.rb +1 -1
- data/lib/types/core/regexp.rb +2 -2
- data/lib/types/core/set.rb +1 -1
- data/lib/types/core/string.rb +408 -16
- data/lib/types/core/symbol.rb +3 -3
- data/lib/types/core/time.rb +1 -1
- data/lib/types/core/uri.rb +13 -13
- data/lib/types/rails/_helpers.rb +7 -1
- data/lib/types/rails/action_controller/mime_responds.rb +2 -0
- data/lib/types/rails/active_record/associations.rb +42 -30
- data/lib/types/rails/active_record/comp_types.rb +637 -0
- data/lib/types/rails/active_record/finder_methods.rb +1 -1
- data/lib/types/rails/active_record/model_schema.rb +28 -16
- data/lib/types/rails/active_record/relation.rb +5 -3
- data/lib/types/rails/active_record/sql-strings.rb +166 -0
- data/lib/types/rails/string.rb +1 -1
- data/lib/types/sequel.rb +1 -0
- data/lib/types/sequel/comp_types.rb +581 -0
- data/rdl.gemspec +5 -4
- data/test/test_alias.rb +4 -0
- data/test/test_array_types.rb +244 -0
- data/test/test_bound_types.rb +80 -0
- data/test/test_contract.rb +4 -0
- data/test/test_dsl.rb +5 -0
- data/test/test_dyn_comptype_checks.rb +206 -0
- data/test/test_generic.rb +21 -20
- data/test/test_hash_types.rb +322 -0
- data/test/test_intersection.rb +1 -0
- data/test/test_le.rb +29 -4
- data/test/test_member.rb +3 -1
- data/test/test_parser.rb +5 -0
- data/test/test_query.rb +1 -0
- data/test/test_rdl.rb +63 -28
- data/test/test_rdl_type.rb +4 -0
- data/test/test_string_types.rb +102 -0
- data/test/test_type_contract.rb +59 -37
- data/test/test_typecheck.rb +480 -75
- data/test/test_types.rb +17 -0
- data/test/test_wrap.rb +5 -0
- metadata +35 -5
- 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
|
data/lib/rdl/types/bot.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
65
|
-
|
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
|
-
|
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
|
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
|
81
|
-
return
|
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
|
data/lib/rdl/types/generic.rb
CHANGED
@@ -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
|