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 9 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 9 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 9 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) almost 9 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 9 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 9 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 6 years ago
- Related to Bug #16504: `foo(*args, &args.pop)` should pass all elements of args added