diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index eb0cda34736c1..b7564a95fad59 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -464,6 +464,7 @@ namespace ts { signatureToSignatureDeclaration: nodeBuilder.signatureToSignatureDeclaration, symbolToEntityName: nodeBuilder.symbolToEntityName, symbolToExpression: nodeBuilder.symbolToExpression, + symbolToNode: nodeBuilder.symbolToNode, symbolToTypeParameterDeclarations: nodeBuilder.symbolToTypeParameterDeclarations, symbolToParameterDeclaration: nodeBuilder.symbolToParameterDeclaration, typeParameterToDeclaration: nodeBuilder.typeParameterToDeclaration, @@ -4822,7 +4823,10 @@ namespace ts { if (flags & SymbolFormatFlags.DoNotIncludeSymbolChain) { nodeFlags |= NodeBuilderFlags.DoNotIncludeSymbolChain; } - const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToExpression : nodeBuilder.symbolToEntityName; + if (flags & SymbolFormatFlags.WriteComputedProps) { + nodeFlags |= NodeBuilderFlags.WriteComputedProps; + } + const builder = flags & SymbolFormatFlags.AllowAnyNodeKind ? nodeBuilder.symbolToNode : nodeBuilder.symbolToEntityName; return writer ? symbolToStringWorker(writer).getText() : usingSingleLineStringWriter(symbolToStringWorker); function symbolToStringWorker(writer: EmitTextWriter) { @@ -4919,8 +4923,25 @@ namespace ts { withContext(enclosingDeclaration, flags, tracker, context => typeParameterToDeclaration(parameter, context)), symbolTableToDeclarationStatements: (symbolTable: SymbolTable, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker, bundled?: boolean) => withContext(enclosingDeclaration, flags, tracker, context => symbolTableToDeclarationStatements(symbolTable, context, bundled)), + symbolToNode: (symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration?: Node, flags?: NodeBuilderFlags, tracker?: SymbolTracker) => + withContext(enclosingDeclaration, flags, tracker, context => symbolToNode(symbol, context, meaning)), }; + function symbolToNode(symbol: Symbol, context: NodeBuilderContext, meaning: SymbolFlags) { + if (context.flags & NodeBuilderFlags.WriteComputedProps) { + if (symbol.valueDeclaration) { + const name = getNameOfDeclaration(symbol.valueDeclaration); + if (name && isComputedPropertyName(name)) return name; + } + const nameType = getSymbolLinks(symbol).nameType; + if (nameType && nameType.flags & (TypeFlags.EnumLiteral | TypeFlags.UniqueESSymbol)) { + context.enclosingDeclaration = nameType.symbol.valueDeclaration; + return factory.createComputedPropertyName(symbolToExpression(nameType.symbol, context, meaning)); + } + } + return symbolToExpression(symbol, context, meaning); + } + function withContext(enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined, tracker: SymbolTracker | undefined, cb: (context: NodeBuilderContext) => T): T | undefined { Debug.assert(enclosingDeclaration === undefined || (enclosingDeclaration.flags & NodeFlags.Synthesized) === 0); const context: NodeBuilderContext = { @@ -20310,7 +20331,7 @@ namespace ts { shouldSkipElaboration = true; // Retain top-level error for interface implementing issues, otherwise omit it } if (props.length === 1) { - const propName = symbolToString(unmatchedProperty); + const propName = symbolToString(unmatchedProperty, /*enclosingDeclaration*/ undefined, SymbolFlags.None, SymbolFormatFlags.AllowAnyNodeKind | SymbolFormatFlags.WriteComputedProps); reportError(Diagnostics.Property_0_is_missing_in_type_1_but_required_in_type_2, propName, ...getTypeNamesForErrorDisplay(source, target)); if (length(unmatchedProperty.declarations)) { associateRelatedInfo(createDiagnosticForNode(unmatchedProperty.declarations![0], Diagnostics._0_is_declared_here, propName)); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 461a750028356..a50ce161f660a 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4595,6 +4595,8 @@ namespace ts { /** Note that the resulting nodes cannot be checked. */ symbolToExpression(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Expression | undefined; /** Note that the resulting nodes cannot be checked. */ + /* @internal */ symbolToNode(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Node | undefined; + /** Note that the resulting nodes cannot be checked. */ symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeArray | undefined; /** Note that the resulting nodes cannot be checked. */ symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined; @@ -4872,6 +4874,7 @@ namespace ts { AllowEmptyTuple = 1 << 19, AllowUniqueESSymbolType = 1 << 20, AllowEmptyIndexInfoType = 1 << 21, + /* @internal */ WriteComputedProps = 1 << 30, // { [E.A]: 1 } // Errors (cont.) AllowNodeModulesRelativePaths = 1 << 26, @@ -4930,27 +4933,30 @@ namespace ts { } export const enum SymbolFormatFlags { - None = 0x00000000, + None = 0, // Write symbols's type argument if it is instantiated symbol // eg. class C { p: T } <-- Show p as C.p here // var a: C; // var p = a.p; <--- Here p is property of C so show it as C.p instead of just C.p - WriteTypeParametersOrArguments = 0x00000001, + WriteTypeParametersOrArguments = 1 << 0, // Use only external alias information to get the symbol name in the given context // eg. module m { export class c { } } import x = m.c; // When this flag is specified m.c will be used to refer to the class instead of alias symbol x - UseOnlyExternalAliasing = 0x00000002, + UseOnlyExternalAliasing = 1 << 1, // Build symbol name using any nodes needed, instead of just components of an entity name - AllowAnyNodeKind = 0x00000004, + AllowAnyNodeKind = 1 << 2, // Prefer aliases which are not directly visible - UseAliasDefinedOutsideCurrentScope = 0x00000008, + UseAliasDefinedOutsideCurrentScope = 1 << 3, + + // { [E.A]: 1 } + /* @internal */ WriteComputedProps = 1 << 4, // Skip building an accessible symbol chain - /* @internal */ DoNotIncludeSymbolChain = 0x00000010, + /* @internal */ DoNotIncludeSymbolChain = 1 << 5, } /* @internal */ diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index f08e86983a8af..6e175092d0d4a 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -642,11 +642,9 @@ namespace ts.codefix { } function createPropertyNameFromSymbol(symbol: Symbol, target: ScriptTarget, quotePreference: QuotePreference, checker: TypeChecker) { - if (isTransientSymbol(symbol) && symbol.nameType && symbol.nameType.flags & TypeFlags.UniqueESSymbol) { - const expression = checker.symbolToExpression((symbol.nameType as UniqueESSymbolType).symbol, SymbolFlags.Value, symbol.valueDeclaration, NodeBuilderFlags.AllowUniqueESSymbolType); - if (expression) { - return factory.createComputedPropertyName(expression); - } + if (isTransientSymbol(symbol)) { + const prop = checker.symbolToNode(symbol, SymbolFlags.Value, /*enclosingDeclaration*/ undefined, NodeBuilderFlags.WriteComputedProps); + if (prop && isComputedPropertyName(prop)) return prop; } return createPropertyNameNodeForIdentifierOrLiteral(symbol.name, target, quotePreference === QuotePreference.Single); } diff --git a/tests/baselines/reference/assignmentCompatWithEnumIndexer.errors.txt b/tests/baselines/reference/assignmentCompatWithEnumIndexer.errors.txt new file mode 100644 index 0000000000000..bf317b7ea7514 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithEnumIndexer.errors.txt @@ -0,0 +1,12 @@ +tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts(5,5): error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record'. + + +==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts (1 errors) ==== + enum E { + A + } + + let foo: Record = {} + ~~~ +!!! error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record'. + \ No newline at end of file diff --git a/tests/baselines/reference/assignmentCompatWithEnumIndexer.js b/tests/baselines/reference/assignmentCompatWithEnumIndexer.js new file mode 100644 index 0000000000000..a1281e7243b7f --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithEnumIndexer.js @@ -0,0 +1,14 @@ +//// [assignmentCompatWithEnumIndexer.ts] +enum E { + A +} + +let foo: Record = {} + + +//// [assignmentCompatWithEnumIndexer.js] +var E; +(function (E) { + E[E["A"] = 0] = "A"; +})(E || (E = {})); +var foo = {}; diff --git a/tests/baselines/reference/assignmentCompatWithEnumIndexer.symbols b/tests/baselines/reference/assignmentCompatWithEnumIndexer.symbols new file mode 100644 index 0000000000000..8a6f91a543e61 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithEnumIndexer.symbols @@ -0,0 +1,13 @@ +=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts === +enum E { +>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0)) + + A +>A : Symbol(E.A, Decl(assignmentCompatWithEnumIndexer.ts, 0, 8)) +} + +let foo: Record = {} +>foo : Symbol(foo, Decl(assignmentCompatWithEnumIndexer.ts, 4, 3)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0)) + diff --git a/tests/baselines/reference/assignmentCompatWithEnumIndexer.types b/tests/baselines/reference/assignmentCompatWithEnumIndexer.types new file mode 100644 index 0000000000000..35d08cfa698e3 --- /dev/null +++ b/tests/baselines/reference/assignmentCompatWithEnumIndexer.types @@ -0,0 +1,12 @@ +=== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts === +enum E { +>E : E + + A +>A : E.A +} + +let foo: Record = {} +>foo : Record +>{} : {} + diff --git a/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts new file mode 100644 index 0000000000000..e68c1414c7597 --- /dev/null +++ b/tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts @@ -0,0 +1,5 @@ +enum E { + A +} + +let foo: Record = {} diff --git a/tests/cases/fourslash/codeFixAddMissingProperties23.ts b/tests/cases/fourslash/codeFixAddMissingProperties23.ts new file mode 100644 index 0000000000000..ac6f0816569f9 --- /dev/null +++ b/tests/cases/fourslash/codeFixAddMissingProperties23.ts @@ -0,0 +1,18 @@ +/// + +////enum E { +//// A +////} +////let obj: Record = {} + +verify.codeFix({ + index: 0, + description: ts.Diagnostics.Add_missing_properties.message, + newFileContent: +`enum E { + A +} +let obj: Record = { + [E.A]: undefined +}`, +});