Skip to content

Class name is missing in Function when es6 target is set #5386

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

Closed
pleerock opened this issue Oct 23, 2015 · 10 comments
Closed

Class name is missing in Function when es6 target is set #5386

pleerock opened this issue Oct 23, 2015 · 10 comments
Assignees
Labels
Bug A bug in TypeScript ES6 Relates to the ES6 Spec

Comments

@pleerock
Copy link

I have decorator:

export function Document() {
   return function(objectConstructor: Function) {
        console.log(objectConstructor.name); // when targeting es5 it gives me what I want - the name of my class. When targeting es6 it does not give anything
    }
}

Here is decorator usage:

@Document()
export class User {
}

Im using "User" class name to create a document called "User", I need information how class is called. I always used es5, but now when I switched to es6 compile target I dont have information about class name anymore. Is it a bug, or it by design? If second then what is the way to get the class name?

@DanielRosenwasser
Copy link
Member

It sounds like this a bug with your ES6 runtime, not a TypeScript bug. What are you running on and which version of TypeScript are you using?

@DanielRosenwasser DanielRosenwasser added the Needs More Info The issue still hasn't been fully clarified label Oct 23, 2015
@basarat
Copy link
Contributor

basarat commented Oct 24, 2015

Did a quick test. Work's fine in node latest foo.js:

"use strict";
class Foo {
}
console.log(Foo.name);
function Bar(){
}
console.log(Bar.name);

image

@pleerock
Copy link
Author

Here is what I'm running:

export function Document() {
    return function(objectConstructor: Function) {
        console.log(objectConstructor.name);
        console.log('Hello world');
    }
}

@Document()
export class User {
}
console.log(User.name);

And the result:
running

and looks like the problems is in the way it generates a "class", it does it without a name:

var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
    var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
    if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
    else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
    return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
    if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
function Document() {
    return function (objectConstructor) {
        console.log(objectConstructor.name); // when targeting es5 it gives me what I want - the name of my class. When targeting es6 it does not give anything
        console.log('Hello world');
    };
}
exports.Document = Document;
let User = class {
};
User = __decorate([
    Document(), 
    __metadata('design:paramtypes', [])
], User);
exports.User = User;
console.log(User.name);

the issue is here: let User = class { };, if I change it manually to let User = class User { }; everything will work.

Im using typescript@next and running this code on node 4.1.2

@basarat
Copy link
Contributor

basarat commented Oct 24, 2015

let User = class { };, if I change it manually to let User = class User { }; everything will work.

Yup that would cause this error.

@mhegazy
Copy link
Contributor

mhegazy commented Oct 26, 2015

Here is some background: #2836 (comment)

The issue is that classes really have two symbols, one visible inside the class, and one outside, if you are using a decorators, and the decorator changes the class constructor, this only affects the outer symbol. all accesses inside of the body of the class still bind to the original class name. the fix is to change the class into a class expression, this way we only have one symbol to bind against. but that means that Function.name is not set. the fix is to set the name property explicitly.

this was the original behavior, but then due to issue with Chrome/V8 this was not working (see issue #2836) so we disabled it in #3063. if V8 has addressed the issue, we should put back the name setting.

@mhegazy mhegazy added the Bug A bug in TypeScript label Oct 26, 2015
@mhegazy mhegazy added this to the TypeScript 1.8 milestone Oct 26, 2015
@mhegazy mhegazy added ES6 Relates to the ES6 Spec and removed Needs More Info The issue still hasn't been fully clarified labels Nov 13, 2015
@hbakhtiyor
Copy link

👍

@TheLevenCreations
Copy link

Hi, @mhegazy thanks for setting #6420 as duplicate and directing me to know the underlying issue. I am struggling for it for a while.

From the comment "if you are using a decorators, and the decorator changes the class constructor, this only affects the outer symbol. all accesses inside of the body of the class still bind to the original class name", I can see why the code like 'let User = class {}' generated.

Currently, I didn't follow the workaround(setting the name explicitly) as I think the typescript code is correct while something wrong with the underlying transpiling mechanism and incorrect behaviour. In my scenario, I will NEVER change a class name as it will confuse others I think. To avoid further changes when the fix comes, my workaround is:

I forked the typescript source code and made a minor change to let it generate code like 'let User = class User {}'. It works fine for me for now.

Please let me know if it is still dangerous :-)

Thanks again.

@mhegazy
Copy link
Contributor

mhegazy commented Jan 15, 2016

@TheLevenCreations, yes this fix is not correct. now decorators, and methods have a different view of the class, and that can cause different problems down the road. @rbuckton should have the correct fix out shortly.

@rbuckton
Copy link
Contributor

@TheLevenCreations Classes in ES6 are specified to create two bindings for the class name, one in the scope in which the class is defined, and one inside the class body:

class A {
  a() { return A; }
}
let objA = new A();
A = undefined;
console.log(A === objA.a()); // false

This becomes a problem for decorators, if the class decorator replaces the constructor:

function logInstantiation(target) {
  return class extends target {
    constructor(...args) {
      super(...args);
      console.log("instantiated");
    }
  }
}
@logInstantiation
class C {
  newInstance() { return new C(); }
}

let objC = new C(); // logs "instantiated";
let objC2 = objC.newInstance(); // nothing is logged as 'C' is the original class

We at one point tried to address the fact that the Function#name is missing by setting the name on the result using:

Object.defineProperty(C, "name", { value: "C" });

While this is perfectly legal ES6, it only works in strict mode in Node v4. Also, we saw issues when people tried to further transpile ES6 output from TypeScript in Babel.

The only other approach we've considered so far is to create a temp variable and alias all references to the class inside the class body to this temp variable, effectively turning the original example above into this:

// input.ts
@Document
export class User {
  newInstance() { return new User(); }
}

// output.js
export class User {
  newInstance() { return new _a(); }
}
User = _a = __decorate([Document], User);
var _a;

@TheLevenCreations
Copy link

Hi @rbuckton
Thanks for your full detailed explanation. Personally, I like the second approach, although it is a bit difficult to read.

Anyway, hope it will be fixed soon, and I will test my code again. Thank you :-)

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

No branches or pull requests

7 participants