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

Gems: Fix gem RBI compilation for aliased methods where the original method has a sig #2051

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

marknuzz
Copy link
Contributor

Motivation

I was working on porting a small gem to use Sorbet, with sigs on all of the methods defined within the gem. I noticed that the generated RBI from tapioca gem did not produce the correct output for the methods which were aliased. The gem contained a couple of aliased methods. It seemed worth looking into so I gave it a shot, and was able to fix it.

Implementation

I made sure to play it safe, by confirming that it is an alias method, and that the original method is a valid method that can be referenced. We use the signature for the original method instead of the alias method, and only if the alias method has no signature directly available (which may be true for 100% of alias methods, but I didn't want to assume).

sorbet-runtime does have a way to internally build the signature of an aliased method, but it proved to be too deep in the private namespace and I abandoned that idea. If you actually call the aliased method, not once, but twice, the method will be unwrapped and you can get the signature of the aliased method through sorbet's API. But I couldn't identify any way to trigger that without calling the method, so we just use the signature of the original method if it is available.

If it is an abstract method, we set the aliased signature to nil (as it already would have been before this change), as unintended effects may occur from an aliasing an abstract method. I'm not sure if anyone does it, but in case they do, we don't want to cause a breaking change for them.

Tests

Added unit tests, and tested the gems command (with this fix) on all of the gems I had installed locally. Gems typically don't use signatures in methods, and of those that do, fewer will alias them. So I could only trigger this for my own gem I was working on locally. However, this may have more of a noticeable effect for companies that are using private gems internally.

@marknuzz marknuzz requested a review from a team as a code owner October 18, 2024 05:06
@KaanOzkan KaanOzkan added the enhancement New feature or request label Oct 18, 2024
Copy link
Contributor

@KaanOzkan KaanOzkan left a comment

Choose a reason for hiding this comment

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

Thanks for the contribution.

lib/tapioca/gem/listeners/methods.rb Show resolved Hide resolved

method_name = method.name.to_s
return unless valid_method_name?(method_name)
return if struct_method?(constant, method_name)
return if method_name.start_with?("__t_props_generated_")

parameters = T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]])
method = T.let(signature.method, UnboundMethod) if signature
Copy link
Contributor

@KaanOzkan KaanOzkan Oct 18, 2024

Choose a reason for hiding this comment

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

Why was this moved after the checks on line 92? Does it matter for aliased methods?

I think it'll be a bit cleaner if it was done in the elsif instead.
We could remove the if signature completely and have this assignment after the elsif.

Copy link
Contributor Author

@marknuzz marknuzz Oct 19, 2024

Choose a reason for hiding this comment

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

I agree its a bit complex but wasn't sure if it would be appropriate to refactor it to make it more readable. Would that be appropriate here?

If you move this to line 91, signature could be nil and you get a crash
method = T.let(signature.method, UnboundMethod)

If you leave if signature but move it to line 91, you get duplicate rbi defs for the original method, and the alias method doesn't get the rbi def.

The checks would be done on the original method name and not the alias method name if we replace the method and assign method_name before the checks, because line 97 sets it to the original method. I think the problem here is that the meaning of method is ambiguous between the original method and the alias method - that should at least be fixed if nothing else, but let me know your thoughts

otherwise I think it is correct as is but not as readable as it could be

I'll update the test to ensure that attempting to alias to a name like __t_props_generated_foo will not produce rbi for that method as well

Copy link
Contributor

@KaanOzkan KaanOzkan Oct 21, 2024

Choose a reason for hiding this comment

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

Sorry I wasn't clear in my original message, I wanted to get rid of duplicate method = T.let(signature.method, UnboundMethod) if signature in case of no alias methods (lines 81 and 97), but yes I see the need now.

It might be good enough to just have a comment on line 97 mentioning that it's only for the alias case and that after running checks on the original method we are setting it to the aliased method for generation. But feel free to refactor if there are opportunities.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok, will get to this soon

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

Successfully merging this pull request may close these issues.

2 participants