name anonymous functions on property assignments

# a.d.bergi at web.de (9 years ago)

Currently, unnamed function (and class) definitions that are part of a variable assignment or property definition are automatically given the name of the target identifier (see www.ecma-international.org/ecma-262/6.0/#sec-assignment-operators-runtime-semantics-evaluation and www.ecma-international.org/ecma-262/6.0/#sec-object-initializer-runtime-semantics-propertydefinitionevaluation). Same thing happens for (destructuring) default initialisers and variable initialisers.

However, if a function (or class) definition is assigned to a property of an object, this doesn't happen:

var o = {};
o.someProperty = function() { … };
o.otherProperty = class { … };

I don't see any reason for not doing this, it just seems to be advantageous and make the language more consistent. I'm quite sure it wouldn't break any compatibility.

This would only require a minor change to section 12.3.1.4 www.ecma-international.org/ecma-262/6.0/#sec-static-semantics-static-semantics-isidentifierref, namely making IsIdentifierRef for

MemberExpression:
MemberExpression [ Expression ]
MemberExpression . IdentifierName

return true.

(where it currently yields false). Everything else had already been abstracted out enough :-)

What do you think, can we fix this? Do I need to make a more explicit proposal?

# Benjamin Gruenbaum (9 years ago)

In theory this sounds like a cool idea, I didn't even know variable assignments named functions.

The only issue I see here is how we're now differentiating assignment by where it happens - what if the property is computed? As far as I know function names are more constrained (like variable names) in what they can be. Object properties may contain characters that function names may not.

# Allen Wirfs-Brock (9 years ago)

On Jul 26, 2015, at 5:11 AM, Benjamin Gruenbaum wrote:

In theory this sounds like a cool idea, I didn't even know variable assignments named functions.

The only issue I see here is how we're now differentiating assignment by where it happens - what if the property is computed? As far as I know function names are more constrained (like variable names) in what they can be. Object properties may contain characters that function names may not.

the possibility that the property key is a symbol is a primary reason that this expression form does not set the name property.

There may also be security concerns. The name property potentially leaks via the function object the name of the variable it is initially assigned to. But there isn't much someone could do with a local variable name, outside of the originating function. But a leaked property name potentially carries a greater capability.

# Andrea Giammarchi (9 years ago)

with all due respect Allen, I'm completely against magic-function-name assignment for various reason and leaking ain't one.

What could you assign in ES6 that cannot be retrieved anyway through getOwnPropertySymbols and getOwnPropertyNames ? A triple-magic private Proxy handler or what?

I mean, the moment you could access that method is the moment it could leak with or without a name, right?

Just curious about what you had in mind, again I agree having this in is a no-go.

Best

# Benjamin Gruenbaum (9 years ago)

I just want to point out that Bergus (OP) informed me that the assignment behavior happens in computed object literal keys. So:

let o = {
     ["fn" + "name"] () {}
}
o.fnname.name; // fnname
# Jordan Harband (9 years ago)

What is the function's name if the computed object literal key is a Symbol? ie, what does the following output:

const sym = Symbol('something');
const o = {
    [sym] () {}
};
console.log(o[sym].name);

Currently it appears Babel outputs an empty string for this case.

If the current spec handles symbols just fine in this way, why would "the possibility that the property key is a symbol" be a reason for an expression form not to set the "name" property?

# Benjamin Gruenbaum (9 years ago)

Babel does not shim this deliberately, it is "impossible enough" as it is: see babel/babel#2080

Defined in www.ecma-international.org/ecma-262/6.0/index.html#sec-object-initializer-runtime-semantics-propertydefinitionevaluation you can see that if it's a symbol it sets the function name to a symbol.

To make this extra clear - it does "Let description be name’s [[Description]] value." and then definePropertys it with that, this is specified in www.ecma-international.org/ecma-262/6.0/index.html#sec

# Allen Wirfs-Brock (9 years ago)

