rdl 2.0.0.rc4 → 2.0.0.rc5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
 - data/README.md +56 -84
 - data/lib/rdl/boot.rb +4 -0
 - data/lib/rdl/boot_rails.rb +3 -1
 - data/lib/rdl/contracts/flat.rb +1 -2
 - data/lib/rdl/typecheck.rb +138 -27
 - data/lib/rdl/types/annotated_arg.rb +8 -0
 - data/lib/rdl/types/finite_hash.rb +32 -17
 - data/lib/rdl/types/lexer.rex +1 -0
 - data/lib/rdl/types/lexer.rex.rb +3 -0
 - data/lib/rdl/types/method.rb +24 -4
 - data/lib/rdl/types/optional.rb +8 -1
 - data/lib/rdl/types/parser.racc +12 -9
 - data/lib/rdl/types/parser.tab.rb +293 -255
 - data/lib/rdl/types/type.rb +20 -11
 - data/lib/rdl/types/union.rb +3 -0
 - data/lib/rdl/types/vararg.rb +7 -2
 - data/lib/rdl/wrap.rb +15 -2
 - data/lib/types/core-ruby-2.x/proc.rb +16 -0
 - data/lib/types/rails-5.x/_helpers.rb +20 -0
 - data/lib/types/rails-5.x/active_model/errors.rb +15 -0
 - data/lib/types/rails-5.x/active_model/validations.rb +5 -0
 - data/lib/types/rails-5.x/active_record/associations.rb +190 -0
 - data/lib/types/rails-5.x/active_record/core.rb +3 -0
 - data/lib/types/rails-5.x/active_record/model_schema.rb +7 -10
 - data/rdl.gemspec +2 -2
 - data/test/test_le.rb +24 -0
 - data/test/test_member.rb +24 -2
 - data/test/test_parser.rb +21 -10
 - data/test/test_query.rb +5 -2
 - data/test/test_type_contract.rb +34 -2
 - data/test/test_typecheck.rb +296 -36
 - data/test/test_types.rb +8 -4
 - metadata +7 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA1:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: 37bb60eb1678b69aca3b6967c386f31c2d0c882f
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 8824ba76d0daa9d4049df6d4677846386e5c6733
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 593c30a12e453dc79c796b65c8598c345b0bb562978fb4a21fcb903c9a8d0fffcbc0b0534bb0187933ae8399c9dc8022bac689c77601282f442bb687dc79d985
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 4daaeafc5cc639db5afe714c9b4c71a8f9455dea7d8151741f3dcd914f384ca728389cc9f1931a255e97513be9b2423c7a0773dc09eba8959905fdfc6a568658
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -49,23 +49,7 @@ 
     | 
|
| 
       49 
49 
     | 
    
         | 
| 
       50 
50 
     | 
    
         
             
            # Introduction
         
     | 
| 
       51 
51 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
            RDL is a lightweight system for adding  
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
            ```ruby
         
     | 
| 
       55 
     | 
    
         
            -
            require 'rdl'
         
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
            pre { |x| x > 0 }
         
     | 
| 
       58 
     | 
    
         
            -
            post { |r,x| r > 0 }
         
     | 
| 
       59 
     | 
    
         
            -
            def sqrt(x)
         
     | 
| 
       60 
     | 
    
         
            -
              # return the square root of x
         
     | 
| 
       61 
     | 
    
         
            -
            end
         
     | 
| 
       62 
     | 
    
         
            -
            ```
         
     | 
| 
       63 
     | 
    
         
            -
             
     | 
| 
       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...)
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       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`.
         
     | 
| 
       67 
     | 
    
         
            -
             
     | 
| 
       68 
     | 
    
         
            -
            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:
         
     | 
| 
      
 52 
     | 
    
         
            +
            RDL is a lightweight system for adding types, type checking, and contracts to Ruby.  In RDL, *types* can be used to decorate methods:
         
     | 
| 
       69 
53 
     | 
    
         | 
| 
       70 
54 
     | 
    
         
             
            ```ruby
         
     | 
| 
       71 
55 
     | 
    
         
             
            require 'rdl'
         
     | 
| 
         @@ -74,9 +58,9 @@ type '(Fixnum, Fixnum) -> String' 
     | 
|
| 
       74 
58 
     | 
    
         
             
            def m(x,y) ... end
         
     | 
| 
       75 
59 
     | 
    
         
             
            ```
         
     | 
| 
       76 
60 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
            This indicates that `m`  
     | 
| 
      
 61 
     | 
    
         
            +
            This indicates that `m` returns a `String` if given two `Fixnum` arguments. When written as above, RDL enforces this type as a *contract* checked 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`.
         
     | 
| 
       78 
62 
     | 
    
         | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
      
 63 
     | 
    
         
            +
            RDL can also *statically type check* method bodies against their signatures. For example:
         
     | 
| 
       80 
64 
     | 
    
         | 
| 
       81 
65 
     | 
    
         
             
            ```ruby
         
     | 
| 
       82 
66 
     | 
    
         
             
            file.rb:
         
     | 
| 
         @@ -98,7 +82,9 @@ $ ruby file.rb 
     | 
|
| 
       98 
82 
     | 
    
         | 
| 
       99 
83 
     | 
    
         
             
            Passing `typecheck: :now` to `type` checks the method body immediately or as soon as it is defined. Passing `typecheck: :call` to `type` statically type checks the method body whenever it is called. Passing `typecheck: sym` for some other symbol statically type checks the method body when `rdl_do_typecheck sym` is called.
         
     | 
| 
       100 
84 
     | 
    
         | 
| 
       101 
     | 
    
         
            -
            RDL  
     | 
| 
      
 85 
     | 
    
         
            +
            RDL supports many more complex type annotations; see below for a complete discussion and examples.
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            RDL 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, query, and use during type checking. 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.
         
     | 
| 
       102 
88 
     | 
    
         | 
| 
       103 
89 
     | 
    
         
             
            ```shell
         
     | 
| 
       104 
90 
     | 
    
         
             
            $ rdl_query String#include?            # print type for instance method of another class
         
     | 
| 
         @@ -122,7 +108,19 @@ $ irb 
     | 
|
| 
       122 
108 
     | 
    
         
             
            > rdl_query '...' # as above
         
     | 
| 
       123 
109 
     | 
    
         
             
            ```
         
     | 
| 
       124 
110 
     | 
    
         | 
| 
       125 
     | 
    
         
            -
             
     | 
| 
      
 111 
     | 
    
         
            +
            RDL also supports more general contracts, though these can only be enforced at run time and are not statically checked. These more general contracts take the form of *preconditions*, describing what a method assumes about its inputs, and *postconditions*, describing what a method guarantees about its outputs. For example:
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 114 
     | 
    
         
            +
            require 'rdl'
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
            pre { |x| x > 0 }
         
     | 
| 
      
 117 
     | 
    
         
            +
            post { |r,x| r > 0 }
         
     | 
| 
      
 118 
     | 
    
         
            +
            def sqrt(x)
         
     | 
| 
      
 119 
     | 
    
         
            +
              # return the square root of x
         
     | 
| 
      
 120 
     | 
    
         
            +
            end
         
     | 
| 
      
 121 
     | 
    
         
            +
            ```
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
            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...) 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`. Note that pre- and postconditions can't be searched for using `rdl_query`.
         
     | 
| 
       126 
124 
     | 
    
         | 
| 
       127 
125 
     | 
    
         
             
            # Using RDL
         
     | 
| 
       128 
126 
     | 
    
         | 
| 
         @@ -136,11 +134,11 @@ RDL currently supports Ruby 2.x. It may or may not work with other versions. 
     | 
|
| 
       136 
134 
     | 
    
         | 
| 
       137 
135 
     | 
    
         
             
            ## Loading RDL
         
     | 
| 
       138 
136 
     | 
    
         | 
| 
       139 
     | 
    
         
            -
            Use `require 'rdl'` to load the RDL library. If you want to use the core and standard library type signatures that come with RDL, follow it with `require ' 
     | 
| 
      
 137 
     | 
    
         
            +
            Use `require 'rdl'` to load the RDL library. If you want to use the core and standard library type signatures that come with RDL, follow it with `require 'types/core'`.  This will load the types based on the current `RUBY_VERSION`. Currently RDL has types for the following versions of Ruby:
         
     | 
| 
       140 
138 
     | 
    
         | 
| 
       141 
139 
     | 
    
         
             
            * 2.x
         
     | 
| 
       142 
140 
     | 
    
         | 
| 
       143 
     | 
    
         
            -
            (Currently all  
     | 
| 
      
 141 
     | 
    
         
            +
            (Currently all 2.x versions are assumed to have the same library type signatures, which may not be correct.)
         
     | 
| 
       144 
142 
     | 
    
         | 
| 
       145 
143 
     | 
    
         
             
            ## Disabling RDL
         
     | 
| 
       146 
144 
     | 
    
         | 
| 
         @@ -164,13 +162,18 @@ to get the head version from github. 
     | 
|
| 
       164 
162 
     | 
    
         | 
| 
       165 
163 
     | 
    
         
             
            In development and test mode, you will now have access to `rdl`, `types/core` from RDL, and extra type annotations for Rails and some related gems. In production mode, RDL will be disabled (by loading `rdl_disable`).
         
     | 
| 
       166 
164 
     | 
    
         | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
      
 165 
     | 
    
         
            +
            **Warning:** Rails support is currently extremely limited, not well tested, and generally needs more work...please send bug reports/pull requests/etc and we will fix things.
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
            Currently, RDL has types for the following versions of Rails:
         
     | 
| 
       168 
168 
     | 
    
         | 
| 
       169 
169 
     | 
    
         
             
            * Rails 5.x support - limited to the following:
         
     | 
| 
       170 
     | 
    
         
            -
              *  
     | 
| 
       171 
     | 
    
         
            -
                *  
     | 
| 
       172 
     | 
    
         
            -
             
     | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
      
 170 
     | 
    
         
            +
              * Models
         
     | 
| 
      
 171 
     | 
    
         
            +
                * Generates type annotations for model column getters and setters
         
     | 
| 
      
 172 
     | 
    
         
            +
                * `find_by` and `find_by!`
         
     | 
| 
      
 173 
     | 
    
         
            +
                * Generates types annotations for methods added by `belongs_to`, `has_one`, `has_many`, `has_and_belongs_to_many`.
         
     | 
| 
      
 174 
     | 
    
         
            +
                  * Note currently RDL doesn't do very precise type checking of relations
         
     | 
| 
      
 175 
     | 
    
         
            +
              * Controllers
         
     | 
| 
      
 176 
     | 
    
         
            +
                * `errors`
         
     | 
| 
       174 
177 
     | 
    
         | 
| 
       175 
178 
     | 
    
         
             
            ## Preconditions and Postconditions
         
     | 
| 
       176 
179 
     | 
    
         | 
| 
         @@ -559,6 +562,12 @@ type MyClass, :foo, '(a: Fixnum, b: String) { () -> %any } -> %any' 
     | 
|
| 
       559 
562 
     | 
    
         
             
            ```
         
     | 
| 
       560 
563 
     | 
    
         
             
            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.
         
     | 
| 
       561 
564 
     | 
    
         | 
| 
      
 565 
     | 
    
         
            +
            RDL also allows a "rest" type in finite hashes (of course, they're not so finite if they use it!):
         
     | 
| 
      
 566 
     | 
    
         
            +
            ```ruby
         
     | 
| 
      
 567 
     | 
    
         
            +
            type MyClass, :foo, '(a: Fixnum, b: String, **Float) -> %any'
         
     | 
| 
      
 568 
     | 
    
         
            +
            ```
         
     | 
| 
      
 569 
     | 
    
         
            +
            In this method, `a` is a `Fixnum`, `b` is a `String`, and any number (zero or more) remaining keyword arguments can be passed where the values have type `Float`, e.g., a call `foo(a: 3, b: 'b', pi: 3.14)` is allowed.
         
     | 
| 
      
 570 
     | 
    
         
            +
             
     | 
| 
       562 
571 
     | 
    
         
             
            ## Type Casts
         
     | 
| 
       563 
572 
     | 
    
         | 
| 
       564 
573 
     | 
    
         
             
            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`.
         
     | 
| 
         @@ -577,23 +586,19 @@ Types can be prefixed with `!` to indicate the associated value is not `nil`. Fo 
     | 
|
| 
       577 
586 
     | 
    
         | 
| 
       578 
587 
     | 
    
         
             
            # Static Type Checking
         
     | 
| 
       579 
588 
     | 
    
         | 
| 
       580 
     | 
    
         
            -
             
     | 
| 
      
 589 
     | 
    
         
            +
            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.
         
     | 
| 
       581 
590 
     | 
    
         | 
| 
       582 
591 
     | 
    
         
             
            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.
         
     | 
| 
       583 
592 
     | 
    
         | 
| 
       584 
593 
     | 
    
         
             
            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.
         
     | 
| 
       585 
594 
     | 
    
         | 
| 
       586 
     | 
    
         
            -
            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.
         
     | 
| 
       587 
     | 
    
         
            -
             
     | 
| 
       588 
     | 
    
         
            -
            Typechecking does work in `pry` (this feature has only limited testing) as long as typechecking is delayed until after the method is defined:
         
     | 
| 
      
 595 
     | 
    
         
            +
            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. Typechecking does work in `pry` (this feature has only limited testing) as long as typechecking is delayed until after the method is defined:
         
     | 
| 
       589 
596 
     | 
    
         | 
| 
       590 
597 
     | 
    
         
             
            ```ruby
         
     | 
| 
       591 
598 
     | 
    
         
             
            [2] pry(main)> require 'rdl'
         
     | 
| 
       592 
     | 
    
         
            -
            [3] pry(main)> require ' 
     | 
| 
      
 599 
     | 
    
         
            +
            [3] pry(main)> require 'types/core'
         
     | 
| 
       593 
600 
     | 
    
         
             
            [4] pry(main)> type '() -> Fixnum', typecheck: :later    # note: typecheck: :now doesn't work in pry
         
     | 
| 
       594 
     | 
    
         
            -
            [5] pry(main)> def f
         
     | 
| 
       595 
     | 
    
         
            -
            [5] pry(main)*   'haha'  
         
     | 
| 
       596 
     | 
    
         
            -
            [5] pry(main)* end  
         
     | 
| 
      
 601 
     | 
    
         
            +
            [5] pry(main)> def f; 'haha'; end
         
     | 
| 
       597 
602 
     | 
    
         
             
            [6] pry(main)> rdl_do_typecheck :later
         
     | 
| 
       598 
603 
     | 
    
         
             
            RDL::Typecheck::StaticTypeError:
         
     | 
| 
       599 
604 
     | 
    
         
             
            (string):2:3: error: got type `String' where return type `Fixnum' expected
         
     | 
| 
         @@ -639,7 +644,7 @@ x = 1 
     | 
|
| 
       639 
644 
     | 
    
         
             
            m() { x = 'bar' }
         
     | 
| 
       640 
645 
     | 
    
         
             
            # what is x's type here?
         
     | 
| 
       641 
646 
     | 
    
         
             
            ```
         
     | 
| 
       642 
     | 
    
         
            -
            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  
     | 
| 
      
 647 
     | 
    
         
            +
            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 variable is the union of all types that are ever assigned to it.
         
     | 
| 
       643 
648 
     | 
    
         | 
| 
       644 
649 
     | 
    
         
             
            RDL always treats instance, class, and global variables flow-insensitively, hence their types must be defined with `var_type`:
         
     | 
| 
       645 
650 
     | 
    
         | 
| 
         @@ -656,7 +661,7 @@ end 
     | 
|
| 
       656 
661 
     | 
    
         | 
| 
       657 
662 
     | 
    
         
             
            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`.
         
     | 
| 
       658 
663 
     | 
    
         | 
| 
       659 
     | 
    
         
            -
            As a short-hand, RDL defines methods `attr_accessor_type`, `attr_reader_type`, and `attr_writer_type` to behave like their corresponding non-`_type` analogs but   
     | 
| 
      
 664 
     | 
    
         
            +
            As a short-hand, RDL defines methods `attr_accessor_type`, `attr_reader_type`, and `attr_writer_type` to behave like their corresponding non-`_type` analogs but  assign types to the attributes. For example, `attr_accessor_type :f, 'Fixnum', :g, 'String'` is equivalent to:
         
     | 
| 
       660 
665 
     | 
    
         | 
| 
       661 
666 
     | 
    
         
             
            ```ruby
         
     | 
| 
       662 
667 
     | 
    
         
             
            var_type :@f, 'Fixnum'
         
     | 
| 
         @@ -694,16 +699,18 @@ var_type @g, 'Array<Fixnum or String>' 
     | 
|
| 
       694 
699 
     | 
    
         
             
            @g = x          # uh oh
         
     | 
| 
       695 
700 
     | 
    
         
             
            ```
         
     | 
| 
       696 
701 
     | 
    
         | 
| 
       697 
     | 
    
         
            -
            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  
     | 
| 
      
 702 
     | 
    
         
            +
            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]` of `@f`, so the assignment to `@g` signals an error.
         
     | 
| 
       698 
703 
     | 
    
         | 
| 
       699 
704 
     | 
    
         
             
            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`.
         
     | 
| 
       700 
705 
     | 
    
         | 
| 
       701 
706 
     | 
    
         
             
            ## Other Features and Limitations
         
     | 
| 
       702 
707 
     | 
    
         | 
| 
       703 
     | 
    
         
            -
            *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.
         
     | 
| 
      
 708 
     | 
    
         
            +
            *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.
         
     | 
| 
       704 
709 
     | 
    
         | 
| 
       705 
710 
     | 
    
         
             
            * *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`.
         
     | 
| 
       706 
711 
     | 
    
         | 
| 
      
 712 
     | 
    
         
            +
            * *Case analysis by class.* If the guard of a `case` statement is a variable, and then `when` branches compare against classes, RDL refines the type of the guard to be those classes within the corresponding `when` branch. For example, in `case x when Fixnum ...(1)... when String ...(2)... end`, RDL will assume `x` is a `Fixnum` within `(1)` and a `String` within `(2)`.
         
     | 
| 
      
 713 
     | 
    
         
            +
             
     | 
| 
       707 
714 
     | 
    
         
             
            * *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.
         
     | 
| 
       708 
715 
     | 
    
         | 
| 
       709 
716 
     | 
    
         
             
            * *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.
         
     | 
| 
         @@ -714,16 +721,17 @@ RDL uses the same approach for hashes: hash literals are treated as finite hashe 
     | 
|
| 
       714 
721 
     | 
    
         | 
| 
       715 
722 
     | 
    
         
             
            * *Unsupported Features.* There are several features of Ruby that are currently not handled by RDL. Here is a non-exhaustive list:
         
     | 
| 
       716 
723 
     | 
    
         
             
              * `super` is not supported.
         
     | 
| 
       717 
     | 
    
         
            -
              * `lambda` has special semantics for `return`; this is not supported.
         
     | 
| 
       718 
     | 
    
         
            -
              * Only simple  
     | 
| 
      
 724 
     | 
    
         
            +
              * `lambda` has special semantics for `return`; this is not supported. Stabby lambda is also not supported.
         
     | 
| 
      
 725 
     | 
    
         
            +
              * Only simple `for` iteration variables are supported.
         
     | 
| 
       719 
726 
     | 
    
         
             
              * Control flow for exceptions is not analyzed fully soundly; some things are not reported as possibly `nil` that could be.
         
     | 
| 
       720 
727 
     | 
    
         
             
              * Only simple usage of constants is handled.
         
     | 
| 
       721 
728 
     | 
    
         | 
| 
       722 
729 
     | 
    
         
             
            ## Assumptions
         
     | 
| 
       723 
730 
     | 
    
         | 
| 
       724 
     | 
    
         
            -
            RDL makes some assumptions that should hold unless your Ruby code is doing something highly unusual:
         
     | 
| 
      
 731 
     | 
    
         
            +
            RDL's static type checker makes some assumptions that should hold unless your Ruby code is doing something highly unusual. RDL assumes:
         
     | 
| 
       725 
732 
     | 
    
         | 
| 
       726 
733 
     | 
    
         
             
            * `Class#===` is not redefined
         
     | 
| 
      
 734 
     | 
    
         
            +
            * `Proc#call` is not redefined
         
     | 
| 
       727 
735 
     | 
    
         | 
| 
       728 
736 
     | 
    
         
             
            (More assumptions will be added here as they are added to RDL...)
         
     | 
| 
       729 
737 
     | 
    
         | 
| 
         @@ -739,6 +747,8 @@ RDL also includes a few other useful methods: 
     | 
|
| 
       739 
747 
     | 
    
         | 
| 
       740 
748 
     | 
    
         
             
            * `rdl_query` prints information about types; see below for details.
         
     | 
| 
       741 
749 
     | 
    
         | 
| 
      
 750 
     | 
    
         
            +
            * `rdl_do_at(sym, &blk)` invokes `blk.call(sym)` when `rdl_do_typecheck(sym)` is called. Useful when type annotations need to be generated at some later time, e.g., because not all classes are loaded.
         
     | 
| 
      
 751 
     | 
    
         
            +
             
     | 
| 
       742 
752 
     | 
    
         
             
            # Queries
         
     | 
| 
       743 
753 
     | 
    
         | 
| 
       744 
754 
     | 
    
         
             
            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:
         
     | 
| 
         @@ -811,7 +821,7 @@ RDL supports the following configuration options: 
     | 
|
| 
       811 
821 
     | 
    
         | 
| 
       812 
822 
     | 
    
         
             
            # Bibliography
         
     | 
| 
       813 
823 
     | 
    
         | 
| 
       814 
     | 
    
         
            -
            Here are some research papers we have written exploring  
     | 
| 
      
 824 
     | 
    
         
            +
            Here are some research papers we have written exploring types, contracts, and Ruby.
         
     | 
| 
       815 
825 
     | 
    
         | 
| 
       816 
826 
     | 
    
         
             
            * Brianna M. Ren and Jeffrey S. Foster.
         
     | 
| 
       817 
827 
     | 
    
         
             
            [Just-in-Time Static Type Checking for Dynamic Languages](http://www.cs.umd.edu/~jfoster/papers/pldi16.pdf).
         
     | 
| 
         @@ -863,44 +873,6 @@ Copyright (c) 2014-2016, University of Maryland, College Park. All rights reserv 
     | 
|
| 
       863 
873 
     | 
    
         
             
            * Alexander T. Yu
         
     | 
| 
       864 
874 
     | 
    
         
             
            * Milod Kazerounian
         
     | 
| 
       865 
875 
     | 
    
         | 
| 
       866 
     | 
    
         
            -
            #  
     | 
| 
       867 
     | 
    
         
            -
             
     | 
| 
       868 
     | 
    
         
            -
            * How to check whether initialize? is user-defined? method_defined? always
         
     | 
| 
       869 
     | 
    
         
            -
              returns true, meaning wrapping isn't fully working with initialize.
         
     | 
| 
       870 
     | 
    
         
            -
             
     | 
| 
       871 
     | 
    
         
            -
            * Currently if a NominalType name is expressed differently, e.g., A
         
     | 
| 
       872 
     | 
    
         
            -
              vs. EnclosingClass::A, the types will be different when compared
         
     | 
| 
       873 
     | 
    
         
            -
              with ==.
         
     | 
| 
       874 
     | 
    
         
            -
             
     | 
| 
       875 
     | 
    
         
            -
            * Macros, %bool should really be %any
         
     | 
| 
       876 
     | 
    
         
            -
             
     | 
| 
       877 
     | 
    
         
            -
            * Method types that are parametric themselves (not just ones that use
         
     | 
| 
       878 
     | 
    
         
            -
              enclosing class parameters)
         
     | 
| 
       879 
     | 
    
         
            -
             
     | 
| 
       880 
     | 
    
         
            -
            * Rails types
         
     | 
| 
       881 
     | 
    
         
            -
             
     | 
| 
       882 
     | 
    
         
            -
            * Deferred contracts on new (watch for class addition)
         
     | 
| 
       883 
     | 
    
         
            -
             
     | 
| 
       884 
     | 
    
         
            -
            * DSL contracts
         
     | 
| 
       885 
     | 
    
         
            -
             
     | 
| 
       886 
     | 
    
         
            -
            * double-splat arguments, which bind to an arbitrary set of keywords
         
     | 
| 
       887 
     | 
    
         
            -
             
     | 
| 
       888 
     | 
    
         
            -
            * included versus extended modules, e.g., Kernel is included in Object, so its
         
     | 
| 
       889 
     | 
    
         
            -
              class methods become Object's instance methods
         
     | 
| 
       890 
     | 
    
         
            -
             
     | 
| 
       891 
     | 
    
         
            -
            * Better story for different types for different Ruby versions
         
     | 
| 
       892 
     | 
    
         
            -
             
     | 
| 
       893 
     | 
    
         
            -
            * Better query facility (more kinds of searches). Contract queries?
         
     | 
| 
       894 
     | 
    
         
            -
             
     | 
| 
       895 
     | 
    
         
            -
            * Write documentation on: Raw Contracts and Types, RDL Configuration, Code Overview
         
     | 
| 
       896 
     | 
    
         
            -
             
     | 
| 
       897 
     | 
    
         
            -
            * Structural type queries, allow name to be unknown; same with finite hash keys,
         
     | 
| 
       898 
     | 
    
         
            -
              same with generic base types?
         
     | 
| 
       899 
     | 
    
         
            -
             
     | 
| 
       900 
     | 
    
         
            -
            * Allow ... in named args list in queries
         
     | 
| 
       901 
     | 
    
         
            -
             
     | 
| 
       902 
     | 
    
         
            -
            * Queries, include more regexp operators aside from . and ...
         
     | 
| 
       903 
     | 
    
         
            -
             
     | 
| 
       904 
     | 
    
         
            -
            * Queries, allow regexp in class and method names; suggested by Andreas Adamcik, Vienna
         
     | 
| 
      
 876 
     | 
    
         
            +
            # Discussion group
         
     | 
| 
       905 
877 
     | 
    
         | 
| 
       906 
     | 
    
         
            -
             
     | 
| 
      
 878 
     | 
    
         
            +
            [RDL Users](https://groups.google.com/forum/#!forum/rdl-users)
         
     | 
    
        data/lib/rdl/boot.rb
    CHANGED
    
    | 
         @@ -46,6 +46,9 @@ $__rdl_to_wrap = Set.new 
     | 
|
| 
       46 
46 
     | 
    
         
             
            $__rdl_to_typecheck = Hash.new
         
     | 
| 
       47 
47 
     | 
    
         
             
            $__rdl_to_typecheck[:now] = Set.new
         
     | 
| 
       48 
48 
     | 
    
         | 
| 
      
 49 
     | 
    
         
            +
            # Map from symbols to Array<Proc> where the Procs are called when those symbols are rdl_do_typecheck'd
         
     | 
| 
      
 50 
     | 
    
         
            +
            $__rdl_at = Hash.new
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
       49 
52 
     | 
    
         
             
            # List of contracts that should be applied to the next method definition
         
     | 
| 
       50 
53 
     | 
    
         
             
            $__rdl_deferred = []
         
     | 
| 
       51 
54 
     | 
    
         | 
| 
         @@ -122,6 +125,7 @@ $__rdl_symbol_type = RDL::Type::NominalType.new Symbol 
     | 
|
| 
       122 
125 
     | 
    
         
             
            $__rdl_range_type = RDL::Type::NominalType.new Range
         
     | 
| 
       123 
126 
     | 
    
         
             
            $__rdl_regexp_type = RDL::Type::NominalType.new Regexp
         
     | 
| 
       124 
127 
     | 
    
         
             
            $__rdl_standard_error_type = RDL::Type::NominalType.new StandardError
         
     | 
| 
      
 128 
     | 
    
         
            +
            $__rdl_proc_type = RDL::Type::NominalType.new Proc
         
     | 
| 
       125 
129 
     | 
    
         | 
| 
       126 
130 
     | 
    
         
             
            # Hash from special type names to their values
         
     | 
| 
       127 
131 
     | 
    
         
             
            $__rdl_special_types = {'%any' => $__rdl_top_type,
         
     | 
    
        data/lib/rdl/boot_rails.rb
    CHANGED
    
    | 
         @@ -7,7 +7,9 @@ if Rails.env.development? || Rails.env.test? 
     | 
|
| 
       7 
7 
     | 
    
         
             
              Dir[File.dirname(__FILE__) + "/../types/rails-#{dir}/**/*.rb"].each { |f| require f }
         
     | 
| 
       8 
8 
     | 
    
         
             
            elsif Rails.env.production?
         
     | 
| 
       9 
9 
     | 
    
         
             
              require 'rdl_disable'
         
     | 
| 
       10 
     | 
    
         
            -
               
     | 
| 
      
 10 
     | 
    
         
            +
              class ActionController::Base
         
     | 
| 
      
 11 
     | 
    
         
            +
                def self.params_type(typs); end
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
       11 
13 
     | 
    
         
             
            else
         
     | 
| 
       12 
14 
     | 
    
         
             
              raise RuntimeError, "Don't know what to do in Rails environment #{Rails.env}"
         
     | 
| 
       13 
15 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rdl/contracts/flat.rb
    CHANGED
    
    | 
         @@ -9,8 +9,7 @@ module RDL::Contract 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                def check(slf, *v, &blk)
         
     | 
| 
       11 
11 
     | 
    
         
             
                  $__rdl_contract_switch.off {
         
     | 
| 
       12 
     | 
    
         
            -
                    if  
     | 
| 
       13 
     | 
    
         
            -
                        ((@pred.arity < 0) ? (@pred.arity.abs - 1) <= v.size : @pred.arity == v.size)) then
         
     | 
| 
      
 12 
     | 
    
         
            +
                    if @pred && v.length >= @pred.arity
         
     | 
| 
       14 
13 
     | 
    
         
             
                      unless blk ? slf.instance_exec(*v, blk, &@pred) : slf.instance_exec(*v, &@pred) # TODO: Fix blk
         
     | 
| 
       15 
14 
     | 
    
         
             
            #          unless blk ? pred.call(*v, &blk) : pred.call(*v)
         
     | 
| 
       16 
15 
     | 
    
         
             
                        raise ContractError,
         
     | 
    
        data/lib/rdl/typecheck.rb
    CHANGED
    
    | 
         @@ -2,7 +2,7 @@ module RDL::Typecheck 
     | 
|
| 
       2 
2 
     | 
    
         | 
| 
       3 
3 
     | 
    
         
             
              class StaticTypeError < StandardError; end
         
     | 
| 
       4 
4 
     | 
    
         | 
| 
       5 
     | 
    
         
            -
              @@empty_hash_type = RDL::Type::FiniteHashType.new(Hash.new)
         
     | 
| 
      
 5 
     | 
    
         
            +
              @@empty_hash_type = RDL::Type::FiniteHashType.new(Hash.new, nil)
         
     | 
| 
       6 
6 
     | 
    
         
             
              @@asgn_to_var = { lvasgn: :lvar, ivasgn: :ivar, cvasgn: :cvar, gvasgn: :gvar }
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              # Create mapping from file/line numbers to the def that appears at that location
         
     | 
| 
         @@ -208,7 +208,6 @@ module RDL::Typecheck 
     | 
|
| 
       208 
208 
     | 
    
         
             
                raise RuntimeError, "Method #{name} defined where method #{meth} expected" if name.to_sym != meth
         
     | 
| 
       209 
209 
     | 
    
         
             
                context_types = $__rdl_info.get(klass, meth, :context_types)
         
     | 
| 
       210 
210 
     | 
    
         
             
                types.each { |type|
         
     | 
| 
       211 
     | 
    
         
            -
                  # TODO will need fancier logic here for matching up more complex arg lists
         
     | 
| 
       212 
211 
     | 
    
         
             
                  if RDL::Util.has_singleton_marker(klass)
         
     | 
| 
       213 
212 
     | 
    
         
             
                    # to_class gets the class object itself, so remove singleton marker to get class rather than singleton class
         
     | 
| 
       214 
213 
     | 
    
         
             
                    self_type = RDL::Type::SingletonType.new(RDL::Util.to_class(RDL::Util.remove_singleton_marker(klass)))
         
     | 
| 
         @@ -217,10 +216,7 @@ module RDL::Typecheck 
     | 
|
| 
       217 
216 
     | 
    
         
             
                  end
         
     | 
| 
       218 
217 
     | 
    
         
             
                  inst = {self: self_type}
         
     | 
| 
       219 
218 
     | 
    
         
             
                  type = type.instantiate inst
         
     | 
| 
       220 
     | 
    
         
            -
                   
     | 
| 
       221 
     | 
    
         
            -
                    error :arg_count_mismatch, ['method', type.args.length, 'method', args.children.length], (if args.children.empty? then ast else args end)
         
     | 
| 
       222 
     | 
    
         
            -
                  end
         
     | 
| 
       223 
     | 
    
         
            -
                  targs = args.children.map { |arg| arg.children[0] }.zip(type.args).to_h
         
     | 
| 
      
 219 
     | 
    
         
            +
                  _, targs = args_hash({}, Env.new, type, args, ast, 'method')
         
     | 
| 
       224 
220 
     | 
    
         
             
                  targs[:self] = self_type
         
     | 
| 
       225 
221 
     | 
    
         
             
                  scope = { tret: type.ret, tblock: type.block, captured: Hash.new, context_types: context_types }
         
     | 
| 
       226 
222 
     | 
    
         
             
                  begin
         
     | 
| 
         @@ -236,6 +232,79 @@ module RDL::Typecheck 
     | 
|
| 
       236 
232 
     | 
    
         
             
                $__rdl_info.set(klass, meth, :typechecked, true)
         
     | 
| 
       237 
233 
     | 
    
         
             
              end
         
     | 
| 
       238 
234 
     | 
    
         | 
| 
      
 235 
     | 
    
         
            +
              # [+ scope +] is used to typecheck default values for optional arguments
         
     | 
| 
      
 236 
     | 
    
         
            +
              # [+ env +] is used to typecheck default values for optional arguments
         
     | 
| 
      
 237 
     | 
    
         
            +
              # [+ type +] is a MethodType
         
     | 
| 
      
 238 
     | 
    
         
            +
              # [+ args +] is an `args` node from the AST
         
     | 
| 
      
 239 
     | 
    
         
            +
              # [+ ast +] is where to report an error if `args` is empty
         
     | 
| 
      
 240 
     | 
    
         
            +
              # [+ kind +] is either `'method'` or `'block'`, and is only used for printing error messages
         
     | 
| 
      
 241 
     | 
    
         
            +
              # Returns a Hash<Symbol, Type> mapping formal argument names to their types
         
     | 
| 
      
 242 
     | 
    
         
            +
              def self.args_hash(scope, env, type, args, ast, kind)
         
     | 
| 
      
 243 
     | 
    
         
            +
                targs = Hash.new
         
     | 
| 
      
 244 
     | 
    
         
            +
                tpos = 0 # position in type.args
         
     | 
| 
      
 245 
     | 
    
         
            +
                kw_args_matched = []
         
     | 
| 
      
 246 
     | 
    
         
            +
                kw_rest_matched = false
         
     | 
| 
      
 247 
     | 
    
         
            +
                args.children.each { |arg|
         
     | 
| 
      
 248 
     | 
    
         
            +
                  error :type_args_fewer, [kind, kind], arg if tpos >= type.args.length && arg.type != :blockarg  # blocks could be called with yield
         
     | 
| 
      
 249 
     | 
    
         
            +
                  targ = type.args[tpos]
         
     | 
| 
      
 250 
     | 
    
         
            +
                  if arg.type == :arg
         
     | 
| 
      
 251 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'optional', 'required'], arg if targ.optional?
         
     | 
| 
      
 252 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'vararg', 'required'], arg if targ.vararg?
         
     | 
| 
      
 253 
     | 
    
         
            +
                    targs[arg.children[0]] = targ
         
     | 
| 
      
 254 
     | 
    
         
            +
                    tpos += 1
         
     | 
| 
      
 255 
     | 
    
         
            +
                  elsif arg.type == :optarg
         
     | 
| 
      
 256 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'vararg', 'optional'], arg if targ.vararg?
         
     | 
| 
      
 257 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'required', 'optional'], arg if !targ.optional?
         
     | 
| 
      
 258 
     | 
    
         
            +
                    env, default_type = tc(scope, env, arg.children[1])
         
     | 
| 
      
 259 
     | 
    
         
            +
                    error :optional_default_type, [default_type, targ.type], arg.children[1] unless default_type <= targ.type
         
     | 
| 
      
 260 
     | 
    
         
            +
                    targs[arg.children[0]] = targ.type
         
     | 
| 
      
 261 
     | 
    
         
            +
                    tpos += 1
         
     | 
| 
      
 262 
     | 
    
         
            +
                  elsif arg.type == :restarg
         
     | 
| 
      
 263 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'optional', 'vararg'], arg if targ.optional?
         
     | 
| 
      
 264 
     | 
    
         
            +
                    error :type_arg_kind_mismatch, [kind, 'required', 'vararg'], arg if !targ.vararg?
         
     | 
| 
      
 265 
     | 
    
         
            +
                    targs[arg.children[0]] = RDL::Type::GenericType.new($__rdl_array_type, targ.type)
         
     | 
| 
      
 266 
     | 
    
         
            +
                    tpos += 1
         
     | 
| 
      
 267 
     | 
    
         
            +
                  elsif arg.type == :kwarg
         
     | 
| 
      
 268 
     | 
    
         
            +
                    error :type_args_no_kws, [kind], arg unless targ.is_a?(RDL::Type::FiniteHashType)
         
     | 
| 
      
 269 
     | 
    
         
            +
                    kw = arg.children[0]
         
     | 
| 
      
 270 
     | 
    
         
            +
                    error :type_args_no_kw, [kind, kw], arg unless targ.elts.has_key? kw
         
     | 
| 
      
 271 
     | 
    
         
            +
                    tkw = targ.elts[kw]
         
     | 
| 
      
 272 
     | 
    
         
            +
                    error :type_args_kw_mismatch, [kind, 'optional', kw, 'required'], arg if tkw.is_a? RDL::Type::OptionalType
         
     | 
