Bug #11808
closedDifferent behavior between Enumerable#grep and Array#grep
Description
Regex special global variables are available within the block for Array#grep, but are nil within the block for Enumerable#grep.
Here is an example that explains it better:
class Test
include Enumerable
def each
return enum_for(:each) unless block_given?
yield "Hello"
yield "World"
end
end
enum = Test.new
array = ["Hello", "World"]
enum.grep(/^(.)/) {$1} # => [nil, nil]
array.grep(/^(.)/) {$1} # => ["H", "W"]
Tested on 2.0.0, 2.1.5, & 2.2.2
Updated by jeremyevans0 (Jeremy Evans) about 7 years ago
Array#grep is actually Enumerable#grep:
If I had to guess, the cause of the difference is that Array#each is implemented in C, and Test#each is implemented in Ruby, and this affects Regexp special variable scope. You see similar behavior as Array in other classes that implement #each in C, such as Range or File.
The documentation for the special global variables states: These global variables are thread-local and method-local variables. This indicates to me that the bug is that the variables are accessible inside the Array#each block, since that block executes inside the current method, it's not local to the Array#each method. However, I would assume removing the current behavior would break too much existing code.
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Tracker changed from Bug to Feature
- Description updated (diff)
- ruby -v deleted (
2.2.2) - Backport deleted (
2.0.0: UNKNOWN, 2.1: UNKNOWN, 2.2: UNKNOWN)
There is no API for it now.
Updated by nobu (Nobuyoshi Nakada) about 7 years ago
- Subject changed from DIfferent behavior between Enumerable#grep and Array#grep to Different behavior between Enumerable#grep and Array#grep
Updated by matz (Yukihiro Matsumoto) about 7 years ago
- Tracker changed from Feature to Bug
- Backport set to 2.5: UNKNOWN, 2.6: UNKNOWN
It is a bug. It has been hidden for 10+ years and seems to be very difficult to fix.
It should be fixed in the long run.
Matz.
Updated by ko1 (Koichi Sasada) about 7 years ago
- Assignee set to ko1 (Koichi Sasada)
Updated by ko1 (Koichi Sasada) almost 6 years ago
Sorry we need more time to consider.
Updated by noelrap (Noel Rappin) almost 3 years ago
This appears to be fixed in 3.3.0dev as of Nov 2023,
irb(main):001* class Test
irb(main):002* include Enumerable
irb(main):003* def each
irb(main):004* return enum_for(:each) unless block_given?
irb(main):005* yield "Hello"
irb(main):006* yield "World"
irb(main):007* end
irb(main):008> end
=> :each
irb(main):009>
irb(main):010> enum = Test.new
=> #<Test:0x0000000102ba5038>
irb(main):011> array = ["Hello", "World"]
=> ["Hello", "World"]
irb(main):012>
irb(main):013> enum.grep(/^(.)/) {$1}
=> ["H", "W"]
However, it was still broken in 3.2.2. It's not clear to me when the behavior changed.
Updated by hsbt (Hiroshi SHIBATA) over 2 years ago
- Status changed from Open to Assigned
Updated by jeremyevans0 (Jeremy Evans) over 1 year ago
- Related to Bug #20807: String#gsub fails when called from string subclass with a block passed added
Updated by wanabe (_ wanabe) about 5 hours ago
- Status changed from Assigned to Closed
It has been fixed at 0a82bfe5e18ac86da72c27389db6eb8da156a0b5 == https://github.com/ruby/ruby/pull/7225
$ cat a.rb
class Test
include Enumerable
def each
return enum_for(:each) unless block_given?
yield "Hello"
yield "World"
end
end
enum = Test.new
array = ["Hello", "World"]
p enum.grep(/^(.)/) {$1}
p array.grep(/^(.)/) {$1}
$ git checkout 0a82bfe5e18ac86da72c27389db6eb8da156a0b5~ && make -j miniruby > /dev/null 2>&1 && ./miniruby a.rb
HEAD is now at 2675f2c864f Remove whitespace
[nil, nil]
["H", "W"]
$ git checkout 0a82bfe5e18ac86da72c27389db6eb8da156a0b5 && make -j miniruby > /dev/null 2>&1 && ./miniruby a.rb
Previous HEAD position was 2675f2c864f Remove whitespace
HEAD is now at 0a82bfe5e18 use correct svar (#7225)
["H", "W"]
["H", "W"]
This commit is included in 3.3.
Since 3.2 is end-of-life, there is no need to backport it.