rdl 2.0.0.rc1 → 2.0.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 797b63a785ea5ae5e9600d8df722c66cf2a2fe17
4
- data.tar.gz: dc28d44117114d21fccd6472c77f45b6efa3ae25
3
+ metadata.gz: b977016fb74ea728b29a3eaa767aec63ee0d6a7b
4
+ data.tar.gz: 45b460bc49df3fca8248811e582617229142c35d
5
5
  SHA512:
6
- metadata.gz: 0ea7b41ad216861c4b05a3f7c0204a3c71b8c1ecd9ce2e8c33ab26526dc2f079358bddc0cb18af0cf4e29420fdf2d8e5a574692a1b215c491bbf26f6d6f97804
7
- data.tar.gz: 18acb05e6f25b0a80716a306c6b340c3d10d6e4f2bed954e42b502b52f240808a1db03652a693f74377ee6a76c1582cc52a0d23000f599710727f5a05184e415
6
+ metadata.gz: 2922f3d18c4c5398a520202d8930806545e05db55a449110a9837fef4c06293dc44ef98a3c3ca70a74c639d69114ae2d93f711ad0099dd11ae9ba730ab05b7b8
7
+ data.tar.gz: c36055f80360a2b6cdf4d732d7d7419e5783512b9d947f08ab933358d9ed0fef34c62fdb8b1d8ed2ec34d24f2cdcf9dd12c5cf03472b550714dce384ffa56cf6
data/README.md CHANGED
@@ -77,7 +77,7 @@ The beta version of RDL also has an experimental mode in which method bodies can
77
77
  file.rb:
78
78
  require 'rdl'
79
79
 
80
- type '(Fixnum) -> Fixnum', typecheck_now: true
80
+ type '(Fixnum) -> Fixnum', typecheck: :now
81
81
  def id(x)
82
82
  "forty-two"
83
83
  end
@@ -91,7 +91,7 @@ $ ruby file.rb
91
91
  .../file.rb:5: ^~~~~~~~~~~
92
92
  ```
93
93
 
94
- Passing `typecheck_now: true` to `type` checks the method body immediately or as soon as it is defined. Passing `typecheck: true` to `type` statically type checks the method body whenever it is called.
94
+ Passing `typecheck: :now` to `type` checks the method body immediately or as soon as it is defined. Passing `typecheck: :call` to `type` statically type checks the method body whenever it is called. Passing `typecheck: sym` for some other symbol statically type checks the method body when `rdl_do_typecheck sym` is called.
95
95
 
96
96
  RDL contracts and types are stored in memory at run time, so it's also possible for programs to query them. RDL includes lots of contracts and types for the core and standard libraries. Since those methods are generally trustworthy, RDL doesn't actually enforce the contracts (since that would add overhead), but they are available to search and query. RDL includes a small script `rdl_query` to look up type information from the command line. Note you might need to put the argument in quotes depending on your shell.
97
97
 
@@ -540,9 +540,11 @@ RDL also includes a special *bottom* type `%bot` that is a subtype of any type,
540
540
 
541
541
  # Static Type Checking
542
542
 
543
- RDL has experimental support (note: this is in beta release) for static type checking. As mentioned in the introduction, calling `type` with `typecheck_now: true` statically type checks the body of the annotated method body against the given signature. If the method has already been defined, RDL will try to check the method immediately. Otherwise, RDL will statically type check the method as soon as it is loaded.
543
+ RDL has experimental support (note: this is in beta release) for static type checking. As mentioned in the introduction, calling `type` with `typecheck: :now` statically type checks the body of the annotated method body against the given signature. If the method has already been defined, RDL will try to check the method immediately. Otherwise, RDL will statically type check the method as soon as it is loaded.
544
544
 
545
- Often method bodies cannot be type checked as soon as they are loaded because they refer to classes, methods, and variables that have not been created yet. To support these cases, `type` can be called with `typecheck: true`, which will delay checking the method's type until the method is called. Currently these checks are not cached, so expect a big performance hit for using this feature.
545
+ Often method bodies cannot be type checked as soon as they are loaded because they refer to classes, methods, and variables that have not been created yet. To support these cases, some other symbol can be supplied as `typecheck: sym`. Then when `rdl_do_typecheck sym` is called, all methods typechecked at `sym` will be statically checked.
546
+
547
+ Addditionally, `type` can be called with `typecheck: :call`, which will delay checking the method's type until the method is called. Currently these checks are not cached, so expect a big performance hit for using this feature.
546
548
 
547
549
  To perform type checking, RDL needs source code, which it gets by parsing the file containing the to-be-typechecked method. Hence, static type checking does not work in `irb` since RDL has no way of getting the source. RDL currently uses the [parser Gem](https://github.com/whitequark/parser) to parse Ruby source code. (And RDL uses the parser gem's amazing diagnostic output facility to print type error messages.)
548
550
 
@@ -590,6 +592,17 @@ end
590
592
 
591
593
  The `var_type` method may also be called as `var_type klass, :name, typ` to assign a type to an instance or class variable of class `klass`.
592
594
 
595
+ As a short-hand, RDL defines methods `attr_accessor_type`, `attr_reader_type`, and `attr_writer_type` to behave like their corresponding non-`_type` analogs but attribute types follow the attribute names. For example, `attr_accessor_type :f, 'Fixnum', :g, 'String'` is equivalent to:
596
+
597
+ ```ruby
598
+ var_type :@f, 'Fixnum'
599
+ var_type :@g, 'String'
600
+ type :f, '() -> Fixnum'
601
+ type :f=, '(Fixnum) -> Fixnum'
602
+ type :g, '() -> String'
603
+ type :g=, '(String) -> String'
604
+ ```
605
+
593
606
  ## Tuples, Finite Hashes, and Subtyping
594
607
 
595
608
  When RDL encounters a literal array in the program, it assigns it a tuple type, which allows, among other things, precise handling of multiple assignment. For example:
@@ -791,3 +804,5 @@ Copyright (c) 2014-2016, University of Maryland, College Park. All rights reserv
791
804
  * Queries, include more regexp operators aside from . and ...
792
805
 
793
806
  * Queries, allow regexp in class and method names; suggested by Andreas Adamcik, Vienna
807
+
808
+ * Tag for private methods
data/lib/rdl.rb CHANGED
@@ -39,8 +39,10 @@ $__rdl_aliases = Hash.new
39
39
  # method is a symbol
40
40
  $__rdl_to_wrap = Set.new
41
41
 
42
- # Same as $__rdl_to_wrap, but records [class, method] pairs to type check when they're defined
43
- $__rdl_to_typecheck_now = Set.new
42
+ # Map from symbols to set of [class, method] pairs to type check when those symbols are rdl_do_typecheck'd
43
+ # (or the methods are defined, for the symbol :now)
44
+ $__rdl_to_typecheck = Hash.new
45
+ $__rdl_to_typecheck[:now] = Set.new
44
46
 
45
47
  # List of contracts that should be applied to the next method definition
46
48
  $__rdl_deferred = []
data/lib/rdl/config.rb CHANGED
@@ -5,10 +5,12 @@ class RDL::Config
5
5
 
6
6
  attr_accessor :nowrap
7
7
  attr_accessor :gather_stats
8
-
8
+ attr_accessor :report
9
+
9
10
  def initialize
10
11
  @nowrap = Set.new
11
- @gather_stats = true
12
+ @gather_stats = false
13
+ @report = false
12
14
  end
13
15
 
14
16
  def add_nowrap(*klasses)
@@ -18,7 +20,7 @@ class RDL::Config
18
20
  def remove_nowrap(*klasses)
19
21
  klasses.each { |klass| @nowrap.delete klass }
20
22
  end
21
-
23
+
22
24
  # To use, copy these 3 lines to the test file of a gem
23
25
  =begin
24
26
  require_relative '../rdl3/rdl/lib/rdl.rb'
@@ -28,7 +30,7 @@ RDL::Config.instance.profile_stats
28
30
  def profile_stats(outname="/#{__FILE__[0...-3]}",outdir="")
29
31
  require 'profile'
30
32
  Profiler__.stop_profile # Leave setup out of stats
31
-
33
+
32
34
  at_exit do
33
35
  Profiler__.stop_profile
34
36
  $__rdl_contract_switch.off {
@@ -40,7 +42,7 @@ RDL::Config.instance.profile_stats
40
42
  # [-1] -> Contract exists for method, but method not profiled
41
43
  # [-1, ...] -> Method profiled, but no contract exists
42
44
  totals = {}
43
-
45
+
44
46
  puts "Retrieving Profiler Data"
45
47
  Profiler__.class_variable_get(:@@maps).values.each do |threadmap|
46
48
  threadmap.each do |key, data|
@@ -50,7 +52,7 @@ RDL::Config.instance.profile_stats
50
52
  total_data[3] += data[2]
51
53
  end
52
54
  end
53
-
55
+
54
56
  puts "Scanning Object Space"
55
57
  kls = []
56
58
  ObjectSpace.each_object { |obj|
@@ -65,7 +67,7 @@ RDL::Config.instance.profile_stats
65
67
  totals["#{obj.class}::#{mthd.to_s}".gsub('::','#')] ||= [nil] unless mthd.to_s =~ /new/
66
68
  }
67
69
  }
68
-
70
+
69
71
  p "Scanning RDL Contract Log"
70
72
  $__rdl_wrapped_calls.each{ |mname,ct|
71
73
  if (totals[mname]) then
@@ -76,13 +78,13 @@ RDL::Config.instance.profile_stats
76
78
  end
77
79
  end
78
80
  }
79
-
81
+
80
82
  puts "Analyzing Statistics"
81
83
  filtered = {}
82
84
  totals.each{ |k,v|
83
85
  if (not (k=~/(rdl)|(RDL)/)) and (v[0].nil? or v[0]==-1) then filtered[k]=v end
84
86
  }
85
-
87
+
86
88
  puts "Writing Output"
87
89
  require 'json'
88
90
  fpath = "#{outdir}/#{outname}_rdlstat.json".gsub('//','')
@@ -115,7 +117,36 @@ RDL::Config.instance.profile_stats
115
117
  puts "DONE."
116
118
  }
117
119
  end
118
-
120
+
119
121
  Profiler__.start_profile # Restart profiler after setup
120
122
  end
121
- end
123
+ end
124
+
125
+ at_exit do
126
+ if RDL::Config.instance.report
127
+ typechecked = []
128
+ missing = []
129
+ $__rdl_info.info.each_pair { |klass, meths|
130
+ meths.each { |meth, kinds|
131
+ if kinds[:typecheck]
132
+ if kinds[:typechecked]
133
+ typechecked << [klass, meth]
134
+ else
135
+ missing << [klass, meth]
136
+ end
137
+ elsif kinds[:typechecked]
138
+ raise RuntimeError, "#{RDL::Util.pp_klass_method(klass, meth)} typechecked but not annotated to do so?!"
139
+ end
140
+ }
141
+ }
142
+ unless typechecked.empty?
143
+ puts "TYPECHECKED METHODS:"
144
+ typechecked.each { |klass, meth| puts RDL::Util.pp_klass_method(klass, meth) }
145
+ end
146
+ unless missing.empty?
147
+ puts unless typechecked.empty?
148
+ puts "METHODS ANNOTATED TO BE TYPECHECKED BUT NOT TYPECHECKED:"
149
+ missing.each { |klass, meth| puts RDL::Util.pp_klass_method(klass, meth) }
150
+ end
151
+ end
152
+ end
data/lib/rdl/typecheck.rb CHANGED
@@ -152,6 +152,7 @@ module RDL::Typecheck
152
152
  def self.typecheck(klass, meth)
153
153
  file, line = $__rdl_info.get(klass, meth, :source_location)
154
154
  raise RuntimeError, "static type checking in irb not supported" if file == "(irb)"
155
+ raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil?
155
156
  digest = Digest::MD5.file file
156
157
  cache_hit = (($__rdl_ruby_parser_cache.has_key? file) &&
157
158
  ($__rdl_ruby_parser_cache[file][0] == digest))
@@ -185,13 +186,16 @@ module RDL::Typecheck
185
186
  end
186
187
  inst = {self: self_type}
187
188
  type = type.instantiate inst
188
- error :arg_count_mismatch, ['method', type.args.length, 'method', args.children.length], args unless type.args.length == args.children.length
189
+ unless type.args.length == args.children.length
190
+ error :arg_count_mismatch, ['method', type.args.length, 'method', args.children.length], (if args.children.empty? then ast else args end)
191
+ end
189
192
  a = args.children.map { |arg| arg.children[0] }.zip(type.args).to_h
190
193
  a[:self] = self_type
191
194
  scope = {tret: type.ret, tblock: type.block }
192
195
  _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(scope, Env.new(a), body) end
193
196
  error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || body_type <= type.ret
194
197
  }
198
+ $__rdl_info.set(klass, meth, :typechecked, true)
195
199
  end
196
200
 
197
201
  # The actual type checking logic.
@@ -1012,7 +1016,8 @@ RUBY
1012
1016
  end
1013
1017
  tancestor = $__rdl_info.get(ancestor_name, name, :type)
1014
1018
  return tancestor if tancestor
1015
- if (if RDL::Util.has_singleton_marker(ancestor_name) then ancestor.singleton_methods.member?(name) else ancestor.instance_methods.member?(name) end)
1019
+ if (if RDL::Util.has_singleton_marker(ancestor_name) then ancestor.singleton_methods(false).member?(name) else ancestor.instance_methods(false).member?(name) end)
1020
+ klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass
1016
1021
  error :missing_ancestor_type, [ancestor_name, klass, name], e
1017
1022
  end
1018
1023
  }
data/lib/rdl/wrap.rb CHANGED
@@ -8,7 +8,7 @@ class RDL::Wrap
8
8
  meth = meth.to_sym
9
9
  while $__rdl_aliases[klass] && $__rdl_aliases[klass][meth]
10
10
  if $__rdl_info.has_any?(klass, meth, [:pre, :post, :type])
11
- raise RuntimeError, "Alias #{klass}\##{meth} has contracts. Contracts are only allowed on methods, not aliases."
11
+ raise RuntimeError, "Alias #{RDL::Util.pp_klass_method(klass, meth)} has contracts. Contracts are only allowed on methods, not aliases."
12
12
  end
13
13
  meth = $__rdl_aliases[klass][meth]
14
14
  end
@@ -30,9 +30,8 @@ class RDL::Wrap
30
30
  klass_str = klass_str.to_s
31
31
  klass = RDL::Util.to_class klass_str
32
32
  return if wrapped? klass, meth
33
- $__rdl_info.set(klass_str, meth, :source_location, klass.instance_method(meth).source_location) # get locs for nowrap meths!
34
33
  return if RDL::Config.instance.nowrap.member? klass
35
- raise ArgumentError, "Attempt to wrap #{klass.to_s}\##{meth.to_s}" if klass.to_s =~ /^RDL::/
34
+ raise ArgumentError, "Attempt to wrap #{RDL::Util.pp_klass_method(klass, meth)}" if klass.to_s =~ /^RDL::/
36
35
  meth_old = wrapped_name(klass, meth) # meth_old is a symbol
37
36
  # return if (klass.method_defined? meth_old) # now checked above by wrapped? call
38
37
  is_singleton_method = RDL::Util.has_singleton_marker(klass_str)
@@ -55,7 +54,7 @@ class RDL::Wrap
55
54
  #{if not(is_singleton_method) then "inst[:self] = RDL::Type::NominalType.new(self.class)" end}
56
55
  # puts "Intercepted #{full_method_name}(\#{args.join(", ")}) { \#{blk} }, inst = \#{inst.inspect}"
57
56
  meth = RDL::Wrap.resolve_alias(klass, #{meth.inspect})
58
- RDL::Typecheck.typecheck(klass, meth) if $__rdl_info.get(klass, meth, :typecheck)
57
+ RDL::Typecheck.typecheck(klass, meth) if $__rdl_info.get(klass, meth, :typecheck) == :call
59
58
  pres = $__rdl_info.get(klass, meth, :pre)
60
59
  RDL::Contract::AndContract.check_array(pres, self, *args, &blk) if pres
61
60
  types = $__rdl_info.get(klass, meth, :type)
@@ -176,6 +175,53 @@ RUBY
176
175
  return $__rdl_parser.scan_str type
177
176
  end
178
177
  end
178
+
179
+ # called by Object#method_added (sing=false) and Object#singleton_method_added (sing=true)
180
+ def self.do_method_added(the_self, sing, klass, meth)
181
+ # Apply any deferred contracts and reset list
182
+ if sing
183
+ loc = the_self.singleton_method(meth).source_location
184
+ else
185
+ loc = the_self.instance_method(meth).source_location
186
+ end
187
+
188
+ if $__rdl_deferred.size > 0
189
+ $__rdl_info.set(klass, meth, :source_location, loc)
190
+ a = $__rdl_deferred
191
+ $__rdl_deferred = [] # Reset before doing more work to avoid infinite recursion
192
+ a.each { |prev_klass, kind, contract, h|
193
+ if RDL::Util.has_singleton_marker(klass)
194
+ tmp_klass = RDL::Util.remove_singleton_marker(klass)
195
+ else
196
+ tmp_klass = klass
197
+ end
198
+ raise RuntimeError, "Deferred contract from class #{prev_klass} being applied in class #{tmp_klass}" if prev_klass != tmp_klass
199
+ $__rdl_info.add(klass, meth, kind, contract)
200
+ RDL::Wrap.wrap(klass, meth) if h[:wrap]
201
+ unless $__rdl_info.set(klass, meth, :typecheck, h[:typecheck])
202
+ raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}"
203
+ end
204
+ RDL::Typecheck.typecheck(klass, meth) if h[:typecheck] == :now
205
+ if (h[:typecheck] && h[:typecheck] != :call)
206
+ $__rdl_to_typecheck[h[:typecheck]] = Set.new unless $__rdl_to_typecheck[h[:typecheck]]
207
+ $__rdl_to_typecheck[h[:typecheck]].add([klass, meth])
208
+ end
209
+ }
210
+ end
211
+
212
+ # Wrap method if there was a prior contract for it.
213
+ if $__rdl_to_wrap.member? [klass, meth]
214
+ $__rdl_to_wrap.delete [klass, meth]
215
+ RDL::Wrap.wrap(klass, meth)
216
+ $__rdl_info.set(klass, meth, :source_location, loc)
217
+ end
218
+
219
+ # Type check method if requested; must typecheck before wrap
220
+ if $__rdl_to_typecheck[:now].member? [klass, meth]
221
+ $__rdl_to_typecheck[:now].delete [klass, meth]
222
+ RDL::Typecheck.typecheck(klass, meth)
223
+ end
224
+ end
179
225
  end
180
226
 
181
227
  class Object
@@ -229,17 +275,20 @@ class Object
229
275
  }
230
276
  end
231
277
 
232
- # [+klass+] may be Class, Symbol, or String
233
- # [+method+] may be Symbol or String
234
- # [+type+] may be Type or String
235
- # [+wrap+] indicates whether the type should be enforced (true) or just recorded (false)
236
- # [+typecheck+] indicates whether the method should be statically checked against this type (not yet implemented)
278
+ # [+ klass +] may be Class, Symbol, or String
279
+ # [+ method +] may be Symbol or String
280
+ # [+ type +] may be Type or String
281
+ # [+ wrap +] indicates whether the type should be enforced (true) or just recorded (false)
282
+ # [+ typecheck +] indicates a method that should be statically type checked, as follows
283
+ # if :call, indicates method should be typechecked when called
284
+ # if :now, indicates method should be typechecked immediately
285
+ # if other-symbol, indicates method should be typechecked when rdl_do_typecheck(other-symbol) is called
237
286
  #
238
287
  # Set a method's type. Possible invocations:
239
288
  # type(klass, meth, type)
240
289
  # type(meth, type)
241
290
  # type(type)
242
- def type(*args, wrap: true, typecheck: false, typecheck_now: false, &blk)
291
+ def type(*args, wrap: true, typecheck: false, &blk)
243
292
  $__rdl_contract_switch.off {
244
293
  klass, meth, type = begin
245
294
  RDL::Wrap.process_type_args(self, *args, &blk)
@@ -260,71 +309,78 @@ class Object
260
309
  # end
261
310
  $__rdl_info.add(klass, meth, :type, type)
262
311
  unless $__rdl_info.set(klass, meth, :typecheck, typecheck)
263
- raise RuntimeError, "Inconsistent typecheck flag on #{klass}##{meth}"
312
+ raise RuntimeError, "Inconsistent typecheck flag on #{RDL::Util.pp_klass_method(klass, meth)}"
264
313
  end
265
314
  if wrap
266
315
  if RDL::Util.method_defined?(klass, meth) || meth == :initialize
267
- RDL::Typecheck.typecheck(klass, meth) if typecheck_now
316
+ $__rdl_info.set(klass, meth, :source_location, RDL::Util.to_class(klass).instance_method(meth).source_location)
317
+ RDL::Typecheck.typecheck(klass, meth) if typecheck == :now
268
318
  RDL::Wrap.wrap(klass, meth)
269
319
  else
270
320
  $__rdl_to_wrap << [klass, meth]
271
- $__rdl_to_typecheck_now << [klass, meth] if typecheck_now
321
+ if (typecheck && typecheck != :call)
322
+ $__rdl_to_typecheck[typecheck] = Set.new unless $__rdl_to_typecheck[typecheck]
323
+ $__rdl_to_typecheck[typecheck].add([klass, meth])
324
+ end
272
325
  end
273
326
  end
274
327
  else
275
328
  $__rdl_deferred << [klass, :type, type, {wrap: wrap,
276
- typecheck: typecheck,
277
- typecheck_now: typecheck_now}]
329
+ typecheck: typecheck}]
278
330
  end
279
331
  }
280
332
  end
281
333
 
334
+ # [+ klass +] is the class containing the variable; self if omitted; ignored for local and global variables
335
+ # [+ var +] is a symbol or string containing the name of the variable
336
+ # [+ typ +] is a string containing the type
282
337
  def var_type(klass=self, var, typ)
283
338
  raise RuntimeError, "Variable cannot begin with capital" if var.to_s =~ /^[A-Z]/
284
339
  return if var.to_s =~ /^[a-z]/ # local variables handled specially, inside type checker
285
- raise Runtimeerror, "Global variables can't be typed in a class" unless klass = self
340
+ raise RuntimeError, "Global variables can't be typed in a class" unless klass = self
286
341
  klass = RDL::Util::GLOBAL_NAME if var.to_s =~ /^\$/
287
342
  unless $__rdl_info.set(klass, var, :type, $__rdl_parser.scan_str("#T #{typ}"))
288
343
  raise RuntimeError, "Type already declared for #{var}"
289
344
  end
290
345
  end
291
346
 
292
- def self.method_added(meth)
293
- $__rdl_contract_switch.off {
294
- klass = self.to_s
295
- klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
347
+ # In the following three methods
348
+ # [+ args +] is a sequence of symbol, typ. attr_reader is called for each symbol,
349
+ # and var_type is called to assign the immediately following type to the
350
+ # attribute named after that symbol.
351
+ def attr_accessor_type(*args)
352
+ args.each_slice(2) { |name, typ|
353
+ attr_accessor name
354
+ var_type ("@" + name.to_s), typ
355
+ type name, "() -> #{typ}"
356
+ type name.to_s + "=", "(#{typ}) -> #{typ}"
357
+ }
358
+ end
296
359
 
297
- # Apply any deferred contracts and reset list
298
- if $__rdl_deferred.size > 0
299
- a = $__rdl_deferred
300
- $__rdl_deferred = [] # Reset before doing more work to avoid infinite recursion
301
- a.each { |prev_klass, kind, contract, h|
302
- raise RuntimeError, "Deferred contract from class #{prev_klass} being applied in class #{klass}" if prev_klass != klass
303
- $__rdl_info.add(klass, meth, kind, contract)
304
- RDL::Wrap.wrap(klass, meth) if h[:wrap]
305
- RDL::Typecheck.typecheck(klass, meth) if h[:typecheck_now]
306
- unless $__rdl_info.set(klass, meth, :typecheck, h[:typecheck])
307
- raise RuntimeError, "Inconsistent typecheck flag on #{klass}##{meth}"
308
- end
360
+ def attr_reader_type(*args)
361
+ args.each_slice(2) { |name, typ|
362
+ attr_reader name
363
+ var_type ("@" + name.to_s), typ
364
+ type name, "() -> #{typ}"
365
+ }
366
+ end
309
367
 
310
- # It turns out Ruby core/stdlib don't always follow this convention...
311
- # if (kind == :type) && (meth.to_s[-1] == "?") && (contract.ret != $__rdl_type_bool)
312
- # warn "#{RDL::Util.pp_klass_method(klass, meth)}: methods that end in ? should have return type %bool"
313
- # end
314
- }
315
- end
368
+ alias_method :attr_type, :attr_reader_type
316
369
 
317
- # Wrap method if there was a prior contract for it.
318
- if $__rdl_to_wrap.member? [klass, meth]
319
- $__rdl_to_wrap.delete [klass, meth]
320
- RDL::Wrap.wrap(klass, meth)
321
- end
370
+ def attr_writer_type(*args)
371
+ args.each_slice(2) { |name, typ|
372
+ attr_writer name
373
+ var_type ("@" + name.to_s), typ
374
+ type name.to_s + "=", "(#{typ}) -> #{typ}"
375
+ }
376
+ end
322
377
 
323
- # Type check method if requested; must typecheck before wrap
324
- if $__rdl_to_typecheck_now.member? [klass, meth]
325
- $__rdl_to_typecheck_now.delete [klass, meth]
326
- RDL::Typecheck.typecheck(klass, meth)
327
- end
378
+
379
+ def self.method_added(meth)
380
+ $__rdl_contract_switch.off {
381
+ klass = self.to_s
382
+ klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
383
+ RDL::Wrap.do_method_added(self, false, klass, meth)
328
384
  }
329
385
  end
330
386
 
@@ -333,33 +389,7 @@ class Object
333
389
  klass = self.to_s
334
390
  klass = "Object" if (klass.is_a? Object) && (klass.to_s == "main")
335
391
  sklass = RDL::Util.add_singleton_marker(klass)
336
-
337
- # Apply any deferred contracts and reset list
338
- if $__rdl_deferred.size > 0
339
- a = $__rdl_deferred
340
- $__rdl_deferred = [] # Reset before doing more work to avoid infinite recursion
341
- a.each { |prev_klass, kind, contract, h|
342
- raise RuntimeError, "Deferred contract from class #{prev_klass} being applied in class #{klass}" if prev_klass != klass
343
- $__rdl_info.add(sklass, meth, kind, contract)
344
- RDL::Wrap.wrap(sklass, meth) if h[:wrap]
345
- RDL::Typecheck.typecheck(sklass, meth) if h[:typecheck_now]
346
- unless $__rdl_info.set(klass, meth, :typecheck, h[:typecheck])
347
- raise RuntimeError, "Inconsistent typecheck flag on #{klass}##{meth}"
348
- end
349
- }
350
- end
351
-
352
- # Wrap method if there was a prior contract for it.
353
- if $__rdl_to_wrap.member? [sklass, meth]
354
- $__rdl_to_wrap.delete [sklass, meth]
355
- RDL::Wrap.wrap(sklass, meth)
356
- end
357
-
358
- # Type check method if requested
359
- if $__rdl_to_typecheck_now.member? [sklass, meth]
360
- $__rdl_to_typecheck_now.delete [sklass, meth]
361
- RDL::Typecheck.typecheck(sklass, meth)
362
- end
392
+ RDL::Wrap.do_method_added(self, true, sklass, meth)
363
393
  }
364
394
  end
365
395
 
@@ -498,5 +528,12 @@ class Object
498
528
  }
499
529
  end
500
530
 
531
+ # Type check all methods that had annotation `typecheck: sym' at type call
532
+ def rdl_do_typecheck(sym)
533
+ $__rdl_to_typecheck[sym].each { |klass, meth|
534
+ RDL::Typecheck.typecheck(klass, meth)
535
+ }
536
+ $__rdl_to_typecheck[sym] = Array.new
537
+ end
501
538
 
502
539
  end
data/rdl.gemspec CHANGED
@@ -4,8 +4,8 @@
4
4
 
5
5
  Gem::Specification.new do |s|
6
6
  s.name = 'rdl'
7
- s.version = '2.0.0.rc1'
8
- s.date = '2016-07-17'
7
+ s.version = '2.0.0.rc2'
8
+ s.date = '2016-07-19'
9
9
  s.summary = 'Ruby type and contract system'
10
10
  s.description = <<-EOF
11
11
  RDL is a gem that adds types and contracts to Ruby. RDL includes extensive
@@ -41,54 +41,68 @@ class TestTypecheck < Minitest::Test
41
41
 
42
42
  def test_def
43
43
  self.class.class_eval {
44
- type "(Fixnum) -> Fixnum", typecheck_now: true
44
+ type "(Fixnum) -> Fixnum", typecheck: :now
45
45
  def def_ff(x) x; end
46
46
  }
47
47
 
48
48
  assert_raises(RDL::Typecheck::StaticTypeError) {
49
49
  self.class.class_eval {
50
- type "(Fixnum) -> Fixnum", typecheck_now: true
50
+ type "(Fixnum) -> Fixnum", typecheck: :now
51
51
  def def_fs(x) "42"; end
52
52
  }
53
53
  }
54
54
 
55
55
  self.class.class_eval {
56
- type "(Fixnum) -> Fixnum", typecheck_now: true
56
+ type "(Fixnum) -> Fixnum", typecheck: :now
57
57
  def def_ff2(x) x; end
58
58
  }
59
59
  assert_equal 42, def_ff2(42)
60
60
 
61
61
  self.class.class_eval {
62
- type "(Fixnum) -> Fixnum", typecheck: true
62
+ type "(Fixnum) -> Fixnum", typecheck: :call
63
63
  def def_fs2(x) "42"; end
64
64
  }
65
65
  assert_raises(RDL::Typecheck::StaticTypeError) { def_fs2(42) }
66
66
 
67
67
  assert_raises(RDL::Typecheck::StaticTypeError) {
68
68
  self.class.class_eval {
69
- type "(Fixnum) -> Fixnum", typecheck_now: true
69
+ type "(Fixnum) -> Fixnum", typecheck: :now
70
70
  def def_ff3(x, y) 42; end
71
71
  }
72
72
  }
73
+
74
+ self.class.class_eval {
75
+ type "(Fixnum) -> Fixnum", typecheck: :later
76
+ def def_ff4(x, y) 42; end
77
+ }
78
+
79
+ assert_raises(RDL::Typecheck::StaticTypeError) { rdl_do_typecheck :later }
73
80
  end
74
81
 
75
82
  def test_defs
76
83
  self.class.class_eval {
77
- type "(Fixnum) -> Class", typecheck_now: true
84
+ type "(Fixnum) -> Class", typecheck: :now
78
85
  def self.defs_ff(x) self; end
79
86
  }
80
87
 
81
88
  self.class.class_eval {
82
- type "() -> Class", typecheck_now: true
89
+ type "() -> Class", typecheck: :now
83
90
  def self.defs_nn() defs_ff(42); end
84
91
  }
85
92
 
86
93
  assert_raises(RDL::Typecheck::StaticTypeError) {
87
94
  self.class.class_eval {
88
- type "() -> Class", typecheck_now: true
95
+ type "() -> Class", typecheck: :now
89
96
  def self.defs_other() fdsakjfhds(42); end
90
97
  }
91
98
  }
99
+
100
+ self.class.class_eval {
101
+ type "(Fixnum) -> Fixnum", typecheck: :later
102
+ def self.defs_ff2(x, y) 42; end
103
+ }
104
+
105
+ assert_raises(RDL::Typecheck::StaticTypeError) { rdl_do_typecheck :later }
92
106
  end
93
107
 
94
108
  def test_lits
@@ -107,7 +121,7 @@ class TestTypecheck < Minitest::Test
107
121
 
108
122
  def test_empty
109
123
  self.class.class_eval {
110
- type "() -> nil", typecheck_now: true
124
+ type "() -> nil", typecheck: :now
111
125
  def empty() end
112
126
  }
113
127
  end
@@ -115,10 +129,10 @@ class TestTypecheck < Minitest::Test
115
129
  def test_dstr_xstr
116
130
  # Hard to read if these are inside of strings, so leave like this
117
131
  self.class.class_eval {
118
- type "() -> String", typecheck_now: true
132
+ type "() -> String", typecheck: :now
119
133
  def dstr() "Foo #{42} Bar #{43}"; end
120
134
 
121
- type "() -> String", typecheck_now: true
135
+ type "() -> String", typecheck: :now
122
136
  def xstr() `ls #{42}`; end
123
137
  }
124
138
  end
@@ -130,7 +144,7 @@ class TestTypecheck < Minitest::Test
130
144
  def test_dsym
131
145
  # Hard to read if these are inside of strings, so leave like this
132
146
  self.class.class_eval {
133
- type "() -> Symbol", typecheck_now: true
147
+ type "() -> Symbol", typecheck: :now
134
148
  def dsym() :"foo#{42}"; end
135
149
  }
136
150
  end
@@ -140,7 +154,7 @@ class TestTypecheck < Minitest::Test
140
154
 
141
155
  self.class.class_eval {
142
156
  # Hard to read if these are inside of strings, so leave like this
143
- type "() -> Regexp", typecheck_now: true
157
+ type "() -> Regexp", typecheck: :now
144
158
  def regexp2() /foo#{42}bar#{"baz"}/i; end
145
159
  }
146
160
  end
@@ -166,18 +180,18 @@ class TestTypecheck < Minitest::Test
166
180
  def test_self
167
181
  # These need to be inside an actual class
168
182
  self.class.class_eval {
169
- type "() -> self", typecheck_now: true
183
+ type "() -> self", typecheck: :now
170
184
  def self1() self; end
171
185
  }
172
186
 
173
187
  self.class.class_eval {
174
- type "() -> self", typecheck_now: true
188
+ type "() -> self", typecheck: :now
175
189
  def self2() TestTypecheck.new; end
176
190
  }
177
191
 
178
192
  assert_raises(RDL::Typecheck::StaticTypeError) {
179
193
  self.class.class_eval {
180
- type "() -> self", typecheck_now: true
194
+ type "() -> self", typecheck: :now
181
195
  def self3() Object.new; end
182
196
  }
183
197
  }
@@ -199,19 +213,19 @@ class TestTypecheck < Minitest::Test
199
213
 
200
214
  def test_lvar
201
215
  self.class.class_eval {
202
- type "(Fixnum, String) -> Fixnum", typecheck_now: true
216
+ type "(Fixnum, String) -> Fixnum", typecheck: :now
203
217
  def lvar1(x, y) x; end
204
218
  }
205
219
 
206
220
  self.class.class_eval {
207
- type "(Fixnum, String) -> String", typecheck_now: true
221
+ type "(Fixnum, String) -> String", typecheck: :now
208
222
  def lvar2(x, y) y; end
209
223
  }
210
224
 
211
225
  assert_raises(RDL::Typecheck::StaticTypeError) {
212
226
  # really a send
213
227
  self.class.class_eval {
214
- type "(Fixnum, String) -> String", typecheck_now: true
228
+ type "(Fixnum, String) -> String", typecheck: :now
215
229
  def lvar3(x, y) z; end
216
230
  }
217
231
  }
@@ -235,12 +249,12 @@ class TestTypecheck < Minitest::Test
235
249
  assert_equal $__rdl_fixnum_type, do_tc("var_type :x, 'Fixnum'; x = 3; x", env: @env)
236
250
  assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("var_type :x, 'Fixnum'; x = 'three'", env: @env) }
237
251
  self.class.class_eval {
238
- type "(Fixnum) -> nil", typecheck_now: true
252
+ type "(Fixnum) -> nil", typecheck: :now
239
253
  def lvar_type_ff(x) x = 42; nil; end
240
254
  }
241
255
  assert_raises(RDL::Typecheck::StaticTypeError) {
242
256
  self.class.class_eval {
243
- type "(Fixnum) -> nil", typecheck_now: true
257
+ type "(Fixnum) -> nil", typecheck: :now
244
258
  def lvar_type_ff2(x) x = "forty-two"; nil; end
245
259
  }
246
260
  }
@@ -508,7 +522,7 @@ class TestTypecheck < Minitest::Test
508
522
 
509
523
  def test_yield
510
524
  self.class.class_eval {
511
- type "(Fixnum) { (Fixnum) -> Fixnum } -> Fixnum", typecheck_now: true
525
+ type "(Fixnum) { (Fixnum) -> Fixnum } -> Fixnum", typecheck: :now
512
526
  def _yield1(x)
513
527
  yield x
514
528
  end
@@ -516,7 +530,7 @@ class TestTypecheck < Minitest::Test
516
530
 
517
531
  assert_raises(RDL::Typecheck::StaticTypeError) {
518
532
  self.class.class_eval {
519
- type "(Fixnum) { (Fixnum) -> Fixnum } -> Fixnum", typecheck_now: true
533
+ type "(Fixnum) { (Fixnum) -> Fixnum } -> Fixnum", typecheck: :now
520
534
  def _yield2(x)
521
535
  yield 'forty-two'
522
536
  end
@@ -525,7 +539,7 @@ class TestTypecheck < Minitest::Test
525
539
 
526
540
  assert_raises(RDL::Typecheck::StaticTypeError) {
527
541
  self.class.class_eval {
528
- type "(Fixnum) { (Fixnum) -> String } -> Fixnum", typecheck_now: true
542
+ type "(Fixnum) { (Fixnum) -> String } -> Fixnum", typecheck: :now
529
543
  def _yield3(x)
530
544
  yield 42
531
545
  end
@@ -534,7 +548,7 @@ class TestTypecheck < Minitest::Test
534
548
 
535
549
  assert_raises(RDL::Typecheck::StaticTypeError) {
536
550
  self.class.class_eval {
537
- type "(Fixnum) -> Fixnum", typecheck_now: true
551
+ type "(Fixnum) -> Fixnum", typecheck: :now
538
552
  def _yield4(x)
539
553
  yield 42
540
554
  end
@@ -543,7 +557,7 @@ class TestTypecheck < Minitest::Test
543
557
 
544
558
  assert_raises(RDL::Typecheck::StaticTypeError) {
545
559
  self.class.class_eval {
546
- type "(Fixnum) { (Fixnum) { (Fixnum) -> Fixnum } -> Fixnum } -> Fixnum", typecheck_now: true
560
+ type "(Fixnum) { (Fixnum) { (Fixnum) -> Fixnum } -> Fixnum } -> Fixnum", typecheck: :now
547
561
  def _yield5(x)
548
562
  yield 42
549
563
  end
@@ -552,14 +566,14 @@ class TestTypecheck < Minitest::Test
552
566
  end
553
567
 
554
568
  # class Sup1
555
- # type '(Fixnum) -> Fixnum', typecheck: true
569
+ # type '(Fixnum) -> Fixnum', typecheck: :call
556
570
  # def foo(y)
557
571
  # return y
558
572
  # end
559
573
  # end
560
574
  #
561
575
  # class Sup2 < Sup1
562
- # type '(Fixnum) -> Fixnum', typecheck: true
576
+ # type '(Fixnum) -> Fixnum', typecheck: :call
563
577
  # def foo(x)
564
578
  # super(x+1)
565
579
  # end
@@ -693,7 +707,7 @@ class TestTypecheck < Minitest::Test
693
707
 
694
708
  def test_return
695
709
  assert self.class.class_eval {
696
- type "(Fixnum) -> Fixnum", typecheck_now: true
710
+ type "(Fixnum) -> Fixnum", typecheck: :now
697
711
  def return_ff(x)
698
712
  return 42
699
713
  end
@@ -701,7 +715,7 @@ class TestTypecheck < Minitest::Test
701
715
 
702
716
  assert_raises(RDL::Typecheck::StaticTypeError) {
703
717
  self.class.class_eval {
704
- type "(Fixnum) -> Fixnum", typecheck_now: true
718
+ type "(Fixnum) -> Fixnum", typecheck: :now
705
719
  def return_ff2(x)
706
720
  return "forty-two"
707
721
  end
@@ -890,4 +904,30 @@ class TestTypecheck < Minitest::Test
890
904
  assert_equal tt("Hash<Symbol or String, Fixnum or String>"), do_tc("x = {'a' => 1, **_kwsplathsf, b: 'two'}", env: @env)
891
905
  end
892
906
 
907
+ def test_attr_etc
908
+ self.class.class_eval {
909
+ attr_reader_type :f_attr_reader, "Fixnum", :f_attr_reader2, "String"
910
+ attr_writer_type :f_attr_writer, "Fixnum"
911
+ attr_type :f_attr, "Fixnum"
912
+ attr_accessor_type :f_attr_accessor, "Fixnum"
913
+ }
914
+ assert_equal $__rdl_fixnum_type, do_tc("@f_attr_reader", env: @env)
915
+ assert_equal $__rdl_fixnum_type, do_tc("TestTypecheck.new.f_attr_reader", env: @env)
916
+ assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("TestTypecheck.new.f_attr_reader = 4", env: @env) }
917
+ assert_equal $__rdl_string_type, do_tc("@f_attr_reader2", env: @env)
918
+ assert_equal $__rdl_string_type, do_tc("TestTypecheck.new.f_attr_reader2", env: @env)
919
+
920
+ assert_equal $__rdl_fixnum_type, do_tc("@f_attr", env: @env) # same as attr_reader
921
+ assert_equal $__rdl_fixnum_type, do_tc("TestTypecheck.new.f_attr", env: @env)
922
+ assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("TestTypecheck.new.f_attr = 42", env: @env) }
923
+
924
+ assert_equal @t3, do_tc("@f_attr_writer = 3", env: @env)
925
+ assert_equal $__rdl_fixnum_type, do_tc("TestTypecheck.new.f_attr_writer = 3", env: @env)
926
+ assert_raises(RDL::Typecheck::StaticTypeError) { do_tc("TestTypecheck.new.f_attr_writer", env: @env) }
927
+
928
+ assert_equal $__rdl_fixnum_type, do_tc("@f_attr_accessor", env: @env)
929
+ assert_equal $__rdl_fixnum_type, do_tc("TestTypecheck.new.f_attr_accessor", env: @env)
930
+ assert_equal $__rdl_fixnum_type, do_tc("TestTypecheck.new.f_attr_accessor = 42", env: @env)
931
+ end
932
+
893
933
  end
@@ -0,0 +1,83 @@
1
+ class Module
2
+ rdl_nowrap
3
+
4
+ type 'self.constants', '() -> Array<Fixnum>' # also constants(inherited), but undocumented
5
+ type 'self.nesting', '() -> Array<Module>'
6
+ type 'self.new', '() -> Module'
7
+ type 'self.new', '() { (Module) -> %any } -> Module'
8
+
9
+ type :<, '(Module other) -> %bool or nil'
10
+ type :<=, '(Module other) -> %bool or nil'
11
+ type :<=>, '(Module other) -> -1 or 0 or 1 or nil'
12
+ type :==, '(%any other) -> %bool'
13
+ type :equal, '(%any other) -> %bool'
14
+ type :eql, '(%any other) -> %bool'
15
+ type :===, '(%any other) -> %bool'
16
+ type :>, '(Module other) -> %bool or nil'
17
+ type :>=, '(Module other) -> %bool or nil'
18
+ type :ancestors, '() -> Array<Module>'
19
+ type :autoload, '(Symbol module, String filename) -> nil'
20
+ type :autoload?, '(Symbol name) -> String or nil'
21
+ type :class_eval, '(String, ?String filename, ?Fixnum lineno) -> %any'
22
+ type :class_exec, '(*%any args) { (*%any args) -> %any } -> %any'
23
+ type :class_variable_defined?, '(Symbol or String) -> %bool'
24
+ type :class_variable_get, '(Symbol or String) -> %any'
25
+ type :class_variable_set, '(Symbol or String, %any) -> %any'
26
+ type :class_variables, '(?%bool inherit) -> Array<Symbol>'
27
+ type :const_defined?, '(Symbol or String, ?%bool inherit) -> %bool'
28
+ type :const_get, '(Symbol or String, ?%bool inherit) -> %any'
29
+ type :const_missing, '(Symbol) -> %any'
30
+ type :const_set, '(Symbol or String, %any) -> %any'
31
+ type :constants, '(?%bool inherit) -> Array<Symbol>'
32
+ type :freeze, '() -> self'
33
+ type :include, '(*Module) -> self'
34
+ type :include?, '(Module) -> %bool'
35
+ type :included_modules, '() -> Array<Module>'
36
+ rdl_alias :inspect, :to_s
37
+ type :instance_method, '(Symbol) -> UnboundMethod'
38
+ type :instance_methods, '(?%bool include_super) -> Array<Symbol>'
39
+ type :method_defined?, '(Symbol or String) -> %bool'
40
+ type :module_eval, '(String, ?String filename, ?Fixnum lineno) -> %any' # matches rdoc example but not type
41
+ type :module_exec, '(*%any args) { (*%any args) -> %any } -> %any'
42
+ type :name, '() -> String'
43
+ type :prepend, '(*Module) -> self'
44
+ type :private_class_method, '(*(Symbol or String)) -> self'
45
+ type :private_constant, '(*Symbol) -> self'
46
+ type :private_instance_methods, '(?%bool include_super) -> Array<Symbol>'
47
+ type :private_method_defined?, '(Symbol or String) -> %bool'
48
+ type :protected_instance_methods, '(?%bool include_super) -> Array<Symbol>'
49
+ type :protected_method_defined?, '(Symbol or String) -> %bool'
50
+ type :public_class_method, '(*(Symbol or String)) -> self'
51
+ type :public_constant, '(*Symbol) -> self'
52
+ type :public_instance_method, '(Symbol) -> UnboundMethod'
53
+ type :public_instance_methods, '(?%bool include_super) -> Array<Symbol>'
54
+ type :public_method_defined?, '(Symbol or String) -> %bool'
55
+ type :remove_class_variable, '(Symbol) -> %any'
56
+ type :singleton_class?, '() -> %bool'
57
+ type :to_s, '() -> String'
58
+ # private methods below here
59
+ type :alias_method, '(Symbol new_name, Symbol old_name) -> self'
60
+ type :append_features, '(Module) -> self'
61
+ rdl_alias :attr, :attr_reader
62
+ type :attr_accessor, '(*(Symbol or String)) -> nil'
63
+ type :attr_reader, '(*(Symbol or String)) -> nil'
64
+ type :attr_writer, '(*(Symbol or String)) -> nil'
65
+ type :define_method, '(Symbol, Method) -> Symbol'
66
+ type :define_method, '(Symbol) { (*%any) -> %any } -> Symbol'
67
+ type :extend_object, '(%any) -> %any'
68
+ type :extended, '(Module othermod) -> %any'
69
+ type :included, '(Module othermod) -> %any'
70
+ type :method_added, '(Symbol method_name) -> %any'
71
+ type :method_removed, '(Symbol method_name) -> %any'
72
+ type :module_function, '(*(Symbol or String)) -> self'
73
+ type :prepend_features, '(Module) -> self'
74
+ type :prepended, '(Module othermod) -> %any'
75
+ type :private, '(*(Symbol or String)) -> self'
76
+ type :protected, '(*(Symbol or String)) -> self'
77
+ type :public, '(*(Symbol or String)) -> self'
78
+ type :refine, '(Class) { (%any) -> %any } -> self' # ??
79
+ type :remove_const, '(Symbol) -> %any'
80
+ type :remove_method, '(Symbol or String) -> self'
81
+ type :undef_method, '(Symbol or String) -> self'
82
+ type :using, '(Module) -> self'
83
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rdl
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc1
4
+ version: 2.0.0.rc2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeffrey S. Foster
@@ -12,7 +12,7 @@ authors:
12
12
  autorequire:
13
13
  bindir: bin
14
14
  cert_chain: []
15
- date: 2016-07-17 00:00:00.000000000 Z
15
+ date: 2016-07-19 00:00:00.000000000 Z
16
16
  dependencies:
17
17
  - !ruby/object:Gem::Dependency
18
18
  name: parser
@@ -206,6 +206,7 @@ files:
206
206
  - types/ruby-2.x/marshal.rb
207
207
  - types/ruby-2.x/matchdata.rb
208
208
  - types/ruby-2.x/math.rb
209
+ - types/ruby-2.x/module.rb
209
210
  - types/ruby-2.x/nil.rb
210
211
  - types/ruby-2.x/numeric.rb
211
212
  - types/ruby-2.x/object.rb