soutaro / rbs-inline

Inline RBS type declaration
MIT License
231 stars 7 forks source link

Proposal: Singleton Class Definition within Specific Blocks #33

Closed euglena1215 closed 4 months ago

euglena1215 commented 4 months ago

Hello,

I have been experimenting with using rbs-inline to type a Sinatra application and encountered a challenge.

In Sinatra, we use DSL methods such as helpers and get to implement the application. Here is a concrete example:

class MyApp < Sinatra::Base
  helpers do
    def bar
      'bar'
    end
  end

  get '/foo' do
    json(bar: bar) # => {"bar": "bar"}
  end
end

To call the bar method within the block of get, it is necessary to treat the method defined within the helpers block as if it were a singleton class definition.

class MyApp
  # Ideally defined as follows
  def self.bar: () -> String
end

Based on the roadmap, I understand that support for singleton class definitions is forthcoming. However, treating method definitions within specific blocks as singleton class definitions would greatly simplify type definitions for Sinatra applications using rbs-inline.

As a preliminary idea, how about the following approach? There might be better suggestions.

  helpers do #: class << self
    def bar
      'bar'
    end
  end

Thank you for reading!

ParadoxV5 commented 4 months ago

:up:

This isn’t Sinatra-specific:

class Example
  instance_eval do
    def this_is_also_a_class_method = …
  end
end

While we can manually bail out with @rbs!, class << self is not in RBS.

While the use of Ruby class << self makes it unmistakable, #9 wishes to keep rbs-inline to RBS semantics as much as possible. I don’t recall any conflicts with the existing rbs-inline by having a #: here, but it probably conflicts with Steep’s assertions.

euglena1215 commented 4 months ago

Additionally, while we expect singleton class definitions for methods defined in helpers when called from get, I realized that methods defined in one helpers block can be called from another helpers block as well.

class MyApp < Sinatra::Base
  helpers do
    def bar
      'bar'
    end

    def bar2
      # Here we expect `def bar`
      bar
    end
  end

  get '/foo' do
    # Here we expect `def self.bar`
    json(bar: bar) # => {"bar": "bar"}
  end
end

Therefore, to represent methods defined in Sinatra's helpers in RBS, we seem to need both singleton class method definitions and regular class method definitions.

Supporting this use case in rbs-inline might not be realistic...

euglena1215 commented 4 months ago

Following our previous discussions on the support for singleton class definitions within specific blocks in Sinatra applications, I have another idea that might be more feasible.

I propose that Steep should interpret get '/foo' do ... end blocks in Sinatra as equivalent to instance method definitions using a special annotation. This approach would mean that methods called within the get block, such as bar, are treated as instance methods. Here is a concrete example:

class MyApp < Sinatra::Base
  helpers do
    def bar
      'bar'
    end
  end

  # @some_annotation
  get '/foo' do
    json(bar: bar) # => {"bar": "bar"}
  end
end

If the above get block were interpreted as an instance method definition, it would look like this:

class MyApp
  def get_foo
    json(bar: bar) # => {"bar": "bar"}
  end
end

In this interpretation, the bar method called within the get block is clearly an instance method. This solution simplifies the type definitions for Sinatra applications using rbs-inline without requiring the simultaneous definition of singleton class methods and instance methods.

Implementing this logic within Steep might be challenging, but it would significantly enhance the usability of rbs-inline for Sinatra applications. If this idea is worth considering, I can create an issue in the soutaro/steep to further discuss its feasibility.

euglena1215 commented 4 months ago

Oh, my apologies. I wasn't aware of the [self: instance] annotation. The issue wasn't with the helpers method but rather that the block argument of the get method wasn't treated within the same scope as instance methods. This can be resolved using [self: instance].

https://github.com/ruby/gem_rbs_collection/pull/569

I realized this issue can be fixed with the RBS correction for the Sinatra gem, so I'll close this. Sorry for the confusion!