On Jul 26, 2015, at 12:55 PM, Andrea Giammarchi wrote:

with all due respect Allen, I'm completely against magic-function-name assignment for various reason and leaking ain't one.

Implicit function name property assignment is part of ES2015.

What could you assign in ES6 that cannot be retrieved anyway through getOwnPropertySymbols and getOwnPropertyNames ? A triple-magic private Proxy handler or what?

A sandbox can censor getOwnPropertySymbol and other reflection functions.

I mean, the moment you could access that method is the moment it could leak with or without a name, right?

Just curious about what you had in mind, again I agree having this in is a no-go.

Just saying that an exposed property name is a different (and potentially more broadly exploitable) capability than exposing a local variable name.

TC39 reached consensus on automatically assigning the name property for expression forms like: Identifier = FunctionExpression

and so it is part of ES2015. We did not have consensus on doing the same for: MemberExpression.IdentifierName = FunctionExpression or MemberExpression[Expression] = FunctionExpression so it is not part of ES2015. There were various objections that would have to be overcome before we could adopt that.

# Allen Wirfs-Brock (9 years ago)

On Jul 26, 2015, at 1:17 PM, Jordan Harband wrote:

What is the function's name if the computed object literal key is a Symbol? ie, what does the following output:

specified in step 4 of ecma-international.org/ecma-262/6.0/#sec-setfunctionname

const sym = Symbol('something');
const o = {
    [sym] () {}
};
console.log(o[sym].name);

should be: "[something]"

Currently it appears Babel outputs an empty string for this case.

If the current spec handles symbols just fine in this way, why would "the possibility that the property key is a symbol" be a reason for an expression form not to set the "name" property?

Yes, the ES2015 spec. does say how to handle Symbols as name property values.

# a.d.bergi at web.de (9 years ago)

I'm completely against magic-function-name assignment for various reason and leaking ain't one.

What are these reasons? (I couldn't find previous discussions; if there are any, a link would be sufficient)

I agree that explicit naming is better than implicit, and like to use function declarations over unnamed function expressions myself, but for named arrow functions this seems like a good idea. And browsers are doing it already anyway for a better debugging experience. Regardless, it's in ES6 now, I just wondered why it's not as consistent as I expected it to be.

Bergi

# a.d.bergi at web.de (9 years ago)

Just saying that an exposed property name is a different (and potentially more broadly exploitable) capability than exposing a local variable name.   TC39 reached consensus on automatically assigning the name property for expression forms like:       Identifier = FunctionExpression and so it is part of ES2015.  We did not have consensus on doing the same for:        MemberExpression.IdentifierName = FunctionExpression or        MemberExpression[Expression] = FunctionExpression so it is not part of ES2015. There were various objections that would have to be overcome before we could adopt that.

Ah, that's what I wanted to know. Can you detail these objections, please? Or link to the TC39 notes where these were discussed?

I see that method names are more capable than local variable names, but aren't we already naming methods by default everywhere else? Even those with computed keys? To me, there is not much difference between

let o = {
    method: () => {…}
};

and

let o = {};
o.method = () => {…};

yet the first function gets a name while the second one doesn't.

Bergi

# Bradley Meck (9 years ago)

a big concern for me is what to do about functions being passed around:

var a = {};
a.foo = function () {}
// .name would be "foo"
var b = {};
b.bar = a.foo;
// .name would be "bar"

or

function doThing(callback) {
   var async = {};
   async.fn = callback;
   // ...
}
doThing(()=>{})
// what is .name?

Most of my functions are declared separately from where they are attached to functions. I think naming them is fine, but this could get confusing about the rules of when / which functions are named.

# a.d.bergi at web.de (9 years ago)

a big concern for me is what to do about functions being passed around

Nothing happens to them. This feature is only about assignments where the right hand side consists of a function expression.

var a = {}; a.foo = function () {} // .name would be "foo"

Yes.

var b = {}; b.bar = a.foo; // .name would be "bar"

No, the name would still be "foo". It doesn't change when the function already has a .name, and a.foo is not an anonymous function definition but a memberexpression.

doThing(()=>{}) // what is .name?

Nothing. There is no identifier hanging around the function expression. It's anonymous, and will stay so.

You see, no magic! :-)

