Typescript: Impossible to define static 'length' function on class

Created on 12 Aug 2014  ·  22Comments  ·  Source: microsoft/TypeScript

The following typescript program is accepted:

class C {
    static length ()  { return "twelve"; }
}

However, in the generated code:

var C = (function () {
    function C() {
    }
    C.length = function () {
        return "twelve";
    };
    return C;
})();

Here, an attempt is made to assign to the length property of a function. This doesn't work (at least in Firefox and Chrome), causing subsequent calls of C.length() to crash. Perhaps calling a static class function length should just be disallowed?

Migrated from codeplex issue #1260.

Bug Fixed good first issue help wanted

Most helpful comment

Meanwhile ES6/2015 native support has landed in Chrome and Firefox. If we observe how these handle the issue natively then it comes close to what @nicolo-ribaudo and I have proposed (see #9778).

Snippet to illustrate native behavior:

class Foo {
    constructor() {}
}
class Bar {
    static length() {}
    static name() {}
    static caller() {}
}
Foo.name = "FooChanged";
Bar.name = "Baz";

console.log(Foo.name) // Logs "Foo". Foo.name remains unwritable
console.log(Bar.name) // Logs "Baz".  Bar.name became writable

With respect to what @tinganho has written earlier about the readonlyness of name, length and caller we can observe for the native implementations that the properties are writable on a constructor function once we have defined either of those as a static class member (feel free to review or modify my recent warnings on MDN regarding dangerous assumptions about Function.name for getting a class name, because of this).

So using Object.defineProperty as suggested by @nicolo-ribaudo would emulate the native behavior accurately. A thing which needs consideration is that some older but ES5 compatible browser versions had set the properties to be configurable: false. So trying to make them writable: true will fail for them (e.g. see configurability of Function.name)

To sum things up, my take on the issue were:

  • We can't use Object.defineProperty() for compile-target es3. Without Object.defineProperty, running ES3 code in an ES5 environment, though, would hit the read-only problem. Therefore we need a compile error for target es3
  • compile it with Object.defineProperty() and writable: true for compile-target es5 and issue a warning/error stating that using these property names can cause errors in browsers x of version <= ysuggesting that, if these browsers must be supported, the safest option were to choose another name.
  • if you absolutely don't want people to use the properties, generate a compile error for either target.

Independent of the compile output I think any kind of additional message would be better than how the situation is today (TS 1.8.10) where we get no hint at all about potential problems with the compiler output.

Edit: Snipped added

All 22 comments

I ran into this recently. :(

Also a few others are disallowed e.g. the following code should error (if we do this feature):

class C {
    static name = 'something';
    static arguments = 'args';
    static caller = 'caller';
}

console.log(C.name); // 'C'
console.log(C.arguments); // null
console.log(C.caller); // null

Not all of these are stardard however.

I don't think the properties name, caller and length are doable. They are read-only properties and can't be overridden. All assignments to them will be ignored.

@tinganho agreed

Ok, but since people catch this kind of error from time to time I think that TS should notify them about doing something wrong. It because properties like name or length are so natural to keep in mind so people will try to redefine them again and again.

May be because Lengh, Name are Already Defined.

I'm going to try and work on this. Apart from what has already been mentioned, are there more property names that shouldn't be allowed?

So far I see:
length, name, arguments, caller

What exactly should happen when someone tries to set a static property with one of the forbidden names? Compiler error? Visible warning to the user?

I'm new to this so if I'm missing something, then some guidance would be appreciated. :+1:

Why not compile

class C {
    static name = 'something';
    static arguments = 'args';
    static caller = 'caller';
}

console.log(C.name); // 'C'
console.log(C.arguments); // null
console.log(C.caller); // null

to something like

var C = (function () {
    function C() {
    }

    C.__statics = {
        name: 'something',
        arguments: 'args',
        caller: 'caller'
    };

    return C;
})();
console.log(C.__statics.name); // 'something'
console.log(C.__statics.arguments); // 'args'
console.log(C.__statics.caller); // 'caller'

Question came up on stackoverflow as well : http://stackoverflow.com/a/34644236/390330 :rose:

@zerkms please don't post the same comment in two places; it's confusing.

You can't arbitrarily sometimes rewrite names in a structural type system.

Consider some code like this

interface HasName {
  name: string;
}
class Foo {
  static name = 'fooClass';
}
let bar: HasName = { name: string };
let q = Math.random() > 0.5 ? Foo : bar;
console.log(q.name);

@RyanCavanaugh that's a fair example, indeed (I don't develop on TS, but gosh - its type system is weird as soon as it treats this code as valid).

If it's not possible to transpile them without risk, perhaps we should just disallow them?

If it's not possible to transpile them without risk, perhaps we should just disallow them?

Definitely not changing the transpile, just making it a compile error :rose:

[...] perhaps we should just disallow them?

Some enterprising developer should send us a PR! :wink:

Why not use Object.defineProperty?

e.g.

class C {
    static length() { return "twelve"; }
}

would be transplied to something like

var C = (function () {
    function C() {
    }
    Object.defineProperty(C, "length", {
        value: function () { return "twelve"; },
        writable: true
    });
    return C;
}());

@nicolo-ribaudo That does indeed work. My opinion: but I would rather see an error instead of such a transpile. TypeScript generally leans on the side of graceful error instead of _fixing JavaScript_ :rose:

TypeScript generally leans on the side of graceful error instead of fixing JavaScript

Is not the whole idea of TS is to fix the JS by filling the gaps it cannot and will never be able to.

Is not the whole idea of TS is to fix the JS by filling the gaps it cannot and will never be able to.

Yes. But by understanding how JavaScript works. Take an example of null and undefined. TypeScript choses to understand both (instead of consolidating it into a single thing like Dart does https://www.dartlang.org/docs/synonyms/). TypeScript gives errors (instead of fixing it in transpile) if you use the wrong JavaScript pattern :rose:

My opinions are my own and not endorsed by anyone but me :rose:

Meanwhile ES6/2015 native support has landed in Chrome and Firefox. If we observe how these handle the issue natively then it comes close to what @nicolo-ribaudo and I have proposed (see #9778).

Snippet to illustrate native behavior:

class Foo {
    constructor() {}
}
class Bar {
    static length() {}
    static name() {}
    static caller() {}
}
Foo.name = "FooChanged";
Bar.name = "Baz";

console.log(Foo.name) // Logs "Foo". Foo.name remains unwritable
console.log(Bar.name) // Logs "Baz".  Bar.name became writable

With respect to what @tinganho has written earlier about the readonlyness of name, length and caller we can observe for the native implementations that the properties are writable on a constructor function once we have defined either of those as a static class member (feel free to review or modify my recent warnings on MDN regarding dangerous assumptions about Function.name for getting a class name, because of this).

So using Object.defineProperty as suggested by @nicolo-ribaudo would emulate the native behavior accurately. A thing which needs consideration is that some older but ES5 compatible browser versions had set the properties to be configurable: false. So trying to make them writable: true will fail for them (e.g. see configurability of Function.name)

To sum things up, my take on the issue were:

  • We can't use Object.defineProperty() for compile-target es3. Without Object.defineProperty, running ES3 code in an ES5 environment, though, would hit the read-only problem. Therefore we need a compile error for target es3
  • compile it with Object.defineProperty() and writable: true for compile-target es5 and issue a warning/error stating that using these property names can cause errors in browsers x of version <= ysuggesting that, if these browsers must be supported, the safest option were to choose another name.
  • if you absolutely don't want people to use the properties, generate a compile error for either target.

Independent of the compile output I think any kind of additional message would be better than how the situation is today (TS 1.8.10) where we get no hint at all about potential problems with the compiler output.

Edit: Snipped added

@RyanCavanaugh: could you please revisit this issue for milestone TS 2.0.1. Solution seems straight forward but depending on which option you choose emitter output may need to change (see my previous comment). Release of TS 2.0 then may be best to include the fix.

The tags on the PR indicate that the TypeScript team, which have limited resources, are leaving this open to the community to address. If you want this issue addressed, someone in the wider community will have to tackle it until the TypeScript core team feel they have sufficient room in the backlog to put it into a release (which likely would be a long long time).

@kitsonk Thank you for explaining the PR label. I think I understood that. What I suggested was to reevaluate whether its still the right way to handle the issue. Static class properties called _name_ or _length_ are not so unlikely to happen and TS produces errorneous output for that. Hence I wouldn't even call the issue and "edge case".

I consider static properties being at the core of the language and even if there aren't pull requests yet, solutions for the issue have been presented in this thread.

Nevertheless, I will evaluate whether I can provide a PR but I must admit that I am not yet that knowledgeable about TS sources and I am afraid TS 2.0 will be released before.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

seanzer picture seanzer  ·  3Comments

manekinekko picture manekinekko  ·  3Comments

DanielRosenwasser picture DanielRosenwasser  ·  3Comments

siddjain picture siddjain  ·  3Comments

jbondc picture jbondc  ·  3Comments