-
Notifications
You must be signed in to change notification settings - Fork 12.5k
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
Decorators broken with private fields, generated code has syntax error #48515
Comments
After fixing esbuild's issues with parameter decorator scope myself, I believe the correct thing to do would be for TypeScript to make this case a compile error. My reasoning is that it appears that TypeScript evaluates decorator expressions in the scope that encloses the class declaration (since that's where the generated code will be inserted). By that logic, any private names in the class body should be inaccessible here since they are not in scope. So private names can still be allowed in decorator expressions but they need to be in scope, which is determined by where the class declaration itself lives. |
The PR for this does fix the code in the bug report. However, it introduces an inconsistency where TypeScript now sometimes evaluates decorators outside the class body and sometimes evaluates them inside the class body. Also TypeScript's type checker seems to allow things to happen both outside and inside the class body even though the code will only ever be evaluated in one of those places, which is confusing. For example: async function foo() {
console.log('before')
class Foo {
static #bar = '#bar'
// Either of these decorators work individually, but using
// them both together generates code with a syntax error
@((() => { console.log(Foo.#bar) }) as PropertyDecorator)
@(await new Promise<PropertyDecorator>(r => setTimeout(() => r(() => {}), 1000)))
static foo = 'foo'
}
console.log('after')
}
foo() TypeScript generates code that looks like this, which contains a syntax error: async function foo() {
console.log('before');
class Foo {
static #bar = '#bar';
// Either of these decorators work individually, but using
// them both together generates code with a syntax error
static { this.foo = 'foo'; }
static {
__decorate([
(() => { console.log(Foo.#bar); }),
(await new Promise(r => setTimeout(() => r(() => { }), 1000)))
], Foo, "foo", void 0);
}
}
console.log('after');
}
foo(); TypeScript's checker allows both I'm trying to figure out what to do about this in esbuild. I guess the semantics right now are sort of "decorator evaluation happens inside the class body (and therefore await should be forbidden) if any member decorator contains a private name (even if it's not for the class with the decorator), otherwise decorator evaluation happens outside the class body (and therefore await is allowed)." Basically treating Can someone from the TypeScript team describe the semantics of decorator evaluation? Are decorators evaluated inside or outside of the class body? What's the right approach for esbuild to take in this case? See also: evanw/esbuild#3230 |
@rbuckton can you comment on the above? |
Decorator expressions should behave like expressions in computed property names, where they are evaluated outside of the class body using the surrounding There are several different approaches we can take:
We've generally fallen back to down-level emit (1) in similar circumstances, but (2) is potentially viable given that you usually have some named element to use a decorator (barring classes with only private elements): // source
async function f() {
class Foo {
static #bar = '#bar';
@(dec1(() => Foo.#bar))
@(await Promise.resolve(dec2()))
static foo = 'foo';
}
}
// downlevel-ish (--experimentalDecorators)
async function f() {
var _a;
class Foo {
static #bar = '#bar';
static [(_a = [dec1(() => Foo.#bar), await Promise.resolve(dec2())], "foo")] = 'foo';
static {
__decorate(_a, Foo, "foo", void 0);
}
}
} We do something similar to (2) today when we emit down-level stage 3 decorators if a static method has a computed property name, though it still doesn't work with |
That doesn't appear to be exactly how TypeScript experimental decorators behave. For example, the code in the esbuild bug reported to me essentially looks like this: let ArrayMaxSize = (x: number): PropertyDecorator => () => console.log(x)
class DocumentSearchOptions {
static MaxFiltersSize = 20
@ArrayMaxSize(DocumentSearchOptions.MaxFiltersSize)
filters = []
} TypeScript runs experimental decorators after class initialization, so referencing the class name from within a decorator works. However, expressions in computed property names run before class initialization, so referencing the class name from within a computed property name doesn't work. For example: class DocumentSearchOptions {
static MaxFiltersSize = 20
static [(DocumentSearchOptions.MaxFiltersSize, 'filters')] = []
} This code will throw an error when run:
The bug report I got for esbuild is happening because I gave computed property name semantics to decorator expressions. It might be that this is correct for the new JavaScript decorators because JavaScript decorators do have computed property name semantics. But TypeScript experimental decorators appear to not have computed property name semantics. Edit: Applying the transform you suggested to your code example also introduces a |
Ah, that's true. My comment about evaluation order is true of Stage 3 decorators, not For legacy decorators and var C = /** @class */ (function () {
function C() {
}
C = __decorate([
dec
], C);
return C;
}()); The easiest thing to do for |
Bug Report
π Search Terms
decorator private field syntax error
π Version & Regression Information
β― Playground Link
Playground link with relevant code
π» Code
π Actual behavior
I expect this code to either be a compile error or to compile successfully and print
123
when run.π Expected behavior
The code compiles without any errors but the generated code contains a syntax error:
Context: I discovered this issue while investigating how TypeScript handles various decorator edge cases, which I'm doing because I'm trying to replicate TypeScript's behavior into esbuild's TypeScript transpiler for this issue: evanw/esbuild#2147.
The text was updated successfully, but these errors were encountered: