rbs icon indicating copy to clipboard operation
rbs copied to clipboard

Add signatures for the `main` object

Open ybiquitous opened this issue 3 years ago • 9 comments

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

  • ruby 3.1.2p20 (2022-04-12 revision 4491bb740a) [arm64-darwin21]
  • rbs ~~2.6.0~~ 2.7.0
  • steep ~~1.1.0~~ 1.2.0

Note

The Module#using method signature has been existing already:

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

ybiquitous avatar Oct 06 '22 12:10 ybiquitous

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

soutaro avatar Oct 07 '22 08:10 soutaro

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?

ybiquitous avatar Oct 07 '22 08:10 ybiquitous

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 = _ = ""

soutaro avatar Oct 08 '22 05:10 soutaro

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

ybiquitous avatar Oct 09 '22 15:10 ybiquitous

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

Seems like we need a support for main object!

soutaro avatar Oct 11 '22 05:10 soutaro

Seems like we need a support for main object!

I see. That makes sense. 👍🏼

ybiquitous avatar Oct 11 '22 05:10 ybiquitous

@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

ybiquitous avatar Oct 13 '22 05:10 ybiquitous

@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

soutaro avatar Oct 18 '22 06:10 soutaro

  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! 👍🏼

ybiquitous avatar Oct 18 '22 07:10 ybiquitous