Skip to content

Commit 16bf029

Browse files
authored
Allow indexing generics with unique symbols (#22339)
* Allow indexing generics with unique symbols * Move condition to assert
1 parent 53ae507 commit 16bf029

6 files changed

+199
-11
lines changed

src/compiler/checker.ts

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3725,8 +3725,8 @@ namespace ts {
37253725
return "(Anonymous function)";
37263726
}
37273727
}
3728-
if ((symbol as TransientSymbol).syntheticLiteralTypeOrigin) {
3729-
const stringValue = (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value;
3728+
if ((symbol as TransientSymbol).nameType && (symbol as TransientSymbol).nameType.flags & TypeFlags.StringLiteral) {
3729+
const stringValue = ((symbol as TransientSymbol).nameType as StringLiteralType).value;
37303730
if (!isIdentifierText(stringValue, compilerOptions.target)) {
37313731
return `"${escapeString(stringValue, CharacterCodes.doubleQuote)}"`;
37323732
}
@@ -5460,6 +5460,12 @@ namespace ts {
54605460
lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late);
54615461
}
54625462

5463+
const symbolLinks = getSymbolLinks(lateSymbol);
5464+
if (!symbolLinks.nameType) {
5465+
// Retain link to name type so that it can be reused later
5466+
symbolLinks.nameType = type;
5467+
}
5468+
54635469
addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags);
54645470
if (lateSymbol.parent) {
54655471
Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one");
@@ -5948,7 +5954,7 @@ namespace ts {
59485954
// If the current iteration type constituent is a string literal type, create a property.
59495955
// Otherwise, for type string create a string index signature.
59505956
if (t.flags & TypeFlags.StringLiteral) {
5951-
const propName = escapeLeadingUnderscores((<StringLiteralType>t).value);
5957+
const propName = getLateBoundNameFromType(t as LiteralType | UniqueESSymbolType);
59525958
const modifiersProp = getPropertyOfType(modifiersType, propName);
59535959
const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional ||
59545960
!(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional);
@@ -5965,7 +5971,7 @@ namespace ts {
59655971
prop.syntheticOrigin = propertySymbol;
59665972
prop.declarations = propertySymbol.declarations;
59675973
}
5968-
prop.syntheticLiteralTypeOrigin = t as StringLiteralType;
5974+
prop.nameType = t;
59695975
members.set(propName, prop);
59705976
}
59715977
else if (t.flags & (TypeFlags.Any | TypeFlags.String)) {
@@ -7999,9 +8005,19 @@ namespace ts {
79998005
}
80008006

80018007
function getLiteralTypeFromPropertyName(prop: Symbol) {
8002-
return getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || isKnownSymbol(prop) ?
8003-
neverType :
8004-
getLiteralType(symbolName(prop));
8008+
const links = getSymbolLinks(prop);
8009+
if (!links.nameType) {
8010+
if (links.target) {
8011+
Debug.assert(links.target.escapedName === prop.escapedName, "Target symbol and symbol do not have the same name");
8012+
links.nameType = getLiteralTypeFromPropertyName(links.target);
8013+
}
8014+
else {
8015+
links.nameType = getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || isKnownSymbol(prop) ?
8016+
neverType :
8017+
getLiteralType(symbolName(prop));
8018+
}
8019+
}
8020+
return links.nameType;
80058021
}
80068022

80078023
function getLiteralTypeFromPropertyNames(type: Type) {
@@ -11222,7 +11238,7 @@ namespace ts {
1122211238
result.type = undefinedType;
1122311239
const associatedKeyType = getLiteralType(unescapeLeadingUnderscores(name));
1122411240
if (associatedKeyType.flags & TypeFlags.StringLiteral) {
11225-
result.syntheticLiteralTypeOrigin = associatedKeyType as StringLiteralType;
11241+
result.nameType = associatedKeyType;
1122611242
}
1122711243
undefinedProperties.set(name, result);
1122811244
return result;
@@ -15004,10 +15020,15 @@ namespace ts {
1500415020
typeFlags |= type.flags;
1500515021

1500615022
const nameType = hasLateBindableName(memberDecl) ? checkComputedPropertyName(memberDecl.name) : undefined;
15007-
const prop = nameType && isTypeUsableAsLateBoundName(nameType)
15008-
? createSymbol(SymbolFlags.Property | member.flags, getLateBoundNameFromType(nameType), CheckFlags.Late)
15023+
const hasLateBoundName = nameType && isTypeUsableAsLateBoundName(nameType);
15024+
const prop = hasLateBoundName
15025+
? createSymbol(SymbolFlags.Property | member.flags, getLateBoundNameFromType(nameType as LiteralType | UniqueESSymbolType), CheckFlags.Late)
1500915026
: createSymbol(SymbolFlags.Property | member.flags, literalName || member.escapedName);
1501015027

15028+
if (hasLateBoundName) {
15029+
prop.nameType = nameType;
15030+
}
15031+
1501115032
if (inDestructuringPattern) {
1501215033
// If object literal is an assignment pattern and if the assignment pattern specifies a default value
1501315034
// for the property, make the property optional.

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3344,7 +3344,6 @@ namespace ts {
33443344
leftSpread?: Symbol; // Left source for synthetic spread property
33453345
rightSpread?: Symbol; // Right source for synthetic spread property
33463346
syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property
3347-
syntheticLiteralTypeOrigin?: StringLiteralType; // For a property on a mapped type, indicates the type whose text to use as the declaration name, instead of the symbol name
33483347
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
33493348
resolvedExports?: SymbolTable; // Resolved exports of module or combined early- and late-bound static members of a class.
33503349
resolvedMembers?: SymbolTable; // Combined early- and late-bound members of a symbol
@@ -3356,6 +3355,7 @@ namespace ts {
33563355
enumKind?: EnumKind; // Enum declaration classification
33573356
originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol`
33583357
lateSymbol?: Symbol; // Late-bound symbol for a computed property
3358+
nameType?: Type; // Type associate with a late-bound or mapped type property symbol's name
33593359
}
33603360

33613361
/* @internal */
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
//// [lateBoundConstraintTypeChecksCorrectly.ts]
2+
declare const fooProp: unique symbol;
3+
declare const barProp: unique symbol;
4+
5+
type BothProps = typeof fooProp | typeof barProp;
6+
7+
export interface Foo<T> {
8+
[fooProp]: T;
9+
[barProp]: string;
10+
}
11+
12+
function f<T extends Foo<number>>(x: T) {
13+
const abc = x[fooProp]; // expected: 'T[typeof fooProp]'
14+
15+
/**
16+
* Expected: no error
17+
*/
18+
const def: T[typeof fooProp] = x[fooProp];
19+
const def2: T[typeof barProp] = x[barProp];
20+
}
21+
22+
23+
//// [lateBoundConstraintTypeChecksCorrectly.js]
24+
"use strict";
25+
exports.__esModule = true;
26+
function f(x) {
27+
var abc = x[fooProp]; // expected: 'T[typeof fooProp]'
28+
/**
29+
* Expected: no error
30+
*/
31+
var def = x[fooProp];
32+
var def2 = x[barProp];
33+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
=== tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts ===
2+
declare const fooProp: unique symbol;
3+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
4+
5+
declare const barProp: unique symbol;
6+
>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13))
7+
8+
type BothProps = typeof fooProp | typeof barProp;
9+
>BothProps : Symbol(BothProps, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 37))
10+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
11+
>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13))
12+
13+
export interface Foo<T> {
14+
>Foo : Symbol(Foo, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 3, 49))
15+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 21))
16+
17+
[fooProp]: T;
18+
>[fooProp] : Symbol(Foo[fooProp], Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 25))
19+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
20+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 21))
21+
22+
[barProp]: string;
23+
>[barProp] : Symbol(Foo[barProp], Decl(lateBoundConstraintTypeChecksCorrectly.ts, 6, 15))
24+
>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13))
25+
}
26+
27+
function f<T extends Foo<number>>(x: T) {
28+
>f : Symbol(f, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 8, 1))
29+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11))
30+
>Foo : Symbol(Foo, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 3, 49))
31+
>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34))
32+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11))
33+
34+
const abc = x[fooProp]; // expected: 'T[typeof fooProp]'
35+
>abc : Symbol(abc, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 11, 9))
36+
>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34))
37+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
38+
39+
/**
40+
* Expected: no error
41+
*/
42+
const def: T[typeof fooProp] = x[fooProp];
43+
>def : Symbol(def, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 16, 9))
44+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11))
45+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
46+
>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34))
47+
>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13))
48+
49+
const def2: T[typeof barProp] = x[barProp];
50+
>def2 : Symbol(def2, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 17, 9))
51+
>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11))
52+
>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13))
53+
>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34))
54+
>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13))
55+
}
56+
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
=== tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts ===
2+
declare const fooProp: unique symbol;
3+
>fooProp : unique symbol
4+
5+
declare const barProp: unique symbol;
6+
>barProp : unique symbol
7+
8+
type BothProps = typeof fooProp | typeof barProp;
9+
>BothProps : unique symbol | unique symbol
10+
>fooProp : unique symbol
11+
>barProp : unique symbol
12+
13+
export interface Foo<T> {
14+
>Foo : Foo<T>
15+
>T : T
16+
17+
[fooProp]: T;
18+
>[fooProp] : T
19+
>fooProp : unique symbol
20+
>T : T
21+
22+
[barProp]: string;
23+
>[barProp] : string
24+
>barProp : unique symbol
25+
}
26+
27+
function f<T extends Foo<number>>(x: T) {
28+
>f : <T extends Foo<number>>(x: T) => void
29+
>T : T
30+
>Foo : Foo<T>
31+
>x : T
32+
>T : T
33+
34+
const abc = x[fooProp]; // expected: 'T[typeof fooProp]'
35+
>abc : number
36+
>x[fooProp] : number
37+
>x : T
38+
>fooProp : unique symbol
39+
40+
/**
41+
* Expected: no error
42+
*/
43+
const def: T[typeof fooProp] = x[fooProp];
44+
>def : T[unique symbol]
45+
>T : T
46+
>fooProp : unique symbol
47+
>x[fooProp] : number
48+
>x : T
49+
>fooProp : unique symbol
50+
51+
const def2: T[typeof barProp] = x[barProp];
52+
>def2 : T[unique symbol]
53+
>T : T
54+
>barProp : unique symbol
55+
>x[barProp] : string
56+
>x : T
57+
>barProp : unique symbol
58+
}
59+
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
declare const fooProp: unique symbol;
2+
declare const barProp: unique symbol;
3+
4+
type BothProps = typeof fooProp | typeof barProp;
5+
6+
export interface Foo<T> {
7+
[fooProp]: T;
8+
[barProp]: string;
9+
}
10+
11+
function f<T extends Foo<number>>(x: T) {
12+
const abc = x[fooProp]; // expected: 'T[typeof fooProp]'
13+
14+
/**
15+
* Expected: no error
16+
*/
17+
const def: T[typeof fooProp] = x[fooProp];
18+
const def2: T[typeof barProp] = x[barProp];
19+
}

0 commit comments

Comments
 (0)