Feature #14267
closedLazy proc allocation introduced in #14045 creates regression
Description
The following script consistently prints Proc equality: true
on versions of Ruby before 2.5, but prints Proc equality: false
on Ruby 2.5:
# regression.rb
def return_proc(&block)
block
end
def return_procs(&block)
block.inspect if ENV['INSPECT_BLOCK']
proc_1 = return_proc(&block)
proc_2 = return_proc(&block)
return proc_1, proc_2
end
proc_1, proc_2 = return_procs { }
puts RUBY_VERSION
puts "Proc equality: #{proc_1 == proc_2}"
Here's the output on Ruby 2.4 and 2.5:
$ chruby 2.4
$ ruby regression.rb
2.4.2
Proc equality: true
$ chruby 2.5
$ ruby regression.rb
2.5.0
Proc equality: false
As the output shows, the two procs were equal on 2.4 but are no longer equal on 2.5. I believe this is due to the lazy proc allocation introduced in #14045. Note that if I call a method on the proc (such as inspect
) it defeats the lazy allocation and "fixes" the regression:
$ chruby 2.5
$ INSPECT_BLOCK=1 ruby regression.rb
2.5.0
Proc equality: true
This caused a bug in RSpec, which I've worked around for now by calling __id__
on the proc.
Is there a way to keep the lazy proc allocation while fixing this regression?
Updated by duerst (Martin Dürst) almost 7 years ago
- Related to Feature #14045: Lazy Proc allocation for block parameters added
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
- Tracker changed from Bug to Feature
- ruby -v deleted (
ruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin15]) - Backport deleted (
2.3: UNKNOWN, 2.4: UNKNOWN, 2.5: UNKNOWN)
I don't think this is a bug, just a detail of the implementation that changed when the optimization was applied. The Proc objects created are distinct. As Proc#== and #eql? are not defined, Object#== and #eql? are used, and as the objects are distinct, two distinct procs will not be considered equal.
I do think adding Proc#== and Proc#eql? makes sense, so I added a pull request for it: https://github.com/ruby/ruby/pull/3174
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
In my pull request, I propose the following definition of proc equivalence:
- The procs have the same class
- The procs either are both lambdas or both non-lambdas
- The procs either are both created from a method or both not created from a method
- The procs have the same block type (iseq, ifunc, symbol, proc)
- If iseq, the proc blocks have the same ec and iseq
- If ifunc, the proc blocks have the same ec and ifunc
- If symbol, the proc blocks use the same symbol
- If proc, the proc blocks use the same proc
I'm not sure if this definition is perfect, though it seems better than the object equivalence currently used. Someone with more knowledge of proc types should probably review and see if there are other conditions that should be met for equivalence.
Updated by mame (Yusuke Endoh) over 4 years ago
I'm not so positive for the change. In general, comparing Procs is not a good idea, and I'm unsure if it is worth helping such a use case.
The motivation of OP is not explained, so I'm unsure if the PR help OP's problem. If we merge the PR, it would be good to confirm it.
Updated by ko1 (Koichi Sasada) over 4 years ago
matz is positive.
Is RSpec problem solved with this patch?
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
ko1 (Koichi Sasada) wrote in #note-5:
matz is positive.
Is RSpec problem solved with this patch?
I've added a GitHub issue asking the RSpec developers to confirm whether the patch removes the need for the workaround: https://github.com/rspec/rspec-core/issues/2740
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
jeremyevans0 (Jeremy Evans) wrote in #note-6:
I've added a GitHub issue asking the RSpec developers to confirm whether the patch removes the need for the workaround: https://github.com/rspec/rspec-core/issues/2740
RSpec developers confirm the patch fixes the problem and removes the need for their workaround. Since matz is positive, I plan to merge the pull request later this week.
Updated by jeremyevans (Jeremy Evans) over 4 years ago
- Status changed from Open to Closed
Applied in changeset git|878af5147def7fed089d3cc388742f0111db58ae.
Implement Proc#== and #eql?
Previously, these were not implemented, and Object#== and #eql?
were used. This tries to check the proc internals to make sure
that procs created from separate blocks are treated as not equal,
but procs created from the same block are treated as equal, even
when the lazy proc allocation optimization is used.
Implements [Feature #14267]