-
Notifications
You must be signed in to change notification settings - Fork 15
Reapply "Merge pull request #46 from byroot/use-forward-send" #51
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
Conversation
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.
Pull request overview
This PR reapplies changes from PR #46 that attempted to optimize method delegation by using class_eval with string interpolation instead of define_method for most methods. The original PR failed due to a test failure where protected methods could not be properly delegated.
Key Changes:
- Methods are partitioned into "normal" (simple identifier names) and "special" (operators and other special syntax) categories
- Normal methods use
class_evalfor definition, while special methods continue usingdefine_method - Protected method marking is moved to after all method definitions
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| normal, special = instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } | ||
|
|
||
| source = normal.map do |method| | ||
| "def #{method}(...); __getobj__.#{method}(...); end" |
Copilot
AI
Dec 17, 2025
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.
The direct method call syntax __getobj__.#{method}(...) doesn't work for protected methods because it enforces visibility restrictions. When a protected method is called from outside the class context, Ruby raises a NoMethodError. This is the root cause of the test failure mentioned in the PR description.
The previous implementation used __send__ (via delegating_block), which bypasses visibility checks and works correctly for protected methods. The fix should use __send__ instead of direct method calls to maintain the same behavior.
| "def #{method}(...); __getobj__.#{method}(...); end" | |
| "def #{method}(...); __getobj__.__send__(:#{method}, ...); end" |
lib/delegate.rb
Outdated
| public_instance_methods -= ignores | ||
|
|
||
| instance_methods = (public_instance_methods + protected_instance_methods) | ||
| normal, special = instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } |
Copilot
AI
Dec 17, 2025
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.
The regex pattern /\A[a-zA-Z]\w*[!\?]?\z/ is too restrictive. It requires methods to start with a letter, but Ruby allows method names to start with underscores (e.g., _internal_method). These valid Ruby methods would be incorrectly classified as "special" and handled differently.
The pattern should be /\A[a-zA-Z_]\w*[!\?]?\z/ to also match methods starting with underscores.
| normal, special = instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } | |
| normal, special = instance_methods.partition { |m| m.match?(/\A[a-zA-Z_]\w*[!\?]?\z/) } |
| end | ||
| public_instance_methods.each do |method| | ||
|
|
||
| class_eval(source.join(";"), __FILE__, __LINE__) |
Copilot
AI
Dec 17, 2025
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.
The line number argument __LINE__ in class_eval is incorrect. Since multiple method definitions are joined with semicolons on a single line, all methods will appear to be defined on line 430 in stack traces, making debugging difficult.
Additionally, using __LINE__ here will report the line number of the class_eval call itself, not the line where the method definitions actually start (line 412-414). This will cause confusion when debugging errors in delegated methods.
| source = normal.map do |method| | ||
| "def #{method}(...); __getobj__.#{method}(...); end" | ||
| end |
Copilot
AI
Dec 17, 2025
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.
The generated method definitions are joined with semicolons on a single line, which will make stack traces harder to read and debug. When an error occurs in a delegated method, the backtrace will show all methods as being on the same line.
Consider joining with newlines instead of semicolons to provide clearer stack traces and better debuggability.
|
Ah indeed. |
b0f401c to
119c58a
Compare
This reverts commit fc2bd04. Co-authored-by: Jean Boussier <byroot@ruby-lang.org>
119c58a to
7d5c1e0
Compare
|
I added a regression test and fixed the PR. |
| public_instance_methods = superclass.public_instance_methods | ||
| public_instance_methods -= ignores | ||
|
|
||
| normal, special = public_instance_methods.partition { |m| m.match?(/\A[a-zA-Z]\w*[!\?]?\z/) } |
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.
Why "normal" methods need to start with an alphabet, e.g., _ is not the case?
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.
It's a mistake _ should be there too. But it's not a big deal, just means they don't benefit from the optimization.
The original PR is #46
@byroot This change failed with https://github.com/ruby/ruby/blob/master/spec/ruby/library/delegate/delegate_class/instance_method_spec.rb#L16