Skip to content

Place generic extend last - #1019

Open
vinistock wants to merge 1 commit into
mainfrom
vs_place_generic_extend_last
Open

Place generic extend last#1019
vinistock wants to merge 1 commit into
mainfrom
vs_place_generic_extend_last

Conversation

@vinistock

Copy link
Copy Markdown
Member

When we re-write RBS generic namespaces, we place the extend T::Generic first, in the very beginning of the body. This means that they appear last in the singleton class' ancestor chain, which makes it very easy to bypass Tapioca's patches for tracking generic type parameters.

module Patch
  def [](*types)
  end
end

#: [Elem]
class Something
  extend Patch
end

That's enough to bypass the patch because extend T::Generic appears before extend Patch, which means the singleton ancestors are [<Something>, <Patch>, <T::Generic>...]. This breaks Tapioca RBI generation for generics.

Trying to ensure that Tapioca can always generate regardless of [] being overridden is not super straight forward, so I propose we flip the rewriter instead. This PR places extend T::Generic immediately after the last extend in a scope's body.

The idea is simply to change the insert position, which already fixes the issue, but then it also means we need to be a touch smarter about when to add line breaks in the body.

@vinistock vinistock self-assigned this Aug 28, 2026
@vinistock
vinistock requested a review from a team as a code owner August 28, 2026 16:35
@vinistock vinistock added the bugfix Fix a bug label Aug 28, 2026

if body.is_a?(Prism::StatementsNode)
last_extend = body.body.reverse_each.find do |node|
node.is_a?(Prism::CallNode) && node.message == "extend" && !node.receiver

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.

Technically we could have a receiver with self.extend right? We could also have a conditional call extend Foo if x which seems to be a Prism::IfNode. Rare to see though, so your call if you want to support it.

extend OtherThing; extend ::T::Generic; Elem = type_member
end
RUBY
)

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.

It might be good to add a test for an extend with an inline comment. I'm working on another translator feature and inline comments caught me off guard.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Fix a bug

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants