Skip to content

Commit 1c80de9

Browse files
committed
[GR-19220] Add Enumerator.product
PullRequest: truffleruby/3929
2 parents 252b26c + 082290a commit 1c80de9

21 files changed

+425
-26
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Compatibility:
3535
* Add `Thread.each_caller_location` (#3039, @itarato).
3636
* Add `timeout` argument to `Thread::SizedQueue#push` (#3039, @itarato).
3737
* Add `rb_syserr_new` function (@rwstauner).
38+
* Add `Enumerator#product` (#3039, @itarato).
3839

3940
Performance:
4041

spec/ruby/core/enumerator/chain/initialize_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@
2222
end
2323

2424
describe "on frozen instance" do
25-
it "raises a RuntimeError" do
25+
it "raises a FrozenError" do
2626
-> {
2727
@uninitialized.freeze.send(:initialize)
28-
}.should raise_error(RuntimeError)
28+
}.should raise_error(FrozenError)
2929
end
3030
end
3131
end

spec/ruby/core/enumerator/each_spec.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,41 +10,41 @@ def object_each_with_arguments.each_with_arguments(arg, *args)
1010

1111
@enum_with_arguments = object_each_with_arguments.to_enum(:each_with_arguments, :arg0, :arg1, :arg2)
1212

13-
@enum_with_yielder = Enumerator.new {|y| y.yield :ok}
13+
@enum_with_yielder = Enumerator.new { |y| y.yield :ok }
1414
end
1515

1616
it "yields each element of self to the given block" do
1717
acc = []
18-
[1,2,3].to_enum.each {|e| acc << e }
19-
acc.should == [1,2,3]
18+
[1, 2, 3].to_enum.each { |e| acc << e }
19+
acc.should == [1, 2, 3]
2020
end
2121

2222
it "calls #each on the object given in the constructor by default" do
2323
each = mock('each')
2424
each.should_receive(:each)
25-
each.to_enum.each {|e| e }
25+
each.to_enum.each { |e| e }
2626
end
2727

2828
it "calls #each on the underlying object until it's exhausted" do
2929
each = mock('each')
3030
each.should_receive(:each).and_yield(1).and_yield(2).and_yield(3)
3131
acc = []
32-
each.to_enum.each {|e| acc << e }
33-
acc.should == [1,2,3]
32+
each.to_enum.each { |e| acc << e }
33+
acc.should == [1, 2, 3]
3434
end
3535

3636
it "calls the method given in the constructor instead of #each" do
3737
each = mock('peach')
3838
each.should_receive(:peach)
39-
each.to_enum(:peach).each {|e| e }
39+
each.to_enum(:peach).each { |e| e }
4040
end
4141

4242
it "calls the method given in the constructor until it's exhausted" do
4343
each = mock('peach')
4444
each.should_receive(:peach).and_yield(1).and_yield(2).and_yield(3)
4545
acc = []
46-
each.to_enum(:peach).each {|e| acc << e }
47-
acc.should == [1,2,3]
46+
each.to_enum(:peach).each { |e| acc << e }
47+
acc.should == [1, 2, 3]
4848
end
4949

5050
it "raises a NoMethodError if the object doesn't respond to #each" do

spec/ruby/core/enumerator/generator/initialize_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,10 @@
1717
end
1818

1919
describe "on frozen instance" do
20-
it "raises a RuntimeError" do
20+
it "raises a FrozenError" do
2121
-> {
2222
@uninitialized.freeze.send(:initialize) {}
23-
}.should raise_error(RuntimeError)
23+
}.should raise_error(FrozenError)
2424
end
2525
end
2626
end

spec/ruby/core/enumerator/initialize_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@
4848
end
4949

5050
describe "on frozen instance" do
51-
it "raises a RuntimeError" do
51+
it "raises a FrozenError" do
5252
-> {
5353
@uninitialized.freeze.send(:initialize) {}
54-
}.should raise_error(RuntimeError)
54+
}.should raise_error(FrozenError)
5555
end
5656
end
5757
end

spec/ruby/core/enumerator/lazy/initialize_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,8 @@ def receiver.each
5656
end
5757

