rdl 1.0.0.rc1
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 +7 -0
- data/lib/rails_types.rb +1 -0
- data/lib/rdl.rb +56 -0
- data/lib/rdl/config.rb +121 -0
- data/lib/rdl/contracts/and.rb +29 -0
- data/lib/rdl/contracts/contract.rb +7 -0
- data/lib/rdl/contracts/flat.rb +31 -0
- data/lib/rdl/contracts/or.rb +25 -0
- data/lib/rdl/contracts/proc.rb +24 -0
- data/lib/rdl/switch.rb +20 -0
- data/lib/rdl/types/annotated_arg.rb +41 -0
- data/lib/rdl/types/finitehash.rb +81 -0
- data/lib/rdl/types/generic.rb +100 -0
- data/lib/rdl/types/intersection.rb +66 -0
- data/lib/rdl/types/lexer.rex +39 -0
- data/lib/rdl/types/lexer.rex.rb +148 -0
- data/lib/rdl/types/method.rb +219 -0
- data/lib/rdl/types/nil.rb +50 -0
- data/lib/rdl/types/nominal.rb +80 -0
- data/lib/rdl/types/optional.rb +54 -0
- data/lib/rdl/types/parser.racc +150 -0
- data/lib/rdl/types/parser.tab.rb +654 -0
- data/lib/rdl/types/singleton.rb +62 -0
- data/lib/rdl/types/structural.rb +72 -0
- data/lib/rdl/types/top.rb +50 -0
- data/lib/rdl/types/tuple.rb +61 -0
- data/lib/rdl/types/type.rb +24 -0
- data/lib/rdl/types/type_inferencer.rb +73 -0
- data/lib/rdl/types/union.rb +74 -0
- data/lib/rdl/types/var.rb +51 -0
- data/lib/rdl/types/vararg.rb +54 -0
- data/lib/rdl/types/wild.rb +26 -0
- data/lib/rdl/util.rb +56 -0
- data/lib/rdl/wrap.rb +505 -0
- data/lib/rdl_types.rb +2 -0
- data/types/other/chronic.rb +5 -0
- data/types/other/paperclip_attachment.rb +7 -0
- data/types/other/securerandom.rb +4 -0
- data/types/rails-4.2.1/fixnum.rb +3 -0
- data/types/rails-4.2.1/string.rb +3 -0
- data/types/rails-tmp/action_dispatch.rb +406 -0
- data/types/rails-tmp/active_record.rb +406 -0
- data/types/rails-tmp/devise_contracts.rb +216 -0
- data/types/ruby-2.2.0/_aliases.rb +4 -0
- data/types/ruby-2.2.0/abbrev.rb +5 -0
- data/types/ruby-2.2.0/array.rb +137 -0
- data/types/ruby-2.2.0/base64.rb +10 -0
- data/types/ruby-2.2.0/basic_object.rb +13 -0
- data/types/ruby-2.2.0/benchmark.rb +11 -0
- data/types/ruby-2.2.0/bigdecimal.rb +15 -0
- data/types/ruby-2.2.0/bigmath.rb +12 -0
- data/types/ruby-2.2.0/class.rb +17 -0
- data/types/ruby-2.2.0/complex.rb +42 -0
- data/types/ruby-2.2.0/coverage.rb +6 -0
- data/types/ruby-2.2.0/csv.rb +5 -0
- data/types/ruby-2.2.0/date.rb +6 -0
- data/types/ruby-2.2.0/dir.rb +38 -0
- data/types/ruby-2.2.0/encoding.rb +23 -0
- data/types/ruby-2.2.0/enumerable.rb +98 -0
- data/types/ruby-2.2.0/enumerator.rb +26 -0
- data/types/ruby-2.2.0/exception.rb +15 -0
- data/types/ruby-2.2.0/file.rb +124 -0
- data/types/ruby-2.2.0/fileutils.rb +6 -0
- data/types/ruby-2.2.0/fixnum.rb +45 -0
- data/types/ruby-2.2.0/float.rb +54 -0
- data/types/ruby-2.2.0/gem.rb +245 -0
- data/types/ruby-2.2.0/hash.rb +72 -0
- data/types/ruby-2.2.0/integer.rb +31 -0
- data/types/ruby-2.2.0/io.rb +103 -0
- data/types/ruby-2.2.0/kernel.rb +89 -0
- data/types/ruby-2.2.0/marshal.rb +5 -0
- data/types/ruby-2.2.0/matchdata.rb +26 -0
- data/types/ruby-2.2.0/math.rb +53 -0
- data/types/ruby-2.2.0/numeric.rb +46 -0
- data/types/ruby-2.2.0/object.rb +73 -0
- data/types/ruby-2.2.0/pathname.rb +106 -0
- data/types/ruby-2.2.0/process.rb +127 -0
- data/types/ruby-2.2.0/random.rb +15 -0
- data/types/ruby-2.2.0/range.rb +38 -0
- data/types/ruby-2.2.0/rational.rb +31 -0
- data/types/ruby-2.2.0/regexp.rb +30 -0
- data/types/ruby-2.2.0/set.rb +58 -0
- data/types/ruby-2.2.0/string.rb +145 -0
- data/types/ruby-2.2.0/strscan.rb +7 -0
- data/types/ruby-2.2.0/symbol.rb +29 -0
- data/types/ruby-2.2.0/time.rb +68 -0
- data/types/ruby-2.2.0/uri.rb +18 -0
- data/types/ruby-2.2.0/yaml.rb +5 -0
- metadata +131 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require_relative 'type'
|
|
2
|
+
|
|
3
|
+
module RDL::Type
|
|
4
|
+
class VarargType < Type
|
|
5
|
+
attr_reader :type
|
|
6
|
+
|
|
7
|
+
@@cache = {}
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
alias :__new__ :new
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.new(type)
|
|
14
|
+
t = @@cache[type]
|
|
15
|
+
return t if t
|
|
16
|
+
raise RuntimeError, "Attempt to create vararg type with non-type" unless type.is_a? Type
|
|
17
|
+
t = VarargType.__new__ type
|
|
18
|
+
return (@@cache[type] = t) # assignment evaluates to t
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize(type)
|
|
22
|
+
raise "Can't have vararg optional type" if type.class == OptionalType
|
|
23
|
+
raise "Can't have vararg vararg type" if type.class == VarargType
|
|
24
|
+
@type = type
|
|
25
|
+
super()
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def to_s
|
|
29
|
+
if @type.instance_of? UnionType
|
|
30
|
+
"*(#{@type.to_s})"
|
|
31
|
+
else
|
|
32
|
+
"*#{@type.to_s}"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def eql?(other)
|
|
37
|
+
self == other
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def ==(other) # :nodoc:
|
|
41
|
+
return (other.instance_of? VarargType) && (other.type == @type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Note: no member?, because these can only appear in MethodType, where they're handled specially
|
|
45
|
+
|
|
46
|
+
def instantiate(inst)
|
|
47
|
+
return VarargType.new(@type.instantiate(inst))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def hash # :nodoc:
|
|
51
|
+
return 59 + @type.hash
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module RDL::Type
|
|
2
|
+
class Wild < Type
|
|
3
|
+
@@cache = nil
|
|
4
|
+
|
|
5
|
+
class << self
|
|
6
|
+
alias :__new__ :new
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def self.new
|
|
10
|
+
@@cache = Wild.__new__ unless @@cache
|
|
11
|
+
return @@cache
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_s
|
|
15
|
+
"*"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def ==(other)
|
|
19
|
+
return (other.instance_of? Wild)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def match(other)
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
data/lib/rdl/util.rb
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
class RDL::Util
|
|
2
|
+
def self.to_class(klass)
|
|
3
|
+
return klass if klass.class == Class
|
|
4
|
+
if has_singleton_marker(klass)
|
|
5
|
+
klass = remove_singleton_marker(klass)
|
|
6
|
+
sing = true
|
|
7
|
+
end
|
|
8
|
+
c = klass.to_s.split("::").inject(Object) { |base, name| base.const_get(name) }
|
|
9
|
+
c = c.singleton_class if sing
|
|
10
|
+
return c
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def self.has_singleton_marker(klass)
|
|
14
|
+
return (klass =~ /^\[singleton\]/)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.remove_singleton_marker(klass)
|
|
18
|
+
if klass =~ /^\[singleton\](.*)/
|
|
19
|
+
return $1
|
|
20
|
+
else
|
|
21
|
+
return nil
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.add_singleton_marker(klass)
|
|
26
|
+
return "[singleton]" + klass
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def self.method_defined?(klass, method)
|
|
30
|
+
begin
|
|
31
|
+
(self.to_class klass).method_defined?(method.to_sym) ||
|
|
32
|
+
(self.to_class klass).private_instance_methods.include?(method.to_sym) ||
|
|
33
|
+
(self.to_class klass).protected_instance_methods.include?(method.to_sym)
|
|
34
|
+
rescue NameError
|
|
35
|
+
return false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the @__rdl_type field of [+obj+]
|
|
40
|
+
def self.rdl_type(obj)
|
|
41
|
+
return (obj.instance_variable_defined?('@__rdl_type') && obj.instance_variable_get('@__rdl_type'))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.rdl_type_or_class(obj)
|
|
45
|
+
return self.rdl_type(obj) || obj.class
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.pp_klass_method(klass, meth)
|
|
49
|
+
klass = klass.to_s
|
|
50
|
+
if has_singleton_marker klass
|
|
51
|
+
remove_singleton_marker(klass) + "." + meth.to_s
|
|
52
|
+
else
|
|
53
|
+
klass + "#" + meth.to_s
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/rdl/wrap.rb
ADDED
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
class RDL::Wrap
|
|
2
|
+
def self.wrapped?(klass, meth)
|
|
3
|
+
RDL::Util.method_defined?(klass, wrapped_name(klass, meth))
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
def self.resolve_alias(klass, meth)
|
|
7
|
+
klass = klass.to_s
|
|
8
|
+
meth = meth.to_sym
|
|
9
|
+
while $__rdl_aliases[klass] && $__rdl_aliases[klass][meth]
|
|
10
|
+
raise RuntimeError, "Alias #{klass}\##{meth} has contracts. Contracts are only allowed on methods, not aliases." if has_any_contracts?(klass, meth)
|
|
11
|
+
meth = $__rdl_aliases[klass][meth]
|
|
12
|
+
end
|
|
13
|
+
return meth
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.add_contract(klass, meth, kind, val)
|
|
17
|
+
klass = klass.to_s
|
|
18
|
+
meth = meth.to_sym
|
|
19
|
+
$__rdl_contracts[klass] = {} unless $__rdl_contracts[klass]
|
|
20
|
+
$__rdl_contracts[klass][meth] = {} unless $__rdl_contracts[klass][meth]
|
|
21
|
+
$__rdl_contracts[klass][meth][kind] = [] unless $__rdl_contracts[klass][meth][kind]
|
|
22
|
+
$__rdl_contracts[klass][meth][kind] << val
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.has_contracts?(klass, meth, kind)
|
|
26
|
+
klass = klass.to_s
|
|
27
|
+
meth = meth.to_sym
|
|
28
|
+
return ($__rdl_contracts.has_key? klass) &&
|
|
29
|
+
($__rdl_contracts[klass].has_key? meth) &&
|
|
30
|
+
($__rdl_contracts[klass][meth].has_key? kind)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.has_any_contracts?(klass, meth)
|
|
34
|
+
klass = klass.to_s
|
|
35
|
+
meth = meth.to_sym
|
|
36
|
+
return ($__rdl_contracts.has_key? klass) &&
|
|
37
|
+
($__rdl_contracts[klass].has_key? meth)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.get_contracts(klass, meth, kind)
|
|
41
|
+
klass = klass.to_s
|
|
42
|
+
meth = meth.to_sym
|
|
43
|
+
return $__rdl_contracts[klass][meth][kind]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.get_type_params(klass)
|
|
47
|
+
klass = klass.to_s
|
|
48
|
+
$__rdl_type_params[klass]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# [+klass+] may be a Class, String, or Symbol
|
|
52
|
+
# [+meth+] may be a String or Symbol
|
|
53
|
+
#
|
|
54
|
+
# Wraps klass#method to check contracts and types. Does not rewrap
|
|
55
|
+
# if already wrapped.
|
|
56
|
+
def self.wrap(klass_str, meth)
|
|
57
|
+
$__rdl_wrap_switch.off {
|
|
58
|
+
klass_str = klass_str.to_s
|
|
59
|
+
klass = RDL::Util.to_class klass_str
|
|
60
|
+
return if RDL::Config.instance.nowrap.member? klass
|
|
61
|
+
raise ArgumentError, "Attempt to wrap #{klass.to_s}\##{meth.to_s}" if klass.to_s =~ /^RDL::/
|
|
62
|
+
meth_old = wrapped_name(klass, meth) # meth_old is a symbol
|
|
63
|
+
return if (klass.method_defined? meth_old)
|
|
64
|
+
is_singleton_method = RDL::Util.has_singleton_marker(klass_str)
|
|
65
|
+
full_method_name = RDL::Util.pp_klass_method(klass_str, meth)
|
|
66
|
+
|
|
67
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__
|
|
68
|
+
alias_method meth_old, meth
|
|
69
|
+
def #{meth}(*args, &blk)
|
|
70
|
+
klass = "#{klass_str}"
|
|
71
|
+
meth = types = matches = nil
|
|
72
|
+
inst = nil
|
|
73
|
+
$__rdl_wrap_switch.off {
|
|
74
|
+
$__rdl_wrapped_calls["#{full_method_name}"] += 1 if RDL::Config.instance.gather_stats
|
|
75
|
+
inst = @__rdl_inst
|
|
76
|
+
inst = Hash[$__rdl_type_params[klass][0].zip []] if (not(inst) && $__rdl_type_params[klass])
|
|
77
|
+
inst = {} if not inst
|
|
78
|
+
#{if not(is_singleton_method) then "inst[:self] = RDL::Type::SingletonType.new(self)" end}
|
|
79
|
+
# puts "Intercepted #{full_method_name}(\#{args.join(", ")}) { \#{blk} }, inst = \#{inst.inspect}"
|
|
80
|
+
meth = RDL::Wrap.resolve_alias(klass, #{meth.inspect})
|
|
81
|
+
if RDL::Wrap.has_contracts?(klass, meth, :pre)
|
|
82
|
+
pres = RDL::Wrap.get_contracts(klass, meth, :pre)
|
|
83
|
+
RDL::Contract::AndContract.check_array(pres, self, *args, &blk)
|
|
84
|
+
end
|
|
85
|
+
if RDL::Wrap.has_contracts?(klass, meth, :type)
|
|
86
|
+
types = RDL::Wrap.get_contracts(klass, meth, :type)
|
|
87
|
+
matches = RDL::Type::MethodType.check_arg_types("#{full_method_name}", types, inst, *args, &blk)
|
|
88
|
+
end
|
|
89
|
+
}
|
|
90
|
+
ret = send(#{meth_old.inspect}, *args, &blk)
|
|
91
|
+
$__rdl_wrap_switch.off {
|
|
92
|
+
if RDL::Wrap.has_contracts?(klass, meth, :post)
|
|
93
|
+
posts = RDL::Wrap.get_contracts(klass, meth, :post)
|
|
94
|
+
RDL::Contract::AndContract.check_array(posts, self, ret, *args, &blk)
|
|
95
|
+
end
|
|
96
|
+
if matches
|
|
97
|
+
RDL::Type::MethodType.check_ret_types("#{full_method_name}", types, inst, matches, ret, *args, &blk)
|
|
98
|
+
end
|
|
99
|
+
}
|
|
100
|
+
return ret
|
|
101
|
+
end
|
|
102
|
+
if (public_method_defined? meth_old) then public meth
|
|
103
|
+
elsif (protected_method_defined? meth_old) then protected meth
|
|
104
|
+
elsif (private_method_defined? meth_old) then private meth
|
|
105
|
+
end
|
|
106
|
+
RUBY
|
|
107
|
+
}
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# [+default_class+] should be a class
|
|
111
|
+
# [+name+] is the name to give the block as a contract
|
|
112
|
+
def self.process_pre_post_args(default_class, name, *args, &blk)
|
|
113
|
+
klass = slf = meth = contract = nil
|
|
114
|
+
if args.size == 3
|
|
115
|
+
klass = class_to_string args[0]
|
|
116
|
+
slf, meth = meth_to_sym args[1]
|
|
117
|
+
contract = args[2]
|
|
118
|
+
elsif args.size == 2 && blk
|
|
119
|
+
klass = class_to_string args[0]
|
|
120
|
+
slf, meth = meth_to_sym args[1]
|
|
121
|
+
contract = RDL::Contract::FlatContract.new(name, &blk)
|
|
122
|
+
elsif args.size == 2
|
|
123
|
+
klass = default_class.to_s
|
|
124
|
+
slf, meth = meth_to_sym args[0]
|
|
125
|
+
contract = args[1]
|
|
126
|
+
elsif args.size == 1 && blk
|
|
127
|
+
klass = default_class.to_s
|
|
128
|
+
slf, meth = meth_to_sym args[0]
|
|
129
|
+
contract = RDL::Contract::FlatContract.new(name, &blk)
|
|
130
|
+
elsif args.size == 1
|
|
131
|
+
klass = default_class.to_s
|
|
132
|
+
contract = args[0]
|
|
133
|
+
elsif blk
|
|
134
|
+
klass = default_class.to_s
|
|
135
|
+
contract = RDL::Contract::FlatContract.new(name, &blk)
|
|
136
|
+
else
|
|
137
|
+
raise ArgumentError, "Invalid arguments"
|
|
138
|
+
end
|
|
139
|
+
raise ArgumentError, "#{contract.class} received where Contract expected" unless contract.class < RDL::Contract::Contract
|
|
140
|
+
# meth = :initialize if meth && meth.to_sym == :new # actually wrap constructor
|
|
141
|
+
klass = RDL::Util.add_singleton_marker(klass) if slf # && (meth != :initialize)
|
|
142
|
+
return [klass, meth, contract]
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# [+default_class+] should be a class
|
|
146
|
+
def self.process_type_args(default_class, *args, &blk)
|
|
147
|
+
klass = meth = type = nil
|
|
148
|
+
if args.size == 3
|
|
149
|
+
klass = class_to_string args[0]
|
|
150
|
+
slf, meth = meth_to_sym args[1]
|
|
151
|
+
type = type_to_type args[2]
|
|
152
|
+
elsif args.size == 2
|
|
153
|
+
klass = default_class.to_s
|
|
154
|
+
slf, meth = meth_to_sym args[0]
|
|
155
|
+
type = type_to_type args[1]
|
|
156
|
+
elsif args.size == 1
|
|
157
|
+
klass = default_class.to_s
|
|
158
|
+
type = type_to_type args[0]
|
|
159
|
+
else
|
|
160
|
+
raise ArgumentError, "Invalid arguments"
|
|
161
|
+
end
|
|
162
|
+
raise ArgumentError, "Excepting method type, got #{type.class} instead" if type.class != RDL::Type::MethodType
|
|
163
|
+
# meth = :initialize if meth && slf && meth.to_sym == :new # actually wrap constructor
|
|
164
|
+
klass = RDL::Util.add_singleton_marker(klass) if slf
|
|
165
|
+
return [klass, meth, type]
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
private
|
|
169
|
+
|
|
170
|
+
def self.wrapped_name(klass, meth)
|
|
171
|
+
"__rdl_#{meth.to_s}_old".to_sym
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def self.class_to_string(klass)
|
|
175
|
+
case klass
|
|
176
|
+
when Class
|
|
177
|
+
return klass.to_s
|
|
178
|
+
when String
|
|
179
|
+
return klass
|
|
180
|
+
when Symbol
|
|
181
|
+
return klass.to_s
|
|
182
|
+
else
|
|
183
|
+
raise ArgumentError, "#{klass.class} received where klass (Class, Symbol, or String) expected"
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def self.meth_to_sym(meth)
|
|
188
|
+
raise ArgumentError, "#{meth.class} received where method (Symbol or String) expected" unless meth.class == String || meth.class == Symbol
|
|
189
|
+
meth = meth.to_s
|
|
190
|
+
meth =~ /^(.*\.)?(.*)$/
|
|
191
|
+
raise RuntimeError, "Only self.method allowed" if $1 && $1 != "self."
|
|
192
|
+
return [$1, $2.to_sym]
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def self.type_to_type(type)
|
|
196
|
+
case type
|
|
197
|
+
when RDL::Type::Type
|
|
198
|
+
return type
|
|
199
|
+
when String
|
|
200
|
+
return $__rdl_parser.scan_str type
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
class Object
|
|
206
|
+
|
|
207
|
+
# [+klass+] may be Class, Symbol, or String
|
|
208
|
+
# [+method+] may be Symbol or String
|
|
209
|
+
# [+contract+] must be a Contract
|
|
210
|
+
#
|
|
211
|
+
# Add a precondition to a method. Possible invocations:
|
|
212
|
+
# pre(klass, meth, contract)
|
|
213
|
+
# pre(klass, meth) { block } = pre(klass, meth, FlatContract.new { block })
|
|
214
|
+
# pre(meth, contract) = pre(self, meth, contract)
|
|
215
|
+
# pre(meth) { block } = pre(self, meth, FlatContract.new { block })
|
|
216
|
+
# pre(contract) = pre(self, next method, contract)
|
|
217
|
+
# pre { block } = pre(self, next method, FlatContract.new { block })
|
|
218
|
+
def pre(*args, &blk)
|
|
219
|
+
$__rdl_contract_switch.off { # Don't check contracts inside RDL code itself
|
|
220
|
+
klass, meth, contract = RDL::Wrap.process_pre_post_args(self, "Precondition", *args, &blk)
|
|
221
|
+
if meth
|
|
222
|
+
RDL::Wrap.add_contract(klass, meth, :pre, contract)
|
|
223
|
+
if RDL::Util.method_defined?(klass, meth) || meth == :initialize # there is always an initialize
|
|
224
|
+
RDL::Wrap.wrap(klass, meth)
|
|
225
|
+
else
|
|
226
|
+
$__rdl_to_wrap << [klass, meth]
|
|
227
|
+
end
|
|
228
|
+
else
|
|
229
|
+
$__rdl_deferred << [klass, :pre, contract]
|
|
230
|
+
end
|
|
231
|
+
}
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Add a postcondition to a method. Same possible invocations as pre.
|
|
235
|
+
def post(*args, &blk)
|
|
236
|
+
$__rdl_contract_switch.off {
|
|
237
|
+
klass, meth, contract = RDL::Wrap.process_pre_post_args(self, "Postcondition", *args, &blk)
|
|
238
|
+
if meth
|
|
239
|
+
RDL::Wrap.add_contract(klass, meth, :post, contract)
|
|
240
|
+
if RDL::Util.method_defined?(klass, meth) || meth == :initialize
|
|
241
|
+
RDL::Wrap.wrap(klass, meth)
|
|
242
|
+
else
|
|
243
|
+
$__rdl_to_wrap << [klass, meth]
|
|
244
|
+
end
|
|
245
|
+
else
|
|
246
|
+
$__rdl_deferred << [klass, :post, contract]
|
|
247
|
+
end
|
|
248
|
+
}
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# [+klass+] may be Class, Symbol, or String
|
|
252
|
+
# [+method+] may be Symbol or String
|
|
253
|
+
# [+type+] may be Type or String
|
|
254
|
+
#
|
|
255
|
+
# Set a method's type. Possible invocations:
|
|
256
|
+
# type(klass, meth, type)
|
|
257
|
+
# type(meth, type)
|
|
258
|
+
# type(type)
|
|
259
|
+
def type(*args, &blk)
|
|
260
|
+
$__rdl_contract_switch.off {
|
|
261
|
+
klass, meth, type = begin
|
|
262
|
+
RDL::Wrap.process_type_args(self, *args, &blk)
|
|
263
|
+
rescue Racc::ParseError => err
|
|
264
|
+
# Remove enough backtrace to only include actual source line
|
|
265
|
+
# Warning: Adjust the -5 below if the code (or this comment) changes
|
|
266
|
+
bt = err.backtrace
|
|
267
|
+
bt.shift until bt[0] =~ /^#{__FILE__}:#{__LINE__-5}/
|
|
268
|
+
bt.shift # remove $__rdl_contract_switch.off call
|
|
269
|
+
bt.shift # remove type call itself
|
|
270
|
+
err.set_backtrace bt
|
|
271
|
+
raise err
|
|
272
|
+
end
|
|
273
|
+
if meth
|
|
274
|
+
# It turns out Ruby core/stdlib don't always follow this convention...
|
|
275
|
+
# if (meth.to_s[-1] == "?") && (type.ret != $__rdl_type_bool)
|
|
276
|
+
# warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool"
|
|
277
|
+
# end
|
|
278
|
+
RDL::Wrap.add_contract(klass, meth, :type, type)
|
|
279
|
+
if RDL::Util.method_defined?(klass, meth) || meth == :initialize
|
|
280
|
+
RDL::Wrap.wrap(klass, meth)
|
|
281
|
+
else
|
|
282
|
+
$__rdl_to_wrap << [klass, meth]
|
|
283
|
+
end
|
|
284
|
+
else
|
|
285
|
+
$__rdl_deferred << [klass, :type, type]
|
|
286
|
+
end
|
|
287
|
+
}
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def self.method_added(meth)
|
|
291
|
+
$__rdl_contract_switch.off {
|
|
292
|
+
klass = self.to_s
|
|
293
|
+
|
|
294
|
+
# Apply any deferred contracts and reset list
|
|
295
|
+
if $__rdl_deferred.size > 0
|
|
296
|
+
a = $__rdl_deferred
|
|
297
|
+
$__rdl_deferred = [] # Reset before doing more work to avoid infinite recursion
|
|
298
|
+
a.each { |prev_klass, kind, contract|
|
|
299
|
+
raise RuntimeError, "Deferred contract from class #{prev_klass} being applied in class #{klass}" if prev_klass != klass
|
|
300
|
+
RDL::Wrap.add_contract(klass, meth, kind, contract)
|
|
301
|
+
RDL::Wrap.wrap(klass, meth)
|
|
302
|
+
# It turns out Ruby core/stdlib don't always follow this convention...
|
|
303
|
+
# if (kind == :type) && (meth.to_s[-1] == "?") && (contract.ret != $__rdl_type_bool)
|
|
304
|
+
# warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool"
|
|
305
|
+
# end
|
|
306
|
+
}
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
# Wrap method if there was a prior contract for it.
|
|
310
|
+
if $__rdl_to_wrap.member? [klass, meth]
|
|
311
|
+
$__rdl_to_wrap.delete [klass, meth]
|
|
312
|
+
RDL::Wrap.wrap(klass, meth)
|
|
313
|
+
end
|
|
314
|
+
}
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def self.singleton_method_added(meth)
|
|
318
|
+
$__rdl_contract_switch.off {
|
|
319
|
+
klass = self.to_s
|
|
320
|
+
sklass = RDL::Util.add_singleton_marker(klass)
|
|
321
|
+
|
|
322
|
+
# Apply any deferred contracts and reset list
|
|
323
|
+
if $__rdl_deferred.size > 0
|
|
324
|
+
a = $__rdl_deferred
|
|
325
|
+
$__rdl_deferred = [] # Reset before doing more work to avoid infinite recursion
|
|
326
|
+
a.each { |prev_klass, kind, contract|
|
|
327
|
+
raise RuntimeError, "Deferred contract from class #{prev_klass} being applied in class #{klass}" if prev_klass != klass
|
|
328
|
+
RDL::Wrap.add_contract(sklass, meth, kind, contract)
|
|
329
|
+
RDL::Wrap.wrap(sklass, meth)
|
|
330
|
+
}
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Wrap method if there was a prior contract for it.
|
|
334
|
+
if $__rdl_to_wrap.member? [sklass, meth]
|
|
335
|
+
$__rdl_to_wrap.delete [sklass, meth]
|
|
336
|
+
RDL::Wrap.wrap(sklass, meth)
|
|
337
|
+
end
|
|
338
|
+
}
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
# Aliases contracts for meth_old and meth_new. Currently, this must
|
|
342
|
+
# be called for any aliases or they will not be wrapped with
|
|
343
|
+
# contracts. Only creates aliases in the current class.
|
|
344
|
+
def rdl_alias(new_name, old_name)
|
|
345
|
+
$__rdl_contract_switch.off {
|
|
346
|
+
klass = self.to_s
|
|
347
|
+
$__rdl_aliases[klass] = {} unless $__rdl_aliases[klass]
|
|
348
|
+
if $__rdl_aliases[klass][new_name]
|
|
349
|
+
raise RuntimeError,
|
|
350
|
+
"Tried to alias #{new_name}, already aliased to #{$__rdl_aliases[klass][new_name]}"
|
|
351
|
+
end
|
|
352
|
+
$__rdl_aliases[klass][new_name] = old_name
|
|
353
|
+
|
|
354
|
+
if self.method_defined? new_name
|
|
355
|
+
RDL::Wrap.wrap(klass, new_name)
|
|
356
|
+
else
|
|
357
|
+
$__rdl_to_wrap << [klass, old_name]
|
|
358
|
+
end
|
|
359
|
+
}
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# [+params+] is an array of symbols or strings that are the
|
|
363
|
+
# parameters of this (generic) type
|
|
364
|
+
# [+variance+] is an array of the corresponding variances, :+ for
|
|
365
|
+
# covariant, :- for contravariant, and :~ for invariant. If omitted,
|
|
366
|
+
# all parameters are assumed to be invariant
|
|
367
|
+
# [+all+] should be a symbol naming an all? method that behaves like Array#all?, and that accepts
|
|
368
|
+
# a block that takes arguments in the same order as the type parameters
|
|
369
|
+
# [+blk+] is for advanced use only. If present, [+all+] must be
|
|
370
|
+
# nil. Whenever an instance of this class is instantiated!, the
|
|
371
|
+
# block will be passed an array typs corresponding to the type
|
|
372
|
+
# parameters of the class, and the block should return true if and
|
|
373
|
+
# only if self is a member of self.class<typs>.
|
|
374
|
+
def type_params(params, all, variance: nil, &blk)
|
|
375
|
+
$__rdl_contract_switch.off {
|
|
376
|
+
raise RuntimeError, "Empty type parameters not allowed" if params.empty?
|
|
377
|
+
klass = self.to_s
|
|
378
|
+
if $__rdl_type_params[klass]
|
|
379
|
+
raise RuntimeError, "#{klass} already has type parameters #{$__rdl_type_params[klass]}"
|
|
380
|
+
end
|
|
381
|
+
params = params.map { |v|
|
|
382
|
+
raise RuntimeError, "Type parameter #{v.inspect} is not symbol or string" unless v.class == String || v.class == Symbol
|
|
383
|
+
v.to_sym
|
|
384
|
+
}
|
|
385
|
+
raise RuntimeError, "Duplicate type parameters not allowed" unless params.uniq.size == params.size
|
|
386
|
+
raise RuntimeError, "Expecting #{params.size} variance annotations, got #{variance.size}" if variance && params.size != variance.size
|
|
387
|
+
raise RuntimeError, "Only :+, +-, and :~ are allowed variance annotations" unless (not variance) || variance.all? { |v| [:+, :-, :~].member? v }
|
|
388
|
+
raise RuntimeError, "Can't pass both all and a block" if all && blk
|
|
389
|
+
raise RuntimeError, "all must be a symbol" unless (not all) || (all.instance_of? Symbol)
|
|
390
|
+
chk = all || blk
|
|
391
|
+
raise RuntimeError, "At least one of {all, blk} required" unless chk
|
|
392
|
+
variance = params.map { |p| :~ } unless variance # default to invariant
|
|
393
|
+
$__rdl_type_params[klass] = [params, variance, chk]
|
|
394
|
+
}
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
def nowrap
|
|
398
|
+
$__rdl_contract_switch.off {
|
|
399
|
+
RDL.config { |config| config.add_nowrap(self, self.singleton_class) }
|
|
400
|
+
}
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# [+typs+] is an array of types, classes, symbols, or strings to instantiate
|
|
404
|
+
# the type parameters. If a class, symbol, or string is given, it is
|
|
405
|
+
# converted to a NominalType.
|
|
406
|
+
def instantiate!(*typs)
|
|
407
|
+
$__rdl_contract_switch.off {
|
|
408
|
+
klass = self.class.to_s
|
|
409
|
+
formals, variance, all = $__rdl_type_params[klass]
|
|
410
|
+
raise RuntimeError, "Receiver is of class #{klass}, which is not parameterized" unless formals
|
|
411
|
+
raise RuntimeError, "Expecting #{params.size} type parameters, got #{typs.size}" unless formals.size == typs.size
|
|
412
|
+
raise RuntimeError, "Instance already has type instantiation" if @__rdl_type
|
|
413
|
+
new_typs = typs.map { |t| if t.is_a? RDL::Type::Type then t else $__rdl_parser.scan_str "## #{t}" end }
|
|
414
|
+
t = RDL::Type::GenericType.new(RDL::Type::NominalType.new(klass), *new_typs)
|
|
415
|
+
if all.instance_of? Symbol
|
|
416
|
+
self.send(all) { |*objs|
|
|
417
|
+
new_typs.zip(objs).each { |t, obj|
|
|
418
|
+
if t.instance_of? RDL::Type::GenericType # require obj to be instantiated
|
|
419
|
+
t_obj = RDL::Util.rdl_type(obj)
|
|
420
|
+
raise RDL::Type::TypeError, "Expecting element of type #{t.to_s}, but got uninstantiated object #{obj.inspect}" unless t_obj
|
|
421
|
+
raise RDL::Type::TypeError, "Expecting type #{t.to_s}, got type #{t_obj.to_s}" unless t_obj <= t
|
|
422
|
+
else
|
|
423
|
+
raise RDL::Type::TypeError, "Expecting type #{t.to_s}, got #{obj.inspect}" unless t.member? obj
|
|
424
|
+
end
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
else
|
|
428
|
+
raise RDL::Type::TypeError, "Not an instance of #{t}" unless instance_exec(*new_typs, &all)
|
|
429
|
+
end
|
|
430
|
+
@__rdl_type = t
|
|
431
|
+
self
|
|
432
|
+
}
|
|
433
|
+
end
|
|
434
|
+
|
|
435
|
+
def deinstantiate!
|
|
436
|
+
$__rdl_contract_switch.off {
|
|
437
|
+
raise RuntimeError, "Class #{self.to_s} is not parameterized" unless $__rdl_type_params[klass]
|
|
438
|
+
raise RuntimeError, "Instance is not instantiated" unless @__rdl_type && @@__rdl_type.instance_of?(RDL::Type::GenericType)
|
|
439
|
+
@__rdl_type = nil
|
|
440
|
+
}
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
# Returns a new object that wraps self in a type cast. This cast is *unchecked*, so use with caution
|
|
444
|
+
def type_cast(typ)
|
|
445
|
+
$__rdl_contract_switch.off {
|
|
446
|
+
new_typ = if typ.is_a? RDL::Type::Type then typ else $__rdl_parser.scan_str "## #{typ}" end
|
|
447
|
+
obj = SimpleDelegator.new(self)
|
|
448
|
+
obj.instance_variable_set('@__rdl_type', new_typ)
|
|
449
|
+
return obj
|
|
450
|
+
}
|
|
451
|
+
end
|
|
452
|
+
|
|
453
|
+
# Add a new type alias.
|
|
454
|
+
# [+name+] must be a string beginning with %.
|
|
455
|
+
# [+typ+] can be either a string, in which case it will be parsed
|
|
456
|
+
# into a type, or a Type.
|
|
457
|
+
def type_alias(name, typ)
|
|
458
|
+
$__rdl_contract_switch.off {
|
|
459
|
+
raise RuntimeError, "Attempt to redefine type #{name}" if $__rdl_special_types[name]
|
|
460
|
+
case typ
|
|
461
|
+
when String
|
|
462
|
+
t = $__rdl_parser.scan_str "## #{typ}"
|
|
463
|
+
$__rdl_special_types[name] = t
|
|
464
|
+
when RDL::Type::Type
|
|
465
|
+
$__rdl_special_types[name] = typ
|
|
466
|
+
else
|
|
467
|
+
raise RuntimeError, "Unexpected typ argument #{typ.inspect}"
|
|
468
|
+
end
|
|
469
|
+
}
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
def rdl_query(q)
|
|
473
|
+
$__rdl_contract_switch.off {
|
|
474
|
+
if q =~ /^(\w+(#|\.))?(\w+(!|\?|=)?|!|~|\+|\*\*|-|\*|\/|%|<<|>>|&|\||\^|<|<=|=>|>|==|===|!=|=~|!~|<=>|\[\]|\[\]=)$/
|
|
475
|
+
klass = nil
|
|
476
|
+
klass_pref = nil
|
|
477
|
+
meth = nil
|
|
478
|
+
if q =~ /(.+)#(.+)/
|
|
479
|
+
klass = $1
|
|
480
|
+
klass_pref = "#{klass}#"
|
|
481
|
+
meth = $2.to_sym
|
|
482
|
+
elsif q =~ /(.+)\.(.+)/
|
|
483
|
+
klass_pref = "#{$1}."
|
|
484
|
+
klass = RDL::Util.add_singleton_marker($1)
|
|
485
|
+
meth = $2.to_sym
|
|
486
|
+
else
|
|
487
|
+
klass = self.class.to_s
|
|
488
|
+
klass_pref = "#{klass}#"
|
|
489
|
+
meth = q.to_sym
|
|
490
|
+
end
|
|
491
|
+
if RDL::Wrap.has_contracts?(klass, meth, :type)
|
|
492
|
+
typs = RDL::Wrap.get_contracts(klass, meth, :type)
|
|
493
|
+
typs.each { |t|
|
|
494
|
+
puts "#{klass_pref}#{meth}: #{t}"
|
|
495
|
+
}
|
|
496
|
+
nil
|
|
497
|
+
else
|
|
498
|
+
puts "No type for #{klass_pref}#{meth}"
|
|
499
|
+
end
|
|
500
|
+
else
|
|
501
|
+
puts "Not implemented"
|
|
502
|
+
end
|
|
503
|
+
}
|
|
504
|
+
end
|
|
505
|
+
end
|