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