Reapply "Merge pull request #46 from byroot/use-forward-send"#51
Conversation
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
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/) } |
There was a problem hiding this comment.
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__) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
Why "normal" methods need to start with an alphabet, e.g., _ is not the case?
There was a problem hiding this comment.
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