-
Notifications
You must be signed in to change notification settings - Fork 133
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,15 +73,29 @@ def compile_method(tree, symbol_name, constant, method, visibility = RBI::Public | |
return unless method_owned_by_constant?(method, constant) | ||
return if @pipeline.symbol_in_payload?(symbol_name) && [email protected]_in_gem?(method) | ||
|
||
is_alias = method.name != method.original_name | ||
signature = lookup_signature_of(method) | ||
method = T.let(signature.method, UnboundMethod) if signature | ||
parameters = T.let(nil, T.nilable(T::Array[[Symbol, T.nilable(Symbol)]])) | ||
|
||
if signature | ||
method = T.let(signature.method, UnboundMethod) | ||
elsif is_alias && signature.nil? && constant.method_defined?(method.original_name) | ||
alias_source_method = constant.instance_method(method.original_name) | ||
signature = lookup_signature_of(alias_source_method) | ||
|
||
# Skip abstract methods if they are defined this way. | ||
# Aliasing abstract methods is likely to yield unwanted results | ||
signature = nil if signature&.mode == "abstract" | ||
parameters = T.let(signature&.method&.parameters, T.nilable(T::Array[[Symbol, T.nilable(Symbol)]])) | ||
KaanOzkan marked this conversation as resolved.
Show resolved
Hide resolved
|
||
end | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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?
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 If you leave 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ok, will get to this soon |
||
parameters ||= T.let(method.parameters, T::Array[[Symbol, T.nilable(Symbol)]]) | ||
|
||
sanitized_parameters = parameters.each_with_index.map do |(type, name), index| | ||
fallback_arg_name = "_arg#{index}" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In general, this is incorrect since alias binding happens at call site and doesn't get mutated. See: https://bsky.app/profile/fxn.bsky.social/post/3lbehp5rtes2j
That is: The instance method
bar
ofB
is not an alias of instance methodfoo
ofB
, but this line of code would treat them as such.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good find, but I think at worst, wouldn't it be the case that we have a signature that is valid for both A and B, where the alternative is no signature at all? We can redefine a method, but not with an incompatible signature.
I imagine there are edge cases where this is done, but I am not sure if it is ever correct to do so. Though there may be other ways to redefine methods that are affected by this.
Since I had not run into a situation in any the gems I ever used in practice, where this PR would affect any of the RBI output for those gems, maybe it is just not worth the effort to get it just right (I can't easily think of how to address this in a simple way, though I'm sure a solution is possible). So I'm fine with closing this as there doesn't seem to be much demand for this to work :).