Why do duplicate parameter names cause an arguments object to be unmapped?

Edit: Silly mistake. They are mapped. Read more below


I've noticed when having duplicate named parameters in a function, the arguments object is no longer mapped to the function parameters.

Normal mapping without a duplicate param:

// sloppy mode
function fn(param) {
  param = "changed"
  console.log(arguments[0])
}
fn("original") //> "changed"

Adding duplicate param:

// sloppy mode
function fn(param, param) {
  param = "changed"
  console.log(arguments[0])
}
fn("original") //> "original"

Tested on Mac Chrome/Safari/Firefox

A mapped arguments object is created in 10.2.11 FunctionDeclarationInstantiation dependent on strict mode and 15.1.3 Static Semantics: IsSimpleParameterList of the formal parameters.

 22. If argumentsObjectNeeded is true, then
   a. If strict is true or simpleParameterList is false, then
     i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
   b. Else,
     i. NOTE: A mapped argument object is only provided for non-strict functions that don't have a rest parameter, any parameter default value initializers, or any destructured parameters.
     ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).

As far as I can tell IsSimpleParameterList should be true here, and this seems to be backed up by 15.1.1 Static Semantics: Early Errors where it says

  • It is a Syntax Error if IsSimpleParameterList of FormalParameterList is false and the BoundNames of FormalParameterList contains any duplicate elements.

suggesting the presence of duplicate names are independent of IsSimpleParameterList and can only exist if IsSimpleParameterList is true.

So if strict is false and IsSimpleParameterList is true, wouldn't a mapped arguments object be expected in the second example above?

Also, from a backwards compatibility perspective, wouldn't this have the potential to break legacy code if it both had duplicate parameters and relied on a mapped arguments object?

Note: this is purely for curiosity and in no way am I reliant on this behavior or these features.

It is mapped. It's just that the mapping is last-wins, not first-wins.

function fn(param, param) {
  param = "changed";
  console.log(arguments[1]);
}
fn("x", "y"); // "changed"

Last-wins is also how the assignment of arguments to formal parameters works:

function fn(param, param) {
  console.log(param); // prints the second argument, not the first
}
fn("x", "y") // "y"
3 Likes

Ah, of course. Thanks!