tapioca icon indicating copy to clipboard operation
tapioca copied to clipboard

Aliasing `.new` doesn't reuse the signature from `#initialize`

Open magni- opened this issue 10 months ago • 3 comments

I'm running into this with the money gem:

# .rb
class Money
  class << self
    alias_method :from_cents, :new
  end

 def initialize( obj, currency = Money.default_currency, options = {})
    # ...
  end
end

# generated .rbi
class Money
  # source://money//lib/money/money.rb#341
  def initialize(obj, currency = T.unsafe(nil), options = T.unsafe(nil)); end

  class << self
    def from_cents(*_arg0); end
  end
end

Also note that no source:// pointer comment is generated for the .new alias.

I get that technically you could override both .new and #initialize to have different signatures, but I'd be surprised if that's a common pattern. AFAICT Sorbet uses #initialize's signature to validate calls to .new.

magni- avatar Mar 03 '25 01:03 magni-

What do you mean by signature in this case?

KaanOzkan avatar Mar 04 '25 14:03 KaanOzkan

The method signature? Tapioca doesn't generate a signature for .new, but it does for the method aliasing .new. The issue is that it doesn't use #initialize's signature for that, which makes Sorbet complain about mismatched arguments when trying to improve the method signature in a shim.

I'm not too familiar with tapioca internals, but I think what would be needed would be to add something like the following:

# UnboundMethod method -> UnboundMethod?
def fallback(method) # there's most likely a better name for this?
  return unless method.source_location.nil?
  return unless method.original_name == :new
  
  initialize = method.receiver.instance_method(:initialize)
  return if initialize.source_location.nil?

  initialize
end

# in Tapioca::Gem::Listeners::Methods#compile_method
parameters = T.let(fallback(method) || method).parameters, T::Array[[Symbol, T.nilable(Symbol)]])

# in Tapioca::Gem::Listeners::SourceLocation#on_method
file, line = (fallback(event.method) || event.method).source_location

magni- avatar Mar 05 '25 06:03 magni-

In Tapioca and static typing context signature usually means sig { } so I was confused. Looks like what you want is from_cents having the same parameter list as initialize, def from_cents(obj, currency = Money.default_currency, options = {})

Yes we can special case new like in your example. I don't think we support aliased methods at all so you can first build out setting the parameter list from method.original_name but then do the special case for new/initialize.

I don't think this is a feature we'll prioritize but if you want to explore this feel free to go ahead.

KaanOzkan avatar Mar 06 '25 22:03 KaanOzkan