diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4b4c1813ac46d..7bd297b482d0d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3725,8 +3725,8 @@ namespace ts { return "(Anonymous function)"; } } - if ((symbol as TransientSymbol).syntheticLiteralTypeOrigin) { - const stringValue = (symbol as TransientSymbol).syntheticLiteralTypeOrigin.value; + if ((symbol as TransientSymbol).nameType && (symbol as TransientSymbol).nameType.flags & TypeFlags.StringLiteral) { + const stringValue = ((symbol as TransientSymbol).nameType as StringLiteralType).value; if (!isIdentifierText(stringValue, compilerOptions.target)) { return `"${escapeString(stringValue, CharacterCodes.doubleQuote)}"`; } @@ -5460,6 +5460,12 @@ namespace ts { lateSymbol = createSymbol(SymbolFlags.None, memberName, CheckFlags.Late); } + const symbolLinks = getSymbolLinks(lateSymbol); + if (!symbolLinks.nameType) { + // Retain link to name type so that it can be reused later + symbolLinks.nameType = type; + } + addDeclarationToLateBoundSymbol(lateSymbol, decl, symbolFlags); if (lateSymbol.parent) { Debug.assert(lateSymbol.parent === parent, "Existing symbol parent should match new one"); @@ -5948,7 +5954,7 @@ namespace ts { // If the current iteration type constituent is a string literal type, create a property. // Otherwise, for type string create a string index signature. if (t.flags & TypeFlags.StringLiteral) { - const propName = escapeLeadingUnderscores((t).value); + const propName = getLateBoundNameFromType(t as LiteralType | UniqueESSymbolType); const modifiersProp = getPropertyOfType(modifiersType, propName); const isOptional = !!(templateModifiers & MappedTypeModifiers.IncludeOptional || !(templateModifiers & MappedTypeModifiers.ExcludeOptional) && modifiersProp && modifiersProp.flags & SymbolFlags.Optional); @@ -5965,7 +5971,7 @@ namespace ts { prop.syntheticOrigin = propertySymbol; prop.declarations = propertySymbol.declarations; } - prop.syntheticLiteralTypeOrigin = t as StringLiteralType; + prop.nameType = t; members.set(propName, prop); } else if (t.flags & (TypeFlags.Any | TypeFlags.String)) { @@ -7999,9 +8005,19 @@ namespace ts { } function getLiteralTypeFromPropertyName(prop: Symbol) { - return getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || isKnownSymbol(prop) ? - neverType : - getLiteralType(symbolName(prop)); + const links = getSymbolLinks(prop); + if (!links.nameType) { + if (links.target) { + Debug.assert(links.target.escapedName === prop.escapedName, "Target symbol and symbol do not have the same name"); + links.nameType = getLiteralTypeFromPropertyName(links.target); + } + else { + links.nameType = getDeclarationModifierFlagsFromSymbol(prop) & ModifierFlags.NonPublicAccessibilityModifier || isKnownSymbol(prop) ? + neverType : + getLiteralType(symbolName(prop)); + } + } + return links.nameType; } function getLiteralTypeFromPropertyNames(type: Type) { @@ -11225,7 +11241,7 @@ namespace ts { result.type = undefinedType; const associatedKeyType = getLiteralType(unescapeLeadingUnderscores(name)); if (associatedKeyType.flags & TypeFlags.StringLiteral) { - result.syntheticLiteralTypeOrigin = associatedKeyType as StringLiteralType; + result.nameType = associatedKeyType; } undefinedProperties.set(name, result); return result; @@ -14998,10 +15014,15 @@ namespace ts { typeFlags |= type.flags; const nameType = hasLateBindableName(memberDecl) ? checkComputedPropertyName(memberDecl.name) : undefined; - const prop = nameType && isTypeUsableAsLateBoundName(nameType) - ? createSymbol(SymbolFlags.Property | member.flags, getLateBoundNameFromType(nameType), CheckFlags.Late) + const hasLateBoundName = nameType && isTypeUsableAsLateBoundName(nameType); + const prop = hasLateBoundName + ? createSymbol(SymbolFlags.Property | member.flags, getLateBoundNameFromType(nameType as LiteralType | UniqueESSymbolType), CheckFlags.Late) : createSymbol(SymbolFlags.Property | member.flags, literalName || member.escapedName); + if (hasLateBoundName) { + prop.nameType = nameType; + } + if (inDestructuringPattern) { // If object literal is an assignment pattern and if the assignment pattern specifies a default value // for the property, make the property optional. diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b933d6853afb4..86c08b068f2ee 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3344,7 +3344,6 @@ namespace ts { leftSpread?: Symbol; // Left source for synthetic spread property rightSpread?: Symbol; // Right source for synthetic spread property syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property - 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 isDiscriminantProperty?: boolean; // True if discriminant synthetic property resolvedExports?: SymbolTable; // Resolved exports of module or combined early- and late-bound static members of a class. resolvedMembers?: SymbolTable; // Combined early- and late-bound members of a symbol @@ -3356,6 +3355,7 @@ namespace ts { enumKind?: EnumKind; // Enum declaration classification originatingImport?: ImportDeclaration | ImportCall; // Import declaration which produced the symbol, present if the symbol is marked as uncallable but had call signatures in `resolveESModuleSymbol` lateSymbol?: Symbol; // Late-bound symbol for a computed property + nameType?: Type; // Type associate with a late-bound or mapped type property symbol's name } /* @internal */ diff --git a/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.js b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.js new file mode 100644 index 0000000000000..991f62e4f3b70 --- /dev/null +++ b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.js @@ -0,0 +1,33 @@ +//// [lateBoundConstraintTypeChecksCorrectly.ts] +declare const fooProp: unique symbol; +declare const barProp: unique symbol; + +type BothProps = typeof fooProp | typeof barProp; + +export interface Foo { + [fooProp]: T; + [barProp]: string; +} + +function f>(x: T) { + const abc = x[fooProp]; // expected: 'T[typeof fooProp]' + + /** + * Expected: no error + */ + const def: T[typeof fooProp] = x[fooProp]; + const def2: T[typeof barProp] = x[barProp]; +} + + +//// [lateBoundConstraintTypeChecksCorrectly.js] +"use strict"; +exports.__esModule = true; +function f(x) { + var abc = x[fooProp]; // expected: 'T[typeof fooProp]' + /** + * Expected: no error + */ + var def = x[fooProp]; + var def2 = x[barProp]; +} diff --git a/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.symbols b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.symbols new file mode 100644 index 0000000000000..9a2739bbd56d8 --- /dev/null +++ b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.symbols @@ -0,0 +1,56 @@ +=== tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts === +declare const fooProp: unique symbol; +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) + +declare const barProp: unique symbol; +>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13)) + +type BothProps = typeof fooProp | typeof barProp; +>BothProps : Symbol(BothProps, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 37)) +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) +>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13)) + +export interface Foo { +>Foo : Symbol(Foo, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 3, 49)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 21)) + + [fooProp]: T; +>[fooProp] : Symbol(Foo[fooProp], Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 25)) +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 5, 21)) + + [barProp]: string; +>[barProp] : Symbol(Foo[barProp], Decl(lateBoundConstraintTypeChecksCorrectly.ts, 6, 15)) +>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13)) +} + +function f>(x: T) { +>f : Symbol(f, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 8, 1)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11)) +>Foo : Symbol(Foo, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 3, 49)) +>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11)) + + const abc = x[fooProp]; // expected: 'T[typeof fooProp]' +>abc : Symbol(abc, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 11, 9)) +>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34)) +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) + + /** + * Expected: no error + */ + const def: T[typeof fooProp] = x[fooProp]; +>def : Symbol(def, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 16, 9)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11)) +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) +>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34)) +>fooProp : Symbol(fooProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 0, 13)) + + const def2: T[typeof barProp] = x[barProp]; +>def2 : Symbol(def2, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 17, 9)) +>T : Symbol(T, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 11)) +>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13)) +>x : Symbol(x, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 10, 34)) +>barProp : Symbol(barProp, Decl(lateBoundConstraintTypeChecksCorrectly.ts, 1, 13)) +} + diff --git a/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.types b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.types new file mode 100644 index 0000000000000..d68f373032a92 --- /dev/null +++ b/tests/baselines/reference/lateBoundConstraintTypeChecksCorrectly.types @@ -0,0 +1,59 @@ +=== tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts === +declare const fooProp: unique symbol; +>fooProp : unique symbol + +declare const barProp: unique symbol; +>barProp : unique symbol + +type BothProps = typeof fooProp | typeof barProp; +>BothProps : unique symbol | unique symbol +>fooProp : unique symbol +>barProp : unique symbol + +export interface Foo { +>Foo : Foo +>T : T + + [fooProp]: T; +>[fooProp] : T +>fooProp : unique symbol +>T : T + + [barProp]: string; +>[barProp] : string +>barProp : unique symbol +} + +function f>(x: T) { +>f : >(x: T) => void +>T : T +>Foo : Foo +>x : T +>T : T + + const abc = x[fooProp]; // expected: 'T[typeof fooProp]' +>abc : number +>x[fooProp] : number +>x : T +>fooProp : unique symbol + + /** + * Expected: no error + */ + const def: T[typeof fooProp] = x[fooProp]; +>def : T[unique symbol] +>T : T +>fooProp : unique symbol +>x[fooProp] : number +>x : T +>fooProp : unique symbol + + const def2: T[typeof barProp] = x[barProp]; +>def2 : T[unique symbol] +>T : T +>barProp : unique symbol +>x[barProp] : string +>x : T +>barProp : unique symbol +} + diff --git a/tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts b/tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts new file mode 100644 index 0000000000000..09aa9cfdd94bb --- /dev/null +++ b/tests/cases/compiler/lateBoundConstraintTypeChecksCorrectly.ts @@ -0,0 +1,19 @@ +declare const fooProp: unique symbol; +declare const barProp: unique symbol; + +type BothProps = typeof fooProp | typeof barProp; + +export interface Foo { + [fooProp]: T; + [barProp]: string; +} + +function f>(x: T) { + const abc = x[fooProp]; // expected: 'T[typeof fooProp]' + + /** + * Expected: no error + */ + const def: T[typeof fooProp] = x[fooProp]; + const def2: T[typeof barProp] = x[barProp]; +}