You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Correct symbol resolution matters in the linter for e.g. no-unused-vars rule.
We want our semantic data to be correct!
Why it's hard to solve
The binding of enum fields does not follow our model for symbols/reference. It's unlike anything else.
Currently the Symbol for A is bound in the scope for 1st enum's body block. When there's only 1 enum block for an enum, this makes sense. That binding is not visible outside the block. But when there are 2 or more enum {} blocks comprising the enum, it breaks down.
So we need to hoist the binding up to enclosing scope (top level scope in this case) so it is in scope of both enum blocks.
But then how do we deal with this?
enumFoo{A=1,}enumBar{A=2,}enumQux{A=3,}
Here we have 3 x A bindings which are not the same. We can't put them all in top level scope as they'd all occupy the same "slot", and get conflated.
We have a similar problem with separate namespaces for types and values:
typeA=number;letA='hello';
We handle that by making A only one Symbol, and distinguish whether an IdentifierReference is referring to type A or let A by setting/not setting ReferenceFlags::Type.
But we can't use that trick for enums, because there can be any number of bindings with the same name. An on/off flag doesn't cover it.
Possible solution
I can only think of one solution, which unfortunately feels quite hacky.
Ultimately the problem is: An enum field binding is uniquely identified by the field name plus enum name.
Solution:
Record enum field bindings with pseudo-name <enum name>.<field name> i.e. Foo.A, Bar.A etc.
. is illegal in real identifiers so these pseudo-names cannot conflict with any other bindings.
Give these bindings SymbolFlags::EnumField.
In SemanticBuilder, resolve IdentifierReferences inside enums using these pseudo-symbols.
We can then rely on correctness of the data in SymbolTable in the TS enums transform, and simplify it considerably.
Everywhere else, following Reference -> Symbol will work as expected with no "special case" workarounds for enum fields.
Like I said, this feels unsatisfyingly hacky. Can anyone see a better way?
Possibly relevant to #8342. The way we record references between TS enum fields in
SymbolTableis wonky.The problem
This is legal:
Playground
i.e. it's essentially the same as:
Our output from transformer for this case is correct, but the data in
SymbolTableis not:Ahere gets aSymbol. But it only has 1 reference, not 2.Ain 2nd enum block has aReferenceId, but that reference has noSymbolId(i.e.SymbolTablesays it resolves toglobal.A.If you add
let A = 'hello';at top level, thenReferenceforAin 2nd enum block hasSymbolIdpointing tolet A(similar to the problem in #8342).Why this matters
Semanticit'd make solving transformer: tsc vs babel on enum transformation #8342 trivial.no-unused-varsrule.Why it's hard to solve
The binding of enum fields does not follow our model for symbols/reference. It's unlike anything else.
Currently the
SymbolforAis bound in the scope for 1st enum's body block. When there's only 1 enum block for an enum, this makes sense. That binding is not visible outside the block. But when there are 2 or moreenum {}blocks comprising the enum, it breaks down.So we need to hoist the binding up to enclosing scope (top level scope in this case) so it is in scope of both
enumblocks.But then how do we deal with this?
Here we have 3 x
Abindings which are not the same. We can't put them all in top level scope as they'd all occupy the same "slot", and get conflated.We have a similar problem with separate namespaces for types and values:
We handle that by making
Aonly oneSymbol, and distinguish whether anIdentifierReferenceis referring totype Aorlet Aby setting/not settingReferenceFlags::Type.But we can't use that trick for enums, because there can be any number of bindings with the same name. An on/off flag doesn't cover it.
Possible solution
I can only think of one solution, which unfortunately feels quite hacky.
Ultimately the problem is: An enum field binding is uniquely identified by the field name plus enum name.
Solution:
<enum name>.<field name>i.e.Foo.A,Bar.Aetc..is illegal in real identifiers so these pseudo-names cannot conflict with any other bindings.SymbolFlags::EnumField.SemanticBuilder, resolveIdentifierReferences inside enums using these pseudo-symbols.SymbolTablein the TS enums transform, and simplify it considerably.Reference->Symbolwill work as expected with no "special case" workarounds for enum fields.Like I said, this feels unsatisfyingly hacky. Can anyone see a better way?