Skip to content

Support finding signatures through prepended methods - #2706

Draft
KaanOzkan wants to merge 1 commit into
mainfrom
ko-fix-prepended-method-signatures
Draft

Support finding signatures through prepended methods#2706
KaanOzkan wants to merge 1 commit into
mainfrom
ko-fix-prepended-method-signatures

Conversation

@KaanOzkan

@KaanOzkan KaanOzkan commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Motivation

Resolves #2705

Implementation

Find signatures hidden by prepend by walking super_method through prepended ancestor positions and stopping at the first ordinary implementation.

This requires a few things:

  1. Require call sites to pass the constant or receiver from which the method was resolved (lookup_from) because a wrapper-owned method does not retain that scope.
  2. Infer prepended positions by comparing the lookup scope’s flattened ancestor chain with each ancestor’s own chain because Ruby does not expose this information.
  3. Repeat the lookup once more because lazy evaluation of a sig may register it under the wrapper that was already inspected.

For example in this case lookup_from lets us identify Wrapper as prepended, so we inspect Child#foo, and stop before incorrectly falling through to Parent#foo.

class Parent
  #: (Integer) -> Integer
  def foo(value) = value
end

module Wrapper
  def foo(...) = super
end

class Child < Parent
  prepend Wrapper

  #: (String) -> String
  def foo(value) = value
end

method = Child.instance_method(:foo)

method.owner                         # => Wrapper
method.super_method.owner            # => Child
method.super_method.super_method.owner # => Parent

signature_of(method, lookup_from: Child) # => (String) -> String

Tests

TBD on Core

@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch 2 times, most recently from f7dbd97 to f141140 Compare August 25, 2026 18:19
Comment thread lib/tapioca/runtime/reflection.rb Outdated
Comment on lines +136 to +139
# Looking up the original `Example#foo` evaluates its `sig`. Because `Wrapper`
# was prepended, Ruby now resolves `Example#foo` to `Wrapper#foo`, so Sorbet
# stores the signature for `Wrapper#foo`. The first pass causes the store; the
# second pass finds `Example#foo`'s signature on `Wrapper#foo`.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is too hard-coded to the prepend case, but this implementation doesn't do anything to limit to prepended modules (and it's surprisingly difficult to identify them)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a precursor to the comment, wdyt?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also think this comment could focus more on the purpose of the method rather than describing the implementation. Not a blocker.

Comment thread lib/tapioca/runtime/reflection.rb Outdated
current_method = current_method.super_method
end

return nil if pass.zero? && !T::Utils.signature_for_method(method)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to make it faster in the normal case and return early, new version should be easier to understand

@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch from f141140 to dfe8fab Compare August 25, 2026 19:29
@KaanOzkan
KaanOzkan marked this pull request as ready for review August 25, 2026 19:33
@KaanOzkan
KaanOzkan requested a review from a team as a code owner August 25, 2026 19:33
@KaanOzkan
KaanOzkan requested a review from amomchilov August 26, 2026 13:34
@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch from dfe8fab to ab2cb42 Compare August 27, 2026 13:48
jesse-shopify
jesse-shopify previously approved these changes Aug 28, 2026
@KaanOzkan

Copy link
Copy Markdown
Contributor Author

This is more difficult than I originally thought because walking the entire super_method chain to find a signature also affects ordinary overrides:

class Parent
  #: (String) -> String
  def foo(value) = value
end

class Child < Parent
  def foo(value, suffix) = ""
end

foo = Child.instance_method(:foo)

# Before: signature_of(foo) # => nil
# Current PR: signature_of(foo) # => Parent#foo's signature (incorrect)

I’m looking for a clean way to support prepended methods without changing this behavior.

@KaanOzkan
KaanOzkan marked this pull request as draft August 31, 2026 14:28
@KaanOzkan KaanOzkan changed the title Fix signature lookup through prepended methods Support finding signatures through prepended methods Aug 31, 2026
@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch from ab2cb42 to 604a3a5 Compare August 31, 2026 14:39
@KaanOzkan
KaanOzkan dismissed jesse-shopify’s stale review August 31, 2026 14:46

It was for outdated code

@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch from 604a3a5 to 2b750a5 Compare August 31, 2026 15:03
@KaanOzkan
KaanOzkan force-pushed the ko-fix-prepended-method-signatures branch from 2b750a5 to 776c8ba Compare August 31, 2026 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Prepending a module can cause tapioca dsl to produce an untyped signature

3 participants