| 
      
 273 
     | 
    
         
            +
                    kw_args_matched << kw
         
     | 
| 
      
 274 
     | 
    
         
            +
                    targs[kw] = tkw
         
     | 
| 
      
 275 
     | 
    
         
            +
                  elsif arg.type == :kwoptarg
         
     | 
| 
      
 276 
     | 
    
         
            +
                    error :type_args_no_kws, [kind], arg unless targ.is_a?(RDL::Type::FiniteHashType)
         
     | 
| 
      
 277 
     | 
    
         
            +
                    kw = arg.children[0]
         
     | 
| 
      
 278 
     | 
    
         
            +
                    error :type_args_no_kw, [kind, kw], arg unless targ.elts.has_key? kw
         
     | 
| 
      
 279 
     | 
    
         
            +
                    tkw = targ.elts[kw]
         
     | 
| 
      
 280 
     | 
    
         
            +
                    error :type_args_kw_mismatch, [kind, 'required', kw, 'optional'], arg if !tkw.is_a?(RDL::Type::OptionalType)
         
     | 
| 
      
 281 
     | 
    
         
            +
                    env, default_type = tc(scope, env, arg.children[1])
         
     | 
| 
      
 282 
     | 
    
         
            +
                    error :optional_default_kw_type, [kw, default_type, tkw.type], arg.children[1] unless default_type <= tkw.type
         
     | 
| 
      
 283 
     | 
    
         
            +
                    kw_args_matched << kw
         
     | 
| 
      
 284 
     | 
    
         
            +
                    targs[kw] = tkw.type
         
     | 
| 
      
 285 
     | 
    
         
            +
                  elsif arg.type == :kwrestarg
         
     | 
| 
      
 286 
     | 
    
         
            +
                    error :type_args_no_kws, [kind], e unless targ.is_a?(RDL::Type::FiniteHashType)
         
     | 
| 
      
 287 
     | 
    
         
            +
                    error :type_args_no_kw_rest, [kind], arg if targ.rest.nil?
         
     | 
| 
      
 288 
     | 
    
         
            +
                    targs[arg.children[0]] = RDL::Type::GenericType.new($__rdl_hash_type, $__rdl_symbol_type, targ.rest)
         
     | 
| 
      
 289 
     | 
    
         
            +
                    kw_rest_matched = true
         
     | 
| 
      
 290 
     | 
    
         
            +
                  elsif arg.type == :blockarg
         
     | 
| 
      
 291 
     | 
    
         
            +
                    error :type_arg_block, [kind, kind], arg unless type.block
         
     | 
| 
      
 292 
     | 
    
         
            +
                    targs[arg.children[0]] = type.block
         
     | 
| 
      
 293 
     | 
    
         
            +
                    # Note no check that if type.block then method expects block, because blocks can be called with yield
         
     | 
| 
      
 294 
     | 
    
         
            +
                  else
         
     | 
| 
      
 295 
     | 
    
         
            +
                    error :generic_error, ["Don't know what to do with actual argument of type #{arg.type}"], arg
         
     | 
| 
      
 296 
     | 
    
         
            +
                  end
         
     | 
| 
      
 297 
     | 
    
         
            +
                }
         
     | 
| 
      
 298 
     | 
    
         
            +
                if (tpos == type.args.length - 1) && type.args[tpos].is_a?(RDL::Type::FiniteHashType)
         
     | 
| 
      
 299 
     | 
    
         
            +
                  rest = type.args[tpos].elts.keys - kw_args_matched
         
     | 
| 
      
 300 
     | 
    
         
            +
                  error :type_args_kw_more, [kind, rest.map { |s| s.to_s }.join(", "), kind], ast unless rest.empty?
         
     | 
| 
      
 301 
     | 
    
         
            +
                  error :type_args_kw_rest, [kind], ast unless kw_rest_matched || type.args[tpos].rest.nil?
         
     | 
| 
      
 302 
     | 
    
         
            +
                else
         
     | 
| 
      
 303 
     | 
    
         
            +
                  error :type_args_more, [kind, kind], (if args.children.empty? then ast else args end) if type.args.length != tpos
         
     | 
| 
      
 304 
     | 
    
         
            +
                end
         
     | 
| 
      
 305 
     | 
    
         
            +
                return [env, targs]
         
     | 
| 
      
 306 
     | 
    
         
            +
              end
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
       239 
308 
     | 
    
         
             
              # The actual type checking logic.
         
     | 
| 
       240 
309 
     | 
    
         
             
              # [+ scope +] tracks flow-insensitive information about the current scope, excluding local variables
         
     | 
| 
       241 
310 
     | 
    
         
             
              # [+ env +] is the (local variable) Env
         
     | 
| 
         @@ -339,7 +408,7 @@ module RDL::Typecheck 
     | 
|
| 
       339 
408 
     | 
    
         
             
                  if is_fh
         
     | 
| 
       340 
409 
     | 
    
         
             
                    # keys are all symbols
         
     | 
| 
       341 
410 
     | 
    
         
             
                    fh = tlefts.map { |t| t.val }.zip(trights).to_h
         
     | 
| 
       342 
     | 
    
         
            -
                    [envi, RDL::Type::FiniteHashType.new(fh)]
         
     | 
| 
      
 411 
     | 
    
         
            +
                    [envi, RDL::Type::FiniteHashType.new(fh, nil)]
         
     | 
| 
       343 
412 
     | 
    
         
             
                  else
         
     | 
| 
       344 
413 
     | 
    
         
             
                    tleft = RDL::Type::UnionType.new(*tlefts)
         
     | 
| 
       345 
414 
     | 
    
         
             
                    tright = RDL::Type::UnionType.new(*trights)
         
     | 
| 
         @@ -507,6 +576,12 @@ module RDL::Typecheck 
     | 
|
| 
       507 
576 
     | 
    
         
             
                        else
         
     | 
| 
       508 
577 
     | 
    
         
             
                          error :cant_splat, [ti], ei.children[0]
         
     | 
| 
       509 
578 
     | 
    
         
             
                        end
         
     | 
| 
      
 579 
     | 
    
         
            +
                      elsif ei.type == :block_pass
         
     | 
| 
      
 580 
     | 
    
         
            +
                        raise RuntimeError, "impossible to pass block arg and literal block" if scope[:block]
         
     | 
| 
      
 581 
     | 
    
         
            +
                        envi, ti = tc(sscope, envi, ei.children[0])
         
     | 
| 
      
 582 
     | 
    
         
            +
                        # convert using to_proc if necessary
         
     | 
| 
      
 583 
     | 
    
         
            +
                        ti = tc_send(sscope, envi, ti, :to_proc, [], nil, ei) unless ti.is_a? RDL::Type::MethodType
         
     | 
| 
      
 584 
     | 
    
         
            +
                        block = [ti, ei]
         
     | 
| 
       510 
585 
     | 
    
         
             
                      else
         
     | 
| 
       511 
586 
     | 
    
         
             
                        envi, ti = tc(sscope, envi, ei)
         
     | 
| 
       512 
587 
     | 
    
         
             
                        tactuals << ti
         
     | 
| 
         @@ -918,7 +993,7 @@ RUBY 
     | 
|
| 
       918 
993 
     | 
    
         
             
              # [+ trecvs +] is the type of the recevier
         
     | 
| 
       919 
994 
     | 
    
         
             
              # [+ meth +] is a symbol with the method name
         
     | 
| 
       920 
995 
     | 
    
         
             
              # [+ tactuals +] are the actual arguments
         
     | 
| 
       921 
     | 
    
         
            -
              # [+ block +] is a pair of expressions [block-args, block-body], from the block AST node
         
     | 
| 
      
 996 
     | 
    
         
            +
              # [+ block +] is a pair of expressions [block-args, block-body], from the block AST node OR [block-type, block-arg-AST-node]
         
     | 
| 
       922 
997 
     | 
    
         
             
              # [+ e +] is the expression at which location to report an error
         
     | 
| 
       923 
998 
     | 
    
         
             
              def self.tc_send(scope, env, trecvs, meth, tactuals, block, e)
         
     | 
| 
       924 
999 
     | 
    
         
             
                scope[:exn] = Env.join(e, scope[:exn], env) if scope.has_key? :exn # assume this call might raise an exception
         
     | 
| 
         @@ -947,6 +1022,16 @@ RUBY 
     | 
|
| 
       947 
1022 
     | 
    
         
             
                    error :no_singleton_method_type, [trecv.val, meth], e unless ts
         
     | 
| 
       948 
1023 
     | 
    
         
             
                    inst = {self: trecv}
         
     | 
| 
       949 
1024 
     | 
    
         
             
                    tmeth_inter = ts.map { |t| t.instantiate(inst) }
         
     | 
| 
      
 1025 
     | 
    
         
            +
                  elsif trecv.val.is_a?(Symbol) && meth == :to_proc
         
     | 
| 
      
 1026 
     | 
    
         
            +
                    # Symbol#to_proc on a singleton symbol type produces a Proc for the method of the same name
         
     | 
| 
      
 1027 
     | 
    
         
            +
                    if env[:self].is_a?(RDL::Type::NominalType)
         
     | 
| 
      
 1028 
     | 
    
         
            +
                      klass = env[:self].klass
         
     | 
| 
      
 1029 
     | 
    
         
            +
                    else # SingletonType(class)
         
     | 
| 
      
 1030 
     | 
    
         
            +
                      klass = env[:self].val
         
     | 
| 
      
 1031 
     | 
    
         
            +
                    end
         
     | 
| 
      
 1032 
     | 
    
         
            +
                    ts = lookup(scope, klass.to_s, trecv.val, e)
         
     | 
| 
      
 1033 
     | 
    
         
            +
                    error :no_type_for_symbol, [trecv.val.inspect], e if ts.nil?
         
     | 
| 
      
 1034 
     | 
    
         
            +
                    return ts
         
     | 
| 
       950 
1035 
     | 
    
         
             
                  else
         
     | 
| 
       951 
1036 
     | 
    
         
             
                    klass = trecv.val.class.to_s
         
     | 
| 
       952 
1037 
     | 
    
         
             
                    ts = lookup(scope, klass, meth, e)
         
     | 
| 
         @@ -970,6 +1055,14 @@ RUBY 
     | 
|
| 
       970 
1055 
     | 
    
         
             
                  tmeth_inter = ts.map { |t| t.instantiate(inst) }
         
     | 
| 
       971 
1056 
     | 
    
         
             
                when RDL::Type::VarType
         
     | 
| 
       972 
1057 
     | 
    
         
             
                  error :recv_var_type, [trecv], e
         
     | 
| 
      
 1058 
     | 
    
         
            +
                when RDL::Type::MethodType
         
     | 
| 
      
 1059 
     | 
    
         
            +
                  if meth == :call
         
     | 
| 
      
 1060 
     | 
    
         
            +
                    # Special case - invokes the Proc
         
     | 
| 
      
 1061 
     | 
    
         
            +
                    tmeth_inter = [trecv]
         
     | 
| 
      
 1062 
     | 
    
         
            +
                  else
         
     | 
| 
      
 1063 
     | 
    
         
            +
                    # treat as Proc
         
     | 
| 
      
 1064 
     | 
    
         
            +
                    tc_send_one_recv(scope, env, $__rdl_proc_type, meth, tactuals, block, e)
         
     | 
| 
      
 1065 
     | 
    
         
            +
                  end
         
     | 
| 
       973 
1066 
     | 
    
         
             
                else
         
     | 
| 
       974 
1067 
     | 
    
         
             
                  raise RuntimeError, "receiver type #{trecv} not supported yet"
         
     | 
| 
       975 
1068 
     | 
    
         
             
                end
         
     | 
| 
         @@ -1006,12 +1099,14 @@ Actual arg type#{tactuals.size > 1 ? "s" : ""}: 
     | 
|
| 
       1006 
1099 
     | 
    
         
             
                  (#{tactuals.map { |ti| ti.to_s }.join(', ')}) #{if block then '{ block }' end}
         
     | 
| 
       1007 
1100 
     | 
    
         
             
            RUBY
         
     | 
| 
       1008 
1101 
     | 
    
         
             
                  msg.chomp! # remove trailing newline
         
     | 
| 
       1009 
     | 
    
         
            -
                  name = if  
     | 
| 
      
 1102 
     | 
    
         
            +
                  name = if trecv.is_a?(RDL::Type::SingletonType) && trecv.val.is_a?(Class) && (meth == :new) then
         
     | 
| 
       1010 
1103 
     | 
    
         
             
                    :initialize
         
     | 
| 
       1011 
1104 
     | 
    
         
             
                  elsif trecv.is_a? RDL::Type::SingletonType
         
     | 
| 
       1012 
1105 
     | 
    
         
             
                    trecv.val.class.to_s
         
     | 
| 
       1013 
     | 
    
         
            -
                  elsif  
     | 
| 
      
 1106 
     | 
    
         
            +
                  elsif trecv.is_a?(RDL::Type::NominalType) || trecv.is_a?(RDL::Type::GenericType)
         
     | 
| 
       1014 
1107 
     | 
    
         
             
                    trecv.to_s
         
     | 
| 
      
 1108 
     | 
    
         
            +
                  elsif trecv.is_a?(RDL::Type::MethodType)
         
     | 
| 
      
 1109 
     | 
    
         
            +
                    'Proc'
         
     | 
| 
       1015 
1110 
     | 
    
         
             
                  else
         
     | 
| 
       1016 
1111 
     | 
    
         
             
                    raise RuntimeError, "impossible to get type #{trecv}"
         
     | 
| 
       1017 
1112 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -1079,27 +1174,28 @@ RUBY 
     | 
|
| 
       1079 
1174 
     | 
    
         
             
              end
         
     | 
| 
       1080 
1175 
     | 
    
         | 
| 
       1081 
1176 
     | 
    
         
             
              # [+ tblock +] is the type of the block (a MethodType)
         
     | 
| 
       1082 
     | 
    
         
            -
              # [+ block +] is a pair [block-args, block-body] from the block AST node
         
     | 
| 
      
 1177 
     | 
    
         
            +
              # [+ block +] is a pair [block-args, block-body] from the block AST node OR [block-type, block-arg-AST-node]
         
     | 
| 
       1083 
1178 
     | 
    
         
             
              # returns if the block matches type tblock
         
     | 
| 
       1084 
1179 
     | 
    
         
             
              # otherwise throws an exception with a type error
         
     | 
| 
       1085 
1180 
     | 
    
         
             
              def self.tc_block(scope, env, tblock, block, inst)
         
     | 
| 
       1086 
     | 
    
         
            -
                # TODO more complex arg lists (same as self.typecheck?); also for
         
     | 
| 
       1087 
1181 
     | 
    
         
             
                # TODO self is the same *except* instance_exec or instance_eval
         
     | 
| 
       1088 
1182 
     | 
    
         
             
                raise RuntimeError, "block with block arg?" unless tblock.block.nil?
         
     | 
| 
       1089 
     | 
    
         
            -
                args, body = block
         
     | 
| 
       1090 
     | 
    
         
            -
                unless tblock.args.length == args.children.length
         
     | 
| 
       1091 
     | 
    
         
            -
                  error :arg_count_mismatch, ['block', tblock.args.length, 'block', args.children.length], (if block[0].children.empty? then block[1] else block[0] end)
         
     | 
| 
       1092 
     | 
    
         
            -
                end
         
     | 
| 
       1093 
1183 
     | 
    
         
             
                tblock = tblock.instantiate(inst)
         
     | 
| 
       1094 
     | 
    
         
            -
                 
     | 
| 
       1095 
     | 
    
         
            -
             
     | 
| 
       1096 
     | 
    
         
            -
                 
     | 
| 
       1097 
     | 
    
         
            -
                   
     | 
| 
       1098 
     | 
    
         
            -
             
     | 
| 
       1099 
     | 
    
         
            -
                   
     | 
| 
       1100 
     | 
    
         
            -
                   
     | 
| 
       1101 
     | 
    
         
            -
                   
     | 
| 
       1102 
     | 
    
         
            -
             
     | 
| 
      
 1184 
     | 
    
         
            +
                if block[0].is_a? RDL::Type::MethodType
         
     | 
| 
      
 1185 
     | 
    
         
            +
                  error :bad_block_arg_type, [block[0], tblock], block[1] unless block[0] <= tblock
         
     | 
| 
      
 1186 
     | 
    
         
            +
                elsif block[0].is_a?(RDL::Type::NominalType) && block[0].name == 'Proc'
         
     | 
| 
      
 1187 
     | 
    
         
            +
                  error :proc_block_arg_type, [tblock], block[1]
         
     | 
| 
      
 1188 
     | 
    
         
            +
                else # must be [block-args, block-body]
         
     | 
| 
      
 1189 
     | 
    
         
            +
                  args, body = block
         
     | 
| 
      
 1190 
     | 
    
         
            +
                  env, targs = args_hash(scope, env, tblock, args, block, 'block')
         
     | 
| 
      
 1191 
     | 
    
         
            +
                  scope_merge(scope, outer_env: env) { |bscope|
         
     | 
| 
      
 1192 
     | 
    
         
            +
                    # note: okay if outer_env shadows, since nested scope will include outer scope by next line
         
     | 
| 
      
 1193 
     | 
    
         
            +
                    env = env.merge(Env.new(targs))
         
     | 
| 
      
 1194 
     | 
    
         
            +
                    _, body_type = if body.nil? then [nil, $__rdl_nil_type] else tc(bscope, env.merge(Env.new(targs)), body) end
         
     | 
| 
      
 1195 
     | 
    
         
            +
                    error :bad_return_type, [body_type, tblock.ret], body unless body.nil? || RDL::Type::Type.leq(body_type, tblock.ret, inst, false)
         
     | 
| 
      
 1196 
     | 
    
         
            +
                    #
         
     | 
| 
      
 1197 
     | 
    
         
            +
                  }
         
     | 
| 
      
 1198 
     | 
    
         
            +
                end
         
     | 
| 
       1103 
1199 
     | 
    
         
             
              end
         
     | 
| 
       1104 
1200 
     | 
    
         | 
| 
       1105 
1201 
     | 
    
         
             
              # [+ klass +] is a string containing the class name
         
     | 
| 
         @@ -1173,7 +1269,6 @@ type_error_messages = { 
     | 
|
| 
       1173 
1269 
     | 
    
         
             
              masgn_bad_lhs: "no corresponding right-hand side elemnt for left-hand side assignee",
         
     | 
| 
       1174 
1270 
     | 
    
         
             
              kw_not_allowed: "can't use `%s' in current scope",
         
     | 
| 
       1175 
1271 
     | 
    
         
             
              kw_arg_not_allowed: "argument to `%s' not allowed in current scope",
         
     | 
| 
       1176 
     | 
    
         
            -
              arg_count_mismatch: "`%s' signature expects %d arguments, actual `%s' has %d arguments",
         
     | 
| 
       1177 
1272 
     | 
    
         
             
              no_block: "attempt to call yield in method not declared to take a block argument",
         
     | 
| 
       1178 
1273 
     | 
    
         
             
              block_block: "can't call yield on a block expecting another block argument",
         
     | 
| 
       1179 
1274 
     | 
    
         
             
              block_type_error: "argument type error for block\n%s",
         
     | 
| 
         @@ -1187,7 +1282,23 @@ type_error_messages = { 
     | 
|
| 
       1187 
1282 
     | 
    
         
             
              for_collection: "can't type for with collection of type `%s'",
         
     | 
| 
       1188 
1283 
     | 
    
         
             
              note_type: "Type is `%s'",
         
     | 
| 
       1189 
1284 
     | 
    
         
             
              note_message: "%s",
         
     | 
| 
       1190 
     | 
    
         
            -
              recv_var_type: " 
     | 
| 
      
 1285 
     | 
    
         
            +
              recv_var_type: "receiver whose type is unconstrained variable `%s' not allowed",
         
     | 
| 
      
 1286 
     | 
    
         
            +
              type_args_more: "%s type accepts more arguments than actual %s definition",
         
     | 
| 
      
 1287 
     | 
    
         
            +
              type_args_fewer: "%s type accepts fewer arguments than actual %s definition",
         
     | 
| 
      
 1288 
     | 
    
         
            +
              type_arg_kind_mismatch: "%s type has %s argument but actual argument is %s",
         
     | 
| 
      
 1289 
     | 
    
         
            +
              type_args_no_kws: "%s type does not expect keyword arguments but actual expects keywords",
         
     | 
| 
      
 1290 
     | 
    
         
            +
              type_args_no_kw: "%s type does not expect keyword argument `%s'",
         
     | 
| 
      
 1291 
     | 
    
         
            +
              type_args_kw_mismatch: "%s type has %s keyword `%s' but actual argument is %s",
         
     | 
| 
      
 1292 
     | 
    
         
            +
              type_args_kw_more: "%s type expects keywords `%s' that are not expected by actual %s",
         
     | 
| 
      
 1293 
     | 
    
         
            +
              type_args_no_kw_rest: "%s type has no rest keyword but actual method accepts rest keywords",
         
     | 
| 
      
 1294 
     | 
    
         
            +
              type_args_kw_rest: "%s type has rest keyword but actual method does not accept rest keywords",
         
     | 
| 
      
 1295 
     | 
    
         
            +
              optional_default_type: "default value has type `%s' where type `%s' expected",
         
     | 
| 
      
 1296 
     | 
    
         
            +
              optional_default_kw_type: "default value for `%s' has type `%s' where type `%s' expected",
         
     | 
| 
      
 1297 
     | 
    
         
            +
              type_arg_block: "%s type does not expect block but actual %s takes block",
         
     | 
| 
      
 1298 
     | 
    
         
            +
              bad_block_arg_type: "block argument has type `%s' but expecting type `%s'",
         
     | 
| 
      
 1299 
     | 
    
         
            +
              non_block_block_arg: "block argument should have a block type but instead has type `%s'",
         
     | 
| 
      
 1300 
     | 
    
         
            +
              proc_block_arg_type: "block argument is a Proc; can't tell if it matches expected type `%s'",
         
     | 
| 
      
 1301 
     | 
    
         
            +
              no_type_for_symbol: "can't find type for method corresponding to `%s.to_proc'",
         
     | 
| 
       1191 
1302 
     | 
    
         
             
            }
         
     | 
| 
       1192 
1303 
     | 
    
         
             
            old_messages = Parser::MESSAGES
         
     | 
| 
       1193 
1304 
     | 
    
         
             
            Parser.send(:remove_const, :MESSAGES)
         
     |