From: nagachika00@... Date: 2014-06-16T16:00:45+00:00 Subject: [ruby-core:63198] [ruby-trunk - Bug #9776] Ruby double-splat operator unexpectedly modifies hash Issue #9776 has been updated by Tomoyuki Chikanaga. Backport changed from 2.0.0: DONTNEED, 2.1: REQUIRED to 2.0.0: DONTNEED, 2.1: DONE Backported into `ruby_2_1` branch at r46451. ---------------------------------------- Bug #9776: Ruby double-splat operator unexpectedly modifies hash https://bugs.ruby-lang.org/issues/9776#change-47249 * Author: Jesse Sielaff * Status: Closed * Priority: Normal * Assignee: Yukihiro Matsumoto * Category: syntax * Target version: current: 2.2.0 * ruby -v: ruby 2.1.1p76 (2014-02-24 revision 45161) [x86_64-linux] * Backport: 2.0.0: DONTNEED, 2.1: DONE ---------------------------------------- I noticed what I find to be a very surprising behavior with the double-splat (`**`) operator in Ruby 2.1.1. When key-value pairs are used before a `**hash`, the hash remains unmodified. However, when key-value pairs are only used after the `**hash`, the hash is permanently modified. ~~~ h = { b: 2 } { a: 1, **h } # => { a: 1, b: 2 } h # => { b: 2 } { a: 1, **h, c: 3 } # => { a: 1, b: 2, c: 3 } h # => { b: 2 } { **h, c: 3 } # => { b: 2, c: 3 } h # => { b: 2, c: 3 } ~~~ For comparison, consider the behavior of the splat (`*`) operator on arrays: ~~~ a = [2] [1, *a] # => [1, 2] a # => [2] [1, *a, 3] # => [1, 2, 3] a # => [2] [*a, 3] # => [2, 3] a # => [2] ~~~ The array remains unchanged throughout. --------------- Tsuyoshi Sawada has also highlighted that the expression's result is the self-same object as the original hash: ~~~ h.object_id == { **h, c: 3 }.object_id # => true ~~~ --------------- I investigated `parse.y` to try to determine the error there, but I couldn't narrow it down any further than the `list_concat` or `rb_ary_push` function calls in the `assocs :` block of the grammar. Without exhaustively examining the C source, I think the best clue to the mechanism behind the erroneous behavior might be the following: ~~~ h = { a: 1 } { **h, a: 99, **h } # => {:a=>99} ~~~ That we don't see `{:a=>1}` illustrates that `h[:a]` is already overwritten by the time the second `**h` is evaluated. --------------- Here is the use case that led me to this discovery: ~~~ def foo (arg) arg end h = { a: 1 } foo(**h, b: 2) h # => { a: 1, b: 2 } ~~~ In the above example, I don't want `{ b: 2 }` permanently added to my existing hash. I'm currently solving it like this: ~~~ h = { a: 1 } foo(**h.dup, b: 2) h # => { a: 1 } ~~~ The call to #dup feels unnecessary, and is inconsistent with the analogous behavior when using the single `*` operator. If this bug is fixed, I'll be able to eliminate that call. -- https://bugs.ruby-lang.org/