rdl 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +7 -0
  2. data/lib/rails_types.rb +1 -0
  3. data/lib/rdl.rb +56 -0
  4. data/lib/rdl/config.rb +121 -0
  5. data/lib/rdl/contracts/and.rb +29 -0
  6. data/lib/rdl/contracts/contract.rb +7 -0
  7. data/lib/rdl/contracts/flat.rb +31 -0
  8. data/lib/rdl/contracts/or.rb +25 -0
  9. data/lib/rdl/contracts/proc.rb +24 -0
  10. data/lib/rdl/switch.rb +20 -0
  11. data/lib/rdl/types/annotated_arg.rb +41 -0
  12. data/lib/rdl/types/finitehash.rb +81 -0
  13. data/lib/rdl/types/generic.rb +100 -0
  14. data/lib/rdl/types/intersection.rb +66 -0
  15. data/lib/rdl/types/lexer.rex +39 -0
  16. data/lib/rdl/types/lexer.rex.rb +148 -0
  17. data/lib/rdl/types/method.rb +219 -0
  18. data/lib/rdl/types/nil.rb +50 -0
  19. data/lib/rdl/types/nominal.rb +80 -0
  20. data/lib/rdl/types/optional.rb +54 -0
  21. data/lib/rdl/types/parser.racc +150 -0
  22. data/lib/rdl/types/parser.tab.rb +654 -0
  23. data/lib/rdl/types/singleton.rb +62 -0
  24. data/lib/rdl/types/structural.rb +72 -0
  25. data/lib/rdl/types/top.rb +50 -0
  26. data/lib/rdl/types/tuple.rb +61 -0
  27. data/lib/rdl/types/type.rb +24 -0
  28. data/lib/rdl/types/type_inferencer.rb +73 -0
  29. data/lib/rdl/types/union.rb +74 -0
  30. data/lib/rdl/types/var.rb +51 -0
  31. data/lib/rdl/types/vararg.rb +54 -0
  32. data/lib/rdl/types/wild.rb +26 -0
  33. data/lib/rdl/util.rb +56 -0
  34. data/lib/rdl/wrap.rb +505 -0
  35. data/lib/rdl_types.rb +2 -0
  36. data/types/other/chronic.rb +5 -0
  37. data/types/other/paperclip_attachment.rb +7 -0
  38. data/types/other/securerandom.rb +4 -0
  39. data/types/rails-4.2.1/fixnum.rb +3 -0
  40. data/types/rails-4.2.1/string.rb +3 -0
  41. data/types/rails-tmp/action_dispatch.rb +406 -0
  42. data/types/rails-tmp/active_record.rb +406 -0
  43. data/types/rails-tmp/devise_contracts.rb +216 -0
  44. data/types/ruby-2.2.0/_aliases.rb +4 -0
  45. data/types/ruby-2.2.0/abbrev.rb +5 -0
  46. data/types/ruby-2.2.0/array.rb +137 -0
  47. data/types/ruby-2.2.0/base64.rb +10 -0
  48. data/types/ruby-2.2.0/basic_object.rb +13 -0
  49. data/types/ruby-2.2.0/benchmark.rb +11 -0
  50. data/types/ruby-2.2.0/bigdecimal.rb +15 -0
  51. data/types/ruby-2.2.0/bigmath.rb +12 -0
  52. data/types/ruby-2.2.0/class.rb +17 -0
  53. data/types/ruby-2.2.0/complex.rb +42 -0
  54. data/types/ruby-2.2.0/coverage.rb +6 -0
  55. data/types/ruby-2.2.0/csv.rb +5 -0
  56. data/types/ruby-2.2.0/date.rb +6 -0
  57. data/types/ruby-2.2.0/dir.rb +38 -0
  58. data/types/ruby-2.2.0/encoding.rb +23 -0
  59. data/types/ruby-2.2.0/enumerable.rb +98 -0
  60. data/types/ruby-2.2.0/enumerator.rb +26 -0
  61. data/types/ruby-2.2.0/exception.rb +15 -0
  62. data/types/ruby-2.2.0/file.rb +124 -0
  63. data/types/ruby-2.2.0/fileutils.rb +6 -0
  64. data/types/ruby-2.2.0/fixnum.rb +45 -0
  65. data/types/ruby-2.2.0/float.rb +54 -0
  66. data/types/ruby-2.2.0/gem.rb +245 -0
  67. data/types/ruby-2.2.0/hash.rb +72 -0
  68. data/types/ruby-2.2.0/integer.rb +31 -0
  69. data/types/ruby-2.2.0/io.rb +103 -0
  70. data/types/ruby-2.2.0/kernel.rb +89 -0
  71. data/types/ruby-2.2.0/marshal.rb +5 -0
  72. data/types/ruby-2.2.0/matchdata.rb +26 -0
  73. data/types/ruby-2.2.0/math.rb +53 -0
  74. data/types/ruby-2.2.0/numeric.rb +46 -0
  75. data/types/ruby-2.2.0/object.rb +73 -0
  76. data/types/ruby-2.2.0/pathname.rb +106 -0
  77. data/types/ruby-2.2.0/process.rb +127 -0
  78. data/types/ruby-2.2.0/random.rb +15 -0
  79. data/types/ruby-2.2.0/range.rb +38 -0
  80. data/types/ruby-2.2.0/rational.rb +31 -0
  81. data/types/ruby-2.2.0/regexp.rb +30 -0
  82. data/types/ruby-2.2.0/set.rb +58 -0
  83. data/types/ruby-2.2.0/string.rb +145 -0
  84. data/types/ruby-2.2.0/strscan.rb +7 -0
  85. data/types/ruby-2.2.0/symbol.rb +29 -0
  86. data/types/ruby-2.2.0/time.rb +68 -0
  87. data/types/ruby-2.2.0/uri.rb +18 -0
  88. data/types/ruby-2.2.0/yaml.rb +5 -0
  89. 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