Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Naming arrow functions #6433

Closed
jscriptcoder opened this issue Jan 11, 2016 · 45 comments
Closed

Naming arrow functions #6433

jscriptcoder opened this issue Jan 11, 2016 · 45 comments
Labels
Bug A bug in TypeScript ES6 Relates to the ES6 Spec
Milestone

Comments

@jscriptcoder
Copy link

Hi there,

Not sure whether this has been already mentioned before (I would be surpirsed if it never was)... Wouldn't it be a good idea to name arrow functions, which is what apparently ES6 does?. Think about the benefits of this in the stacktrace. It's quite useful to have a "named" function in there, instead of an anonymous one ;-)

So, this:

const double = x => x * 2;

would be transpiled down to:

var double = function double(x) {
  return x * 2;
};

Just to mention that Babel does it like that ;-)

Thanks.

@adidahiya
Copy link
Contributor

This would also be nice to have for arrow functions used as class methods/properties.

private handleDocumentClick = (...) => {
    ...
}

@RyanCavanaugh
Copy link
Member

Wouldn't it be a good idea to name arrow functions, which is what apparently ES6 does?

Can you clarify this part?

@RyanCavanaugh
Copy link
Member

This would also be nice to have for arrow functions used as class methods/properties.

Note that we can't safely name class methods -- a reference to handleDocumentClick in the method body must refer to whatever that name means in the enclosing lexical scope, not the function itself.

@RyanCavanaugh
Copy link
Member

Consulted with @bterlson and we think this is a spec-affecting behavior, specifically the places where the abstract SetFunctionName operation is invoked https://tc39.github.io/ecma262/#sec-setfunctionname

Regular function expressions have very noncomplaint behavior in current runtimes and should probably just be left alone (specifically, Edge, Chrome, and Node all produce an own property of name on functions, when they shouldn't)

@RyanCavanaugh RyanCavanaugh added Bug A bug in TypeScript ES6 Relates to the ES6 Spec labels Jan 11, 2016
@jscriptcoder
Copy link
Author

Hi @RyanCavanaugh, answering your question... as far as I know in ES6 if an arrow function is defined on the right-hand-side of an assignment expression, the ‎engine will take the name on the left-hand-side and set the arrow function's ".name" based on that

Thanks

@nojvek
Copy link
Contributor

nojvek commented May 21, 2016

Hi @RyanCavanaugh, could you explain the SetFunctionName operation? I couldn't fully grasp why it should be an issue.

I tried this in chrome.

let l = {log(){}}
console.log(l.log.name) //outputs 'log'

Generated typescript:

var l = { log: function () { } };
l.log.name // empty

So it seems under the covers v8 makes nice named anonymous functions.

@basarat
Copy link
Contributor

basarat commented May 24, 2016

Tried the following in the chrome dev console. Was surprised 🌹

let foo = function(){}
foo.name; // "foo"

image

@dsherret
Copy link
Contributor

dsherret commented May 25, 2016

@basarat It didn't happen for me in v50.0.2661.102m, but I just upgraded to v51.0.2704.63m and now it's doing that.

@mhegazy
Copy link
Contributor

mhegazy commented Feb 17, 2017

PRs welcomed

@mpodlasin
Copy link

I will attempt to make a PR in following days or weeks, depending on how difficult it turns out to be. ;)

@mpodlasin
Copy link

mpodlasin commented May 17, 2017

@mhegazy For clarification - should PR for this issue fix all naming issues according to ES6 spec (naming arrow function object properties etc...), or only this one case of const x = () => {}?

Also, should it only target arrow functions or regular anonymous functions - function () {} - as well?

Also, is it ok to open work in progress PR to make sure changes go in good direction and get some help from community (as a newbie contributor)?

@mhegazy
Copy link
Contributor

mhegazy commented May 17, 2017

I would say all lambdas/function expressions should behave as such. feel free to send us incomplete PRs if you want to get early feedback. please tag @rbuckton on your PRs.

@sebinsua
Copy link

See also: #6757.

I notice the PR hasn't been merged, is anybody working on this?

@mpodlasin
Copy link

I worked on it for some time but I am currently not.

@bresleveloper
Copy link

using TypeScript 2.3.3, issue hasn't been solved, #6757 is a dup.

@nathonius
Copy link

image

This is the main reason that this change would be useful to me, since this is my React devtools at the moment, at least without manually setting .displayname.

@dragomirtitian
Copy link
Contributor

@OfficerHalf doesn't react use babel for compilation anyway? So what TS transpilation looks like should not really matter..

@DanielRosenwasser In order to fix this babel mangles the variable names beyond recognition (ex).

Wouldn't a partial fix here for functions that do not reference themselves in the body be good enough ? Adding a name to those will not cause (*) any shadowing issues and it will cover most common scenarios while not mangling the output.

On the other hand this solution will require an extra pass over all arrow function to determine if they do reference themselves, which may impact performance so it might just make sense to label this as will not fix and closing it 🤷‍♀️

(*) as far as I can tell

@nathonius
Copy link

Sure, if you use CRA it uses babel. But you don't have to - if you use "jsx": "react" in your tsconfig, typescript will handle that part of the compilation. In this particular project we don't use babel.

@RyanCavanaugh
Copy link
Member

I'm inclined to agree with @dragomirtitian that we should just Won't Fix this due to the rapidly-evaporating ecosystem of ES5-only runtimes. The actual impact here is quite minimal compared to the sheer ugliness of the required emit.

@ljharb
Copy link
Contributor

ljharb commented Jan 11, 2020

IE 11 isn’t vanishing particularly rapidly.

@nathonius
Copy link

Agreed - over 50% of traffic I see in my particular industry comes from IE11. We can't deprecate it yet since some customers are unable to upgrade even if they wanted to.

@DanielRosenwasser
Copy link
Member

Would it be acceptable if the names were set to something "sufficiently close" to the original name? The point in that being that it would make debugging easier even if it's not the correct behavior. So in

const foo = () => {
}

we would rewrite that to

var foo = function foo_1() {
}

@ljharb
Copy link
Contributor

ljharb commented Jan 23, 2020

while that's certainly better than outputting no name at all, or a mangled name, that seems like a short-term workaround pending an actual fix.

@basarat
Copy link
Contributor

basarat commented Feb 18, 2020

Why would A be easier than the desired B?

  • A (proposed workaround)
var foo = function foo_1() {
}
  • B (desired)
var foo = function foo() {
}

@dragomirtitian
Copy link
Contributor

@basarat Picking a name that does not conflict with anything else would remove the need to rewrite variable names if the body of the function references the the variable.

Ex:

var x;
x = recursive => {
    console.log("original x")
    return x(true); // The function assigned to x below is actually called
}
var y = x;
x = function () {
  console.log("new x")
}
y(true)

If you give the first function the name x, then the body of the function will see the function itself as x not the variable x (which is reassigned later) which will make the behavior different to the original code in ES2015 and above.

So you end up having to do a lot of variable name rewrites, which get pretty ugly pretty fast: ex

@ljharb
Copy link
Contributor

ljharb commented Feb 19, 2020

Variable names aren’t observable, however, while function names are.

@remorses
Copy link

Can someone implement this please?

iMobs added a commit to iMobs/keycap that referenced this issue Mar 30, 2020
This is a [bug in typescript](microsoft/TypeScript#6433)
iMobs added a commit to iMobs/keycap that referenced this issue Mar 30, 2020
This is a (bug in typescript)[microsoft/TypeScript#6433]
iMobs added a commit to iMobs/keycap that referenced this issue Mar 30, 2020
This is a bug in typescript (microsoft/TypeScript#6433)
@jednano
Copy link
Contributor

jednano commented May 8, 2021

This appears to be fixed now, no?

@RyanCavanaugh
Copy link
Member

@jedmao no. The relevant test would be

const n = () => "hello";

which should produce

var n = function n() { return "hello"; };
                 ^               

under target: es5

@rbuckton
Copy link
Member

rbuckton commented Oct 5, 2021

At this point I'm not sure its entirely necessary to fix this, except in possibly a very small number of cases. If you emit to ES5/ES3, but are running in an ES2015+ host, function names are automatically assigned from their variables using NamedEvaluation (which evolved from variable declaration evaluation in ES2015).

This means that if you have the following typescript:

const f = () => {};
console.log(f.name);

And we transpile it to the following ES5:

var f = function() {};
console.log(f.name);

Running that transpiled output will print f. However, if you are running in an ES5/ES3 host environment, all bets are off. .name wasn't standardized until ES2015, so there are no guarantees if you'll get "f", "", or undefined. The number of ES3/ES5 runtime environments is vanishingly small, and many (if not most) libraries and frameworks are dropping support for them.

Broadly emulating NamedEvaluation for ES5 emit would artificially inflate the overall emitted file size, for little benefit. In fact, the only place where I could see it have value is for downleveled exports and class fields, as property assignments do not call NamedEvaluation:

// ts
export const f = () => {};
console.log(f.name);
class C {
  x = () => {};
};
console.log(new C().x.name);

// es5 (commonjs)
exports.f = function () {};
console.log(exports.f.name);

var C = (function () {
  function C() {
    this.x = function() {};
  }
  return C;
})();
console.log(new C().x.name);

In ES2015+ with native ES modules, the above TS code prints f and x, but when targeting ES5 and commonjs, it prints empty strings.

This could possibly be resolved by leaning on NamedEvaluation for exports:

// es5 (commonjs)
var f = function() {}
exports.f = f;
console.log(exports.f.name); // prints 'f' in an ES2015+ host

Classes are a bit trickier if there is a naming conflict (though we could rename local variables to accommodate):

// ts
class C {
  x = () => {};
  constructor() {
    var x = 1;
  }
};

// es5 (commonjs)
// option 1, rename locals
var C = (function () {
  function C() {
    var x = function() {};
    this.x = x;
    
    var x_1 = 1;  // renamed local `x` to `x_1`
  }
  return C;
})();

// option 2, leverage _NamedEvaluation_ using object literals
var C = (function () {
  function C() {
    this.x = { x: function () {} }.x; // NamedEvaluation gives the name `x` to the function
    
    var x = 1;  // local not renamed
  }
  return C;
})();

One of the reasons to lean on NamedEvaluation is that we can't blindly add a name to a function expression as that can introduce scope conflicts. The name introduced in the function expression is locally scoped to the function expression body. Consider the two following cases:

// case 1
export let fib = (i: number) => i === 1 ? 0 : i === 2 ? 1 : fib(i - 1) + fib(i - 2);
const fib2 = fib;
fib = i => { console.log("i:", i); return fib2(i); };
console.log("# case 1");
console.log("result:", fib(3));

// case 2
function f(s: string) { console.log(s); }
class C {
  f = (s: string) => { f("Hello " + s); }
}

console.log();
console.log("# case 2");
new C().f("Alice");

If you emit to ESNext using native ES modules, you would get the following results:

# case 1
i: 3
i: 2
i: 1
result: 1

# case 2
Hello Alice

If we were to emit to ES5 and inject names into the functions, the output would change due to scope conflicts:

# case 1
i: 3
result: 1

# case 2
Error: Stack overflow

The reason for this would be the resulting emit:

exports.fib = function fib(i) { return i === 1 ? 0 : i === 2 ? 1 : fib(i - 1) + fib(i - 2) };
var fib2 = fib;
fib = function (i) { console.log("i:", i); return fib2(i); };

function f(s) { console.log(s); }
var C = (function () {
  function C() {
    this.f = function f(s) { f("Hello " + s); };
  }
  return C;
})();
new C().f("Alice");

In case 1, the fib inside of the arrow function ends up pointing at the name fib we might give to the function expression, rather than the outer fib variable.

In case 2, the f we call ends up invoking the function expression we assigned to the field f, rather than the name in the outer scope.

@ljharb
Copy link
Contributor

ljharb commented Oct 5, 2021

When it was standardized isn't important; it was present in every version of every web browser except IE, and it's web reality that code expects it to work. It's also polyfillable in IE 9+, with https://npmjs.com/function.prototype.name, and there's lots of websites that rely on that working, including airbnb.com (when i last checked, at least).

@DanielRosenwasser
Copy link
Member

Every product will have a set of tradeoffs it has to make, and prioritizing the work to do this "by the book" is work with a likely tail of bugs and emit size implications. I can't speak for others who are on this issue, but I tend to agree with Ron that the people who would benefit from this have shrunk a lot as a group. For example:

there's lots of websites that rely on that working, including airbnb.com (when i last checked, at least).

Windows 10 (released late July 2015) actually makes it hard to access IE11. In fact, it will automatically punt you to Edge unless you disable a setting within Edge. But for the sake of it, here's Airbnb running in IE11:

abnb-ie11.mp4

@ljharb
Copy link
Contributor

ljharb commented Oct 7, 2021

@DanielRosenwasser that's a totally reasonable assessment; my pushback was against trying to justify not doing the work as a result of when something was standardized.

Regardless of what's done by default, why not offer this as a config option, for those who want to emit correct code?

@sandersn sandersn removed the Help Wanted You can do this label Dec 3, 2021
@sandersn
Copy link
Member

sandersn commented Dec 3, 2021

This is not practical to fix: the costs far outweigh the benefits.

@sandersn sandersn closed this as completed Dec 3, 2021
@ljharb
Copy link
Contributor

ljharb commented Dec 3, 2021

@sandersn even behind an option?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug A bug in TypeScript ES6 Relates to the ES6 Spec
Projects
None yet
Development

Successfully merging a pull request may close this issue.