rdl 2.0.0.rc2 → 2.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGES.md +7 -1
  3. data/README.md +94 -20
  4. data/lib/rdl.rb +4 -1
  5. data/lib/rdl/config.rb +90 -3
  6. data/lib/rdl/info.rb +16 -0
  7. data/lib/rdl/typecheck.rb +207 -79
  8. data/lib/rdl/types/bot.rb +1 -1
  9. data/lib/rdl/types/dependent_arg.rb +17 -8
  10. data/lib/rdl/types/{finitehash.rb → finite_hash.rb} +3 -29
  11. data/lib/rdl/types/generic.rb +1 -37
  12. data/lib/rdl/types/intersection.rb +1 -0
  13. data/lib/rdl/types/lexer.rex +2 -1
  14. data/lib/rdl/types/lexer.rex.rb +4 -1
  15. data/lib/rdl/types/method.rb +2 -12
  16. data/lib/rdl/types/nominal.rb +1 -22
  17. data/lib/rdl/types/non_null.rb +50 -0
  18. data/lib/rdl/types/parser.racc +3 -1
  19. data/lib/rdl/types/parser.tab.rb +222 -190
  20. data/lib/rdl/types/singleton.rb +1 -6
  21. data/lib/rdl/types/structural.rb +1 -10
  22. data/lib/rdl/types/top.rb +1 -2
  23. data/lib/rdl/types/tuple.rb +3 -19
  24. data/lib/rdl/types/type.rb +223 -0
  25. data/lib/rdl/types/union.rb +12 -2
  26. data/lib/rdl/types/var.rb +4 -1
  27. data/lib/rdl/types/wild_query.rb +1 -0
  28. data/lib/rdl/wrap.rb +199 -169
  29. data/lib/rdl_disable.rb +41 -0
  30. data/lib/rdl_types.rb +1 -4
  31. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/_aliases.rb +0 -0
  32. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/abbrev.rb +0 -0
  33. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/array.rb +56 -56
  34. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/base64.rb +0 -0
  35. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/basic_object.rb +0 -0
  36. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/benchmark.rb +0 -0
  37. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/bigdecimal.rb +0 -0
  38. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/bigmath.rb +0 -0
  39. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/bignum.rb +0 -0
  40. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/class.rb +0 -0
  41. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/complex.rb +0 -0
  42. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/coverage.rb +0 -0
  43. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/csv.rb +0 -0
  44. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/date.rb +0 -0
  45. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/dir.rb +0 -0
  46. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/encoding.rb +0 -0
  47. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/enumerable.rb +0 -0
  48. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/enumerator.rb +0 -0
  49. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/exception.rb +0 -0
  50. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/file.rb +0 -0
  51. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/fileutils.rb +0 -0
  52. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/fixnum.rb +0 -0
  53. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/float.rb +26 -26
  54. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/gem.rb +0 -0
  55. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/hash.rb +0 -0
  56. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/integer.rb +8 -8
  57. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/io.rb +0 -0
  58. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/kernel.rb +12 -11
  59. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/marshal.rb +0 -0
  60. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/matchdata.rb +0 -0
  61. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/math.rb +0 -0
  62. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/module.rb +0 -0
  63. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/nil.rb +0 -0
  64. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/numeric.rb +0 -0
  65. data/lib/types/core-ruby-2.x/object.rb +75 -0
  66. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/pathname.rb +0 -0
  67. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/process.rb +0 -0
  68. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/random.rb +0 -0
  69. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/range.rb +0 -0
  70. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/rational.rb +0 -0
  71. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/regexp.rb +0 -0
  72. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/set.rb +0 -0
  73. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/string.rb +0 -0
  74. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/strscan.rb +0 -0
  75. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/symbol.rb +0 -0
  76. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/time.rb +0 -0
  77. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/uri.rb +0 -0
  78. data/{types/ruby-2.x → lib/types/core-ruby-2.x}/yaml.rb +0 -0
  79. data/lib/types/core.rb +4 -0
  80. data/rdl.gemspec +2 -2
  81. data/test/test_le.rb +77 -35
  82. data/test/test_parser.rb +75 -59
  83. data/test/test_rdl.rb +18 -0
  84. data/test/test_typecheck.rb +73 -4
  85. metadata +54 -57
  86. data/lib/rails_types.rb +0 -1
  87. data/types/rails-4.2.1/fixnum.rb +0 -3
  88. data/types/rails-4.2.1/string.rb +0 -3
  89. data/types/rails-tmp/action_dispatch.rb +0 -406
  90. data/types/rails-tmp/active_record.rb +0 -406
  91. data/types/rails-tmp/devise_contracts.rb +0 -216
  92. data/types/ruby-2.x/object.rb +0 -73
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b977016fb74ea728b29a3eaa767aec63ee0d6a7b
4
- data.tar.gz: 45b460bc49df3fca8248811e582617229142c35d
3
+ metadata.gz: 12683f52b0d365695fbbddde7ab7494ae049202e
4
+ data.tar.gz: 67d2aaf4bde9eba67027f5bc7662fd7d7e33c746
5
5
  SHA512:
6
- metadata.gz: 2922f3d18c4c5398a520202d8930806545e05db55a449110a9837fef4c06293dc44ef98a3c3ca70a74c639d69114ae2d93f711ad0099dd11ae9ba730ab05b7b8
7
- data.tar.gz: c36055f80360a2b6cdf4d732d7d7419e5783512b9d947f08ab933358d9ed0fef34c62fdb8b1d8ed2ec34d24f2cdcf9dd12c5cf03472b550714dce384ffa56cf6
6
+ metadata.gz: f42995d41cd05b3d9e1115c0cf7e20a4873f840659e9fd56e0f476941adac6dfef199dce88bacafdefaacbc3cf410be917fc6213b77ebe0a5428840e75721542
7
+ data.tar.gz: 0ee8aa838a19d9977acc2556bba2ef295bc8be97a6a6407af4a7882a2ecd62c09ef4d5b6464a61f49137d26751bd43c581df932e883424f96126847edf1606f9
data/CHANGES.md CHANGED
@@ -2,11 +2,17 @@
2
2
 
3
3
  ## [Unreleased]
4
4
  ### Added
5
- - Added `wrap: false` optional argument to `type`, `pre`, and `post`
5
+ - `wrap: false` optional argument to `type`, `pre`, and `post`
6
+ - Non-null type annotation (not checked)
7
+ - Default argument configuration for `type`, `pre`, and `post`
8
+ - `attr_*_type` methods
9
+ - Static type checking!
6
10
 
7
11
  ### Changed
8
12
  - Modified `self` type to be any instance of the self's class
9
13
  - Library types now use new aliases %integer and %numeric instead of the Integer and Numeric classes.
14
+ - Fix issue #14 - allow type/pre/post to coexist, improve docs on dependent types
15
+ - Fix typos in README, pull req #13
10
16
 
11
17
  ## [1.1.1] - 2016-05-21
12
18
  ### Fixed
