name anonymous functions on property assignments
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.
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.
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
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
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?
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 defineProperty
s it with that, this is
specified in
www.ecma-international.org/ecma-262/6.0/index.html#sec
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.
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.
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
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
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 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
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
orMemberExpression[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.
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.
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.
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.
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.
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.
Just for a bit of context, can you elaborate on how this broke your code?
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() { };
- 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.)
- 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.
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() { };
- 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.
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.
I fixed it by putting the function inside
Object()
You can also use the comma operator:
var anon = (0, function () {});
anon.name; // ""
;Oriol
Indeed, but my linting rules forbid using the comma operator :-)
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
forMemberExpression:
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?