Reference to self within decorated class refers to undecorated class
TL;DR: Should a decorated class referencing itself in a method refer to the decorated class or undecorated? Currently it does the latter - which feels broken, but admittedly that may just be due to quirky code patterns.
Example code:
const bar = ...
@bar
class Foo {
baz() {
console.log(Foo);
}
}
To be honest, despite having done my best to read the proposal and spec, I cannot be certain whether esbuild is behaving incorrectly in this matter. However, I could not be certain that esbuild is deliberate in its current behavior since I could not spot a relevant case in the tests (https://github.com/evanw/decorator-tests/blob/main/decorator-tests.ts). The only case(s) that seemed similar also seemed distinct enough to not quite be relevant (i.e. the complexities involved with things like accessor might mean it's irrelevant to my simpler situation).
Running the built code via node (20.12.1 in case it matters) results in
[class Bar] { [Symbol(Symbol.metadata)]: [Object: null prototype] {} }
[class Foo]
Notably, if I modify the built code and change one line near the bottom to var _Foo = class _FooInner { (so as to not reuse the _Foo identifier for the class expression itself), then I get the result I had expected:
[class Bar] { [Symbol(Symbol.metadata)]: [Object: null prototype] {} }
[class Bar] { [Symbol(Symbol.metadata)]: [Object: null prototype] {} }
I think it should be the latter case which seems more expected if you "desugar" the decorator:
class A {
f() { return A }
}
const foo = x => class B {
f() { return new x().f() }
}
// unlike `function A` which behaves like `var A` (with a bit different),
// `class A` behaves like `let A = class A` which means it could be reassigned.
A = foo(A)
console.log(A)
console.log(new A().f())
Outputs:
[class B]
[class A]
@evanw any guidance you can provide on this issue? Namely looking for specifics on whether the current behavior is intended, and if not, a quick gut estimation on how easy/complex it'd be to fix this (i.e. whether someone like myself with minimal context could contribute a PR with the fix)
I think it should be the latter case which seems more expected if you "desugar" the decorator: [...]
@hyrious I belatedly realize I've been reading your response wrong. I'm still somewhat surprised that A = foo(A) doesn't cause return A to be actually referencing B 🤔 since by the time that logic is invoked, the value has been reassigned. Per your mention of functionality like let:
let foo = {
someField: "hi",
someFunction: () => {
console.log(foo);
},
};
foo = {
...foo,
someField: "hello",
};
console.log(foo);
foo.someFunction();
which results in
{ someField: 'hello', someFunction: [Function: someFunction] }
{ someField: 'hello', someFunction: [Function: someFunction] }
but to your point, that's how class actually functions in the decorator-less "desugared" code you provided. And perhaps my example code above is over simplified and is missing some nuance.
Regardless, I'd still feel more comfortable closing this with input from someone with familiarity with the decorator spec, and I'm not certain who all fits that description beyond @evanw.