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
{{ message }}
Repository navigation
Symbols in as const objects should be unique symbolsΒ #54100
constA=Symbol();constB=Symbol();constMyEnumA={ A, B }asconst;// ^?typeMyEnumA=typeofMyEnumA[keyoftypeofMyEnumA];// ^?// goodfunctiontestA(val: MyEnumA){}testA(MyEnumA.A);testA(MyEnumA.B);testA(Symbol());// good type errorconstMyEnumB={C: Symbol(),D: Symbol()}asconst;// ^?// should not use generic "symbol" typetypeMyEnumB=typeofMyEnumB[keyoftypeofMyEnumB];// ^?// should not be symbolfunctiontestB(val: MyEnumB){}testB(MyEnumB.C);testB(MyEnumB.D);testB(Symbol());// should be a type error
π Actual behavior
Symbols in as const-ed objects should be unique symbols, instead they're of type symbol.
π Expected behavior
When I write
constobj={a: Symbol()}asconst;
I want obj.a to be a unique symbol.
What I'm trying to do is replace TS enums with symbol-based object "enums" in some scenarios, as it gives me greater typecheck-time and runtime guarantees.
I can do
constA=Symbol();constMyEnum={ A }asconst;typeMyEnum=typeofMyEnum[keyoftypeofMyEnum];
and that works great, but I end up with many const ... = Symbol() which pollute the scope and can be misused, when I'd rather do
That said, I think the limitation here is that unique symbols explicitly have type typeof x, which implies there must be an x in scope that can be used with typeof. Given an anonymous object literal containing Symbol() calls, there's nothing for typeof to refer back to.
could be typeof Symbol('desc') or unique symbol 'desc'.
That's expressible as declare const mySymbol: symbol & {readonly description:'desc'}. Unfortunately, you have to give up uniqueness. (unique symbol) & {readonly description:'desc'} silently ignores the uniqueness. And {readonly description:'desc'} & (unique symbol) errors "'unique symbol' types are not allowed here."
Trying to fix this with an explicit annotation fails:
// ERROR: Type 'symbol' is not assignable to type 'unique symbol'.constmyEnum: {readonlya:unique symbol}={a: Symbol()};
Wrote up some of the related issue in #56535 (namely that the type system disregards the description passed in to the Symbol constructor, even if it's a string literal)
Appending as const seems like a very reasonable approach to have it use a unique symbol.
// Not yet legal
const standaloneUnique = doGenericThing(Symbol('abc') as const);
Possibly off topic, but I wouldn't expect any of this unique functionality to apply to Symbol.for, unless symbols were to start tracking their description (see issue linked above).
However it appears that is not the case:
const sym = Symbol.for('abc');
const restrictToSym = (val: typeof sym) => {};
// _should_ work, but treated as a separate unique symbol
restrictToSym(Symbol.for('abc'));
Activity
[-]`as const` objects should have unique symbols[/-][+]Symbols in `as const` objects should be unique symbols[/+]fatcerberus commentedon May 2, 2023
Possibly related: #53276
nstepien commentedon May 2, 2023
Bruce Pascoe (@fatcerberus) I'm not convinced this issue is related.
fatcerberus commentedon May 2, 2023
Nicolas Stepien (@nstepien) Note I said "related", not "duplicate".
That said, I think the limitation here is that unique symbols explicitly have type
typeof x, which implies there must be anxin scope that can be used withtypeof. Given an anonymous object literal containingSymbol()calls, there's nothing fortypeofto refer back to.nstepien commentedon May 2, 2023
Given
, would it be possible for its type to be
typeof obj.x?Although that wouldn't work in a case like
as there is no specific name to refer back to.
Maybe
unique symbolis good enough.nstepien commentedon May 3, 2023
I wonder if TS could use the description when available, i.e.
Symbol('desc')'s type could betypeof Symbol('desc')or
unique symbol 'desc'.rotu commentedon Nov 24, 2023
That's expressible as
declare const mySymbol: symbol & {readonly description:'desc'}. Unfortunately, you have to give up uniqueness.(unique symbol) & {readonly description:'desc'}silently ignores theuniqueness. And{readonly description:'desc'} & (unique symbol)errors "'unique symbol' types are not allowed here."Trying to fix this with an explicit annotation fails:
The best I achieve is with a cast:
But this doesn't provide the type safety you seek because unique symbols decay too easily:
rotu commentedon Nov 25, 2023
Wrote up some of the related issue in #56535 (namely that the type system disregards the
descriptionpassed in to theSymbolconstructor, even if it's a string literal)JacobLey commentedon Feb 12, 2024
+1 to this feature request.
Running into a similar issue where I would like the type generic to prefer a unique symbol.
e.g.
Doesn't use unique symbol by default:
Can assign to a const first, which works but requires a technically unnecessary variable declaration
Appending
as constseems like a very reasonable approach to have it use a unique symbol.Possibly off topic, but I wouldn't expect any of this unique functionality to apply to
Symbol.for, unless symbols were to start tracking theirdescription(see issue linked above).However it appears that is not the case: