Code Monkey home page Code Monkey logo

steep's Introduction

Steep - Gradual Typing for Ruby

Installation

Install via RubyGems.

$ gem install steep

Requirements

Steep requires Ruby 2.6 or later.

Usage

Steep does not infer types from Ruby programs, but requires declaring types and writing annotations. You have to go on the following three steps.

0. steep init

Run steep init to generate a configuration file.

$ steep init       # Generates Steepfile

Edit the Steepfile:

target :app do
  check "lib"
  signature "sig"

  library "pathname"
end

1. Declare Types

Declare types in .rbs files in sig directory.

class Person
  @name: String
  @contacts: Array[Email | Phone]

  def initialize: (name: String) -> untyped
  def name: -> String
  def contacts: -> Array[Email | Phone]
  def guess_country: -> (String | nil)
end

class Email
  @address: String

  def initialize: (address: String) -> untyped
  def address: -> String
end

class Phone
  @country: String
  @number: String

  def initialize: (country: String, number: String) -> untyped
  def country: -> String
  def number: -> String

  def self.countries: -> Hash[String, String]
end
  • You can use simple generics, like Hash[String, String].
  • You can use union types, like Email | Phone.
  • You have to declare not only public methods but also private methods and instance variables.
  • You can declare singleton methods, like self.countries.
  • There is nil type to represent nullable types.

2. Write Ruby Code

Write Ruby code with annotations.

class Person
  # `@dynamic` annotation is to tell steep that
  # the `name` and `contacts` methods are defined without def syntax.
  # (Steep can skip checking if the methods are implemented.)

  # @dynamic name, contacts
  attr_reader :name
  attr_reader :contacts

  def initialize(name:)
    @name = name
    @contacts = []
  end

  def guess_country()
    contacts.map do |contact|
      # With case expression, simple type-case is implemented.
      # `contact` has type of `Phone | Email` but in the `when` clause, contact has type of `Phone`.
      case contact
      when Phone
        contact.country
      end
    end.compact.first
  end
end

class Email
  # @dynamic address
  attr_reader :address

  def initialize(address:)
    @address = address
  end

  def ==(other)
    # `other` has type of `untyped`, which means type checking is skipped.
    # No type errors can be detected in this method.
    other.is_a?(self.class) && other.address == address
  end

  def hash
    self.class.hash ^ address.hash
  end
end

class Phone
  # @dynamic country, number
  attr_reader :country, :number

  def initialize(country:, number:)
    @country = country
    @number = number
  end

  def ==(other)
    # You cannot use `case` for type case because `other` has type of `untyped`, not a union type.
    # You have to explicitly declare the type of `other` in `if` expression.

    if other.is_a?(Phone)
      # @type var other: Phone
      other.country == country && other.number == number
    end
  end

  def hash
    self.class.hash ^ country.hash ^ number.hash
  end
end

3. Type Check

Run steep check command to type check. πŸ’‘

$ steep check
lib/phone.rb:46:0: MethodDefinitionMissing: module=::Phone, method=self.countries (class Phone)

You now find Phone.countries method is not implemented yet. πŸ™ƒ

Prototyping signature

You can use rbs prototype command to generate a signature declaration.

$ rbs prototype rb lib/person.rb lib/email.rb lib/phone.rb
class Person
  @name: untyped
  @contacts: Array[untyped]
  def initialize: (name: untyped) -> Array[untyped]
  def guess_country: () -> untyped
end

class Email
  @address: untyped
  def initialize: (address: untyped) -> untyped
  def ==: (untyped) -> untyped
  def hash: () -> untyped
end

class Phone
  @country: untyped
  @number: untyped
  def initialize: (country: untyped, number: untyped) -> untyped
  def ==: (untyped) -> void
  def hash: () -> untyped
end

It prints all methods, classes, instance variables, and constants. It can be a good starting point to writing signatures.

Because it just prints all defs, you may find some odd points:

  • The type of initialize in Person looks strange.
  • There are no attr_reader methods extracted.

Generally, these are by our design.

rbs prototype offers options: rbi to generate prototype from Sorbet RBI and runtime to generate from runtime API.

Guides

There are some guides in the guide directory. I know we need more comprehensive set of documentations. Just started writing docs.

Examples

You can find examples in smoke directory.

IDEs

Steep implements some of the Language Server Protocol features.

Other LSP supporting tools may work with Steep where it starts the server as steep langserver.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake test to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/soutaro/steep.

steep's People

Contributors

alpaca-tc avatar alts avatar amatsuda avatar bamchoh avatar dependabot-preview[bot] avatar dependabot[bot] avatar hanachin avatar iliabylich avatar kachick avatar ksss avatar kuredev avatar kymmt90 avatar kyoto7250 avatar mame avatar matsubara0507 avatar meganemura avatar mergecat[bot] avatar nishidayuya avatar paradoxv5 avatar pocke avatar sevenc-nanashi avatar soutaro avatar stereobooster avatar takahashim avatar tk0miya avatar wata727 avatar willnet avatar ybiquitous avatar yuuki77 avatar zachhaitz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

steep's Issues

`steep check` fails with Unexpected error on a code that uses numbered block parameters

Overview

When you use numbered block parameters inside your procs in your code the command steep check fails on code-parsing step (as i understand).

steep: 0.39.0
ruby: 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin20]

Example

# lib/simple_script.rb

[''].each { puts _1 }
# Steepfile

target :lib do
  signature "sig" # empty directory
  check "lib"
end
Gemfile
 # frozen_string_literal: true

source "https://rubygems.org"

gem 'rbs'
gem 'steep'
Gemfile.lock
GEM
  remote: https://rubygems.org/
  specs:
    activesupport (6.1.0)
      concurrent-ruby (~> 1.0, >= 1.0.2)
      i18n (>= 1.6, < 2)
      minitest (>= 5.1)
      tzinfo (~> 2.0)
      zeitwerk (~> 2.3)
    ast (2.4.1)
    ast_utils (0.3.0)
      parser (~> 2.4)
      thor (>= 0.19)
    concurrent-ruby (1.1.7)
    ffi (1.14.2)
    i18n (1.8.5)
      concurrent-ruby (~> 1.0)
    language_server-protocol (3.15.0.1)
    listen (3.3.3)
      rb-fsevent (~> 0.10, >= 0.10.3)
      rb-inotify (~> 0.9, >= 0.9.10)
    minitest (5.14.2)
    parser (2.7.2.0)
      ast (~> 2.4.1)
    rainbow (3.0.0)
    rb-fsevent (0.10.4)
    rb-inotify (0.10.1)
      ffi (~> 1.0)
    rbs (1.0.0)
    steep (0.39.0)
      activesupport (>= 5.1)
      ast_utils (~> 0.3.0)
      language_server-protocol (~> 3.15.0.1)
      listen (~> 3.0)
      parser (~> 2.7.0)
      rainbow (>= 2.2.2, < 4.0)
      rbs (~> 1.0.0)
    thor (1.0.1)
    tzinfo (2.0.4)
      concurrent-ruby (~> 1.0)
    zeitwerk (2.4.2)

PLATFORMS
  x86_64-darwin-20

DEPENDENCIES
  rbs
  steep

BUNDLED WITH
   2.2.3

➜ bundle exec steep check

[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb] Unexpected error: #<RuntimeError: #synthesize should return an instance of Pair: Array, node=s(:numblock,
  s(:send,
    s(:array,
      s(:str, "")), :each), 1,
  s(:send, nil, :puts,
    s(:lvar, _1@1)))>
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2197:in `block (2 levels) in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   <internal:kernel>:90:in `tap'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2193:in `block in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:649:in `synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:83:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:95:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:118:in `parse'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:94:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:227:in `block (2 levels) in run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:226:in `block in run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:110:in `block (4 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:109:in `block (3 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:182:in `load_signatures'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:108:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:107:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:106:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:19:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:18:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/check.rb:27:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/cli.rb:91:in `process_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/cli.rb:50:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/exe/steep:11:in `<top (required)>'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/bin/steep:23:in `load'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/bin/steep:23:in `<top (required)>'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `load'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `kernel_load'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:28:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:497:in `exec'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:30:in `dispatch'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:24:in `start'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/exe/bundle:49:in `block in <top (required)>'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/exe/bundle:37:in `<top (required)>'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/bin/bundle:23:in `load'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [lib/script.rb], validate_signatures: true)] [path=lib/script.rb]   /Users/exclusive/.rbenv/versions/3.0.0/bin/bundle:23:in `<main>'
[Steep 0.39.0] [target=lib] Unexpected error: #<RuntimeError: #synthesize should return an instance of Pair: Array, node=s(:numblock,
  s(:send,
    s(:array,
      s(:str, "")), :each), 1,
  s(:send, nil, :puts,
    s(:lvar, _1@1)))>
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2197:in `block (2 levels) in synthesize'
[Steep 0.39.0] [target=lib]   <internal:kernel>:90:in `tap'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2193:in `block in synthesize'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/type_construction.rb:649:in `synthesize'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:83:in `type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:95:in `block in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:118:in `parse'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:94:in `type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:227:in `block (2 levels) in run_type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:226:in `block in run_type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `each'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `run_type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:110:in `block (4 levels) in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:109:in `block (3 levels) in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:182:in `load_signatures'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:108:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:107:in `block in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/project/target.rb:106:in `type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:19:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:18:in `block in type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `each'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `type_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/drivers/check.rb:27:in `run'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/cli.rb:91:in `process_check'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/lib/steep/cli.rb:50:in `run'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/steep-0.39.0/exe/steep:11:in `<top (required)>'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/bin/steep:23:in `load'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/bin/steep:23:in `<top (required)>'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `load'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:63:in `kernel_load'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli/exec.rb:28:in `run'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:497:in `exec'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/command.rb:27:in `run'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/invocation.rb:127:in `invoke_command'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor.rb:392:in `dispatch'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:30:in `dispatch'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/vendor/thor/lib/thor/base.rb:485:in `start'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/cli.rb:24:in `start'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/exe/bundle:49:in `block in <top (required)>'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/lib/bundler/friendly_errors.rb:130:in `with_friendly_errors'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/bundler-2.2.3/exe/bundle:37:in `<top (required)>'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/bin/bundle:23:in `load'
[Steep 0.39.0] [target=lib]   /Users/exclusive/.rbenv/versions/3.0.0/bin/bundle:23:in `<main>'

Error: Unexpected node: s(:restarg, _args@1)

steep check can raise #<RuntimeError: Unexpected node: s(:restarg, _args@1), 2> when using splat args.

Steps to reproduce

Create a file called x.rb:

class Hi
  def method_missing(*_args)
    self
  end
end

Run steep check x.rb:

#<RuntimeError: Unexpected node: s(:restarg, _args@1), 2>
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:598:in `synthesize'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:301:in `block in synthesize'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:737:in `block in each_child_node'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:735:in `each'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:735:in `each_child_node'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:300:in `synthesize'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:443:in `block in synthesize'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:442:in `tap'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:442:in `synthesize'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:65:in `block in run'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:44:in `each'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:44:in `run'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/cli.rb:76:in `process_check'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/cli.rb:41:in `run'
  /usr/local/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/exe/steep:11:in `<top (required)>'
  /usr/local/bin/steep:23:in `load'
  /usr/local/bin/steep:23:in `<main>'

Rails Type Definitions

Is Rails type definitions planed to add?

For example:

If these types will be pre-defined, I predict Steep's users will increase.
Because defining these types is very hard task. πŸ˜“

I would be glad if we could discuss about this theme! πŸ˜„

`@as`(value casting in TypeScript) needed

I think Steep needs "value casting", which is equivalent to as in TypeScript.

@as is an annotation for a new "value typecast" rather than the currently existing "variable definition modification" (@type var foo: T).

    # @type var parts: {a: Integer, b: Integer}
    parts =
      # @as: {a: Integer, b: Integer}
      {}

The @as chain should also be possible.

    # @type var myvar2: MyType
    myvar2 =
      # @as: Integer
      # @as: untyped
      myvar

A concrete use case is to construct a hash that is sure to contain a particular key without any initial value.

In the following Foo#bar, we want to return a hash {a: Integer, b: Integer} of the result of the calculation for each default key. (This is a simplification of the original implementation of ActiveSupport::Duration.build)

class Foo
  KEYS = [:a, :b]

  def initialize()
    @other = {a: 1, b: 2}
  end

  def bar() # returns {a: Integer, b: Integer}
    parts = {}
    KEYS.each do |k|
      parts[k] = calc(@other[k])
    end
    parts
  end

  def calc(value)
    value * 2
  end
end

I think it's reasonable that the type should look like this

class Foo
  KEYS: [:a, :b]
  @other: {a: Integer, b: Integer}
  def initialize: () -> untyped
  def bar: () -> {a: Integer, b: Integer}
  def calc: (Integer) -> Integer
end

But this will of course throw an error, because parts will be Hash[untyped, untyped].

lib/foo.rb:7:2: MethodBodyTypeMismatch: method=bar, expected={ :a => ::Integer, :b => ::Integer }, actual=::Hash[untyped, untyped] (def bar())
  ::Hash[untyped, untyped] <: { :a => ::Integer, :b => ::Integer }
==> ::Hash[untyped, untyped] <: { :a => ::Integer, :b => ::Integer } does not hold

So add type annotations to parts.

  def bar()
    # @type var parts: {a: Integer, b: Integer}
    parts = {}

But now {} is Hash[untyped, untyped], so parts = {} throws an error.

lib/foo.rb:11:4: IncompatibleAssignment: lhs_type={ :a => ::Integer, :b => ::Integer }, rhs_type={  } (parts = {})
  {  } <: { :a => ::Integer, :b => ::Integer }
   nil <: ::Integer
==> nil <: ::Integer does not hold

This can be solved by putting an initial value like parts = {a: 0, b: 0}, but this is not a natural implementation.

It is not appropriate to change the implementation for types in such obvious cases, and it would be ideal to be able to type with parts = {}.

Gradual typing into dynamically typed languages is for humans, so I think the very annoying hassle of "having to tweak the implementation to get rid of type errors" defeats the purpose. (changing the implementation may require retesting!)

TypeScript provides a workaround for such a case by using as, which can cast to a value even if it is incorrect.

A similar implementation in TypeScript can be written as follows

class Foo {
  static KEYS = ["a", "b"]

  other: {a: number; b: number}

  constructer() {
    this.other = {a: 1, b: 2}
  }

  bar(): {a: number; b: number} {
    const parts = {}
    for (const k of Foo.KEYS) {
      parts[k] = this.calc(this.other[k])
    }
    return parts
  }

  calc(value: number) {
    return value * 2
  }
}

As in ruby, the error is that {} (roughly equivalent to Hash[untyped, untyped]) and {a: number; b: number} are mismatched.

lib/foo.ts:15:5 - error TS2739: Type '{}' is missing the following properties from type '{ a: number; b: number; }': a, b

15     return parts
       ~~~~~~~~~~~~

You can also specify the variable type of parts as a workaround.

  bar(): {a: number; b: number} {
    const parts: {a: number; b: number} = {}

However, as with Ruby, you will get an error that {} cannot be assigned to parts.

lib/foo.ts:11:11 - error TS2739: Type '{}' is missing the following properties from type '{ a: number; b: number; }': a, b

11     const parts: {a: number; b: number} = {}
             ~~~~~

However, TypeScript has as.

  bar(): {a: number; b: number} {
    const parts: {a: number; b: number} = {} as {a: number; b: number}

This will turn {} into {a: number; b: number} (which is different from what it actually is), and there will be no errors.

In addition, TypeScript type checking supports the as equivalent in the form of /** @type {T} */(val), even if type annotations are added as in JavaScript syntax.

// @ts-check

class Foo {
  /** @type {["a", "b"]} */
  static KEYS = ["a", "b"]

  constructer() {
    /** @type {{a: number; b: number}} */
    this.other = {a: 1, b: 2}
  }

  /**
   * @return {{a: number; b: number}}
   */
  bar() {
    /** @type {{a: number; b: number}} */
    const parts = /** @type {{a: number; b: number}} */({})
    for (const k of Foo.KEYS) {
      parts[k] = this.calc(this.other[k])
    }
    return parts
  }

  /**
   * @param {number} value 
   */
  calc(value) {
    return value * 2
  }
}

In Ruby, range comments are difficult to use, so it is difficult to define them in the same way, but I think it is possible to solve the problem by adding annotations to the value side as follows.

    # @type var parts: {a: Integer, b: Integer}
    parts =
      # @as: {a: Integer, b: Integer}
      {}

Note that as in TypeScript cannot be converted to completely incompatible types. However, it is often the case that a completely incompatible cast mystring as any as number (in JavaScript, /** @type {number} */(/** @type {any} */(mystring))) is required due to, for example, incomplete type definitions in libraries. If this is not possible, there will be no workaround for typing errors in the library.

So, if you have a similar specification for as, you also need a chain of as, such as myvar as any as number, which should be defined in two consecutive lines.

    # @type var myvar2: MyType
    myvar2 =
      # @as: Integer
      # @as: untyped
      myvar

alias breaks steep

class Test
  alias foo bar
end

Running steep on this file results in an error:

$ ruby2.6 -S bundle exec steep check lib/test.rb 
#<RuntimeError: Unexpected node: s(:alias,
  s(:sym, :foo),
  s(:sym, :bar)), lib/test.rb:2:3>
  vendor/bundle/ruby/2.6.0/gems/steep-0.10.0/lib/steep/type_construction.rb:1574:in `block in synthesize'
  [... more backtrace ...]

Can check the type of return value?

I expect this code won't pass steep check because the type of return value is not a string but nil.

# rbs
class Object
  def fizz_buzz: (Integer) -> String
end
def fizz_buzz(n)
  nil
end

However, steep check passes. Is it possible to check the return value?

just wanted to say Thanks!

Thanks for creating this project; I had a similar idea years ago (probably a decade now) to add gradual typing with interface files to PHP. Very cool idea!

Have you tried using steep with a large-scale Ruby on Rails codebase?

`steep check` raise ArgumentTypeMismatch for proc type

overview

When input type and output type of proc is defined in rbs file, steep raises ArgumentTypeMismatch error. It seems that steep cannot check input and output of the proc.

example

#app.rb
class App
  def baz(fn)
    fn.call(5)
  end
end

proc1 = Proc.new { |n| n.to_s }

app = App.new
p app.baz(proc1)
#app.rbs
class App
  def baz: ( ^(Integer) -> (String)) -> untyped
end

$steep check
-> lib/app.rb:10:10: ArgumentTypeMismatch: receiver=::App, expected=^(::Integer) -> ::String, actual=::Proc (proc1)

Enhancement: Move type declartions directly into .rb files

Gentle petition to keep types as close to the variable declarations as possible. Done in the right way, this provides a wonderful balance of power and reliability.

For example, in Haskell, types do most of the work. That's for an ML-style language with functors and monads, where types are essential for higher quality, self-documenting and self-testing work (see QuickCheck).

For Ruby, an OOPL, types are potentially even more powerful, as the little . dot operator unlocks a directory of what methods and transformations to do next on a value. The programmer can simply build up programs from method chains and see instantly which methods are available for use. All powered by types.

I think it is easy to come up with a bad type system, even an ugly one. But Ruby is already an excellent language and deserves a Haskell-like type system: Types should be optional, and the interpre-compiler should merely spot check that the programmer is naming valid types.

Arrays and Tuples

Not sure whether this is a parser problem or some failure on my part how to express the correct return type. But here goes.

Method transform is used to parse array elements, i.e. typically called via .map(&method(:transform)). The following extract is a simplification:

 def transform(result, index)
    score = result[:finished] ? result[:score] : result[:tries]
    result[:tries].nil? ? [index, nil] : [index, score.to_s]
  end

# type signature
def transform: (Hash::o result, Integer index) -> Array[[Integer, (String | nil)]]

I can't see how transform can return anything other than a two-element array, the first element always an Integer and the second either a String or nil

However testing this method in isolation from where it's called returns the error:

MethodBodyTypeMismatch: method=transform, expected=::Array[[::Integer, (::String | nil)]], actual=(::Array[(::Integer | [::Integer, (::String | nil)])] | ::Array[(::Integer | nil)]) (def transform(result, index)) (::Array[(::Integer | [::Integer, (::String | nil)])] | ::Array[(::Integer | nil)]) <: ::Array[[::Integer, (::String | nil)]] ::Array[(::Integer | nil)] <: ::Array[[::Integer, (::String | nil)]] (::Integer | nil) <: [::Integer, (::String | nil)] nil <: [::Integer, (::String | nil)] ==> nil <: [::Integer, (::String | nil)] does not hold

This suggests the parser expects an outcome where a one-element array can be returned?

The following signatures also fail:

# try expanding the definition of expected returned elements:
def transform: (Hash::o result, Integer index) -> (Array[[Integer, nil]] | Array[[Integer, String]])

