Skip to content

Commit 4f44445

Browse files
committed
Fix concatenation of multiple ** arguments with duplicate keys (#1469)
PullRequest: truffleruby/678
2 parents bf19fda + e6abd6b commit 4f44445

File tree

3 files changed

+17
-27
lines changed

3 files changed

+17
-27
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Bug fixes:
55
* Implement `rb_io_wait_writable` (#1586).
66
* Fixed error when using arrows keys first within `irb` or `pry` (#1478, #1486).
77
* Coerce the right hand side for all `BigDecimal` operations (#1598).
8+
* Combining multiple `**` arguments containing duplicate keys produced
9+
an incorrect hash. This has now been fixed (#1469).
810

911
Changes:
1012

spec/ruby/language/method_spec.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,7 @@ def m(**k) k end
574574
m(a: 1, b: 2).should == { a: 1, b: 2 }
575575
m(*[]).should == {}
576576
m(**{}).should == {}
577+
m(**{a: 1, b: 2}, **{a: 4, c: 7}).should == { a: 4, b: 2, c: 7 }
577578
lambda { m(2) }.should raise_error(ArgumentError)
578579
end
579580

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

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public static DynamicObject create(RubyContext context, Collection<KeyValue> ent
6060

6161
final int index = BucketsStrategy.getBucketIndex(hashed, newEntries.length);
6262
Entry bucketEntry = newEntries[index];
63+
boolean duplicateKey = false;
6364

6465
if (bucketEntry == null) {
6566
newEntries[index] = newEntry;
@@ -72,47 +73,33 @@ && areKeysEqual(context, bucketEntry.getKey(), key, byIdentity)) {
7273
bucketEntry.setValue(entry.getValue());
7374

7475
actualSize--;
75-
76-
if (bucketEntry.getPreviousInSequence() != null) {
77-
bucketEntry.getPreviousInSequence().setNextInSequence(bucketEntry.getNextInSequence());
78-
}
79-
80-
if (bucketEntry.getNextInSequence() != null) {
81-
bucketEntry.getNextInSequence().setPreviousInSequence(bucketEntry.getPreviousInSequence());
82-
}
83-
84-
if (bucketEntry == lastInSequence) {
85-
lastInSequence = bucketEntry.getPreviousInSequence();
86-
}
87-
88-
// We wasted by allocating newEntry, but never mind
89-
newEntry = bucketEntry;
90-
previousInBucket = null;
91-
76+
duplicateKey = true;
9277
break;
9378
}
9479

9580
previousInBucket = bucketEntry;
9681
bucketEntry = bucketEntry.getNextInLookup();
9782
}
9883

99-
if (previousInBucket != null) {
84+
if (!duplicateKey && previousInBucket != null) {
10085
previousInBucket.setNextInLookup(newEntry);
10186
}
10287
}
10388

104-
if (firstInSequence == null) {
105-
firstInSequence = newEntry;
106-
}
89+
if (!duplicateKey) {
90+
if (firstInSequence == null) {
91+
firstInSequence = newEntry;
92+
}
10793

108-
if (lastInSequence != null) {
109-
lastInSequence.setNextInSequence(newEntry);
110-
}
94+
if (lastInSequence != null) {
95+
lastInSequence.setNextInSequence(newEntry);
96+
}
11197

112-
newEntry.setPreviousInSequence(lastInSequence);
113-
newEntry.setNextInSequence(null);
98+
newEntry.setPreviousInSequence(lastInSequence);
99+
newEntry.setNextInSequence(null);
114100

115-
lastInSequence = newEntry;
101+
lastInSequence = newEntry;
102+
}
116103
}
117104

118105
final DynamicObject nil = context.getCoreLibrary().getNil();

0 commit comments

Comments
 (0)