rdl 1.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|