rdl 1.1.1 → 2.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/README.md +211 -32
- data/gemfiles/Gemfile.travis +1 -1
- data/lib/rdl.rb +85 -18
- data/lib/rdl/info.rb +74 -0
- data/lib/rdl/query.rb +8 -9
- data/lib/rdl/typecheck.rb +1057 -0
- data/lib/rdl/types/annotated_arg.rb +5 -5
- data/lib/rdl/types/{nil.rb → bot.rb} +9 -13
- data/lib/rdl/types/dependent_arg.rb +5 -5
- data/lib/rdl/types/dots_query.rb +2 -0
- data/lib/rdl/types/finitehash.rb +67 -24
- data/lib/rdl/types/generic.rb +13 -21
- data/lib/rdl/types/intersection.rb +9 -8
- data/lib/rdl/types/method.rb +30 -32
- data/lib/rdl/types/nominal.rb +22 -16
- data/lib/rdl/types/optional.rb +8 -22
- data/lib/rdl/types/parser.racc +8 -3
- data/lib/rdl/types/parser.tab.rb +131 -118
- data/lib/rdl/types/singleton.rb +15 -10
- data/lib/rdl/types/structural.rb +6 -6
- data/lib/rdl/types/top.rb +6 -6
- data/lib/rdl/types/tuple.rb +56 -24
- data/lib/rdl/types/type.rb +9 -0
- data/lib/rdl/types/type_inferencer.rb +1 -1
- data/lib/rdl/types/union.rb +52 -26
- data/lib/rdl/types/var.rb +7 -6
- data/lib/rdl/types/vararg.rb +5 -6
- data/lib/rdl/types/wild_query.rb +9 -2
- data/lib/rdl/util.rb +9 -7
- data/lib/rdl/wrap.rb +90 -72
- data/lib/rdl_types.rb +2 -2
- data/rdl.gemspec +6 -8
- data/test/test_alias.rb +4 -3
- data/test/test_contract.rb +5 -4
- data/test/test_dsl.rb +2 -1
- data/test/test_generic.rb +30 -26
- data/test/test_intersection.rb +3 -3
- data/test/test_le.rb +129 -61
- data/test/test_lib_types.rb +3 -2
- data/test/test_member.rb +33 -46
- data/test/test_parser.rb +113 -116
- data/test/test_query.rb +2 -1
- data/test/test_rdl.rb +64 -6
- data/test/test_rdl_type.rb +3 -2
- data/test/test_type_contract.rb +30 -12
- data/test/test_typecheck.rb +893 -0
- data/test/test_types.rb +50 -54
- data/test/test_wrap.rb +2 -1
- data/types/ruby-2.x/_aliases.rb +13 -2
- data/types/ruby-2.x/bigdecimal.rb +60 -85
- data/types/ruby-2.x/bignum.rb +80 -119
- data/types/ruby-2.x/complex.rb +33 -40
- data/types/ruby-2.x/fixnum.rb +81 -120
- data/types/ruby-2.x/float.rb +79 -116
- data/types/ruby-2.x/integer.rb +187 -22
- data/types/ruby-2.x/nil.rb +12 -0
- data/types/ruby-2.x/numeric.rb +38 -38
- data/types/ruby-2.x/object.rb +3 -3
- data/types/ruby-2.x/random.rb +2 -0
- data/types/ruby-2.x/range.rb +20 -19
- data/types/ruby-2.x/rational.rb +40 -40
- data/types/ruby-2.x/regexp.rb +4 -4
- data/types/ruby-2.x/string.rb +15 -17
- metadata +17 -16
- data/lib/rdl/types/.#lexer.rex +0 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 797b63a785ea5ae5e9600d8df722c66cf2a2fe17
|
4
|
+
data.tar.gz: dc28d44117114d21fccd6472c77f45b6efa3ae25
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0ea7b41ad216861c4b05a3f7c0204a3c71b8c1ecd9ce2e8c33ab26526dc2f079358bddc0cb18af0cf4e29420fdf2d8e5a574692a1b215c491bbf26f6d6f97804
|
7
|
+
data.tar.gz: 18acb05e6f25b0a80716a306c6b340c3d10d6e4f2bed954e42b502b52f240808a1db03652a693f74377ee6a76c1582cc52a0d23000f599710727f5a05184e415
|
data/CHANGES.md
CHANGED
@@ -1,6 +1,12 @@
|
|
1
1
|
# Change log!
|
2
2
|
|
3
3
|
## [Unreleased]
|
4
|
+
### Added
|
5
|
+
- Added `wrap: false` optional argument to `type`, `pre`, and `post`
|
6
|
+
|
7
|
+
### Changed
|
8
|
+
- Modified `self` type to be any instance of the self's class
|
9
|
+
- Library types now use new aliases %integer and %numeric instead of the Integer and Numeric classes.
|
4
10
|
|
5
11
|
## [1.1.1] - 2016-05-21
|
6
12
|
### Fixed
|
data/README.md
CHANGED
@@ -1,6 +1,47 @@
|
|
1
1
|
[![Gem Version](https://badge.fury.io/rb/rdl.svg)](https://badge.fury.io/rb/rdl) [![Build Status](https://travis-ci.org/plum-umd/rdl.svg?branch=master)](https://travis-ci.org/plum-umd/rdl)
|
2
2
|
|
3
3
|
|
4
|
+
# Table of Contents
|
5
|
+
|
6
|
+
* [Introduction](#introduction)
|
7
|
+
* [Using RDL](#using-rdl)
|
8
|
+
* [Supported versions of Ruby](#supported-versions-of-ruby)
|
9
|
+
* [Installing RDL](#installing-rdl)
|
10
|
+
* [Loading RDL](#loading-rdl)
|
11
|
+
* [Preconditions and Postconditions](#preconditions-and-postconditions)
|
12
|
+
* [Type Annotations](#type-annotations)
|
13
|
+
* [RDL Types](#rdl-types)
|
14
|
+
* [Nominal Types](#nominal-types)
|
15
|
+
* [Nil Type](#nil-type)
|
16
|
+
* [Top Type (%any)](#top-type-any)
|
17
|
+
* [Union Types](#union-types)
|
18
|
+
* [Intersection Types](#intersection-types)
|
19
|
+
* [Optional Argument Types](#optional-argument-types)
|
20
|
+
* [Variable Length Argument Types](#variable-length-argument-types)
|
21
|
+
* [Named Argument Types](#named-argument-types)
|
22
|
+
* [Dependent Types](#dependent-types)
|
23
|
+
* [Higher-order Types](#higher-order-types)
|
24
|
+
* [Class/Singleton Method Types](#classsingleton-method-types)
|
25
|
+
* [Structural Types](#structural-types)
|
26
|
+
* [Singleton Types](#singleton-types)
|
27
|
+
* [Self Type](#self-type)
|
28
|
+
* [Type Aliases](#type-aliases)
|
29
|
+
* [Generic Class Types](#generic-class-types)
|
30
|
+
* [Tuple Types](#tuple-types)
|
31
|
+
* [Finite Hash Types](#finite-hash-types)
|
32
|
+
* [Type Casts](#type-casts)
|
33
|
+
* [Bottom Type (%bot)](#bottom-type-bot)
|
34
|
+
* [Static Type Checking](#static-type-checking)
|
35
|
+
* [Types for Variables](#types-for-variables)
|
36
|
+
* [Tuples, Finite Hashes, and Subtyping](#tuples-finite-hashes-and-subtyping)
|
37
|
+
* [Other Features and Limitations](#other-features-and-limitations)
|
38
|
+
* [Other RDL Methods](#other-rdl-methods)
|
39
|
+
* [Queries](#queries)
|
40
|
+
* [Bibliography](#bibliography)
|
41
|
+
* [Copyright](#copyright)
|
42
|
+
* [Contributors](#contributors)
|
43
|
+
* [TODO List](#todo-list)
|
44
|
+
|
4
45
|
# Introduction
|
5
46
|
|
6
47
|
RDL is a lightweight system for adding contracts to Ruby. A *contract* decorates a method with assertions describing what the method assumes about its inputs (called a *precondition*) and what the method guarantees about its outputs (called a *postcondition*). For example, using RDL we can write
|
@@ -17,7 +58,6 @@ end
|
|
17
58
|
|
18
59
|
Given this program, RDL intercepts the call to `sqrt` and passes its argument to the `pre` block, which checks that the arugment is positive. Then when `sqrt` returns, RDL passes the return value (as `r`) and the initial argument (as `x`) to the `post` block, which checks that the return is positive. (Let's ignore complex numbers to keep things simple...)
|
19
60
|
|
20
|
-
|
21
61
|
RDL contracts are enforced at method entry and exit. For example, if we call `sqrt(49)`, RDL first checks that `49 > 0`; then it passes `49` to `sqrt`, which (presumably) returns `7`; then RDL checks that `7 > 0`; and finally it returns `7`.
|
22
62
|
|
23
63
|
In addition to arbitrary pre- and post-conditions, RDL also has extensive support for contracts that are *types*. For example, we can write the following in RDL:
|
@@ -31,6 +71,28 @@ def m(x,y) ... end
|
|
31
71
|
|
32
72
|
This indicates that `m` is that method that returns a `String` if given two `Fixnum` arguments. Again this contract is enforced at run-time: When `m` is called, RDL checks that `m` is given exactly two arguments and both are `Fixnums`, and that `m` returns an instance of `String`. RDL supports many more complex type annotations; see below for a complete discussion and examples. We should emphasize here that RDL types are enforced as contracts at method entry and exit. There is no static checking that the method body conforms to the types.
|
33
73
|
|
74
|
+
The beta version of RDL also has an experimental mode in which method bodies can be *statically type checked* against their signatures. (This feature is only in the beta version of the RDL gem.) For example:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
file.rb:
|
78
|
+
require 'rdl'
|
79
|
+
|
80
|
+
type '(Fixnum) -> Fixnum', typecheck_now: true
|
81
|
+
def id(x)
|
82
|
+
"forty-two"
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
```
|
87
|
+
$ ruby file.rb
|
88
|
+
.../lib/rdl/typecheck.rb:32:in `error': (RDL::Typecheck::StaticTypeError)
|
89
|
+
.../file.rb:5:5: error: got type `String' where return type `Fixnum' expected
|
90
|
+
.../file.rb:5: "forty-two"
|
91
|
+
.../file.rb:5: ^~~~~~~~~~~
|
92
|
+
```
|
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.
|
95
|
+
|
34
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.
|
35
97
|
|
36
98
|
```shell
|
@@ -57,7 +119,7 @@ $ irb
|
|
57
119
|
|
58
120
|
Currently only type information is returned by `rdl_query` (and not other pre or postconditions).
|
59
121
|
|
60
|
-
# RDL
|
122
|
+
# Using RDL
|
61
123
|
|
62
124
|
## Supported versions of Ruby
|
63
125
|
|
@@ -105,7 +167,7 @@ The `post` method can be called in the same ways as `pre`.
|
|
105
167
|
|
106
168
|
Methods can have no contracts, `pre` by itself, `post` by itself, both, or multiple instances of either. If there are multiple contracts, RDL checks that *all* contracts are satisfied, in the order that the contracts were bound to the method.
|
107
169
|
|
108
|
-
## Type
|
170
|
+
## Type Annotations
|
109
171
|
|
110
172
|
The `type` method adds a type contract to a method. It supports the same calling patterns as `pre` and `post`, except rather than a block, it takes a string argument describing the type. More specifically, `type` can be called as:
|
111
173
|
|
@@ -113,9 +175,19 @@ The `type` method adds a type contract to a method. It supports the same calling
|
|
113
175
|
* `type m, 'typ'`
|
114
176
|
* `type cls, mth, 'typ'`
|
115
177
|
|
116
|
-
A type string generally has the form `(typ1, ..., typn) -> typ` indicating a method that takes `n` arguments of types `typ1` through `typn` and returns type `typ`.
|
178
|
+
A type string generally has the form `(typ1, ..., typn) -> typ` indicating a method that takes `n` arguments of types `typ1` through `typn` and returns type `typ`. Below, to illustrate the various types RDL supports, we'll use examples from the core library type annotations.
|
179
|
+
|
180
|
+
The `type` method can be called with `wrap: false` so the type information is stored but the type is not enforced. For example, due to the way RDL is implemented, the method `String#=~` can't have a type or contract on it because then it won't set the correct `$1` etc variables:
|
181
|
+
|
182
|
+
```ruby
|
183
|
+
type :=~, '(Object) -> Fixnum or nil', wrap: false # Wrapping this messes up $1 etc
|
184
|
+
```
|
185
|
+
|
186
|
+
For consistency, `pre` and `post` can also be called with `wrap: false`, but this is generally not as useful.
|
187
|
+
|
188
|
+
# RDL Types
|
117
189
|
|
118
|
-
|
190
|
+
## Nominal Types
|
119
191
|
|
120
192
|
A nominal type is simply a class name, and it matches any object of that class or any subclass.
|
121
193
|
|
@@ -123,7 +195,7 @@ A nominal type is simply a class name, and it matches any object of that class o
|
|
123
195
|
type String, :insert, '(Fixnum, String) -> String'
|
124
196
|
```
|
125
197
|
|
126
|
-
|
198
|
+
## Nil Type
|
127
199
|
|
128
200
|
The nominal type `NilClass` can also be written as `nil`. The only object of this type is `nil`:
|
129
201
|
|
@@ -138,7 +210,7 @@ x.insert(0, nil) # RDL does not report a type error
|
|
138
210
|
```
|
139
211
|
We chose this design based on prior experience with static type systems for Ruby, where not allowing this leads to a lot of false positive errors from the type system. However, we may change this in the future.
|
140
212
|
|
141
|
-
|
213
|
+
## Top Type (%any)
|
142
214
|
|
143
215
|
RDL includes a special "top" type `%any` that matches any object:
|
144
216
|
```ruby
|
@@ -146,7 +218,7 @@ type Object, :=~, '(%any) -> nil'
|
|
146
218
|
```
|
147
219
|
We call this the "top" type because it is the top of the subclassing hierarchy RDL uses. Note that `%any` is more general than `Object`, because not all classes inherit from `Object`, e.g., `BasicObject` does not.
|
148
220
|
|
149
|
-
|
221
|
+
## Union Types
|
150
222
|
|
151
223
|
Many Ruby methods can take several different types of arguments or return different types of results. The union operator `or` can be used to indicate a position where multiple types are possible.
|
152
224
|
|
@@ -157,7 +229,7 @@ type String, :getbyte, '(Fixnum) -> Fixnum or nil'
|
|
157
229
|
|
158
230
|
Note that for `getbyte`, we could leave off the `nil`, but we include it to match the current documentation of this method.
|
159
231
|
|
160
|
-
|
232
|
+
## Intersection Types
|
161
233
|
|
162
234
|
Sometimes Ruby methods have several different type signatures. (In Java these would be called *overloaded* methods.) In RDL, such methods are assigned a set of type signatures:
|
163
235
|
|
@@ -189,7 +261,7 @@ type String, :[], '(Range) -> String or nil'
|
|
189
261
|
type String, :[], '(Regexp) -> String or nil'
|
190
262
|
```
|
191
263
|
|
192
|
-
|
264
|
+
## Optional Argument Types
|
193
265
|
|
194
266
|
Optional arguments are denoted in RDL by putting `?` in front of the argument's type. For example:
|
195
267
|
|
@@ -208,7 +280,7 @@ but it helps make types more readable.
|
|
208
280
|
|
209
281
|
Like Ruby, RDL allows optional arguments to appear anywhere in a method's type signature.
|
210
282
|
|
211
|
-
|
283
|
+
## Variable Length Argument Types
|
212
284
|
|
213
285
|
In RDL, `*` is used to decorate an argument that may appear zero or more times. Currently in RDL this annotation may only appear on the rightmost argument. For example, `String#delete` takes one or more `String` arguments:
|
214
286
|
|
@@ -216,7 +288,7 @@ In RDL, `*` is used to decorate an argument that may appear zero or more times.
|
|
216
288
|
type String, :delete, '(String, *String) -> String'
|
217
289
|
```
|
218
290
|
|
219
|
-
|
291
|
+
## Named Argument Types
|
220
292
|
|
221
293
|
RDL allows arguments to be named, for documentation purposes. Names are given after the argument's type, and they do not affect type contract checking in any way. For example:
|
222
294
|
|
@@ -226,7 +298,7 @@ type Fixnum, :to_s, '(?Fixnum base) -> String'
|
|
226
298
|
|
227
299
|
Here we've named the first argument of `to_s` as `base` to give some extra hint as to its meaning.
|
228
300
|
|
229
|
-
|
301
|
+
## Dependent Types
|
230
302
|
|
231
303
|
RDL allows for refinement predicates to be attached to named arguments. These predicates are then checked when the method is called and returns. For instance:
|
232
304
|
|
@@ -248,9 +320,9 @@ def m(x,y) ... end
|
|
248
320
|
|
249
321
|
Any arbitrary code can be placed between the double braces of a type refinement, and RDL will dynmically check that this predicate evaluates to true, or raise a type error if it evaluates to false.
|
250
322
|
|
251
|
-
|
323
|
+
## Higher-order Types
|
252
324
|
|
253
|
-
RDL supports
|
325
|
+
RDL supports types for arguments or return values which are themselves `Proc` objects. Simply enclose the corresponding argument's type with braces to denote that it is a `Proc`. For example:
|
254
326
|
|
255
327
|
```ruby
|
256
328
|
type '(Fixnum, {(Fixnum) -> Fixnum}) -> Fixnum'
|
@@ -264,7 +336,7 @@ type '(Fixnum) -> {(Float) -> Float}'
|
|
264
336
|
def m(x) ... end
|
265
337
|
```
|
266
338
|
|
267
|
-
These higher-order
|
339
|
+
These higher-order types are checked by wrapping the corresponding `Proc` argument/return in a new `Proc` which checks that the type contract holds.
|
268
340
|
|
269
341
|
A type contract can be provided for a method block as well. The block's type should be included after the method argument types:
|
270
342
|
|
@@ -282,7 +354,7 @@ def m(x,y,&blk) ... end
|
|
282
354
|
|
283
355
|
The type contract above states that method `m` returns a `Proc` which takes a `Fixnum z` which must be greater than the argument `Float y`. Whenver this `Proc` is called, it will be checked that this contract holds.
|
284
356
|
|
285
|
-
|
357
|
+
## Class/Singleton Method Types
|
286
358
|
|
287
359
|
RDL method signatures can be used both for instance methods and for class methods (often called *singleton methods* in Ruby). To indicate a type signature applies to a singleton method, prefix the method name with `self.`:
|
288
360
|
|
@@ -298,7 +370,7 @@ Type signatures can be added to `initialize` by giving a type signature for `sel
|
|
298
370
|
type File, 'self.new', '(String file, ?String mode, ?String perm, ?Fixnum opt) -> File'
|
299
371
|
```
|
300
372
|
|
301
|
-
|
373
|
+
## Structural Types
|
302
374
|
|
303
375
|
Some Ruby methods are intended to take any object that has certain methods. RDL uses *structural types* to denote such cases:
|
304
376
|
|
@@ -310,7 +382,7 @@ Here `IO#puts` can take zero or more arguments, all of which must have a `to_s`
|
|
310
382
|
|
311
383
|
The actual checking that RDL does here varies depending on what type information is available. Suppose we call `puts(o)`. If `o` is an instance of a class that has a type signature `t` for `to_s`, then RDL will check that `t` is compatible with `() -> String`. On the other hand, if `o` is an instance of a class with no type signature for `to_s`, RDL only checks that `o` has a `to_s` method, but it doesn't check its argument or return types.
|
312
384
|
|
313
|
-
|
385
|
+
## Singleton Types
|
314
386
|
|
315
387
|
Not to be confused with types for singleton methods, RDL includes *singleton types* that denote positions that always have one particular value; this typically happens only in return positions. For example, `Dir#mkdir` always returns the value 0:
|
316
388
|
|
@@ -326,8 +398,9 @@ type Float, :angle, '() -> 0 or ${Math::PI}'
|
|
326
398
|
|
327
399
|
RDL checks if a value matches a singleton type using `equal?`. As a consequence, singleton string types aren't currently possible.
|
328
400
|
|
401
|
+
Note that the type `nil` is actually implemented as a singleton type with the special behavior that `nil` is a treated as a member of any class. However, while `nil` can in general be used anywhere any type is expected, it *cannot* be used where a different singleton type is expected. For example, `nil` could not be a return value of `Dir#mkdir` or `Float#angle`.
|
329
402
|
|
330
|
-
|
403
|
+
## Self Type
|
331
404
|
|
332
405
|
Consider a method that returns `self`:
|
333
406
|
|
@@ -358,9 +431,9 @@ A.new.id # okay, returns self
|
|
358
431
|
B.new.id # also okay, returns self
|
359
432
|
```
|
360
433
|
|
361
|
-
|
434
|
+
Thus, the type `self` means "any object of self's class."
|
362
435
|
|
363
|
-
|
436
|
+
## Type Aliases
|
364
437
|
|
365
438
|
RDL allows types to be aliases to make them faster to write down and more readable. All type aliases begin with `%`. RDL has one built-in alias, `%bool`, which is shorthand for `TrueClass or FalseClass`:
|
366
439
|
|
@@ -380,7 +453,7 @@ type_alias '%path', '%string or Pathname'
|
|
380
453
|
|
381
454
|
Type aliases have to be created before they are used (so above, `%path` must be defined after `%string`).
|
382
455
|
|
383
|
-
|
456
|
+
## Generic Class Types
|
384
457
|
|
385
458
|
RDL supports *parametric polymorphism* for classes, a.k.a. *generics*. The `type_params` method names the type parameters of the class, and those parameters can then be used inside type signatures:
|
386
459
|
|
@@ -439,7 +512,7 @@ The rules for variances are standard. Let's assume `A` is a subclass of `B`. Als
|
|
439
512
|
* `C<A>` is a subtype of `C<B>` if `C`'s type parameter is covariant
|
440
513
|
* `C<B>` is a subtype of `C<A>` if `C`'s type parameter is contravariant
|
441
514
|
|
442
|
-
|
515
|
+
## Tuple Types
|
443
516
|
|
444
517
|
A type such as `Array<Fixnum>` is useful for homogeneous arrays, where all elements have the same type. But Ruby programs often use heterogenous arrays, e.g., `[1, "two"]`. The best generic type we can give this is `Array<Fixnum or String>`, but that's imprecise.
|
445
518
|
|
@@ -449,7 +522,7 @@ RDL includes special *tuple types* to handle this situation. Tuple types are wri
|
|
449
522
|
type Process, 'self.getrlimit', '(Symbol or String or Fixnum resource) -> [Fixnum, Fixnum] cur_max_limit'
|
450
523
|
```
|
451
524
|
|
452
|
-
|
525
|
+
## Finite Hash Types
|
453
526
|
|
454
527
|
Similarly to tuple types, RDL also supports *finite hash types* for heterogenous hashes. Finite hash types are written `{k1 => v1, ..., kn => vn}` to indicate a `Hash` with `n` mappings of type `ki` maps to `vi`. The `ki` may be strings, integers, floats, or constants denoted with `${.}`. If a key is a symbol, then the mapping should be written `ki: vi`. In the latter case, the `{}`'s can be left off:
|
455
528
|
```ruby
|
@@ -457,19 +530,125 @@ type MyClass, :foo, '(a: Fixnum, b: String) { () -> %any } -> %any'
|
|
457
530
|
```
|
458
531
|
Here `foo`, takes a hash where key `:a` is mapped to a `Fixnum` and key `:b` is mapped to a `String`. Similarly, `{'a'=>Fixnum, 2=>String}` types a hash where keys `'a'` and `2` are mapped to a `Fixnum` and `String`, respectively. Both syntaxes can be used to define hash types.
|
459
532
|
|
460
|
-
##
|
533
|
+
## Type Casts
|
461
534
|
|
462
|
-
RDL
|
535
|
+
Sometimes RDL does not have precise information about an object's type (this is most useful during static type checking). For these cases, RDL supports type casts of the form `o.type_cast(t)`. This call returns a new object that delegates all methods to `o` but that will be treated by RDL as if it had type `t`. If `force: true` is passed to `type_cast`, RDL will perform the cast without checking whether `o` is actually a member of the given type. For example, `x = "a".type_cast('nil', force: true)` will make RDL treat `x` as if it had type `nil`, even though it's a `String`.
|
536
|
+
|
537
|
+
## Bottom Type (%bot)
|
538
|
+
|
539
|
+
RDL also includes a special *bottom* type `%bot` that is a subtype of any type, including any class and any singleton types. In static type checking, the type `%bot` is given to so-called *void value expressions*, which are `return`, `break`, `next`, `redo`, and `retry` (notice that these expressions perform jumps rather than producing a value, hence they can be treated as having an arbitrary type). No Ruby objects have type `%bot`.
|
540
|
+
|
541
|
+
# Static Type Checking
|
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.
|
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.
|
546
|
+
|
547
|
+
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
|
+
|
549
|
+
Next we discuss some special features of RDL's type system and some of its limitations.
|
550
|
+
|
551
|
+
## Types for Variables
|
552
|
+
|
553
|
+
In a standard type system, local variables have one type throughout a method or function body. For example, in C and Java, declaring `int x` means `x` can only be used as an integer. However, in Ruby, variables need not be declared before they are used. Thus, by default, RDL treats local variables *flow-sensitively*, meaning at each assignment to a local variable, the variable's type is replaced by the type of the right hand side. For example:
|
554
|
+
|
555
|
+
```ruby
|
556
|
+
x = 3 # Here `x` is a `Fixnum`
|
557
|
+
x = "three" # Now `x` is a `String`
|
558
|
+
```
|
559
|
+
(Note this is a slight fib, since after the first line, `x` will actually have the singleton type `3`. But we'll ignore this just to keep the discussion a bit simpler, especially since `3` is a subtype of `Fixnum`.)
|
560
|
+
|
561
|
+
After conditionals, variables have the union of the types they have along both branches:
|
562
|
+
|
563
|
+
```ruby
|
564
|
+
if (some condition) then x = 3 else x = "three" end
|
565
|
+
# x has type `Fixnum or String`
|
566
|
+
```
|
567
|
+
|
568
|
+
RDL also provides a method `var_type` that can be used to force a local variable to have a single type through a method body, i.e., to treat it *flow-insensitively* like a standard type system:
|
463
569
|
|
464
|
-
|
570
|
+
```ruby
|
571
|
+
var_type :x, 'Fixnum'
|
572
|
+
x = 3 # okay
|
573
|
+
x = "three" # type error
|
574
|
+
```
|
575
|
+
|
576
|
+
The first argument to `var_type` is a symbol with the local variable name, and the second argument is a string containing the variable's type. Note that `var_type` is most useful at the beginning of method or code block. Using it elsewhere may result in surprising error mesages, since RDL requires variables with fixed types to have the same type along all paths. Method parameters are treated as if `var_type` was called on them at the beginning of the method, fixing them to their declared type. This design choice may be revisited in the future.
|
577
|
+
|
578
|
+
RDL always treats instance, class, and global variables flow-insensitively, hence their types must be defined with `var_type`:
|
579
|
+
|
580
|
+
```ruby
|
581
|
+
class A
|
582
|
+
var_type :@f, 'Fixnum'
|
583
|
+
def m
|
584
|
+
@f = 3 # type safe
|
585
|
+
@f = "three" # type error, incompatible type in assignment
|
586
|
+
@g = 42 # type error, no var_type for @g
|
587
|
+
end
|
588
|
+
end
|
589
|
+
```
|
590
|
+
|
591
|
+
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
|
+
|
593
|
+
## Tuples, Finite Hashes, and Subtyping
|
594
|
+
|
595
|
+
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:
|
596
|
+
|
597
|
+
```ruby
|
598
|
+
x = [1, 'foo'] # x has type [1, String]
|
599
|
+
a, b = x # a has type 1, b has type String
|
600
|
+
```
|
601
|
+
|
602
|
+
RDL also allows a tuple `[t1, ..., tn]` to be used where `Array<t1 or ... or tn>` is expected. This means both when a tuple is passed to an `Array` position, and when any method is invoked on the tuple (even if RDL could safely apply that method to the tuple; this may change in the future):
|
603
|
+
|
604
|
+
```ruby
|
605
|
+
var_type @f, 'Array<Fixnum or String>'
|
606
|
+
@f = [1, 'foo'] # okay
|
607
|
+
@f.length # also okay
|
608
|
+
```
|
609
|
+
|
610
|
+
To maintain soundness, a tuple that is used as an `Array` is treated as if it were always an array. For example:
|
611
|
+
|
612
|
+
```ruby
|
613
|
+
x = [1, 'foo'] # at this point, x has type [1, String]
|
614
|
+
var_type @f, '[1, String]'
|
615
|
+
@f = x # okay so far
|
616
|
+
var_type @g, 'Array<Fixnum or String>'
|
617
|
+
@g = x # uh oh
|
618
|
+
```
|
619
|
+
|
620
|
+
When RDL encounters the assignment to `@g`, it retroactively changes `x` to have type `Array<Fixnum or String>`, which is incompatible with type `[1, String]`, so the last assignment signals an error.
|
621
|
+
|
622
|
+
RDL uses the same approach for hashes: hash literals are treated as finite hashes. A finite hash `{k1=>v1, ..., kn=>vn}` can be used where `Hash<k1 or ... or kn, v1 or ... or vn>` is expected. And if a finite hash is used as a `Hash` (including invoking methods on the finite hash; this may change in the future), then it is retroactively converted to a `Hash`.
|
623
|
+
|
624
|
+
## Other Features and Limitations
|
625
|
+
|
626
|
+
* *Conditional guards and singletons.* If an `if` or `unless` guard has a singleton type, RDL will typecheck both branches but not include types from the unrealizable branch in the expression type. For example, `if true then 1 else 'two' end` has type `1`. RDL behaves similarly for `&&` and `||`. However, RDL does not implement this logic for `case`.
|
627
|
+
|
628
|
+
* *Multiple Assignment and nil.* In Ruby, extra left-hand sides of multiple assignments are set to `nil`, e.g., `x, y = [1]` sets `x` to `1` and `y` to `nil`. However, RDL reports an error in this case; this may change in the future.
|
629
|
+
|
630
|
+
* *Block formal arguments.* Similarly, RDL reports an error if a block is called with the wrong number of arguments even though Ruby does not signal an error in this case.
|
631
|
+
|
632
|
+
* *Caching.* If `typecheck: true` is specified on a method, Ruby will type check the method every time it is called. In the future, RDL will cache these checks.
|
633
|
+
|
634
|
+
* *Unsupported Features.* There are several features of Ruby that are currently not handled by RDL. Here is a non-exhaustive list:
|
635
|
+
* `super` is not supported.
|
636
|
+
* `lambda` has special semantics for `return`; this is not supported.
|
637
|
+
* Only simple block argument lists and `for` iteration variables are supported.
|
638
|
+
* Control flow for exceptions is not analyzed fully soundly; some things are not reported as possibly `nil` that could be.
|
639
|
+
* Only simple usage of constants is handled.
|
640
|
+
|
641
|
+
# Other RDL Methods
|
642
|
+
|
643
|
+
RDL also includes a few other useful methods:
|
465
644
|
|
466
|
-
* `
|
645
|
+
* `rdl_alias new_name, old_name` tells RDL that method `new_name` is an alias for method `old_name`, and therefore they should have the same contracts and types. This method is only needed when adding contracts and types to method that have already been aliased; it's not needed if the method is aliased after the contract or type has been added.
|
467
646
|
|
468
|
-
* `rdl_nowrap`, if called at the top-level of a class,
|
647
|
+
* `rdl_nowrap`, if called at the top-level of a class, causes RDL to behave as if `wrap: false` were passed to all `type`, `pre`, and `post` calls in the class. This is mostly used for the core and standard libraries, which have trustworthy behavior hence enforcing their types and contracts is not worth the overhead.
|
469
648
|
|
470
649
|
* `rdl_query` prints information about types; see below for details.
|
471
650
|
|
472
|
-
|
651
|
+
# Queries
|
473
652
|
|
474
653
|
As discussed above, RDL includes a small script, `rdl_query`, to look up type information. (Currently it does not support other pre- and postconditions.) The script takes a single argument, which should be a string. Note that when using the shell script, you may need to use quotes depending on your shell. Currently several queries are supported:
|
475
654
|
|
@@ -573,7 +752,7 @@ Copyright (c) 2014-2016, University of Maryland, College Park. All rights reserv
|
|
573
752
|
* Alexander T. Yu
|
574
753
|
* Milod Kazerounian
|
575
754
|
|
576
|
-
# TODO
|
755
|
+
# TODO List
|
577
756
|
|
578
757
|
* How to check whether initialize? is user-defined? method_defined? always
|
579
758
|
returns true, meaning wrapping isn't fully working with initialize.
|
data/gemfiles/Gemfile.travis
CHANGED