Bergi

# T.J. Crowder (7 years ago)

About a year ago, Bergi asked[1] why no name is assigned to a function when it's created as part of an assignment to an object property:

o.method = () => { };

Allen Wirfs-Brock replied:

We did not have consensus on doing the same for: MemberExpression.IdentifierName = FunctionExpression or MemberExpression[Expression] = FunctionExpression so it is not part of ES2015. There were various objections that would have to be overcome before we could adopt that.

Does anyone remember what the objections were?

I've been through the TC39 meeting notes[2] without luck (so far). In the May 28 2015 notes I can find "function name property" listed as an open issue, but nothing in the May 29 notes and those are the last two in the "es6" folder. Other than those, searching for "function name" in meeting notes turns up Mar 24 2015, then Nov 18 2014, then July 23 2013; that last one talks about doing inference (as a whole) and AWB says "It's not an insignificant amount of work" suggesting to me that at that point, it hadn't been done yet. But Nov 18 2014 and Mar 24 2015 don't seem to address this case. Other mentions of "function name" are unrelated (for instance, relate to toString). Searching for "MemberExpression" only turns up one unrelated match.

The strawman[3] (linked from the July 23 2013 notes) does have the name being set in this case (it's the final example).

Thanks,

[1] esdiscuss.org/topic/name-anonymous-functions-on-property-assignments [2] rwaldron/tc39-notes [3] harmony:function_name_property

T.J.

# T.J. Crowder (7 years ago)

If no one can remember what the objection was / objections were, I might submit a proposal to close the gap. I'm not a spec expert in any way, but I think it's simply a matter of removing the IsIdentifierReference check in Step 1.e of assignment evaluation.

-- T.J.

# T.J. Crowder (7 years ago)

I'm mistaken about Step 1.e of [assignment evaluation][1], it's not that we'd want to remove the IsIdentifierRef check, it's that we'd want to make it "IsIdentifierRef is true or IsPropertyRef is true":

e. If IsAnonymousFunctionDefinition(AssignmentExpression) is true and either IsIdentifierRef or IsPropertyRef of LeftHandSideExpression is true, then

-- T.J.

[1] tc39.github.io/ecma262/#sec

# T.J. Crowder (7 years ago)

Two questions on the minor issue of the following not assigning a name to the function:

obj.foo = function() { };

1) Am I correct that the only reason it doesn't (in spec terms) is that Step 1.e. of Assignment Operators - Runtime Semantics: Evaluation reads

If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then

and that changing that to

If IsAnonymousFunctionDefinition(AssignmentExpression) is true and either IsIdentifierRef of LeftHandSideExpression is true or IsPropertyReference of LeftHandSideExpression is true, then

would mean it would assign the name foo? (The subsequent step getting the name to use already uses GetReferencedName, which works for property references as well as identifier references.)

2) Are there any objections to closing this gap in how function names are assigned?

-- T.J.

# Jordan Harband (7 years ago)

I'd have an objection. Function name inference has already broken my code - luckily only tests so far, that i know of - and doing it more often would break more of it.

# T.J. Crowder (7 years ago)

On Fri, Jan 27, 2017 at 4:56 PM, Jordan Harband <ljharb at gmail.com> wrote:

I'd have an objection. Function name inference has already broken my code - luckily only tests so far, that i know of - and doing it more often would break more of it.

:-) Sorry to hear that. The ship has sailed on function name inference, though. It makes no sense to leave a gap in it like we have now.

-- T.J.

# Alexander Jones (7 years ago)

Just for a bit of context, can you elaborate on how this broke your code?

