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

Static class properties are set after class creation, even for inherited read-only properties #46912

Closed
GrantGryczan opened this issue Nov 24, 2021 · 8 comments
Labels
Question An issue which isn't directly actionable in code

Comments

@GrantGryczan
Copy link

GrantGryczan commented Nov 24, 2021

Bug Report

πŸ”Ž Search Terms

class extends promise symbol species getter inherit read-only static

πŸ•— Version & Regression Information

  • This is the behavior in every version I tried, and I reviewed the FAQ for entries about classes

⏯ Playground Link

Playground link with relevant code

πŸ’» Code

Here is an example TypeScript file:
index.ts

'use strict';
class Test extends Promise<any> {
    static [Symbol.species] = Promise;
}

πŸ™ Actual behavior

If I run tsc index.ts with no tsconfig.json, it emits this file:
index.js

'use strict';
var __extends = (this && this.__extends) || (function () {
    var extendStatics = function (d, b) {
        extendStatics = Object.setPrototypeOf ||
            ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
            function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
        return extendStatics(d, b);
    };
    return function (d, b) {
        if (typeof b !== "function" && b !== null)
            throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
        extendStatics(d, b);
        function __() { this.constructor = d; }
        d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
    };
})();
var Test = /** @class */ (function (_super) {
    __extends(Test, _super);
    function Test() {
        return _super !== null && _super.apply(this, arguments) || this;
    }
    var _a;
    _a = Symbol.species;
    Test[_a] = Promise;
    return Test;
}(Promise));

Running this throws an error on line 24:
Uncaught TypeError: Cannot set property Symbol(Symbol.species) of function Promise() { [native code] } which has only a getter

This is due to the fact that line 24 attempts to set the Symbol.species property of the class after the class is already created, which JavaScript (in strict mode) does not allow. Note that this only applies in strict mode.

Or alternatively, if I run tsc index.ts --target es2015, it emits this file:
index.js

'use strict';
var _a;
class Test extends Promise {
}
_a = Symbol.species;
Test[_a] = Promise;

This has the same issue on line 6, and for the same reason.

πŸ™‚ Expected behavior

I expect the JS compiled from index.ts to run exactly the same, without errors, as the following plain JavaScript code:

'use strict';
class Test extends Promise {
    static [Symbol.species] = Promise;
}
@GrantGryczan GrantGryczan changed the title Uncaught TypeError: Cannot set property Symbol(Symbol.species) of function Promise() { [native code] } which has only a getter Static class properties are set after class creation, even for inherited read-only properties Nov 24, 2021
@RyanCavanaugh RyanCavanaugh added the Question An issue which isn't directly actionable in code label Nov 24, 2021
@RyanCavanaugh
Copy link
Member

If you want to do this you'll need to turn on the spec compliance setting useDefineForClassFields

@GrantGryczan
Copy link
Author

GrantGryczan commented Nov 25, 2021

I tried this tsconfig.json, and it doesn't make any difference.

{
    "compilerOptions": {
        "target": "es5",
        "useDefineForClassFields": true
    },
    "include": [
        "**/*.ts"
    ],
    "exclude": [
        "node_modules"
    ]
}

@IllusionMH
Copy link
Contributor

es5 (ES5 runtime doesn't even support classes) and anything below es2022 can't have static [Symbol.species] = Promise; as this proposal only recently reached Stage 4.

Works as expected with "target": "esnext".
image

Only thing that looks strange that "target": "es2022" still downlevels this code.

@GrantGryczan
Copy link
Author

GrantGryczan commented Nov 26, 2021

@IllusionMH I think you misunderstand. One of the important features of TypeScript is being able to use future syntax transpiled to older versions in a way that works identically (or as close as possible) to future specifications. Being able to transpile ESNext classes into an equivalent ES5 implementation is one of the things TypeScript is intended to be able to do. It's just that this edge case doesn't seem to be covered by it due to a bug of some sort.

@IllusionMH
Copy link
Contributor

Sorry, I've missed "run exactly same" and was confused by expected example.

I tried this tsconfig.json, and it doesn't make any difference.

What version are you using and are you sure that this config used?
TS 4.0+ (theoretically 3.7+) will replace assignment with Object.defineProperty when useDefineForClassFields is set and resulted code no longer causes error Uncaught TypeError: Cannot set property Symbol(Symbol.species) of function Promise() { [native code] } which has only a getter.

image

You should get Uncaught TypeError: undefined is not a promise (in Chromium) or good error Uncaught TypeError: calling a builtin Promise constructor without new is forbidden (in Firefox). This is tracked in #15397

@GrantGryczan
Copy link
Author

GrantGryczan commented Nov 26, 2021

Huh, it was definitely using my tsconfig.json earlier since changing the compilerOptions.target option to esnext did change tsc's output to use a native class with a native static property. But now it isn't using it anymore for some reason. Explicitly entering tsc index.ts --target es5 --useDefineForClassFields worked fine. Thanks!

@fatcerberus
Copy link

@GrantGryczan Directly passing filenames to tsc ignores your tsconfig.

@GrantGryczan
Copy link
Author

Directly passing filenames to tsc ignores your tsconfig.

Ah, okay, good to know.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Question An issue which isn't directly actionable in code
Projects
None yet
Development

No branches or pull requests

4 participants