`Module#delegate` avoid creating a unique fstring for each delegator

For example:

```ruby
delegate :negative?, to: :value, as: Numeric
```

Before:

```
def negative?(&block)
  _ = @value
  _.negative?(&block)
rescue NoMethodError => e
  if _.nil? && e.name == :negative?
    raise DelegationError, "ActiveSupport::Duration#negative? delegated to @value.negative?, but @value is nil: #{self.inspect}"
  else
    raise
  end
end
```

After:

```ruby
def negative?(&block)
  _ = @value
  _.negative?(&block)
rescue NoMethodError => e
  if _.nil? && e.name == :negative?
    raise DelegationError.nil_target(:negative?, :"@value")
  else
    raise
  end
end
```

Before almost every delegator would generate a large unique string that gets interned for
the error message that is rarely if ever used.

Rather than to "hardcode" a unique string, we now only pass pre-existing symbols to
a method helper that will build the error message.

This alone saves about 160B per delegator, and the method bytecode is also marginally
smaller (but it's harder to assess how much this actually saves)
This commit is contained in:
Jean Boussier 2024-01-10 16:30:14 +01:00
parent 9dab3e4184
commit 4bded3c00a
1 changed files with 9 additions and 3 deletions

View File

@ -5,7 +5,13 @@ require "set"
class Module
# Error generated by +delegate+ when a method is called on +nil+ and +allow_nil+
# option is not used.
class DelegationError < NoMethodError; end
class DelegationError < NoMethodError
class << self
def nil_target(method_name, target) # :nodoc:
new("#{method_name} delegated to #{target}, but #{target} is nil")
end
end
end
RUBY_RESERVED_KEYWORDS = %w(__ENCODING__ __LINE__ __FILE__ alias and BEGIN begin break
case class def defined? do else elsif END end ensure false for if in module next nil
@ -266,7 +272,7 @@ class Module
" _.#{method}(#{definition})" <<
"rescue NoMethodError => e" <<
" if _.nil? && e.name == :#{method}" <<
%( raise DelegationError, "#{self}##{method_name} delegated to #{receiver}.#{method}, but #{receiver} is nil: \#{self.inspect}") <<
" raise DelegationError.nil_target(:#{method_name}, :'#{receiver}')" <<
" else" <<
" raise" <<
" end" <<
@ -347,7 +353,7 @@ class Module
if #{allow_nil == true}
nil
else
raise DelegationError, "\#{method} delegated to #{target}, but #{target} is nil"
raise DelegationError.nil_target(method, :'#{target}')
end
else
raise