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