Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
magni- opened this issue Mar 3, 2025 · 3 comments
Open

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

magni- opened this issue Mar 3, 2025 · 3 comments

Comments

@magni-
Copy link

magni- commented Mar 3, 2025

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.

@KaanOzkan
Copy link
Contributor

What do you mean by signature in this case?

@magni-
Copy link
Author

magni- commented Mar 5, 2025

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

@KaanOzkan
Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants