ruby / rbs

Type Signature for Ruby
Other
1.92k stars 202 forks source link

Add signatures for the `main` object #1117

Open ybiquitous opened 1 year ago

ybiquitous commented 1 year ago

Hello. The using method at the top level seems not typed. Is there a way to resolve it?

Reprodoction

The following is a reproduction using Steep. First, prepare a few files.

a.rb:

using Module.new

Steepfile:

target :test do
  check "a.rb"
end

Gemfile:

source "https://rubygems.org"
gem "steep", "1.2"
gem "rbs", "2.7"

Then, install the gems and run steep on your terminal:

$ bundle config set --local path vendor/bundle
$ bundle install
$ bundle exec steep check

You should see the following output:

a.rb:1:0: [error] Type `::Object` does not have method `using`
│ Diagnostic ID: Ruby::NoMethod
│
└ using Module.new
  ~~~~~

Detected 1 problem from 1 file

Environment

Note

The Module#using method signature has been existing already:

https://github.com/ruby/rbs/blob/95667d76c725566fa455bbcca0c10cc1e539475a/core/module.rbs#L1519

soutaro commented 1 year ago

Currently, I'm not very open to adding new features to support refinements...

The most straightforward way to support it seems like adding refinement annotation to RBS class/module definitions.

module M
  refinement class String
    def foo: () -> void
  end
end

And type checkers will update the definition of refinement classes/modules when using calls or equivalent is detected.

module Foo
  using M

  String.new.foo    # Will type check
end
ybiquitous commented 1 year ago

Thanks for the response. Indeed, we would like to avoid an addition to the RBS syntax if possible.

Is there a way to avoid Ruby::NoMethod for top-level using now without changing the current RBS?

soutaro commented 1 year ago

Using interface and intersection type may be one of the possible workarounds, but not sure if it solves your problem...

# @type var string: String & _StringRefinement
string = _ = ""
ybiquitous commented 1 year ago

In the case of adding a new method by refinements, the current RBS seems to work even if not using the @type var workaround. For example:

a.rbs:

module M
end

class String
  def foo: () -> void
end

a.rb:

module M
  refine String do
    def foo
      "foo"
    end
  end
end

using M

puts "".foo

Run ruby a.rb:

$ bundle exec ruby a.rb
foo

Run steep check:

$ bundle exec steep check
a.rb:9:0: [error] Type `::Object` does not have method `using`
│ Diagnostic ID: Ruby::NoMethod
│
└ using M
  ~~~~~

Detected 1 problem from 1 file

Even in this case, using fails to type-check.


However, when changing an existing method signature, the refinement fails to type-check. Probably RBS may need a new feature to address refinements, as you commented on https://github.com/ruby/rbs/issues/1117#issuecomment-1271269302.

For example:

a.rbs:

module M
end

class String
  def size: (String name) -> Integer
end

a.rb:

module M
  refine String do
    def size(name)
      puts "size name: #{name}"
      self.length
    end
  end
end

using M

puts "".size("foo")

Run ruby a.rb:

$ bundle exec ruby a.rb
size name: foo
0

Run steep check:

$ bundle exec steep check

vendor/bundle/ruby/3.1.0/gems/rbs-2.7.0/core/string.rbs:2497:2: [error] Non-overloading method definition of `size` in `::String` cannot be duplicated
│ Diagnostic ID: RBS::DuplicatedMethodDefinition
│
└   alias size length
    ~~~~~~~~~~~~~~~~~

a.rb:5:11: [error] Type `(::Object & ::M)` does not have method `length`
│ Diagnostic ID: Ruby::NoMethod
│
└       self.length
             ~~~~~~

a.rb:10:0: [error] Type `::Object` does not have method `using`
│ Diagnostic ID: Ruby::NoMethod
│
└ using M
  ~~~~~

a.rb:12:5: [error] UnexpectedError: /Users/koba/tmp/foo/vendor/bundle/ruby/3.1.0/gems/rbs-2.7.0/core/string.rbs:2497:2...2497:19: ::String#size has duplicated definitions in a.rbs:5:2...5:36
│ Diagnostic ID: Ruby::UnexpectedError
│
└ puts "".size("foo")
       ~~~~~~~~~~~~~~

Detected 4 problems from 2 files
soutaro commented 1 year ago

@ybiquitous Ah! I'm sorry. I was misunderstanding your question.

Seems like we need a support for main object!

ybiquitous commented 1 year ago

Seems like we need a support for main object!

I see. That makes sense. 👍🏼

ybiquitous commented 1 year ago

@soutaro I'd like to add RBS for the main object, but I'm unsure how to do it. Is it correct to add using to Object like this...?

class Object
  def using: (Module module) -> self
end
soutaro commented 1 year ago

@ybiquitous It would be a workaround, if you can accept that the #using is available anywhere in your code...

Another one is using # @type self annotation:

self.then do
  # @type self: Object & _Main
  using FooBar
end

Looks like it works, but 😫...

I don't think it can be done without the support of type checkers. For example,

  1. Add _Main interface in RBS, which has #using method, and
  2. Let the type of toplevel self be Object & _Main in Steep
ybiquitous commented 1 year ago
  1. Add _Main interface in RBS, which has #using method, and
  2. Let the type of toplevel self be Object & _Main in Steep

Sounds good! 👍🏼