# try simplifying the Tuple
def transform: (Hash::o result, Integer index) -> Array[[Integer, untyped]]

But these also produce errors, In fact, the only signature that appears to parse without error is:

def transform: (Hash::o result, Integer index) -> Array[untyped]

But this is more or less giving up? Apologies if I'm missing something, but I'm out of ideas on where exactly I'm going wrong...

Duplicated class errors

Hello,

I was just wondering if it's possible to define signatures for duplicate classes (mostly ones already defined in Steep for stdlib).

Example:

# types.rbi
class Module
  def some_method: -> void
end
# some_method.rb
class Module
  def some_method; end
end
$ steep check -I types.rbi some_method.rb
#<RuntimeError: Duplicated class: ::Module>
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/ast/signature/env.rb:31:in `add'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:131:in `block (3 levels) in reload_signature'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:130:in `each'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:130:in `block (2 levels) in reload_signature'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:125:in `each_value'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:125:in `block in reload_signature'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project/listener.rb:35:in `load_signature'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:124:in `reload_signature'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:55:in `block in type_check'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project/listener.rb:13:in `check'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/project.rb:53:in `type_check'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/drivers/check.rb:56:in `run'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/cli.rb:231:in `block in process_check'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/cli.rb:199:in `with_signature_options'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/cli.rb:209:in `process_check'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/lib/steep/cli.rb:166:in `run'
  /usr/home/adam/.rubies/ruby-2.6.3/lib/ruby/gems/2.6.0/gems/steep-0.11.0/exe/steep:11:in `<top (required)>'
  /home/adam/.rubies/latest/bin/steep:23:in `load'
  /home/adam/.rubies/latest/bin/steep:23:in `<main>'

A way to disable checking for certain line

Hi thanks for the awesome library!

Is there a way to skip steep check for certain line? πŸ€”
I have the following code in my current project.

class Foo
  def bar
    some_time = Time.current
    
    ...
  end
end
class Foo
  def bar: -> void
end

and steep check raises the following error

NoMethodError: type=singleton(::Time), method=current (Time.current)

I think this is because Time's type is defined in rbs, and Rails is extending that class without defining the method πŸ€”

Is it possible to skip this line? πŸ‘€

maybe #70 is related

Guard clauses

Steep sometimes fails to parse data filtered by a guard clause.

Suppose method signatures as follow:

def fetch: () -> (Hash[Symbol, untyped] | nil)
def parse: (String data) -> Hash[Symbol, untyped]

The following implementation of the relevant methods passes with no type-check errors:

def fetch
  response = response_body_is_json_string_or_nil
  response ? parse(response) : nil
end

def parse(data)
  JSON.parse(data, symbolize_names: true).then { ... }
end

The following implementation of the relevant methods fails checking with error: ArgumentTypeMismatch: receiver=self, expected=::String, actual=(::String | nil) (data):

def fetch
  response = response_body_is_json_string_or_nil
  parse(response) unless response.nil?
end

def parse(data)
  JSON.parse(data, symbolize_names: true).then { ... }
end

Curiously, unwrapping the parse method to separate the JSON parsing and subsequent manipulation doesn't have the same problem, e.g. the following refactoring throws no errors during checking:

def fetch: () -> (Hash[Symbol, untyped] | nil)
def parse: (Hash[Symbol, untyped] data) -> Hash[Symbol, untyped]

with implementation:

def fetch
  response = response_body_is_json_string_or_nil
  JSON.parse(response, symbolize_names: true).then { |x| parse(x) } unless response.nil? 
end

def parse(data)
  do_stuff_with_data
end

So the problem is not guard structures per se, but I can't see what the problem is

Inline Types?

Is it possible to decare types inline next to methods? I'm afraid if we separate them into separate files in our source code, it will be easy to forget to update them if we change our method signatures.

Thanks!

NoMethodError with duplicate exntensions

related #154

I met a NoMethodError but the method is actually defined by an extension.

A repository to reproduce is here https://github.com/pocke/steep-random-error

In this repository, steep check command may display NoMethodError or not. It depends on RBS file order.

# With #154  patch to fix the order

# It reports NoMethodError by default
$ steep check
lib/test.rb:2:0: NoMethodError: type=::String, method=bar ("".bar)


# But it reports nothing if I change the order of RBS file.
$ mv sig/a.rbs sig/c.rbs
$ steep check
$ echo $?
0

`steep check`: incorrectly handles visibility

Example:

# a.rb
class A
  def initialize(val)
    self.priv_foo = val
  end

  def foo
    priv_foo
  end

  private

  attr_accessor :priv_foo
end

# a.rbs
class A
  def initialize: (String) -> void
  def foo: -> String

  private

  attr_accessor priv_foo: String
end

Running Steep:

$ steep check

a.rb:3:4: NoMethodError: type=::A, method=priv_foo= (self.priv_foo = val)

Removing private from the signature fixes the issue.

`next` and `Enumerable#map` cause BreakTypeMismatch

Steep displays an error with next and Enumerable#map since v0.38.0.

Reproduce

def foo
  [1,2,3].map do |i|
    next if i.odd?

    i.to_s
  end
end
$ steep check
test.rb:3:4: BreakTypeMismatch: expected=U(1), actual=nil (next)
  nil <: U(1)
==> nil <: U(1) does not hold

It also displays the same error (but actual is different) on next '', next 1, and any argument.

I can suppress the error with @type block annotation.
For example, steep doesn't complain about the following code.

def foo
  [1,2,3].map do |i|
    # @type block: nil | String
    next if i.odd?

    i.to_s
  end
end

I guess it is a bug. I expect Steep introduces the map's returned type as nil | String form the code without annotation, but currently it needs to specify the annotation.

Environment

  • Steep v0.38.0
    • This error isn't occurred with v0.37.0.
    • So I guess #271 introduced it.

steep gem with the ruby 3.0.0 preview1 install issue

After reading The State of Ruby 3 Typing from @soutaro, I want to give a try to this new features.

But I'm stuck as installing steep gem with the ruby-3.0.0-preview1:

When I run gem install steep locally, I get this error message:

ERROR:  Error installing steep:
	There are no versions of listen (~> 3.1) compatible with your Ruby & RubyGems. Maybe try installing an older version of the gem you're looking for?
	listen requires Ruby version ~> 2.2, >= 2.2.7. The current ruby version is 3.0.0.preview1.

With the ruby-2.7.2 version, I can install it.

Is Ruby 3.0.0 preview1 should not be >= 2.2.7 ?

OS:
ruby 3.0.0preview1 (2020-09-25 master 0096d2b895) [x86_64-linux] on GNU/Linux Fedora 32 5.8.11-200.fc32.x86_64
I use rvm as ruby version manager.

Improve formatting for `steep stats`

Just a suggestion, feel free to close this.

Would be great to format the results of steep stats into something more human readable.

On the evilmartian blog post on RBS and Steep this was also a surprise to the author that the output was in CSV format.

Something more like the following would be great (author ended up using awk to format the results):

$ bundle exec steep stats --log-level=fatal | awk -F',' '{ printf "%-28s %-9s %-12s %-14s %-10s\n", $2, $3, $4, $5, $7 }'
File                         Status    Typed calls  Untyped calls  Typed %
lib/rubanok/dsl/mapping.rb   success   7            2              63.64
lib/rubanok/dsl/matching.rb  success   26           18             52.00
lib/rubanok/processor.rb     success   34           8              69.39
lib/rubanok/rule.rb          success   24           12             66.67
lib/rubanok/version.rb       success   0            0              0
lib/rubanok.rb               success   8            4              66.67

Maybe something like the following:

steep stats --format=[pretty,json,csv]

Where pretty is formatted table output like above. It should also be the default formatter and multiple formats can be supported by using --format= option.

I'm happy to work on something like this if we can agree on what it would look like.

What does everyone think?

Thanks

`steep check`: anonymous Module considered to be a part of the class

Example:

# a.rb
class A
  def foo
    "bar"
  end

  Module.new do
    def foo(bar)
      bar.size
    end
  end
end

# a.rbs
class A
  def foo: -> String
end

Running steep check results in MethodArityMismatch:

steep check
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(1:1)] [synthesize:(2:3)] [synthesize:(6:3)] [() -> ::Module] [synthesize:(7:5)] [synthesize:(7:13)] Unknown arg type: (arg bar@1)
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(1:1)] [synthesize:(2:3)] [synthesize:(6:3)] [() { (::Module) -> untyped } -> ::Module] [synthesize:(7:5)] [synthesize:(7:13)] Unknown arg type: (arg bar@1)
a.rb:7:4: MethodArityMismatch: method=foo (def foo(bar))

(The actual use-case was using an anonymous refinements within a class).

`steep check` displays a RuntimeError on implicit to_proc syntax and untyped

steep check displays a RuntimeError on implicit to_proc syntax (&) and untyped.

Step to reproduce

# test.rb

# @type var a: Array[untyped]
a = [1,2,3]
a.map { |x| x.inspect } # ok
a.map(&:inspect)        # UnexpectedError

# @type var b: Array[Integer]
b = [1,2,3]
b.map { |x| x.inspect } # ok
b.map(&:inspect)        # ok
# Steepfile
target :lib do
  check "test.rb"
end
$ steep check

