TC39 Accessor Decorators unable to use `declare`
🔎 Search Terms
decorator, declare, accessor
🕗 Version & Regression Information
afaict, this never worked? (it should tho)
⏯ Playground Link
https://www.typescriptlang.org/play/?#code/JYOwLgpgTgZghgYwgAgLIE8DK0BuwkDeAvgNwBQZEAHgA4D2UYyCANnAM7vIBiddyBMsmQABdrnwpESTg2QATCKzhQUMPgC40WCUnJEKAekPJ2NJchMAVAMIBmAJzIAIkoZwwDMjACuIBGDAdCCmuhAAPABqcCw+EAB8ABRCyGAqAOYQYFo2bJwAgggy7AyuCO6eUFYZWeF+ANYgdADuIAA0yNGxCR0p5eDU2ci5HOyFxaVuUB4MNsGQVGBkAJQ5eWNFELJQZRUMAEpbPixgdSCNLe2dMXHx5GTGyMAAtjQsEM8Q4B5BIYlg6HMCjoWxAAHImM8PJAoB1qIgwCx0MtvH4Ar9QlA8EgojcEokAHREjLsLQNJqtADaAF1VshyZcBClVGAfFAQgAGZAcennCkhHldOL6ChAA
💻 Code
interface MyService{};
export class Foo {
@service accessor declare foo: MyService;
}
// spec / TC39 Decorator
function service<Value>(
target: ClassAccessorDecoratorTarget<unknown, Value>,
context: ClassAccessorDecoratorContext
): ClassAccessorDecoratorResult<unknown, Value>;
// implementation (type doesn't matter, exactly)
function service<Value>(...args: unknown[]): unknown {
return 0 as unknown as Value;
}
🙁 Actual behavior
error here:
export class Foo {
@service accessor declare foo: MyService;
// ^
// \- Decorators are not valid here (1206)
}
🙂 Expected behavior
no error - decorators can define values on access, lazily, so declare should be allowed since declare means "TS doesn't normally know this can have a value, but I'm declaring that 'something' that TS can't see is setting that value".
where as, what does "work" is ! -- which is often linted against, and colloquially means approx "idgaf, I'm right" -- which is an appropriate thing to lint against.
Additional information about the issue
No response
This seems to be unrelated to accessors and applies to declared properties in general.
When implementing this feature it was intentional: #50820
The following items are not currently supported:
Decorators on adeclarefield.
I can't find any updated information.
since declare means "TS doesn't normally know this can have a value, but I'm declaring that 'something' that TS can't see is setting that value".
where as, what does "work" is
!-- which is often linted against, and colloquially means approx "idgaf, I'm right" -- which is an appropriate thing to lint against.
I wouldn't say this is entirely correct. With useDefineForClassFields enabled the declare variant will omit the property entirely. declare is generally used for things outside of TypeScripts realm (e.g. JavaScript libraries).
Using the non-null assertion operator generally means "trust me, this will be provided, even if you can't figure out how", which is your use-case. Common examples are decorators or init functions.
IMO the linter should either complain about both declare and ! or neither, because essentially the result is the same: A value should be provided, but isn't.
I suppose this could be fixed if decorators can set the type of the field.
Since decorators can add an initializer, it kinda makes sense that a decorator could define the type for a decorated thing -- then both declare and ! wouldn't make sense 🎉
This is intended behavior. declare fields are a transitional feature designed specifically to allow decorators on fields where "set" semantics are still required when switching to useDefineForClassFields: true (which is the default for ES2021 and later). accessor fields indicate a runtime transformation to a get/set pair backed by a private field, which is wholly incompatible with the behavior of declare in this context.
It should be noted that declare fields primarily exist so that legacy decorators could utilize Object.defineProperty to convert the field to an accessor pair. This is behavior that major runtimes were strongly opposed to as it results in deoptimizations and performance issues. Instead, accessor fields were added so that this transformation could be performed statically by the runtime. As a result, if you wanted to transition from TypeScript's legacy decorators to native ECMAScript decorators, you would want to change your declare field to an accessor field and change your decorator from one that installs a new accessor pair to one that receives the accessor pair generated by the runtime.