Skip to content

Commit 7c7997b

Browse files
committed
Follow MRI behavior for Hash#{each,each_pair}
PullRequest: truffleruby/721
2 parents 383c885 + eea9202 commit 7c7997b

File tree

5 files changed

+38
-3
lines changed

5 files changed

+38
-3
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ Bug fixes:
2020
to assign the iteration variable.
2121
* Existing global variables can now become aliases of other global variables (#1590).
2222

23+
Compatibility:
24+
25+
* Yield different number of arguments for `Hash#each` and `Hash#each_pair` based
26+
on the block arity like MRI (#1629).
27+
2328
# 1.0 RC 14
2429

2530
Updated to Ruby 2.6.2.

spec/ruby/core/file/mtime_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
it "returns the modification Time of the file" do
1414
File.mtime(@filename).should be_kind_of(Time)
15-
File.mtime(@filename).should be_close(@mtime, 2.0)
15+
File.mtime(@filename).should be_close(@mtime, 60.0)
1616
end
1717

1818
guard -> { platform_is :linux or (platform_is :windows and ruby_version_is '2.5') } do

spec/ruby/core/hash/shared/each.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
describe :hash_each, shared: true do
2+
3+
# This is inconsistent with below, MRI checks the block arity in rb_hash_each_pair()
24
it "yields a [[key, value]] Array for each pair to a block expecting |*args|" do
35
all_args = []
46
{ 1 => 2, 3 => 4 }.send(@method) { |*args| all_args << args }
@@ -19,6 +21,21 @@
1921
ary.sort.should == ["a", "b", "c"]
2022
end
2123

24+
it "yields 2 values and not an Array of 2 elements" do
25+
obj = Object.new
26+
def obj.foo(key, value)
27+
ScratchPad << key << value
28+
end
29+
30+
ScratchPad.record([])
31+
{ "a" => 1 }.each(&obj.method(:foo))
32+
ScratchPad.recorded.should == ["a", 1]
33+
34+
ScratchPad.record([])
35+
{ "a" => 1 }.each(&-> key, value { ScratchPad << key << value })
36+
ScratchPad.recorded.should == ["a", 1]
37+
end
38+
2239
it "uses the same order as keys() and values()" do
2340
h = { a: 1, b: 2, c: 3, d: 5 }
2441
keys = []

src/main/java/org/truffleruby/core/hash/HashNodes.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,8 @@ protected boolean equalKeys(boolean compareByIdentity, Object key, int hashed, O
427427
@ImportStatic(HashGuards.class)
428428
public abstract static class EachNode extends YieldingCoreMethodNode {
429429

430+
private final ConditionProfile arityMoreThanOne = ConditionProfile.createBinaryProfile();
431+
430432
@Specialization(guards = "isNullHash(hash)")
431433
public DynamicObject eachNull(DynamicObject hash, DynamicObject block) {
432434
return hash;
@@ -475,7 +477,12 @@ public DynamicObject eachBuckets(DynamicObject hash, DynamicObject block) {
475477
}
476478

477479
private Object yieldPair(DynamicObject block, Object key, Object value) {
478-
return yield(block, createArray(new Object[]{key, value}, 2));
480+
// MRI behavior, see rb_hash_each_pair()
481+
if (arityMoreThanOne.profile(Layouts.PROC.getSharedMethodInfo(block).getArity().getArityNumber() > 1)) {
482+
return yield(block, key, value);
483+
} else {
484+
return yield(block, createArray(new Object[]{key, value}, 2));
485+
}
479486
}
480487

481488
}

src/main/java/org/truffleruby/language/methods/Arity.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class Arity {
2828
private final int postRequired;
2929
private final boolean hasKeywordsRest;
3030
private final String[] keywordArguments;
31+
private final int arityNumber;
3132

3233
public Arity(int preRequired, int optional, boolean hasRest) {
3334
this(preRequired, optional, hasRest, 0, NO_KEYWORDS, false);
@@ -41,6 +42,7 @@ public Arity(int preRequired, int optional, boolean hasRest, int postRequired, S
4142
this.postRequired = postRequired;
4243
this.keywordArguments = keywordArguments;
4344
this.hasKeywordsRest = hasKeywordsRest;
45+
this.arityNumber = computeArityNumber();
4446

4547
assert keywordArguments != null && preRequired >= 0 && optional >= 0 && postRequired >= 0 : toString();
4648
}
@@ -77,7 +79,7 @@ public boolean hasKeywordsRest() {
7779
return hasKeywordsRest;
7880
}
7981

80-
public int getArityNumber() {
82+
private int computeArityNumber() {
8183
int count = getRequired();
8284

8385
if (acceptsKeywords()) {
@@ -91,6 +93,10 @@ public int getArityNumber() {
9193
return count;
9294
}
9395

96+
public int getArityNumber() {
97+
return arityNumber;
98+
}
99+
94100
public String[] getKeywordArguments() {
95101
return keywordArguments;
96102
}

0 commit comments

Comments
 (0)