# Allen Wirfs-Brock (7 years ago)

On Jan 27, 2017, at 7:26 AM, T.J. Crowder <tj.crowder at farsightsoftware.com> wrote:

Two questions on the minor issue of the following not assigning a name to the function:

obj.foo = function() { };
  1. Am I correct that the only reason it doesn't (in spec terms) is

No, the only reason it doesn’t is: by design, as directed by a decision made within a TC39 meeting.

that Step 1.e. of Runtime Semantics: Evaluation reads

If IsAnonymousFunctionDefinition(AssignmentExpression) and IsIdentifierRef of LeftHandSideExpression are both true, then

and that changing that to

If IsAnonymousFunctionDefinition(AssignmentExpression) is true and either IsIdentifierRef of LeftHandSideExpression is true or IsPropertyReference of LeftHandSideExpression is true, then

would mean it would assign the name foo?

Yes, and for cache[getUserSecret(user)] = function() {}; it would leak the secret user info as the value of name

and for obj[someSymbol] = function() {} it would leak the Symbol value as the value of name

and for table[n]=function() {} name would likely be a numeric string

(The subsequent step getting the name to use already uses GetReferencedName, which works for property references as well as identifier references.)

  1. Are there any objections to closing this gap in how function names are assigned?

Yes there are!

In addition to the above, note that isIdentiferRef is a static semantic operation. That means the the determination of whether a name will be attached and the actual name can be determined statically prior to execution. IsPropertyRef is a runtime operation and the new semantics require runtime determination of the name value. This is all extra runtime work that may slow down the creation of function closures that appear within loops.

# T.J. Crowder (7 years ago)

On Sat, Jan 28, 2017 at 3:46 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 27, 2017, at 7:26 AM, T.J. Crowder <tj.crowder at farsightsoftware.com> wrote:

Two questions on the minor issue of the following not assigning a name to the function:

obj.foo = function() { };
  1. Am I correct that the only reason it doesn't (in spec terms) is

No, the only reason it doesn’t is: by design, as directed by a decision made within a TC39 meeting.

Yes, obviously. :-) By "in spec terms" I meant -- and thought in context was clear -- "in the language in the specification." I'm sorry if it wasn't clear.

would mean it would assign the name foo?

Yes, and for cache[getUserSecret(user)] = function() {}; it would leak the secret user info as the value of name

As does

cache = {
    [getUserSecret(user)]: function() {}
};

...which while perhaps less likely for something called cache is, in the general case, just as much of a potential "leak". If secrecy is important, it's easily achieved:

cache[getUserSecret(user)] = function entry() {};

and for obj[someSymbol] = function() {} it would leak the Symbol value as the value of name

It would use it as the name, yes, just like this does:

obj = {
    [someSymbol]: function() {}
};

Whether that's a leak depends on whether the code in question cares about that information being exposed. And again if that secrecy is important, it's trivially ensured (as above).

and for table[n]=function() {} name would likely be a numeric string

Just as it does here:

obj = {
    [n]: function() {}
};

or here

obj = {
    42: function() {}
};

I appreciate your taking the time to post those examples. I take it these are the objections you referred to in July 2015 as why consensus couldn't be reached for this form?

Thanks again,

-- T.J.

# Jordan Harband (7 years ago)

In var anon = function () {};, I was relying on anon.name being the empty string. One of the packages this matters in is www.npmjs.com/package/function.prototype.name, which attempts to polyfill function names in IE. I fixed it by putting the function inside Object() ljharb/function.prototype.name/blob/master/test/tests.js#L7-L9 but that wouldn't be necessary without function name inference.

# Oriol _ (7 years ago)

I fixed it by putting the function inside Object()

You can also use the comma operator:

var anon = (0, function () {});
anon.name; // ""

;Oriol

# Jordan Harband (7 years ago)

Indeed, but my linting rules forbid using the comma operator :-)