[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)] Unexpected error in #type_send: Unexpected type for interface: untyped (RuntimeError)
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/ast/types/factory.rb:777:in `interface'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2085:in `block (2 levels) in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   <internal:kernel>:148:in `yield_self'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2078:in `block in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:649:in `synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:3109:in `try_method_type'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2712:in `block (4 levels) in type_method_call'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/typing.rb:206:in `new_child'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2711:in `block (3 levels) in type_method_call'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2710:in `map'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2710:in `block (2 levels) in type_method_call'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2707:in `block in type_method_call'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2706:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2706:in `flat_map'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2706:in `type_method_call'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2511:in `type_send_interface'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:2661:in `type_send'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:745:in `block (2 levels) in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   <internal:kernel>:148:in `yield_self'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:734:in `block in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:649:in `synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:659:in `block (3 levels) in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:658:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:658:in `inject'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:658:in `block (2 levels) in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   <internal:kernel>:148:in `yield_self'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:653:in `block in synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/type_construction.rb:649:in `synthesize'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:83:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:95:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:118:in `parse'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/source_file.rb:94:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:227:in `block (2 levels) in run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:226:in `block in run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:225:in `run_type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:110:in `block (4 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:109:in `block (3 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:182:in `load_signatures'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:108:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep.rb:147:in `measure'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:107:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/project/target.rb:106:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:19:in `block (2 levels) in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `block in tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:37:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/activesupport-6.1.0/lib/active_support/tagged_logging.rb:99:in `tagged'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:18:in `block in type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `each'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/drivers/utils/driver_helper.rb:17:in `type_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/drivers/check.rb:27:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/cli.rb:91:in `process_check'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/lib/steep/cli.rb:50:in `run'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/lib/ruby/gems/3.1.0/gems/steep-0.39.0/exe/steep:11:in `<top (required)>'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/bin/steep:23:in `load'
[Steep 0.39.0] [target=lib] [target#type_check(target_sources: [test.rb], validate_signatures: true)] [path=test.rb] [synthesize:(2:1)] [synthesize:(4:1)]   /home/pocke/.rbenv/versions/trunk/bin/steep:23:in `<main>'
test.rb:6:0: UnexpectedError: RuntimeError
>> Unexpected type for interface: untyped
 (a.map(&:inspect))

Versions

$ steep --version
0.39.0
$ ruby -v
ruby 3.1.0dev (2020-12-31T22:55:59Z master 3d7f71801a) [x86_64-linux]

Issue with the "in" method argument

Steep has issues with method arguments named "in". I'm getting the following error during check:

parse error on value: #<Ruby::Signature::Parser::LocatedValue:0x00007ff7fe905558 @location=#<Ruby::Signature::Location:1960 @buffer=example.rbs, @pos=27...29, source='in', start_line=2, start_column=13>, @value=:in> (kIN)

The code is:

class Example
  def test(in:)
  end
end

I'm using rbs prototype to generate signatures. Here's the generated signature:

class Example
  def test: (in: untyped `in`) -> untyped
end

Full example can be downloaded from gist.

This is a minimal example but I have this issue with Rails (ActiveModel validations to be specific) signatures.

I use Steep 0.14.0.

Mention Ruby 2.4 requirement

I tried to use steep on 2.3 and got this error:

#<NoMethodError: undefined method `transform_values' for {}:Hash>
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/interface.rb:149:in `substitute'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/interface.rb:194:in `substitute'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:91:in `block (2 levels) in instance_methods'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:91:in `map'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:91:in `block in instance_methods'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:88:in `each'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:88:in `instance_methods'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/type_assignability.rb:227:in `resolve_interface'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/signature/class.rb:344:in `validate'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/type_assignability.rb:302:in `block in validate'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/type_assignability.rb:301:in `each'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/type_assignability.rb:301:in `validate'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/type_assignability.rb:14:in `initialize'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:31:in `new'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:31:in `run'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/cli.rb:76:in `process_check'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/lib/steep/cli.rb:41:in `run'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/gems/steep-0.1.0.pre/exe/steep:11:in `<top (required)>'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/bin/steep:22:in `load'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/bin/steep:22:in `<main>'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/bin/ruby_executable_hooks:15:in `eval'
  /home/gdot/.rvm/gems/ruby-2.3.4@rublem/bin/ruby_executable_hooks:15:in `<main>'

transform_values is added in 2.4 http://blog.bigbinary.com/2017/06/14/ruby-2-4-added-hash-transform-values-and-its-destructive-version-from-active-support.html

`steep check` raises error when method from Module are used inside static method

When making use of a method from an Module inside a static method, steep check raises an error

method

class Client
  include Helpers
  def self.generate_secured_api_key(parent_key, restrictions)
        url_encoded_restrictions = to_query_string(symbolize_hash(restrictions))
       [...]
  end
end

module Helpers
 def to_query_string(params)
    params.map do |key, value|
      "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}"
    end.join('&')
  end

  def self.included(base)
     base.extend(Helpers)
  end
end

raised error

lib/algolia/search_client.rb:409:35: NoMethodError: type=singleton(::Client), method=to_query_string (to_query_string(symbolize_hash(restrictions)))

Problems when using type vars

Hello, thank you for all the efforts in creating a super nice Ruby tool! It seems an incredibly hard task, and you are definitely doing an amazing job.

I just started playing around with steep. However I was a little confused. I was hoping someone could help me with my doubts...

I used the following code for test.rb:

class Effect
  def initialize(callable)
    @callable = callable
  end

  def lazy(arg1, arg2)
    -> { @callable.(arg1, arg2) }
  end
end

module MyModule
  def self.func(a, b)
    a * b
  end
end

# @type var effect: ^() -> Array[String | Integer]
effect = Effect.new(MyModule.method(:func)).lazy([1, "a"] , 2)
puts "Effect: #{effect.()}" # => [1, "a", 1, "a"]

and the following signature in the test.rbs:

class Effect[T, R, S]
  def initialize: (^(T, R) -> S) -> void
  def lazy: (T, R) -> (^() -> S)
end

interface _Prod
  def *: (Numeric) -> _Prod
end

module MyModule
  def self.func: (_Prod, Numeric) -> _Prod
end

The code runs nicely, however when I try to type check it, I receive the following error

% steep check
lib/test.rb:18:20: ArgumentTypeMismatch: receiver=singleton(::Effect), expected=^(T(1), R(2)) -> S(3), actual=::Method (MyModule.method(:func))
lib/test.rb:18:49: ArgumentTypeMismatch: receiver=::Effect[T, R, S], expected=T, actual=::Array[(::Integer | ::String)] ([1, "a"])
lib/test.rb:18:0: IncompatibleAssignment: lhs_type=^() -> ::Array[(::Integer | ::String)], rhs_type=^() -> S (effect = Effect.new(MyModule.method(:func)).lazy([1, "a"] , 2))
  ^() -> S <: ^() -> ::Array[(::Integer | ::String)]
   S <: ::Array[(::Integer | ::String)]
==> S <: ::Array[(::Integer | ::String)] does not hold

Am I doing something wrong? I quite don't understand the errors:

  1. I kind of hoped method objects could be used interchangeably with procs... Is there any syntax, or way to represent a generic type that can be used for both procs and methods?
  2. I don't get exactly the mismatch on the type vars... I thought they were going to be replaced by the analyser with the concrete type, not checked against class hierarchy... Even when checked, a type var should always satisfy hierarchy checks, isn't it?

Lastly, my goal would be replacing arg1, arg2 in the method definition an proc call by *args and have the type checker matching the type of the list of arguments with the list of arguments of callable... I understand that this is a bit advanced and might not be supported right now, however I don't even know how to annotate the type properly...

According to the example in the document defining the grammar, it seems that typing for splat operations can only cover the case where the arguments are uniformly typed... In my example, the argument list is not uniform ([_Prod, Numeric]). Is there a way to specify a type var for the splat as a whole instead of specifying a single type for every individual element of the splat?

Thank you in advance for any hints πŸ˜„

Option or configuration to exclude paths

Hey! I am trying to adopt steep for a gem that includes generators. However, running steep check breaks on the generator's template files with the following error:

#<Parser::SyntaxError: unterminated string meets end of file>

Template files really don't need to be type checked, so I was wondering if there's a way to exclude certain paths from steep check either by a configuration file (say something like .steep.yml or by a command line argument).

I suspect the changes to exclude paths would have to be done somewhere in here. Let me know your ideas and I can put a pull request together if you wish to implement this.

Thanks!

`Array<Integer>` and `Array<String>` are assignable

If two names are identical, TypeAssignability#test_interface returns true with no check of method signatures. Steep says nothing for the following program. Is this intended?

# @type var a: Array<Integer>
# @type var b: Array<String>

a = []
b = a
a = b

Bug: #<NoMethodError: undefined method `type' for nil:NilClass>

class A; end
A.new.echo
steep check test4.rb
#<NoMethodError: undefined method `type' for nil:NilClass>
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:169:in `synthesize'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:443:in `block in synthesize'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:442:in `tap'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:442:in `synthesize'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:172:in `block in synthesize'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:737:in `block in each_child_node'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:735:in `each'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:735:in `each_child_node'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:171:in `each'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:171:in `map'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/type_construction.rb:171:in `synthesize'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:65:in `block in run'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:44:in `each'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/drivers/check.rb:44:in `run'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/cli.rb:76:in `process_check'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/lib/steep/cli.rb:41:in `run'
  /opt/boxen/rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/steep-0.1.0.pre/exe/steep:11:in `<top (required)>'
  /opt/boxen/rbenv/versions/2.4.0/bin/steep:22:in `load'
  /opt/boxen/rbenv/versions/2.4.0/bin/steep:22:in `<main>'

https://github.com/stereobooster/diamondback-ruby/tree/master/examples#4

Missing commit of vendored ruby-signature

I'm trying to use steep with the HEAD of the master branch, but it does not work well.
Because it cannot find the commit of vendored ruby-signature submodule.

9b3e5fae4d9fa414a1ed6f5027d8ce83a39701ca is specified as the submodule commit, and we can find it from GitHub.
https://github.com/ruby/ruby-signature/tree/9b3e5fae4d9fa414a1ed6f5027d8ce83a39701ca

But the master branch does not include the commit. So git submodule update --init fails.

$ git submodule update --init
error: Server does not allow request for unadvertised object 9b3e5fae4d9fa414a1ed6f5027d8ce83a39701ca
Fetched in submodule path 'vendor/ruby-signature', but it did not contain 9b3e5fae4d9fa414a1ed6f5027d8ce83a39701ca. Direct fetching of that commit failed.

Could you update the submodule?

`steep check` fails with no messages

Versions

  • Steep 0.17.1
  • RBS 0.4.0

Concrete example

https://github.com/tadd/exp-steep-net-http (including --verbose log files)

Details

I wrote a signature net-http.rbs:

class Net::HTTP
  def self.start: (String, Integer) { (instance) -> void } -> void
end

And an implementation app.rb:

require 'net/http'

class App
  def m
    # invalid call
    Net::HTTP.start
  end
end

Then, I executed a command bundle exec steep check app.rb but steep command failed without any outputs.

$ bundle exec steep check app.rb
$ echo $?
1

It runs well if I commented out the Net::HTTP.start line.

$ bundle exec steep check app.rb
$ echo $?
0

I tried the --verbose option in each run. The output message slightly differed.

-[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] type checking 1 files took 0.023457556 seconds
-[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] load signature and type check took 6.499966611 seconds
-[Steep 0.17.1] No type error found
+[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] [synthesize:(1:1)] [synthesize:(3:1)] [synthesize:(4:3)] [synthesize:(6:5)] send
+[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] [synthesize:(1:1)] [synthesize:(3:1)] [synthesize:(4:3)] [synthesize:(6:5)] [synthesize:(6:5)] const
+[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] type checking 1 files took 0.010424531 seconds
+[Steep 0.17.1] [target=root] [target#type_check(target_sources: [app.rb], validate_signatures: true)] load signature and type check took 6.561480807 seconds

I doubt this is an internal bug of Steep.

`steep check` displays `UnexpectedJump` for `retry` syntax with correct usage

steep check displays UnexpectedJump for retry syntax even if the retry is used with correct usage.
For example:

# test.rb

begin
rescue
  retry
end
# Steepfile
target :lib do
  check 'test.rb'
end
$ steep --version
0.39.0
$ steep check
test.rb:5:2: UnexpectedJump (retry)

The retry is correct because retry is allowed only in a rescue clause.
So I expect no error but steep check reports an error.

Because it requires break_context for retry, but rescue clauses doesn't have break_context.

unless break_context
typing.add_error Errors::UnexpectedJump.new(node: node)
end

A value of a less derived type can be assigned to a variable of a more derived type

bundle exec exe/steep check -I t.rbi t.rb does say nothing against the following code. Is this intended?

t.rbi

class A
  def foo: -> any
end

class B
  def foo: -> any
  def bar: -> any
end

t.rb

class A
  # @implements A

  def foo()
  end
end

# @type var b: B
b = A.new
b.bar

(I found this behavior during investigating covariance of tuple. Is there a good example of covariance in steep sample code?)

`steep check` raises errors for Forwardable

When using the Forwardable extension it seems like steep check doesn't recognize the def_delegators method and then the expected usage of Forwardable inside the class

Ruby Class

    class Client
      extend Forwardable

      def_delegators :@transporter, :read, :write

      def initialize(search_config, opts = {})
        ...
        @transporter = Transport::Transport.new(@config, requester)
      end

[...]
      def get_task_status(index_name, task_id, opts = {})
        res = read(:GET, path_encode('/1/indexes/%s/task/%s', index_name, task_id), {}, opts)
        get_option(res, 'status')
      end
end

Raised errors

lib/algolia/search_client.rb:13:6: NoMethodError: type=singleton(::Client), method=def_delegators (def_delegators :@transporter, :read, :write)
lib/algolia/search_client.rb:81:14: NoMethodError: type=::Client, method=read (read(:GET, path_encode('/1/indexes/%s/task/%s', index_name, task_id), {}, opts))

Blocks, Procs and Methods

Moved from ruby/rbs#298.

Thanks @timhatch for reporting an issue. I think it's a problem more related to Steep than RBS and moved to here. The question is the following code doesn't work well with Steep.

  def by_country(person)
    person.slice(:country, :lastname, :id).values
  end
  
  my_array.sort_by(&method(:by_country))

Strictness of Hash assignment to Record type within block

Let's say I have the following example:

# @type var a: [ 1,2,3 ]
# @type var b: Array[{ a: Integer, b: bool }]
a = [1, 2, 3]
b = a.map { |n| { a: n, b: true } }

I would get the following error:

test.rb:4:0: IncompatibleAssignment: lhs_type=::Array[{ :a => ::Integer, :b => bool }], rhs_type=::Array[::Hash[::Symbol, (1 | 2 | 3 | bool)]] (b = a.map { |n| { a: n, b: true } })
  ::Array[::Hash[::Symbol, (1 | 2 | 3 | bool)]] <: ::Array[{ :a => ::Integer, :b => bool }]
   ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool }
==> ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool } does not hold

It would be nice if steep could infer the record type here, or if I could at least make an assertion. If I try the following

# @type var a: [ 1,2,3 ]
# @type var b: Array[{ a: Integer, b: bool }]
a = [1, 2, 3]
b = a.map do |n|
  # @type var item: { a: Integer, b: bool }
  item = { a: n, b: true }
end

I would get this error:

test.rb:6:2: IncompatibleAssignment: lhs_type={ :a => ::Integer, :b => bool }, rhs_type=::Hash[::Symbol, (1 | 2 | 3 | bool)] (item = { a: n, b: true })
  ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool }
==> ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool } does not hold
test.rb:4:0: IncompatibleAssignment: lhs_type=::Array[{ :a => ::Integer, :b => bool }], rhs_type=::Array[::Hash[::Symbol, (1 | 2 | 3 | bool)]] (b = a.map do |n|)
  ::Array[::Hash[::Symbol, (1 | 2 | 3 | bool)]] <: ::Array[{ :a => ::Integer, :b => bool }]
   ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool }
==> ::Hash[::Symbol, (1 | 2 | 3 | bool)] <: { :a => ::Integer, :b => bool } does not hold

But interestingly if I did this assignment outside of a block:

# @type var item: { a: Integer, b: bool }
item = { a: 1, b: true }

it would be OK.

Thanks!

Steep 0.27.0
Ruby 2.7.1
Rbs 0.11.0

NoMethodError if too many type arguments passed for a type

If you define a class which takes type arguments, and then pass it too many type arguments, Steep throws a NoMethodError from something being nil when using steep check.

For example, using this RBS:

module Example
  class Foo[X]
  end

  def example: (Foo[String, Integer] x) -> void
end

Raises this error:

[Steep 0.27.0] [target=lib] Unexpected error: #<NoMethodError: undefined method `variance' for nil:NilClass>
[Steep 0.27.0] [target=lib]   /home/aaron/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/rbs-0.11.0/lib/rbs/variance_calculator.rb:124:in `block in type'

Passing too few type arguments (such as by just using Foo) will result in a proper InvalidTypeApplicationError type error, which is probably what passing too many type arguments should also do.

`steep check`: UnexpectedJumpValue for `next x`

Hey!

Here is an example:

# @type method foo: () -> String
def foo
  # @type block: String
  "bar".then do |str|
    next str.reverse if str == "bar"

    str
  end
end

Gives me:

$ steep check
a.rb:5:4: UnexpectedJumpValue (next str.reverse)

When annotating a block outside of a method, it works:

# @type block: String
"bar".then do |str|
  next str.reverse if str == "bar"

  str
end
$ steep check
# all goood

Debug output:

[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] def
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] block
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [synthesize:(4:3)] str
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] Factory#interface: ::String, private=false, self_type=::String
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(4:18)] procarg0
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] begin
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] if
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] send
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [synthesize:(5:25)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] Factory#interface: ::String, private=false, self_type=::String
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [(untyped) -> bool] [synthesize:(5:32)] str
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [(untyped) -> bool] check_relation: self:::Object |- ::String <: untyped
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [(untyped) -> bool] [::String <: untyped] result=Steep::Subtyping::Result::Success
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] next
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:10)] send
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:10)] [synthesize:(5:10)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:10)] Factory#interface: ::String, private=false, self_type=::String
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [synthesize:(5:5)] [synthesize:(7:5)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] check_relation: self:::Object |- ::String <: X(1)
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [::String <: X(1)] result=Steep::Subtyping::Result::Success
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [[X] () { (::String) -> X } -> X] [::String <: βŸ™] result=Steep::Subtyping::Result::Success
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(4:18)] procarg0
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] begin
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] if
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] send
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [synthesize:(5:25)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:25)] [synthesize:(5:32)] str
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] next
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:10)] send
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:5)] [synthesize:(5:10)] [synthesize:(5:10)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [synthesize:(4:3)] [() -> ::Enumerator[::String, untyped]] [synthesize:(5:5)] [synthesize:(7:5)] lvar
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] check_relation: self:::Object |- ::String <: ::String
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] [path=a.rb] [synthesize:(2:1)] [::String <: ::String] result=Steep::Subtyping::Result::Success
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] type checking 1 files took 0.019494 seconds
[Steep 0.36.0] [target=lib] [target#type_check(target_sources: [a.rb], validate_signatures: true)] load signature and type check took 1.813167 seconds
a.rb:5:4: UnexpectedJumpValue (next str.reverse)

Support: `Thread.new {}` raises UnexpectedBlockGiven

Hey @soutaro, thanks for your hard work on thisπŸ™

Wasn't sure where to ask for support so posting a question here. I'm trying to use steep to type check one of my ruby libraries
but running into an issue with Thread.new {}.

Seems signature maybe incorrect (or more likely I am misunderstanding how RBS works). I have the following code:

Thread.new do
  ...
end

and steep raises:

Detected 1 problems from 1 files

lib/prb/timer_control.rb:11:17: [error] The method cannot be called with a block
β”‚ Diagnostic ID: Ruby::UnexpectedBlockGiven
β”‚
β””       Thread.new do

How do I go about resolving the issue?

Thank you

`steep check` raise "NoMethodError: undefined method `each' for nil:NilClass"

Hi. I encounter an NoMethodError when using Enumerable#each_with_object.
Please see the following reproduction for details.

Reproduction

Ruby code:

class Foo
  def foo(hash)
    hash.each_with_object({}) do |(key, value), new_hash|
      new_hash[key] = value || ""
    end
  end
end

pp Foo.new.foo({ "foo" => 1, "bar" => nil })
#=> {"foo"=>1, "bar"=>""}

RBS code:

class Foo
  def foo: (Hash[untyped, untyped]) -> Hash[untyped, untyped]
end

steep check raises a NoMethodError:

$ bundle exec steep check
...
foo.rb:3:4: UnexpectedError: NoMethodError
>> undefined method `each' for nil:NilClass
 (hash.each_with_object({}) do |(key, value), new_hash|)

This reproduction is available on Gist:
https://gist.github.com/ybiquitous/48139def8bead0f21d9cd140b7ad43ba

Environment

  • Steep: b256507 (master)
  • Ruby: ruby 2.7.2p137 (2020-10-01 revision 5445e04352) [x86_64-darwin19]

Incompatible Assignment for Tuple of ancestors and Array with child types

Imagine I have the following example, this works great:

# @type var a: [Integer, Float]
a = [0, 1.0]

I could also do something like this, and it works:

# @type var a: Numeric
# @type var b: Numeric
a, b = 0, 1.0

I would like to be able to adapt the first example to be a bit more flexible, like this:

# @type var a: [Numeric, Numeric]
a = [0, 1.0]

But this does not work πŸ˜• Unless this behaviour is intentional?

test.rb:2:0: IncompatibleAssignment: lhs_type=[::Numeric, ::Numeric], rhs_type=::Array[(::Float | ::Integer)] (a = [0, 1.0])
  ::Array[(::Float | ::Integer)] <: [::Numeric, ::Numeric]
==> ::Array[(::Float | ::Integer)] <: [::Numeric, ::Numeric] does not hold

steep: 0.25.0
rbs: 0.10.0
ruby: 2.7.1

I upgraded from 0.24.0 to 0.25.0 when I saw #181 but unfortunately the behaviour was still the same

`steep check` silently passes even if no checked files or no signatures

Hello, thanks for the nice gem!

I found a bit weird behavior of the steep check command, so I've opened this issue.

When only the following Steepfile is present in a directory,

# Steep file
target :lib do
  # `sig/` does not exist.
  signature "sig"

  # `lib/` does not exist.
  check "lib"

  # `app/models/**/*.rb` pattern does not match with any files.
  check "app/models/**/*.rb"
end

then steep check silently passes (without any output):

$ steep check
(nothing)

I felt this behavior is not a bit kind.
I think it would be more useful that Steep would tell us non-present files or directories.
(that is, a user specifies wrong files or directories due to typo etc.)

For example:

$ steep check
Signature `sig` does not exist.
Checked `lib` does not exist.
Checked `app/models/**/*.rb` does not exist.

$ echo $?
1

Environment:

$ steep --version
0.28.0

`steep check`: &nil is treated as block

Example:

# a.rb
class A
  def foo(val)
    val
  end
end

class B < A
  def foo(val, &block)
    block.call super(val, &nil)
  end
end

# a.rbs
class A
  def foo: (String) -> String
end

class B < A
  def foo: (String) { (String) -> String} -> String
end

Running Steep check:

$ steep check

a.rb:9:15: UnexpectedBlockGiven: method_type=(::String) -> ::String (super(val, &nil))

Removing &nil works with steep check but breaks when using rbs/test/setup (with UnexpectedBlockError: unexpected block is given for ...).

Unexpected node error when using self-assignment

Summary

steep check raises the following error when using self-assignment:

#<RuntimeError: Unexpected node: s(:op_asgn,
  s(:lvasgn, x@1), :+,
  s(:int, 1)), 10>
  ...

Steps to reproduce

Create a file called x.rb:

class X
  # @type instance: X
  # @type module: X.class

  def foo(x)
    # This seems OK
    x = x + 1

    # The following self-assignment causes an error
    x += 1

    x
  end
end

and Run steep check over it.

BreakTypeMismatch on break without an argument

Hello, thanks for the nice release 0.40.0!

In this version 0.40.0, I found a weird behavior about the use of break without an argument.

Here is a simple Ruby script named a.rb including 2 patterns of break:

array = [1, 2, 3]
p array.each { break }       # break without an argument
p array.each { break array } # break with an argument

When running this script, it works expectedly:

$ ruby a.rb
nil
[1, 2, 3]

But Steep reports an error in this script:

$ bundle exec steep check
# Type checking files:

F

Detected 1 problems from 1 files

a.rb:2:15: [error] Cannot break with a value of type `nil` because type `::Array[::Integer]` is assumed
β”‚   nil <: ::Array[::Integer]
β”‚
β”‚ Diagnostic ID: Ruby::BreakTypeMismatch
β”‚
β”” p array.each { break }
                 ~~~~~

Is this behavior expected and can I fix the script to avoid the Steep error?

Environment:

  • Ruby: ruby 3.0.0p0 (2020-12-25 revision 95aff21468) [x86_64-darwin19]
  • Steep: 0.40.0
Steepfile
target :lib do
  signature "*.rbs"
  check "*.rb"
end

Rails6 Support

steep (~> 0.10.0) was resolved to 0.10.0, which depends on
  activesupport (~> 5.1)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.