5858
describe "on frozen instance" do
59-
it "raises a RuntimeError" do
60-
-> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should raise_error(RuntimeError)
59+
it "raises a FrozenError" do
60+
-> { @uninitialized.freeze.send(:initialize, @receiver) {} }.should raise_error(FrozenError)
6161
end
6262
end
6363
end
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
require_relative '../../../spec_helper'
2+
require_relative '../../enumerable/shared/enumeratorized'
3+
4+
ruby_version_is "3.2" do
5+
describe "Enumerator::Product#each" do
6+
it_behaves_like :enumeratorized_with_origin_size, :each, Enumerator::Product.new([1, 2], [:a, :b])
7+
8+
it "yields each element of Cartesian product of enumerators" do
9+
enum = Enumerator::Product.new([1, 2], [:a, :b])
10+
acc = []
11+
enum.each { |e| acc << e }
12+
acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
13+
end
14+
15+
it "calls #each_entry method on enumerators" do
16+
object1 = Object.new
17+
def object1.each_entry
18+
yield 1
19+
yield 2
20+
end
21+
22+
object2 = Object.new
23+
def object2.each_entry
24+
yield :a
25+
yield :b
26+
end
27+
28+
enum = Enumerator::Product.new(object1, object2)
29+
acc = []
30+
enum.each { |e| acc << e }
31+
acc.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
32+
end
33+
34+
it "raises a NoMethodError if the object doesn't respond to #each_entry" do
35+
-> {
36+
Enumerator::Product.new(Object.new).each {}
37+
}.should raise_error(NoMethodError, /undefined method `each_entry' for/)
38+
end
39+
40+
it "returns enumerator if not given a block" do
41+
enum = Enumerator::Product.new([1, 2], [:a, :b])
42+
enum.each.should.kind_of?(Enumerator)
43+
44+
enum = Enumerator::Product.new([1, 2], [:a, :b])
45+
enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
46+
end
47+
48+
it "returns self if given a block" do
49+
enum = Enumerator::Product.new([1, 2], [:a, :b])
50+
enum.each {}.should.equal?(enum)
51+
end
52+
53+
it "doesn't accept arguments" do
54+
Enumerator::Product.instance_method(:each).arity.should == 0
55+
end
56+
57+
it "yields each element to a block that takes multiple arguments" do
58+
enum = Enumerator::Product.new([1, 2], [:a, :b])
59+
60+
acc = []
61+
enum.each { |x, y| acc << x }
62+
acc.should == [1, 1, 2, 2]
63+
64+
acc = []
65+
enum.each { |x, y| acc << y }
66+
acc.should == [:a, :b, :a, :b]
67+
68+
acc = []
69+
enum.each { |x, y, z| acc << z }
70+
acc.should == [nil, nil, nil, nil]
71+
end
72+
end
73+
end
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
require_relative '../../../spec_helper'
2+
3+
ruby_version_is "3.2" do
4+
describe "Enumerator::Product#initialize_copy" do
5+
it "replaces content of the receiver with content of the other object" do
6+
enum = Enumerator::Product.new([true, false])
7+
enum2 = Enumerator::Product.new([1, 2], [:a, :b])
8+
9+
enum.send(:initialize_copy, enum2)
10+
enum.each.to_a.should == [[1, :a], [1, :b], [2, :a], [2, :b]]
11+
end
12+
13+
it "returns self" do
14+
enum = Enumerator::Product.new([true, false])
15+
enum2 = Enumerator::Product.new([1, 2], [:a, :b])
16+
17+
enum.send(:initialize_copy, enum2).should.equal?(enum)
18+
end
19+
20+
it "is a private method" do
21+
Enumerator::Product.should have_private_instance_method(:initialize_copy, false)
22+
end
23+
24+
it "does nothing if the argument is the same as the receiver" do
25+
enum = Enumerator::Product.new(1..2)
26+
enum.send(:initialize_copy, enum).should.equal?(enum)
27+
28+
enum.freeze
29+
enum.send(:initialize_copy, enum).should.equal?(enum)
30+
end
31+
32+
it "raises FrozenError if the receiver is frozen" do
33+
enum = Enumerator::Product.new(1..2)
34+
enum2 = Enumerator::Product.new(3..4)
35+
36+
-> { enum.freeze.send(:initialize_copy, enum2) }.should raise_error(FrozenError)
37+
end
38+
39+
it "raises TypeError if the objects are of different class" do
40+
enum = Enumerator::Product.new(1..2)
41+
enum2 = Class.new(Enumerator::Product).new(3..4)
42+
43+
-> { enum.send(:initialize_copy, enum2) }.should raise_error(TypeError, 'initialize_copy should take same class object')
44+
-> { enum2.send(:initialize_copy, enum) }.should raise_error(TypeError, 'initialize_copy should take same class object')
45+
end
46+
47+
it "raises ArgumentError if the argument is not initialized yet" do
48+
enum = Enumerator::Product.new(1..2)
49+
enum2 = Enumerator::Product.allocate
50+
51+
-> { enum.send(:initialize_copy, enum2) }.should raise_error(ArgumentError, 'uninitialized product')
52+
end
53+
end
54+
end
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
require_relative '../../../spec_helper'
2+
3+
ruby_version_is "3.2" do
4+
describe "Enumerator::Product#initialize" do
5+
before :each do
6+
@uninitialized = Enumerator::Product.allocate
7+
end
8+
9+
it "is a private method" do
10+
Enumerator::Product.should have_private_instance_method(:initialize, false)
11+
end
12+
13+
it "returns self" do
14+
@uninitialized.send(:initialize).should equal(@uninitialized)
15+
end
16+
17+
it "accepts many arguments" do
18+
@uninitialized.send(:initialize, 0..1, 2..3, 4..5).should equal(@uninitialized)
19+
end
20+
21+
it "accepts arguments that are not Enumerable nor responding to :each_entry" do
22+
@uninitialized.send(:initialize, Object.new).should equal(@uninitialized)
23+
end
24+
25+
describe "on frozen instance" do
26+
it "raises a FrozenError" do
27+
-> {
28+
@uninitialized.freeze.send(:initialize, 0..1)
29+
}.should raise_error(FrozenError)
30+
end
31+
end
32+
end
33+
end
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
require_relative '../../../spec_helper'
2+
3+
ruby_version_is "3.2" do
4+
describe "Enumerator::Product#inspect" do
5+
it "returns a String including enumerators" do
6+
enum = Enumerator::Product.new([1, 2], [:a, :b])
7+
enum.inspect.should == "#<Enumerator::Product: [[1, 2], [:a, :b]]>"
8+
end
9+
10+
it "represents a recursive element with '[...]'" do
11+
enum = [1, 2]
12+
enum_recursive = Enumerator::Product.new(enum)
13+
14+
enum << enum_recursive
15+
enum_recursive.inspect.should == "#<Enumerator::Product: [[1, 2, #<Enumerator::Product: ...>]]>"
16+
end
17+
18+
it "returns a not initialized representation if #initialized is not called yet" do
19+
Enumerator::Product.allocate.inspect.should == "#<Enumerator::Product: uninitialized>"
20+
end
21+
end
22+
end

0 commit comments

Comments
 (0)