Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(50375): Errors for missing enum-named properties should attempt to preserve names #50382

Merged
merged 6 commits into from Aug 24, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
25 changes: 23 additions & 2 deletions src/compiler/checker.ts
Expand Up @@ -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,
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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<T>(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 = {
Expand Down Expand Up @@ -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));
Expand Down
22 changes: 15 additions & 7 deletions src/compiler/types.ts
Expand Up @@ -4545,6 +4545,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. */
symbolToNode(symbol: Symbol, meaning: SymbolFlags, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): Node | undefined;
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved
/** Note that the resulting nodes cannot be checked. */
symbolToTypeParameterDeclarations(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): NodeArray<TypeParameterDeclaration> | undefined;
/** Note that the resulting nodes cannot be checked. */
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
Expand Down Expand Up @@ -4822,6 +4824,7 @@ namespace ts {
AllowEmptyTuple = 1 << 19,
AllowUniqueESSymbolType = 1 << 20,
AllowEmptyIndexInfoType = 1 << 21,
WriteComputedProps = 1 << 30, // { [E.A]: 1 }

// Errors (cont.)
AllowNodeModulesRelativePaths = 1 << 26,
Expand Down Expand Up @@ -4871,36 +4874,41 @@ namespace ts {
InFirstTypeArgument = 1 << 22, // Writing first type argument of the instantiated type
InTypeAlias = 1 << 23, // Writing type in type alias declaration

WriteComputedProps = 1 << 30,

/** @deprecated */ WriteOwnNameForAnyLike = 0, // Does nothing

NodeBuilderFlagsMask = NoTruncation | WriteArrayAsGenericType | UseStructuralFallback | WriteTypeArgumentsOfSignature |
UseFullyQualifiedType | SuppressAnyReturnType | MultilineObjectLiterals | WriteClassExpressionAsTypeLiteral |
UseTypeOfFunction | OmitParameterModifiers | UseAliasDefinedOutsideCurrentScope | AllowUniqueESSymbolType | InTypeAlias |
UseSingleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter
UseSingleQuotesForStringLiteralType | NoTypeReduction | OmitThisParameter | WriteComputedProps
}

export const enum SymbolFormatFlags {
None = 0x00000000,
None = 0,

// Write symbols's type argument if it is instantiated symbol
// eg. class C<T> { p: T } <-- Show p as C<T>.p here
// var a: C<number>;
// var p = a.p; <--- Here p is property of C<number> so show it as C<number>.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 }
WriteComputedProps = 1 << 4,

// Skip building an accessible symbol chain
/* @internal */ DoNotIncludeSymbolChain = 0x00000010,
/* @internal */ DoNotIncludeSymbolChain = 1 << 5,
}

/* @internal */
Expand Down
8 changes: 3 additions & 5 deletions src/services/codefixes/fixAddMissingMember.ts
Expand Up @@ -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);
}
Expand Down
7 changes: 6 additions & 1 deletion tests/baselines/reference/api/tsserverlibrary.d.ts
Expand Up @@ -2338,6 +2338,8 @@ declare 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. */
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<TypeParameterDeclaration> | undefined;
/** Note that the resulting nodes cannot be checked. */
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
Expand Down Expand Up @@ -2436,6 +2438,7 @@ declare namespace ts {
AllowEmptyTuple = 524288,
AllowUniqueESSymbolType = 1048576,
AllowEmptyIndexInfoType = 2097152,
WriteComputedProps = 1073741824,
AllowNodeModulesRelativePaths = 67108864,
IgnoreErrors = 70221824,
InObjectTypeLiteral = 4194304,
Expand Down Expand Up @@ -2465,15 +2468,17 @@ declare namespace ts {
InElementType = 2097152,
InFirstTypeArgument = 4194304,
InTypeAlias = 8388608,
WriteComputedProps = 1073741824,
/** @deprecated */ WriteOwnNameForAnyLike = 0,
NodeBuilderFlagsMask = 848330091
NodeBuilderFlagsMask = 1922071915
}
export enum SymbolFormatFlags {
None = 0,
WriteTypeParametersOrArguments = 1,
UseOnlyExternalAliasing = 2,
AllowAnyNodeKind = 4,
UseAliasDefinedOutsideCurrentScope = 8,
WriteComputedProps = 16,
a-tarasyuk marked this conversation as resolved.
Show resolved Hide resolved
}
export enum TypePredicateKind {
This = 0,
Expand Down
7 changes: 6 additions & 1 deletion tests/baselines/reference/api/typescript.d.ts
Expand Up @@ -2338,6 +2338,8 @@ declare 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. */
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<TypeParameterDeclaration> | undefined;
/** Note that the resulting nodes cannot be checked. */
symbolToParameterDeclaration(symbol: Symbol, enclosingDeclaration: Node | undefined, flags: NodeBuilderFlags | undefined): ParameterDeclaration | undefined;
Expand Down Expand Up @@ -2436,6 +2438,7 @@ declare namespace ts {
AllowEmptyTuple = 524288,
AllowUniqueESSymbolType = 1048576,
AllowEmptyIndexInfoType = 2097152,
WriteComputedProps = 1073741824,
AllowNodeModulesRelativePaths = 67108864,
IgnoreErrors = 70221824,
InObjectTypeLiteral = 4194304,
Expand Down Expand Up @@ -2465,15 +2468,17 @@ declare namespace ts {
InElementType = 2097152,
InFirstTypeArgument = 4194304,
InTypeAlias = 8388608,
WriteComputedProps = 1073741824,
/** @deprecated */ WriteOwnNameForAnyLike = 0,
NodeBuilderFlagsMask = 848330091
NodeBuilderFlagsMask = 1922071915
}
export enum SymbolFormatFlags {
None = 0,
WriteTypeParametersOrArguments = 1,
UseOnlyExternalAliasing = 2,
AllowAnyNodeKind = 4,
UseAliasDefinedOutsideCurrentScope = 8,
WriteComputedProps = 16,
}
export enum TypePredicateKind {
This = 0,
Expand Down
@@ -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<E, any>'.


==== tests/cases/conformance/types/typeRelationships/assignmentCompatibility/assignmentCompatWithEnumIndexer.ts (1 errors) ====
enum E {
A
}

let foo: Record<E, any> = {}
~~~
!!! error TS2741: Property '[E.A]' is missing in type '{}' but required in type 'Record<E, any>'.

14 changes: 14 additions & 0 deletions tests/baselines/reference/assignmentCompatWithEnumIndexer.js
@@ -0,0 +1,14 @@
//// [assignmentCompatWithEnumIndexer.ts]
enum E {
A
}

let foo: Record<E, any> = {}


//// [assignmentCompatWithEnumIndexer.js]
var E;
(function (E) {
E[E["A"] = 0] = "A";
})(E || (E = {}));
var foo = {};
13 changes: 13 additions & 0 deletions 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<E, any> = {}
>foo : Symbol(foo, Decl(assignmentCompatWithEnumIndexer.ts, 4, 3))
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --))
>E : Symbol(E, Decl(assignmentCompatWithEnumIndexer.ts, 0, 0))

12 changes: 12 additions & 0 deletions 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<E, any> = {}
>foo : Record<E, any>
>{} : {}

@@ -0,0 +1,5 @@
enum E {
A
}

let foo: Record<E, any> = {}
18 changes: 18 additions & 0 deletions tests/cases/fourslash/codeFixAddMissingProperties23.ts
@@ -0,0 +1,18 @@
/// <reference path="fourslash.ts" />

////enum E {
//// A
////}
////let obj: Record<E, any> = {}

verify.codeFix({
index: 0,
description: ts.Diagnostics.Add_missing_properties.message,
newFileContent:
`enum E {
A
}
let obj: Record<E, any> = {
[E.A]: undefined
}`,
});