Bug #12860
closedSplatting an argument does not obey left-to-right execution order
Description
Ruby evaluates arguments left to right, but it does not appear to handle construction of the eventual argument list from left to right.
Take this example:
def foo(*args)
p args
end
ary = [1,2]
foo(*ary, ary.shift)
With left-to-right execution, the ary
value should be splatted (1, 2), and THEN shifted (1) producing args == [1, 2, 1]
.
However, on MRI, the shift occurs before the splat, so args == [2, 1]
.
This is counter-intuitive. At the moment in time the splat is encountered, ary
is still [1, 2]
. So the first two arguments should be (1, 2). THEN the shift happens, producing a third argument of (1).
This affects JRuby running Rails because they have a small piece of code that depends on this unusual behavior: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/callbacks.rb#L411-L425
This code appears to have been introduced into Rails recently, and I will file a separate issue to change it to be more explicit about ordering.
Updated by headius (Charles Nutter) about 8 years ago
Rails PR (which unpacks via multi-assign to avoid this bug) filed here: https://github.com/rails/rails/pull/26854
Updated by headius (Charles Nutter) about 8 years ago
This behavior appears to go back as far as Ruby 1.9.3. Ruby 1.8.7 and earlier could not have regular arguments after a splat.
Updated by headius (Charles Nutter) about 8 years ago
It is worth mentioning that if the splatted value is not an array, we can see that to_a
does get called before shift
. That makes this even more unintuitive, since the calls happen in proper order but the assembly of the args list happens in the wrong order.
a = Object.new
a.instance_variable_set :@ary, [1, 2]
def a.to_a
p :to_a
@ary
end
def a.shift
p :shift
@ary.shift
end
def f(*x)
p x
end
f(*a, a.shift)
__END__
Output on MRI:
:to_a
:shift
[2, 1]
Output on JRuby:
:to_a
:shift
[1, 2, 1]
All implementations I tested output to_a
before shift
. However, as with the plain array, MRI delays the application of the splat until after the shift call.
Updated by nobu (Nobuyoshi Nakada) about 8 years ago
- Status changed from Open to Closed
- Backport changed from 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN to 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED
Updated by usa (Usaku NAKAMURA) almost 8 years ago
- Backport changed from 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED to 2.1: REQUIRED, 2.2: DONE, 2.3: REQUIRED
ruby_2_2 r57210 merged revision(s) 56469.
Updated by nagachika (Tomoyuki Chikanaga) almost 8 years ago
- Backport changed from 2.1: REQUIRED, 2.2: DONE, 2.3: REQUIRED to 2.1: REQUIRED, 2.2: DONE, 2.3: DONE
ruby_2_3 r57342 merged revision(s) 56469.
Updated by mame (Yusuke Endoh) almost 5 years ago
- Related to Bug #16504: `foo(*args, &args.pop)` should pass all elements of args added