data/README.md CHANGED
@@ -8,6 +8,8 @@
8
8
  * [Supported versions of Ruby](#supported-versions-of-ruby)
9
9
  * [Installing RDL](#installing-rdl)
10
10
  * [Loading RDL](#loading-rdl)
11
+ * [Disabling RDL](#disabling-rdl)
12
+ * [Rails](#rails)
11
13
  * [Preconditions and Postconditions](#preconditions-and-postconditions)
12
14
  * [Type Annotations](#type-annotations)
13
15
  * [RDL Types](#rdl-types)
@@ -31,12 +33,15 @@
31
33
  * [Finite Hash Types](#finite-hash-types)
32
34
  * [Type Casts](#type-casts)
33
35
  * [Bottom Type (%bot)](#bottom-type-bot)
36
+ * [Non-null Type](#non-null-type)
34
37
  * [Static Type Checking](#static-type-checking)
35
38
  * [Types for Variables](#types-for-variables)
36
39
  * [Tuples, Finite Hashes, and Subtyping](#tuples-finite-hashes-and-subtyping)
37
40
  * [Other Features and Limitations](#other-features-and-limitations)
41
+ * [Assumptions](#assumptions)
38
42
  * [Other RDL Methods](#other-rdl-methods)
39
43
  * [Queries](#queries)
44
+ * [Configuration](#configuration)
40
45
  * [Bibliography](#bibliography)
41
46
  * [Copyright](#copyright)
42
47
  * [Contributors](#contributors)
@@ -56,7 +61,7 @@ def sqrt(x)
56
61
  end
57
62
  ```
58
63
 
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...)
64
+ Given this program, RDL intercepts the call to `sqrt` and passes its argument to the `pre` block, which checks that the argument 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...)
60
65
 
61
66
  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`.
62
67
 
@@ -111,7 +116,7 @@ See below for more details of the query format. The `rdl_query` method performs
111
116
  $ irb
112
117
  > require 'rdl'
113
118
  => true
114
- > require 'rdl_types'
119
+ > require 'types/core'
115
120
  => true
116
121
 
117
122
  > rdl_query '...' # as above
@@ -137,17 +142,13 @@ Use `require 'rdl'` to load the RDL library. If you want to use the core and sta
137
142
 
138
143
  (Currently all these are assumed to have the same library type signatures, which may not be correct.)
139
144
 
140
- If you're using Ruby on Rails, you can similarly `require 'rails_types'` to load in type annotations for the current `Rails::VERSION::STRING`. More specifically, add the following lines in `application.rb` after the `Bundler.require` call. (This placement is needed so the Rails version string is available and the Rails environment is loaded):
145
+ ## Disabling RDL
141
146
 
142
- ```ruby
143
- require 'rdl'
144
- require 'rdl_types'
145
- require 'rails_types'
146
- ```
147
+ For performance reasons you probably don't want to use RDL in production code. To disable RDL, replace `require 'rdl'` with `require 'rdl_disable'`. This will cause all invocations of RDL methods to either be no-ops or to do the minimum necessary to preserve the program's semantics (e.g., if the RDL method returns `self`, then so does the `rdl_disable` method.)
147
148
 
148
- Currently RDL has types for the following versions of Rails:
149
+ ## Rails
149
150
 
150
- * Rails support is currently almost non-existent; more coming in the future
151
+ To use RDL with Rails, use the [rdl-rails](https://github.com/plum-umd/rdl-rails) gem.
151
152
 
152
153
  ## Preconditions and Postconditions
153
154
 
@@ -300,7 +301,7 @@ Here we've named the first argument of `to_s` as `base` to give some extra hint
300
301
 
301
302
  ## Dependent Types
302
303
 
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:
304
+ RDL allows for refinement predicates to be attached to named arguments. The predicates on the arguments are checked when the method is called, and the predicates on the return is checked when then method returns. For instance:
304
305
 
305
306
  ```ruby
306
307
  type '(Float x {{ x>=0 }}) -> Float y {{ y>=0 }}'
@@ -318,7 +319,13 @@ type '(Fixnum x {{ x>y }}, Fixnum y) -> Float z {{ z==(x+y) }}'
318
319
  def m(x,y) ... end
319
320
  ```
320
321
 
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.
322
+ Any arbitrary code can be placed between the double braces of a type refinement, and RDL will dynamically check that this predicate evaluates to true, or raise a type error if it evaluates to false.
323
+
324
+ Most pre- and postconditions can be translated into a dependent type by attaching the precondition to one of the arguments and the postcondition to the return. Note, however, that dependently typed positions must always have a name, even if the associated refinment doesn't refer to that name:
325
+
326
+ ```ruby
327
+ type '(Fixnum x {{ $y > 0 }}) -> nil' # argument name must be present even though refinment doesn't use it.
328
+ ```
322
329
 
323
330
  ## Higher-order Types
324
331
 
@@ -352,7 +359,7 @@ type '(Fixnum x, Float y) -> {(Fixnum z {{ z>y }}) -> Fixnum}'
352
359
  def m(x,y,&blk) ... end
353
360
  ```
354
361
 
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.
362
+ 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`. Whenever this `Proc` is called, it will be checked that this contract holds.
356
363
 
357
364
  ## Class/Singleton Method Types
358
365
 
@@ -485,7 +492,7 @@ To fully enforce generic types, RDL requires that the developer `instantiate!` a
485
492
 
486
493
  ```ruby
487
494
  x = [1,2]
488
- x.instantate!('Fixnum')
495
+ x.instantiate!('Fixnum')
489
496
  x.push("three") # type error
490
497
  ```
491
498
 
@@ -514,7 +521,7 @@ The rules for variances are standard. Let's assume `A` is a subclass of `B`. Als
514
521
 
515
522
  ## Tuple Types
516
523
 
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.
524
+ A type such as `Array<Fixnum>` is useful for homogeneous arrays, where all elements have the same type. But Ruby programs often use heterogeneous arrays, e.g., `[1, "two"]`. The best generic type we can give this is `Array<Fixnum or String>`, but that's imprecise.
518
525
 
519
526
  RDL includes special *tuple types* to handle this situation. Tuple types are written `[t1, ..., tn]`, denoting an `Array` of `n` elements of types `t1` through `tn`, in that order. For example, `[1, "two"]` has type `[Fixnum, String]`. As another example, here is the type of `Process#getrlimit`, which returns a two-element array of `Fixnums`:
520
527
 
@@ -524,7 +531,7 @@ type Process, 'self.getrlimit', '(Symbol or String or Fixnum resource) -> [Fixnu
524
531
 
525
532
  ## Finite Hash Types
526
533
 
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:
534
+ Similarly to tuple types, RDL also supports *finite hash types* for heterogeneous 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:
528
535
  ```ruby
529
536
  type MyClass, :foo, '(a: Fixnum, b: String) { () -> %any } -> %any'
530
537
  ```
@@ -538,15 +545,42 @@ Sometimes RDL does not have precise information about an object's type (this is
538
545
 
539
546
  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
547
 
548
+ ## Non-null Type
549
+
550
+ Types can be prefixed with `!` to indicate the associated value is not `nil`. For example:
551
+
552
+ `type :x=, '(!Fixnum) -> !Fixnum' # x's argument must not be nil`
553
+
554
+ **Warning:** This is simply *documentation* of non-nullness, and **is not checked** by the static type checker. The contract checker might or might not enforce non-nullness. (For those who are curious: RDL has this annotation because it seems useful for descriptive purposes. However, it's quite challenging to build a practical analysis that enforces non-nilness without reporting too many false positives.)
555
+
541
556
  # Static Type Checking
542
557
 
543
558
  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
559
 
545
560
  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
561
 
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.
562
+ Additionally, `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.
563
+
564
+ 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.
565
+
566
+ Typechecking does work in `pry` (this feature has only limited testing) as long as typechecking is delayed until after the method is defined:
567
+
568
+ ```ruby
569
+ [2] pry(main)> require 'rdl'
570
+ [3] pry(main)> require 'rdl_types'
571
+ [4] pry(main)> type '() -> Fixnum', typecheck: :later # note: typecheck: :now doesn't work in pry
572
+ [5] pry(main)> def f
573
+ [5] pry(main)* 'haha'
574
+ [5] pry(main)* end
575
+ [6] pry(main)> rdl_do_typecheck :later
576
+ RDL::Typecheck::StaticTypeError:
577
+ (string):2:3: error: got type `String' where return type `Fixnum' expected
578
+ (string):2: 'haha'
579
+ (string):2: ^~~~~~
580
+ from .../typecheck.rb:158:in `error'
581
+ ```
548
582
 
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.)
583
+ 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.)
550
584
 
551
585
  Next we discuss some special features of RDL's type system and some of its limitations.
552
586
 
@@ -575,7 +609,15 @@ x = 3 # okay
575
609
  x = "three" # type error
576
610
  ```
577
611
 
578
- 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.
612
+ 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 messages, 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.
613
+
614
+ There is one subtlety for local variables and code blocks. Consider the following code:
615
+ ```ruby
616
+ x = 1
617
+ m() { x = 'bar' }
618
+ # what is x's type here?
619
+ ```
620
+ If `m` invokes the code block, `x` will be a `String` after the call. Otherwise `x` will be `1`. Since RDL can't tell whether the code block is ever called, it assigns `x` type `1 or String`. It's actually quite tricky to do very precise reasoning about code blocks. For example, `m` could (pathologically) store its block in a global variable and then only call it the second time `m` is invoked. To keep its reasoning simple, RDL treats any local variables captured (i.e., imported from an outer scope) by a code block flow-insensitively for the lifetime of the method. The type of any such local variables is the union of all types that are ever assigned to it.
579
621
 
580
622
  RDL always treats instance, class, and global variables flow-insensitively, hence their types must be defined with `var_type`:
581
623
 
@@ -636,13 +678,15 @@ RDL uses the same approach for hashes: hash literals are treated as finite hashe
636
678
 
637
679
  ## Other Features and Limitations
638
680
 
681
+ *Displaying types.* As an aid to debugging, the method `rdl_note_type e` will display the type of `e` during type checking. At run time, this method returns its argument. Note that in certain cases RDL may type check the same code repeatedly in which case an expression's type could be printed multiple times.
682
+
639
683
  * *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`.
640
684
 
641
685
  * *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.
642
686
 
643
687
  * *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.
644
688
 
645
- * *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.
689
+ * *Caching.* If `typecheck: :call` is specified on a method, Ruby will type check the method every time it is called. In the future, RDL will cache these checks.
646
690
 
647
691
  * *Unsupported Features.* There are several features of Ruby that are currently not handled by RDL. Here is a non-exhaustive list:
648
692
  * `super` is not supported.
@@ -651,6 +695,14 @@ RDL uses the same approach for hashes: hash literals are treated as finite hashe
651
695
  * Control flow for exceptions is not analyzed fully soundly; some things are not reported as possibly `nil` that could be.
652
696
  * Only simple usage of constants is handled.
653
697
 
698
+ ## Assumptions
699
+
700
+ RDL makes some assumptions that should hold unless your Ruby code is doing something highly unusual:
701
+
702
+ * `Class#===` is not redefined
703
+
704
+ (More assumptions will be added here as they are added to RDL...)
705
+
654
706
  # Other RDL Methods
655
707
 
656
708
  RDL also includes a few other useful methods:
@@ -659,6 +711,8 @@ RDL also includes a few other useful methods:
659
711
 
660
712
  * `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.
661
713
 
714
+ * `rdl_remove_type klass, meth` removes the type annotation for meth in klass. Fails if meth does not have a type annotation.
715
+
662
716
  * `rdl_query` prints information about types; see below for details.
663
717
 
664
718
  # Queries
@@ -711,6 +765,26 @@ $ rdl_query "(.?) -> ." # methods that take one, optional argumen
711
765
 
712
766
  Note that aside from `.` and `...`, the matching is exact. For example `(Fixnum) -> Fixnum` will not match a method of type `(Fixnum or String) -> Fixnum`.
713
767
 
768
+ # Configuration
769
+
770
+ To configure RDL, execute the following shortly after RDL is loaded:
771
+
772
+ ```ruby
773
+ RDL.config { |config|
774
+ # use config to configure RDL here
775
+ }
776
+ ```
777
+
778
+ RDL supports the following configuration options:
779
+
780
+ * `config.nowrap` - `Array<Class>` containing all classes whose methods should not be wrapped.
781
+ * `config.gather_stats` - currently disabled.
782
+ * `config.report` - if true, then when the program exits, RDL will print out a list of methods that were statically type checked, and methods that were annotated to be statically type checked but weren't.
783
+ * `config.guess_types` - List of classes (of type `Array<Symbol>`). For every method added to a listed class *after* this configuration option is set, RDL will record the types of its arguments and returns at run-time. Then when the program exits, RDL will print out a skeleton for each class with types for the monitored methods based on what RDL recorded at run-time and based on what Ruby knows about the methods' signatures. This is probably not going to produce the correct method types, but it might be a good starting place.
784
+ * `config.type_defaults` - Hash containing default options for `type`. Initially `{ wrap: true, typecheck: false }`.
785
+ * `config.pre_defaults` - Hash containing default options for `pre`. Initially `{ wrap: true }`.
786
+ * `config.post_defaults` - same as `pre_defaults`, but for `post`.
787
+
714
788
  # Bibliography
715
789
 
716
790
  Here are some research papers we have written exploring contracts, types, and Ruby.
data/lib/rdl.rb CHANGED
@@ -19,6 +19,8 @@ require 'rdl/info.rb'
19
19
  # :type to array of types
20
20
  # :source_location to [filename, linenumber] location of most recent definition
21
21
  # :typecheck - boolean that is true if method should be statically type checked
22
+ # :otype to set of types that were observed at run time, where a type is a finite hash {:args => Array<Class>, :ret => Class, :block => %bool}
23
+ # :context_types to array of [klass, meth, Type] - method types that exist only within this method. An icky hack to deal with Rails `params`.
22
24
  # For variables
23
25
  # :type to type
24
26
  $__rdl_info = RDL::Info.new
@@ -59,13 +61,14 @@ require 'rdl/types/annotated_arg.rb'
59
61
  require 'rdl/types/bot.rb'
60
62
  require 'rdl/types/dependent_arg.rb'
61
63
  require 'rdl/types/dots_query.rb'
62
- require 'rdl/types/finitehash.rb'
64
+ require 'rdl/types/finite_hash.rb'
63
65
  require 'rdl/types/generic.rb'
64
66
  require 'rdl/types/intersection.rb'
65
67
  require 'rdl/types/lexer.rex.rb'
66
68
  require 'rdl/types/method.rb'
67
69
  require 'rdl/types/singleton.rb'
68
70
  require 'rdl/types/nominal.rb'
71
+ require 'rdl/types/non_null.rb'
69
72
  require 'rdl/types/optional.rb'
70
73
  require 'rdl/types/parser.tab.rb'
71
74
  require 'rdl/types/structural.rb'
@@ -6,11 +6,17 @@ class RDL::Config
6
6
  attr_accessor :nowrap
7
7
  attr_accessor :gather_stats
8
8
  attr_accessor :report
9
+ attr_accessor :guess_types
10
+ attr_accessor :type_defaults, :pre_defaults, :post_defaults
9
11
 
10
12
  def initialize
11
13
  @nowrap = Set.new
12
14
  @gather_stats = false
13
15
  @report = false
16
+ @guess_types = []
17
+ @type_defaults = { wrap: true, typecheck: false }
18
+ @pre_defaults = { wrap: true }
19
+ @post_defaults = { wrap: true }
14
20
  end
15
21
 
16
22
  def add_nowrap(*klasses)
@@ -120,10 +126,10 @@ RDL::Config.instance.profile_stats
120
126
 
121
127
  Profiler__.start_profile # Restart profiler after setup
122
128
  end
123
- end
124
129
 
125
- at_exit do
126
- if RDL::Config.instance.report
130
+ def do_report
131
+ return unless @report
132
+ puts "------------------------------"
127
133
  typechecked = []
128
134
  missing = []
129
135
  $__rdl_info.info.each_pair { |klass, meths|
@@ -149,4 +155,85 @@ at_exit do
149
155
  missing.each { |klass, meth| puts RDL::Util.pp_klass_method(klass, meth) }
150
156
  end
151
157
  end
158
+
159
+ def guess_meth(klass, meth, is_sing, the_meth)
160
+ # first print based on signature according to Ruby
161
+ first = true
162
+ block = false
163
+ print " type #{if is_sing then '\'self.' + meth + '\'' else ':' + meth end}, '("
164
+ params = the_meth.parameters
165
+ params.each_with_index { |param, i|
166
+ kind, name = param
167
+ print ", " unless first || kind == :block
168
+ case kind
169
+ when :req
170
+ print "XXXX #{name}"
171
+ when :opt
172
+ print "?XXXX #{name}"
173
+ when :rest
174
+ print "*#XXXX #{name}"
175
+ when :key
176
+ print "#{name}: XXXX"
177
+ when :block
178
+ block = true
179
+ else
180
+ print "???? param of kind #{kind} for #{name}"
181
+ end
182
+ first = false
183
+ }
184
+ print ")"
185
+ print " { BLOCK }" if block
186
+ puts " -> XXXX'"
187
+
188
+ # next print based on observed types
189
+ otypes = $__rdl_info.get(klass, meth, :otype) if $__rdl_info.has?(klass, meth, :otype) # observed types
190
+ return if otypes.nil?
191
+ first = true
192
+ print " type #{if is_sing then '\'self.' + meth + '\'' else ':' + meth end}, '("
193
+ otargs = []
194
+ otret = $__rdl_bot_type
195
+ otblock = false
196
+ otypes.each { |ot|
197
+ ot[:args].each_with_index { |t, i|
198
+ otargs[i] = $__rdl_bot_type if otargs[i].nil?
199
+ otargs[i] = RDL::Type::UnionType.new(otargs[i], RDL::Type::NominalType.new(t)).canonical
200
+ }
201
+ otret = RDL::Type::UnionType.new(otret, RDL::Type::NominalType.new(ot[:ret])).canonical
202
+ otblock = otblock || ot[:block]
203
+ }
204
+ otargs.each { |t|
205
+ print ", " unless first
206
+ print t
207
+ first = false
208
+ }
209
+ print ")"
210
+ print " { BLOCK }" if otblock
211
+ puts " -> #{otret}'"
212
+ end
213
+
214
+ def do_guess_types
215
+ return if @guess_types.empty?
216
+ puts "------------------------------"
217
+ puts "TYPE GUESSES"
218
+ RDL::Config.instance.guess_types.each { |klass|
219
+ puts
220
+ puts "class #{klass}"
221
+ the_klass = RDL::Util.to_class(klass)
222
+ sklass = RDL::Util.add_singleton_marker(klass.to_s)
223
+ the_klass.singleton_methods(false).each { |meth|
224
+ next unless meth.to_s =~ /^__rdl_(.*)_old/
225
+ guess_meth(sklass, $1, true, the_klass.singleton_method(meth))
226
+ }
227
+ the_klass.instance_methods(false).each { |meth|
228
+ next unless meth.to_s =~ /^__rdl_(.*)_old/
229
+ guess_meth(klass, $1, false, the_klass.instance_method(meth))
230
+ }
231
+ puts "end"
232
+ }
233
+ end
234
+ end
235
+
236
+ at_exit do
237
+ RDL::Config.instance.do_report
238
+ RDL::Config.instance.do_guess_types
152
239
  end
@@ -71,4 +71,20 @@ class RDL::Info
71
71
  return t2[kind]
72
72
  # return @info[klass][label][kind]
73
73
  end
74
+
75
+ def get_with_aliases(klass, label, kind)
76
+ while $__rdl_aliases[klass] && $__rdl_aliases[klass][label]
77
+ label = $__rdl_aliases[klass][label]
78
+ end
79
+ get(klass, label, kind)
80
+ end
81
+
82
+ def remove(klass, label, kind)
83
+ klass = klass.to_s
84
+ label = label.to_sym
85
+ return unless @info.has_key? klass
86
+ return unless @info[klass].has_key? label
87
+ @info[klass][label].delete kind
88
+ end
89
+
74
90
  end
@@ -65,8 +65,9 @@ module RDL::Typecheck
65
65
  return @env[var][:type]
66
66
  end
67
67
 
68
- def bind(var, typ)
69
- raise RuntimeError, "Can't update variable with fixed type" if @env[var] && @env[var][:fixed]
68
+ # force should only be used with care! currently only used when type is being refined to a subtype in a lexical scope
69
+ def bind(var, typ, force: false)
70
+ raise RuntimeError, "Can't update variable with fixed type" if !force && @env[var] && @env[var][:fixed]
70
71
  result = Env.new
71
72
  result.env = @env.merge(var => {type: typ, fixed: false})
72
73
  return result
@@ -132,7 +133,6 @@ module RDL::Typecheck
132
133
  end
133
134
  end
134
135
 
135
-
136
136
  # Call block with new Hash that is the same as Hash [+ scope +] except mappings in [+ elts +] have been merged.
137
137
  # When block returns, copy out mappings in the new Hash to [+ scope +] except keys in [+ elts +].
138
138
  def self.scope_merge(scope, **elts)
@@ -144,15 +144,40 @@ module RDL::Typecheck
144
144
  return r
145
145
  end
146
146
 
147
+ # add x:t to the captured map in scope
148
+ def self.capture(scope, x, t)
149
+ if scope[:captured][x]
150
+ scope[:captured][x] = RDL::Type::UnionType.new(scope[:captured][x], t).canonical unless t <= scope[:captured][x]
151
+ else
152
+ scope[:captured][x] = t
153
+ end
154
+ end
155
+
147
156
  # report msg at ast's loc
148
157
  def self.error(reason, args, ast)
149
158
  raise StaticTypeError, ("\n" + (Parser::Diagnostic.new :error, reason, args, ast.loc.expression).render.join("\n"))
150
159
  end
151
160
 
152
- def self.typecheck(klass, meth)
161
+ def self.note(reason, args, ast)
162
+ puts (Parser::Diagnostic.new :note, reason, args, ast.loc.expression).render
163
+ end
164
+
165
+ def self.get_ast(klass, meth)
153
166
  file, line = $__rdl_info.get(klass, meth, :source_location)
154
- raise RuntimeError, "static type checking in irb not supported" if file == "(irb)"
155
167
  raise RuntimeError, "No file for #{RDL::Util.pp_klass_method(klass, meth)}" if file.nil?
168
+ raise RuntimeError, "static type checking in irb not supported" if file == "(irb)"
169
+ if file == "(pry)"
170
+ # no caching...
171
+ if RDL::Wrap.wrapped?(klass, meth)
172
+ meth_name = RDL::Wrap.wrapped_name(klass, meth)
173
+ else
174
+ meth_name = meth
175
+ end
176
+ the_meth = RDL::Util.to_class(klass).instance_method(meth_name)
177
+ code = Pry::Code.from_method the_meth
178
+ return Parser::CurrentRuby.parse code.to_s
179
+ end
180
+
156
181
  digest = Digest::MD5.file file
157
182
  cache_hit = (($__rdl_ruby_parser_cache.has_key? file) &&
158
183
  ($__rdl_ruby_parser_cache[file][0] == digest))
@@ -165,6 +190,11 @@ module RDL::Typecheck
165
190
  end
166
191
  ast = $__rdl_ruby_parser_cache[file][1][:line_defs][line]
167
192
  raise RuntimeError, "Can't find source for class #{RDL::Util.pp_klass_method(klass, meth)}" if ast.nil?
193
+ return ast
194
+ end
195
+
196
+ def self.typecheck(klass, meth)
197
+ ast = get_ast(klass, meth)
168
198
  types = $__rdl_info.get(klass, meth, :type)
169
199
  raise RuntimeError, "Can't typecheck method with no types?!" if types.nil? or types == []
170
200
 
@@ -176,6 +206,7 @@ module RDL::Typecheck
176
206
  raise RuntimeError, "Unexpected ast type #{ast.type}"
177
207
  end
178
208
  raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth
209
+ context_types = $__rdl_info.get(klass, meth, :context_types)
179
210
  types.each { |type|
180
211
  # TODO will need fancier logic here for matching up more complex arg lists
181
212
  if RDL::Util.has_singleton_marker(klass)
@@ -189,10 +220,17 @@ module RDL::Typecheck
189
220
  unless type.args.length == args.children.length
190
221
  error :arg_count_mismatch, ['method', type.args.length, 'method', args.children.length], (if args.children.empty? then ast else args end)
191
222
  end
192
- a = args.children.map { |arg| arg.children[0] }.zip(type.args).to_h
193
- a[:self] = self_type
194
- scope = {tret: type.ret, tblock: type.block }
195
- _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(scope, Env.new(a), body) end
223
+ targs = args.children.map { |arg| arg.children[0] }.zip(type.args).to_h
224
+ targs[:self] = self_type
225
+ scope = { tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types }
226
+ begin
227
+ old_captured = scope[:captured].dup
228
+ if body.nil?
229
+ body_type = $__rdl_nil_type
230
+ else
231
+ _, body_type = tc(scope, Env.new(targs.merge(scope[:captured])), body)
232
+ end
233
+ end until old_captured == scope[:captured]
196
234
  error :bad_return_type, [body_type.to_s, type.ret.to_s], body unless body.nil? || body_type <= type.ret
197
235
  }
198
236
  $__rdl_info.set(klass, meth, :typechecked, true)
@@ -315,7 +353,7 @@ module RDL::Typecheck
315
353
  # promote singleton types to nominal types; safe since Ranges are immutable
316
354
  t1 = RDL::Type::NominalType.new(t1.val.class) if t1.is_a? RDL::Type::SingletonType
317
355
  t2 = RDL::Type::NominalType.new(t2.val.class) if t2.is_a? RDL::Type::SingletonType
318
- error :nonmatching_range_type, [t1, t2], e if t1 != t2
356
+ error :nonmatching_range_type, [t1, t2], e unless t1 <= t2 || t2 <= t1
319
357
  [env2, RDL::Type::GenericType.new($__rdl_range_type, t1)]
320
358
  when :self
321
359
  [env, env[:self]]
@@ -452,8 +490,9 @@ module RDL::Typecheck
452
490
  # children[0] = receiver; if nil, receiver is self
453
491
  # children[1] = method name, a symbol
454
492
  # children [2..] = actual args
455
- return tc_var_type(scope, env, e) if e.children[1] == :var_type && e.children[0].nil? && scope[:block].nil?
493
+ return tc_var_type(scope, env, e) if e.children[1] == :var_type && e.children[0].nil?
456
494
  return tc_type_cast(scope, env, e) if e.children[1] == :type_cast && scope[:block].nil?
495
+ return tc_note_type(scope, env, e) if e.children[1] == :rdl_note_type && e.children[0].nil?
457
496
  envi = env
458
497
  tactuals = []
459
498
  block = scope[:block]
@@ -464,7 +503,7 @@ module RDL::Typecheck
464
503
  if ti.is_a? RDL::Type::TupleType
465
504
  tactuals.concat ti.params
466
505
  elsif ti.is_a?(RDL::Type::GenericType) && ti.base == $__rdl_array_type
467
- tactuals << RDL::Type::VarargType.new(ti)
506
+ tactuals << RDL::Type::VarargType.new(ti.params[0]) # Turn Array<t> into *t
468
507
  else
469
508
  error :cant_splat, [ti], ei.children[0]
470
509
  end
@@ -537,12 +576,38 @@ RUBY
537
576
  e.children[1..-2].each { |wclause|
538
577
  raise RuntimeError, "Don't know what to do with case clause #{wclause.type}" unless wclause.type == :when
539
578
  envguards = []
579
+ tguards = []
540
580
  wclause.children[0..-2].each { |guard| # first wclause.length-1 children are the guards
541
581
  envi, tguard = tc(scope, envi, guard) # guard type can be anything
582
+ tguards << tguard
542
583
  tc_send(scope, envi, tguard, :===, [tcontrol], nil, guard) unless tcontrol.nil?
543
584
  envguards << envi
544
585
  }
545
- envbody, tbody = tc(scope, Env.join(e, *envguards), wclause.children[-1]) # last wclause child is body
586
+ initial_env = Env.join(e, *envguards)
587
+ if (tguards.all? { |t| t.is_a?(RDL::Type::SingletonType) && t.val.is_a?(Class) }) && (e.children[0].type == :lvar)
588
+ # Special case! We're branching on the type of the guard, which is a local variable.
589
+ # So rebind that local variable to have the union of the guard types
590
+ new_typ = RDL::Type::UnionType.new(*(tguards.map { |t| RDL::Type::NominalType.new(t.val) })).canonical
591
+ # TODO adjust following for generics!
592
+ if tcontrol.is_a? RDL::Type::GenericType
593
+ if new_typ == tcontrol.base
594
+ # special case: exact match of control type's base and type of guard; can use
595
+ # geneirc type on this branch
596
+ initial_env = initial_env.bind(e.children[0].children[0], tcontrol, force: true)
597
+ elsif !(tcontrol.base <= new_typ) && !(new_typ <= tcontrol.base)
598
+ next # can't possibly match this branch
599
+ else
600
+ error :generic_error, ["general refinement for generics not implemented yet"], wclause
601
+ end
602
+ else
603
+ next unless tcontrol <= new_typ || new_typ <= tcontrol # If control can't possibly match type, skip this branch
604
+ initial_env = initial_env.bind(e.children[0].children[0], new_typ, force: true)
605
+ # note force is safe above because the env from this arm will be joined with the other envs
606
+ # (where the type was not refined like this), so after the case the variable will be back to its
607
+ # previous, unrefined type
608
+ end
609
+ end
610
+ envbody, tbody = tc(scope, initial_env, wclause.children[-1]) # last wclause child is body
546
611
  tbodies << tbody
547
612
  envbodies << envbody
548
613
  }
@@ -600,6 +665,7 @@ RUBY
600
665
  [lscope[:break], lscope[:tbreak].canonical]
601
666
  }
602
667
  when :for
668
+ # (for (lvasgn var) collection body)
603
669
  # break: loop exit, which is same as top of body, arg allowed
604
670
  # next: top of body, arg allowed
605
671
  # retry: not allowed
@@ -607,21 +673,18 @@ RUBY
607
673
  raise RuntimeError, "Loop variable #{e.children[0]} in for unsupported" unless e.children[0].type == :lvasgn
608
674
  # TODO: mlhs in e.children[0]
609
675
  x = e.children[0].children[0] # loop variable
610
- if scope[:outer_env] && (scope[:outer_env].has_key? x) && (not (scope[:outer_env].fixed? x))
611
- error :nonlocal_access, [x], e.children[0]
612
- end
613
676
  envi, tcollect = tc(scope, env, e.children[1]) # collection to iterate through
614
677
  teaches = nil
615
678
  tcollect = tcollect.canonical
616
679
  case tcollect
617
680
  when RDL::Type::NominalType
618
- teaches = lookup(tcollect.name, :each, e.children[1])
681
+ teaches = lookup(scope, tcollect.name, :each, e.children[1])
619
682
  when RDL::Type::GenericType, RDL::Type::TupleType, RDL::Type::FiniteHashType
620
683
  unless tcollect.is_a? RDL::Type::GenericType
621
684
  error :tuple_finite_hash_promote, (if tcollect.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e.children[1] unless tcollect.promote!
622
685
  tcollect = tcollect.canonical
623
686
  end
624
- teaches = lookup(tcollect.base.name, :each, e.children[1])
687
+ teaches = lookup(scope, tcollect.base.name, :each, e.children[1])
625
688
  inst = tcollect.to_inst.merge(self: tcollect)
626
689
  teaches = teaches.map { |t| t.instantiate(inst) }
627
690
  else
@@ -639,7 +702,7 @@ RUBY
639
702
  break
640
703
  }
641
704
  error :no_each_type, [tcollect.name], e.children[1] if teach.nil?
642
- envi = envi.bind(x, teach.block.args[0])
705
+ envi, _ = tc_vasgn(scope, envi, :lvasgn, x, teach.block.args[0], e.children[0])
643
706
  scope_merge(scope, break: envi, next: envi, redo: envi, tbreak: teach.ret, tnext: envi[x]) { |lscope|
644
707
  # could exit here
645
708
  # if the loop always exits via break, then return type will come only from break, and otherwise the
@@ -741,10 +804,15 @@ RUBY
741
804
  case kind
742
805
  when :lvar # local variable
743
806
  error :undefined_local_or_method, [name], e unless env.has_key? name
744
- if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
745
- error :nonlocal_access, [name], e
807
+ capture(scope, name, env[name].canonical) if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
808
+ # if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
809
+ # error :nonlocal_access, [name], e
810
+ # end
811
+ if scope[:captured] && scope[:captured].has_key?(name) then
812
+ [env, scope[:captured][name]]
813
+ else
814
+ [env, env[name].canonical]
746
815
  end
747
- [env, env[name].canonical]
748
816
  when :ivar, :cvar, :gvar
749
817
  klass = (if kind == :gvar then RDL::Util::GLOBAL_NAME else env[:self] end)
750
818
  unless $__rdl_info.has?(klass, name, :type)
@@ -764,10 +832,11 @@ RUBY
764
832
  def self.tc_vasgn(scope, env, kind, name, tright, e)
765
833
  case kind
766
834
  when :lvasgn
767
- if scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))
768
- error :nonlocal_access, [name], e
769
- end
770
- if env.fixed? name
835
+ if ((scope[:captured] && scope[:captured].has_key?(name)) ||
836
+ (scope[:outer_env] && (scope[:outer_env].has_key? name) && (not (scope[:outer_env].fixed? name))))
837
+ capture(scope, name, tright.canonical)
838
+ [env, scope[:captured][name]]
839
+ elsif (env.fixed? name)
771
840
  error :vasgn_incompat, [tright, env[name]], e unless tright <= env[name]
772
841
  [env, tright.canonical]
773
842
  else
@@ -787,8 +856,17 @@ RUBY
787
856
  when :send
788
857
  meth = e.children[1] # note method name include =!
789
858
  envi, trecv = tc(scope, env, e.children[0]) # receiver
859
+ typs = []
860
+ if e.children.length > 2
861
+ # special case of []= when there's a second arg (the index)
862
+ # this code is a little more general than it has to be unless other similar operators added
863
+ e.children[2..-1].each { |arg|
864
+ envi, targ = tc(scope, envi, arg)
865
+ typs << targ
866
+ }
867
+ end
790
868
  # name is not useful here
791
- [envi, tc_send(scope, envi, trecv, meth, [tright], nil, e)] # call receiver.meth(tright)
869
+ [envi, tc_send(scope, envi, trecv, meth, [*typs, tright], nil, e)] # call receiver.meth(other args, tright)
792
870
  else
793
871
  raise RuntimeError, "unknown kind #{kind}"
794
872
  end
@@ -796,7 +874,7 @@ RUBY
796
874
 
797
875
  # [+ e +] is the method call
798
876
  def self.tc_var_type(scope, env, e)
799
- error :var_type_format, [], e unless e.children.length == 4
877
+ error :var_type_format, [], e unless e.children.length == 4 && scope[:block].nil?
800
878
  var = e.children[2].children[0] if e.children[2].type == :sym
801
879
  error :var_type_format, [], e.children[2] if var.nil? || (not (var =~ /^[a-z]/))
802
880
  typ_str = e.children[3].children[0] if (e.children[3].type == :str) || (e.children[3].type == :string)
@@ -829,6 +907,13 @@ RUBY
829
907
  [env1, typ]
830
908
  end
831
909
 
910
+ def self.tc_note_type(scope, env, e)
911
+ error :note_type_format, [], e unless e.children.length == 3 && scope[:block].nil?
912
+ env, typ = tc(scope, env, e.children[2])
913
+ note :note_type, [typ], e.children[2]
914
+ [env, typ]
915
+ end
916
+
832
917
  # Type check a send
833
918
  # [+ scope +] is the scope; used only for checking block arguments
834
919
  # [+ env +] is the environment; used only for checking block arguments.
@@ -860,20 +945,20 @@ RUBY
860
945
  when RDL::Type::SingletonType
861
946
  if trecv.val.is_a? Class
862
947
  if meth == :new then name = :initialize else name = meth end
863
- ts = lookup(RDL::Util.add_singleton_marker(trecv.val.to_s), name, e)
948
+ ts = lookup(scope, RDL::Util.add_singleton_marker(trecv.val.to_s), name, e)
864
949
  ts = [RDL::Type::MethodType.new([], nil, RDL::Type::NominalType.new(trecv.val))] if (meth == :new) && (ts.nil?) # there's always a nullary new if initialize is undefined
865
950
  error :no_singleton_method_type, [trecv.val, meth], e unless ts
866
951
  inst = {self: trecv}
867
952
  tmeth_inter = ts.map { |t| t.instantiate(inst) }
868
953
  else
869
954
  klass = trecv.val.class.to_s
870
- ts = lookup(klass, meth, e)
955
+ ts = lookup(scope, klass, meth, e)
871
956
  error :no_instance_method_type, [klass, meth], e unless ts
872
957
  inst = {self: trecv}
873
958
  tmeth_inter = ts.map { |t| t.instantiate(inst) }
874
959
  end
875
960
  when RDL::Type::NominalType
876
- ts = lookup(trecv.name, meth, e)
961
+ ts = lookup(scope, trecv.name, meth, e)
877
962
  error :no_instance_method_type, [trecv.name, meth], e unless ts
878
963
  inst = {self: trecv}
879
964
  tmeth_inter = ts.map { |t| t.instantiate(inst) }
@@ -882,20 +967,38 @@ RUBY
882
967
  error :tuple_finite_hash_promote, (if trecv.is_a? RDL::Type::TupleType then ['tuple', 'Array'] else ['finite hash', 'Hash'] end), e unless trecv.promote!
883
968
  trecv = trecv.canonical
884
969
  end
885
- ts = lookup(trecv.base.name, meth, e)
970
+ ts = lookup(scope, trecv.base.name, meth, e)
886
971
  error :no_instance_method_type, [trecv.base.name, meth], e unless ts
887
972
  inst = trecv.to_inst.merge(self: trecv)
888
973
  tmeth_inter = ts.map { |t| t.instantiate(inst) }
974
+ when RDL::Type::VarType
975
+ error :recv_var_type, [trecv], e
889
976
  else
890
- raise RuntimeError, "receiver type #{t} not supported yet"
977
+ raise RuntimeError, "receiver type #{trecv} not supported yet"
891
978
  end
892
979
 
893
980
  trets = [] # all possible return types
894
981
  # there might be more than one return type because multiple cases of an intersection type might match
895
- tmeth_inter.each { |tmeth| # MethodType
896
- if ((tmeth.block && block) || (tmeth.block.nil? && block.nil?)) && tc_arg_types(tmeth, tactuals)
897
- tc_block(scope, env, tmeth.block, block) if block
898
- trets << tmeth.ret
982
+
983
+ # for ALL of the expanded lists of actuals...
984
+ RDL::Type.expand_product(tactuals).each { |tactuals_expanded|
985
+ # AT LEAST ONE of the possible intesection arms must match
986
+ trets_tmp = []
987
+ tmeth_inter.each { |tmeth| # MethodType
988
+ if ((tmeth.block && block) || (tmeth.block.nil? && block.nil?))
989
+ tmeth_inst = tc_arg_types(tmeth, tactuals_expanded)
990
+ if tmeth_inst
991
+ tc_block(scope, env, tmeth.block, block, tmeth_inst) if block
992
+ trets_tmp << tmeth.ret.instantiate(tmeth_inst) # found a match for this subunion; add its return type to trets_tmp
993
+ end
994
+ end
995
+ }
996
+ if trets_tmp.empty?
997
+ # no arm of the intersection matched this expanded actuals lists, so reset trets to signal error and break loop
998
+ trets = []
999
+ break
1000
+ else
1001
+ trets.concat(trets_tmp)
899
1002
  end
900
1003
  }
901
1004
  if trets.empty? # no possible matching call
@@ -910,10 +1013,10 @@ RUBY
910
1013
  :initialize
911
1014
  elsif trecv.is_a? RDL::Type::SingletonType
912
1015
  trecv.val.class.to_s
913
- elsif trecv.is_a? RDL::Type::NominalType
914
- trecv.name
1016
+ elsif (trecv.is_a? RDL::Type::NominalType) || (trecv.is_a? RDL::Type::GenericType)
1017
+ trecv.to_s
915
1018
  else
916
- raise RutimeError, "impossible"
1019
+ raise RuntimeError, "impossible to get type #{trecv}"
917
1020
  end
918
1021
  error :arg_type_single_receiver_error, [name, meth, msg], e
919
1022
  end
@@ -923,15 +1026,16 @@ RUBY
923
1026
 
924
1027
  # [+ tmeth +] is MethodType
925
1028
  # [+ actuals +] is Array<Type> containing the actual argument types
926
- # return true if actuals match method type, false otherwise
1029
+ # return instiation (possibly empty) that makes actuals match method type (if any), nil otherwise
927
1030
  # Very similar to MethodType#pre_cond?
928
1031
  def self.tc_arg_types(tmeth, tactuals)
929
- states = [[0, 0]] # position in tmeth, position in tactuals
1032
+ states = [[0, 0, Hash.new]] # position in tmeth, position in tactuals, inst of free vars in tmeth
930
1033
  tformals = tmeth.args
931
1034
  until states.empty?
932
- formal, actual = states.pop
1035
+ formal, actual, inst = states.pop
1036
+ inst = inst.dup # avoid aliasing insts in different states since Type.leq mutates inst arg
933
1037
  if formal == tformals.size && actual == tactuals.size # Matched everything
934
- return true
1038
+ return inst
935
1039
  end
936
1040
  next if formal >= tformals.size # Too many actuals to match
937
1041
  t = tformals[formal]
@@ -940,60 +1044,64 @@ RUBY
940
1044
  end
941
1045
  case t
942
1046
  when RDL::Type::OptionalType
943
- t = t.type #TODO .instantiate(inst)
1047
+ t = t.type
944
1048
  if actual == tactuals.size
945
- states << [formal+1, actual] # skip over optinal formal
946
- elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t
947
- states << [formal+1, actual+1] # match
948
- states << [formal+1, actual] # skip
1049
+ states << [formal+1, actual, inst] # skip over optinal formal
1050
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false)
1051
+ states << [formal+1, actual+1, inst] # match
1052
+ states << [formal+1, actual, inst] # skip
949
1053
  else
950
- states << [formal+1, actual] # types don't match; must skip this formal
1054
+ states << [formal+1, actual, inst] # types don't match; must skip this formal
951
1055
  end
952
1056
  when RDL::Type::VarargType
953
- # t = t.type #TODO .instantiate(inst)
954
1057
  if actual == tactuals.size
955
- states << [formal+1, actual] # skip to allow empty vararg at end
956
- elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t.type
957
- states << [formal, actual+1] # match, more varargs coming
958
- states << [formal+1, actual+1] # match, no more varargs
959
- states << [formal+1, actual] # skip over even though matches
960
- elsif tactuals[actual].is_a?(RDL::Type::VarargType) && tactuals[actual] = t
961
- states << [formal+1, actual+1] # match, no more varargs; no other choices!
1058
+ states << [formal+1, actual, inst] # skip to allow empty vararg at end
1059
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t.type, inst, false)
1060
+ states << [formal, actual+1, inst] # match, more varargs coming
1061
+ states << [formal+1, actual+1, inst] # match, no more varargs
1062
+ states << [formal+1, actual, inst] # skip over even though matches
1063
+ elsif tactuals[actual].is_a?(RDL::Type::VarargType) && RDL::Type::Type.leq(tactuals[actual].type, t.type, inst, false) &&
1064
+ RDL::Type::Type.leq(t.type, tactuals[actual].type, inst, true)
1065
+ states << [formal+1, actual+1, inst] # match, no more varargs; no other choices!
962
1066
  else
963
- states << [formal+1, actual] # doesn't match, must skip
1067
+ states << [formal+1, actual, inst] # doesn't match, must skip
964
1068
  end
965
1069
  else
966
1070
  if actual == tactuals.size
967
1071
  next unless t.instance_of? RDL::Type::FiniteHashType
968
1072
  if @@empty_hash_type <= t
969
- states << [formal+1, actual]
1073
+ states << [formal+1, actual, inst]
970
1074
  end
971
- elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && tactuals[actual] <= t
972
- states << [formal+1, actual+1] # match!
1075
+ elsif (not (tactuals[actual].is_a?(RDL::Type::VarargType))) && RDL::Type::Type.leq(tactuals[actual], t, inst, false)
1076
+ states << [formal+1, actual+1, inst] # match!
973
1077
  # no else case; if there is no match, this is a dead end
974
1078
  end
975
1079
  end
976
1080
  end
977
- false
1081
+ return nil
978
1082
  end
979
1083
 
980
1084
  # [+ tblock +] is the type of the block (a MethodType)
981
1085
  # [+ block +] is a pair [block-args, block-body] from the block AST node
982
1086
  # returns if the block matches type tblock
983
1087
  # otherwise throws an exception with a type error
984
- def self.tc_block(scope, env, tblock, block)
1088
+ def self.tc_block(scope, env, tblock, block, inst)
985
1089
  # TODO more complex arg lists (same as self.typecheck?); also for
986
1090
  # TODO self is the same *except* instance_exec or instance_eval
987
1091
  raise RuntimeError, "block with block arg?" unless tblock.block.nil?
988
1092
  args, body = block
989
- error :arg_count_mismatch, ['block', tblock.args.length, 'block', args.children.length], block[0] unless tblock.args.length == args.children.length
1093
+ unless tblock.args.length == args.children.length
1094
+ error :arg_count_mismatch, ['block', tblock.args.length, 'block', args.children.length], (if block[0].children.empty? then block[1] else block[0] end)
1095
+ end
1096
+ tblock = tblock.instantiate(inst)
990
1097
  a = args.children.map { |arg| arg.children[0] }.zip(tblock.args).to_h
991
1098
 
992
1099
  scope_merge(scope, outer_env: env) { |bscope|
993
1100
  # note: okay if outer_env shadows, since nested scope will include outer scope by next line
994
1101
  env = env.merge(Env.new(a))
995
1102
  _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(bscope, env.merge(Env.new(a)), body) end
996
- error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || body_type <= tblock.ret
1103
+ error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false)
1104
+ #
997
1105
  }
998
1106
  end
999
1107
 
@@ -1001,24 +1109,40 @@ RUBY
1001
1109
  # [+ name +] is a symbol naming the thing to look up (either a method or field)
1002
1110
  # returns klass#name's type, walking up the inheritance hierarchy if appropriate
1003
1111
  # returns nil if no type found
1004
- def self.lookup(klass, name, e)
1005
- name = $__rdl_aliases[klass][name] if $__rdl_aliases[klass] && $__rdl_aliases[klass][name]
1006
- t = $__rdl_info.get(klass, name, :type)
1112
+ # types in scope[:context_type] take precedence
1113
+
1114
+ # *always* included module's instance methods only
1115
+ # if included, those methods are added to instance_methods
1116
+ # if extended, those methods are added to singleton_methods
1117
+ def self.lookup(scope, klass, name, e)
1118
+ if scope[:context_types]
1119
+ # return array of all matching types from context_types, if any
1120
+ ts = []
1121
+ scope[:context_types].each { |ctk, ctm, ctt| ts << ctt if ctk.to_s == klass && ctm == name }
1122
+ return ts unless ts.empty?
1123
+ end
1124
+ return scope[:context_types][klass][name] if scope[:context_types] && scope[:context_types][klass] && scope[:context_types][klass][name]
1125
+ t = $__rdl_info.get_with_aliases(klass, name, :type)
1007
1126
  return t if t # simplest case, no need to walk inheritance hierarchy
1008
1127
  the_klass = RDL::Util.to_class(klass)
1128
+ is_singleton = RDL::Util.has_singleton_marker(the_klass.to_s)
1009
1129
  included = the_klass.included_modules
1010
1130
  the_klass.ancestors[1..-1].each { |ancestor|
1011
1131
  # assumes ancestors is proper order to walk hierarchy
1012
- if (ancestor.is_a? Module) && (included.member? ancestor)
1013
- ancestor_name = RDL::Util.add_singleton_marker(ancestor.to_s)
1014
- else
1015
- ancestor_name = ancestor.to_s
1016
- end
1017
- tancestor = $__rdl_info.get(ancestor_name, name, :type)
1132
+ # included modules' instance methods get added as instance methods, so can't be in singleton class
1133
+ next if (ancestor.instance_of? Module) && (included.member? ancestor) && is_singleton
1134
+ # extended (i.e., not included) modules' instance methods get added as singleton methods, so can't be in class
1135
+ next if (ancestor.instance_of? Module) && (not (included.member? ancestor)) && (not is_singleton)
1136
+ tancestor = $__rdl_info.get_with_aliases(ancestor.to_s, name, :type)
1018
1137
  return tancestor if tancestor
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)
1138
+ # special caes: Kernel's singleton methods are *also* added when included?!
1139
+ if ancestor == Kernel
1140
+ tancestor = $__rdl_info.get_with_aliases(RDL::Util.add_singleton_marker('Kernel'), name, :type)
1141
+ return tancestor if tancestor
1142
+ end
1143
+ if ancestor.instance_methods(false).member?(name)
1020
1144
  klass = RDL::Util.remove_singleton_marker klass if RDL::Util.has_singleton_marker klass
1021
- error :missing_ancestor_type, [ancestor_name, klass, name], e
1145
+ error :missing_ancestor_type, [ancestor, klass, name], e
1022
1146
  end
1023
1147
  }
1024
1148
  return nil
@@ -1050,12 +1174,16 @@ type_error_messages = {
1050
1174
  block_block: "can't call yield on a block expecting another block argument",
1051
1175
  block_type_error: "argument type error for block\n%s",
1052
1176
  missing_ancestor_type: "ancestor %s of %s has method %s but no type for it",
1053
- type_cast_format: "type_cast must be called as type_cast('type-string') or type_cast('type-string', force: expr)",
1054
- var_type_format: "var_type must be called as var_type(:var-name, 'type-string')",
1177
+ type_cast_format: "type_cast must be called as `type_cast type-string' or `type_cast type-string, force: expr'",
1178
+ var_type_format: "var_type must be called as `var_type :var-name, type-string'",
1179
+ puts_type_format: "puts_type must be called as `puts_type e'",
1055
1180
  generic_error: "%s",
1056
1181
  exn_type: "can't determine exception type",
1057
1182
  cant_splat: "can't type splat with element of type `%s'",
1058
1183
  for_collection: "can't type for with collection of type `%s'",
1184
+ note_type: "Type is `%s'",
1185
+ note_message: "%s",
1186
+ recv_var_type: "Receiver whose type is unconstrained variable `%s' not allowed",
1059
1187
  }
1060
1188
  old_messages = Parser::MESSAGES
1061
1189
  Parser.send(:remove_const, :MESSAGES)