Add signatures for the `main` object
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
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
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?
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 = _ = ""
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 Ah! I'm sorry. I was misunderstanding your question.
Seems like we need a support for main object!
@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 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,
- Add
_Maininterface in RBS, which has#usingmethod, and - Let the type of toplevel
selfbeObject & _Mainin Steep
- Add
_Maininterface in RBS, which has#usingmethod, and- Let the type of toplevel
selfbeObject & _Mainin Steep
Sounds good! 👍🏼