issue: function hoisting and parameter default value initialization

Allen Wirfs-Brock allen at wirfs-brock.com
Fri Oct 5 17:35:49 PDT 2012


The Sept 20 TC39 meeting notes says the following was (almost) agreed regarding function parameter scoping,  pending some further investigation.

> Temporarily, this happened:

> Conclusion/Resolution
> - `var` bindings and are in scope within the function
> - cannot use `let` to shadow a parameter
> - defaults can refer to any top level binding

> **Conclusion/Resolution**
> Revisit when data is gathered, re: perf or unexpected behaviours


We also agreed, that repeat use of an identifier as a parameter, for example:

function f(x,x,x,x) {}

is only allowed for legacy compatibility and that it will be an early error if repeated parameter names are used in a parameter list that contains any of the new ES6 parameter list syntactic affordances.  (Call this Agreement 0)For example, these would all be errors:

function f(x,x,x,x, ...x) {}
function f(x,x=1)  {}
function f( {x,x} ){ };


An implication of the "defaults can rely on any top level binding" point is that parameter default value initializers can refer to inner functions defined using function top-level function declarations.  For example:

function f(p=computeDefaultValue()) {
   function computeDefaultValue() {return Math.random()}
   return p;
}

The notes don't literally say this, but it was one of the intentions of that item that we discussed at the meeting.

This implies that inner function declaration initialization is hoisted ahead of formal parameter initialization.

This however exposes some strange interactions between ES<=5.1 rules regarding duplicate  parameter names that are the same as declared inner function.

ES5.1 requires that 

(function(p) {
   return typeof p;
   function p() {};
})(1)

returns "function" rather than "number". 

To maintain compatibility, this means that if function initialization is hoisted before parameter initialization, that in cases like the above either
   1) initialization of parameters that have the same name as a function declaration are skipped
   2) declared functions that share names with formal parameters must be reinitialized to their original function value after parameter initialization

The choice of one of the above specification alternatives really doesn't make any observable difference,  as long as default value initializer aren't used.

However, alternative 1 introduces a specification (and perhaps implementation) complication.  Consider:

(function ({a:{b: {p}}}) {
      function p() {};
      return typeof p;
}) ({a: {b: {p: 1}}});

The spec. complication is that deep within the destructuring algorithm we need to be able to skip any binding name that is the same as a declared function name.  

Also, it seems highly unlikely that anyone would actually want to give an inner function the same name as such a interior destructured parameter name.  It almost surely is a bug.  There are no backwards compatibility requires on such destructured parameter names, so:

Proposal #1:  Local bindings introduced via formal parameter destructing are treated as let bindings rather than var binding.  They cannot be the same as top inner function or var declared name.  There similarly are no compatibility issue with the name of the rest parameter, so it also should be treated as a let binding.

When default value initializers, the difference between approach 1 and 2 above becomes observable.  Consider,

function (p=[ ], q=p.length) {
      function p(a,b,c) {};
      return q;
}) ();

If approach 1 above is used, the function call will return 3 -- the number of parameters of the inner function p.  If approach 2 is used, the function will return 0 -- the length of the empty array that provided the default value of the p parameter.

Another odd situation where the approaches would differ is:

function (a=p, p=1, c=p) {
      function p() {};
      return a===c;
}) ();

If approach 1 is used, the function will return true.  If approach 2 is used the function will return false.

These are crazy things for anybody to code and it seems a waste to try to specify rationale runtime behavior for such irrational code.  Instead I suggest:

Proposal #2:  It is an early error if both any formal parameter is the same name as an top level inner function declaration and the formal parameter lists includes a destructuring, default value initializer, or a rest parameter.

Essential the net of Agreement 0 plus Proposals 1 & 2 would be that only ES<=5.1 style simple formal parameter lists may have repeated parameter names or parameter names that are the same as top level inner function declarations.

This could be even more cleanly expressed as:
Proposal #3:
       a) ES<=5.1 style formal parameters lists introduce var bindings and follow ES5.1 initialization rules. This permits top level inner functinon and var declarations that use the same name as a parameter.
       b) parameter lists containing any new ES6 syntax introduce let bindings for the parameters.  This prohibits multiple declaration of a formal parameter name and inner var/function redeclaration of a parameter name.

Bottom line, 
  I suggest we implement proposal 3, rather than the temporary conclusions that were discussed at the Sept. meeting.

Allen


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20121005/baafb997/attachment.html>


More information about the es-discuss mailing list