Project

General

Profile

Bug #12860

Splatting an argument does not obey left-to-right execution order

Added by headius (Charles Nutter) over 3 years ago. Updated over 3 years ago.

Status:
Closed
Priority:
Normal
Assignee:
-
Target version:
-
[ruby-core:77701]

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.


Related issues

Related to Ruby master - Bug #16504: `foo(*args, &args.pop)` should pass all elements of argsClosedjeremyevans0 (Jeremy Evans)Actions

Updated by headius (Charles Nutter) over 3 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) over 3 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) over 3 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) over 3 years ago

  • Backport changed from 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN to 2.1: REQUIRED, 2.2: REQUIRED, 2.3: REQUIRED
  • Status changed from Open to Closed

Updated by usa (Usaku NAKAMURA) over 3 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) over 3 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.

#7

Updated by mame (Yusuke Endoh) 6 months ago

  • Related to Bug #16504: `foo(*args, &args.pop)` should pass all elements of args added

Also available in: Atom PDF