From 4dd1e2f8446fd279e26034fc207c317c6a3da986 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Tue, 7 Nov 2023 15:59:19 -0800 Subject: [PATCH] Ensure instantiation expressions have symbols, preventing crash in signature relations (#56064) --- src/compiler/checker.ts | 33 +++++++------ src/compiler/types.ts | 1 + ...sionGenericIntersectionNoCrash1.errors.txt | 23 +++++++++ ...onExpressionGenericIntersectionNoCrash1.js | 23 +++++++++ ...ressionGenericIntersectionNoCrash1.symbols | 32 +++++++++++++ ...xpressionGenericIntersectionNoCrash1.types | 25 ++++++++++ ...sionGenericIntersectionNoCrash2.errors.txt | 28 +++++++++++ ...onExpressionGenericIntersectionNoCrash2.js | 23 +++++++++ ...ressionGenericIntersectionNoCrash2.symbols | 47 +++++++++++++++++++ ...xpressionGenericIntersectionNoCrash2.types | 33 +++++++++++++ tests/baselines/reference/api/typescript.d.ts | 1 + ...onExpressionGenericIntersectionNoCrash1.ts | 12 +++++ ...onExpressionGenericIntersectionNoCrash2.ts | 17 +++++++ 13 files changed, 283 insertions(+), 15 deletions(-) create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols create mode 100644 tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types create mode 100644 tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts create mode 100644 tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index c803b581a7b..e9bdb4a3d07 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -6838,6 +6838,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { const typeId = type.id; const symbol = type.symbol; if (symbol) { + const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); + if (isInstantiationExpressionType) { + const instantiationExpressionType = type as InstantiationExpressionType; + const existing = instantiationExpressionType.node; + if (isTypeQueryNode(existing) && getTypeFromTypeNode(existing) === type) { + const typeNode = serializeExistingTypeNode(context, existing); + if (typeNode) { + return typeNode; + } + } + if (context.visitedTypes?.has(typeId)) { + return createElidedInformationPlaceholder(context); + } + return visitAndTransformType(type, createTypeNodeFromObjectType); + } const isInstanceType = isClassInstanceSide(type) ? SymbolFlags.Type : SymbolFlags.Value; if (isJSConstructor(symbol.valueDeclaration)) { // Instance and static types share the same symbol; only add 'typeof' for the static side. @@ -6869,20 +6884,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } else { - const isInstantiationExpressionType = !!(getObjectFlags(type) & ObjectFlags.InstantiationExpressionType); - if (isInstantiationExpressionType) { - const instantiationExpressionType = type as InstantiationExpressionType; - if (isTypeQueryNode(instantiationExpressionType.node)) { - const typeNode = serializeExistingTypeNode(context, instantiationExpressionType.node); - if (typeNode) { - return typeNode; - } - } - if (context.visitedTypes?.has(typeId)) { - return createElidedInformationPlaceholder(context); - } - return visitAndTransformType(type, createTypeNodeFromObjectType); - } // Anonymous types without a symbol are never circular. return createTypeNodeFromObjectType(type); } @@ -19542,6 +19543,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): AnonymousType { + Debug.assert(type.symbol, "anonymous type must have symbol to be instantiated"); const result = createObjectType(type.objectFlags & ~(ObjectFlags.CouldContainTypeVariablesComputed | ObjectFlags.CouldContainTypeVariables) | ObjectFlags.Instantiated, type.symbol) as AnonymousType; if (type.objectFlags & ObjectFlags.Mapped) { (result as MappedType).declaration = (type as MappedType).declaration; @@ -23074,6 +23076,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // method). Simply do a pairwise comparison of the signatures in the two signature lists instead // of the much more expensive N * M comparison matrix we explore below. We erase type parameters // as they are known to always be the same. + Debug.assertEqual(sourceSignatures.length, targetSignatures.length); for (let i = 0; i < targetSignatures.length; i++) { const related = signatureRelatedTo(sourceSignatures[i], targetSignatures[i], /*erase*/ true, reportErrors, intersectionState, incompatibleReporter(sourceSignatures[i], targetSignatures[i])); if (!related) { @@ -35677,7 +35680,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { hasSignatures ||= resolved.callSignatures.length !== 0 || resolved.constructSignatures.length !== 0; hasApplicableSignature ||= callSignatures.length !== 0 || constructSignatures.length !== 0; if (callSignatures !== resolved.callSignatures || constructSignatures !== resolved.constructSignatures) { - const result = createAnonymousType(/*symbol*/ undefined, resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; + const result = createAnonymousType(createSymbol(SymbolFlags.None, InternalSymbolName.InstantiationExpression), resolved.members, callSignatures, constructSignatures, resolved.indexInfos) as ResolvedType & InstantiationExpressionType; result.objectFlags |= ObjectFlags.InstantiationExpressionType; result.node = node; return result; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 1926ef4cd77..34a28358223 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5955,6 +5955,7 @@ export const enum InternalSymbolName { ExportEquals = "export=", // Export assignment symbol Default = "default", // Default export symbol (technically not wholly internal, but included here for usability) This = "this", + InstantiationExpression = "__instantiationExpression", // Instantiation expressions } /** diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt new file mode 100644 index 00000000000..4873181c8fb --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.errors.txt @@ -0,0 +1,23 @@ +aliasInstantiationExpressionGenericIntersectionNoCrash1.ts(10,1): error TS2352: Conversion of type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' to type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' is not comparable to type '{ new (): ErrImpl; prototype: ErrImpl; }'. + Type 'ErrImpl' is not comparable to type 'ErrImpl'. + Type 'number' is not comparable to type 'string'. + + +==== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts (1 errors) ==== + class ErrImpl { + e!: E; + } + + declare const Err: typeof ErrImpl & (() => T); + + type ErrAlias = typeof Err; + + declare const e: ErrAlias; + e as ErrAlias; + ~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Conversion of type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' to type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => string)' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. +!!! error TS2352: Type '{ new (): ErrImpl; prototype: ErrImpl; } & (() => number)' is not comparable to type '{ new (): ErrImpl; prototype: ErrImpl; }'. +!!! error TS2352: Type 'ErrImpl' is not comparable to type 'ErrImpl'. +!!! error TS2352: Type 'number' is not comparable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js new file mode 100644 index 00000000000..33b04098256 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] +class ErrImpl { + e!: E; +} + +declare const Err: typeof ErrImpl & (() => T); + +type ErrAlias = typeof Err; + +declare const e: ErrAlias; +e as ErrAlias; + + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash1.js] +"use strict"; +var ErrImpl = /** @class */ (function () { + function ErrImpl() { + } + return ErrImpl; +}()); +e; diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols new file mode 100644 index 00000000000..7cdb48a8d5e --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.symbols @@ -0,0 +1,32 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts === +class ErrImpl { +>ErrImpl : Symbol(ErrImpl, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 0)) +>E : Symbol(E, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 14)) + + e!: E; +>e : Symbol(ErrImpl.e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 18)) +>E : Symbol(E, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 14)) +} + +declare const Err: typeof ErrImpl & (() => T); +>Err : Symbol(Err, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 13)) +>ErrImpl : Symbol(ErrImpl, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 38)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 38)) + +type ErrAlias = typeof Err; +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) +>U : Symbol(U, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 6, 14)) +>Err : Symbol(Err, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 13)) +>U : Symbol(U, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 6, 14)) + +declare const e: ErrAlias; +>e : Symbol(e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 8, 13)) +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) + +e as ErrAlias; +>e : Symbol(e, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 8, 13)) +>ErrAlias : Symbol(ErrAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash1.ts, 4, 49)) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types new file mode 100644 index 00000000000..287ec645bc8 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash1.types @@ -0,0 +1,25 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash1.ts === +class ErrImpl { +>ErrImpl : ErrImpl + + e!: E; +>e : E +} + +declare const Err: typeof ErrImpl & (() => T); +>Err : typeof ErrImpl & (() => T) +>ErrImpl : typeof ErrImpl + +type ErrAlias = typeof Err; +>ErrAlias : { new (): ErrImpl; prototype: ErrImpl; } & (() => U) +>Err : typeof ErrImpl & (() => T) + +declare const e: ErrAlias; +>e : { new (): ErrImpl; prototype: ErrImpl; } & (() => number) + +e as ErrAlias; +>e as ErrAlias : { new (): ErrImpl; prototype: ErrImpl; } & (() => string) +>e : { new (): ErrImpl; prototype: ErrImpl; } & (() => number) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt new file mode 100644 index 00000000000..54c1fc637f6 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.errors.txt @@ -0,0 +1,28 @@ +aliasInstantiationExpressionGenericIntersectionNoCrash2.ts(15,1): error TS2352: Conversion of type 'Wat' to type 'Wat' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. + Type 'Wat' is not comparable to type '{ new (): Class; prototype: Class; }'. + Type 'Class' is not comparable to type 'Class'. + Type 'number' is not comparable to type 'string'. + + +==== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts (1 errors) ==== + declare class Class { + x: T; + } + + declare function fn(): T; + + + type ClassAlias = typeof Class; + type FnAlias = typeof fn; + + type Wat = ClassAlias & FnAlias; + + + declare const wat: Wat; + wat as Wat; + ~~~~~~~~~~~~~~~~~~ +!!! error TS2352: Conversion of type 'Wat' to type 'Wat' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. +!!! error TS2352: Type 'Wat' is not comparable to type '{ new (): Class; prototype: Class; }'. +!!! error TS2352: Type 'Class' is not comparable to type 'Class'. +!!! error TS2352: Type 'number' is not comparable to type 'string'. + \ No newline at end of file diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js new file mode 100644 index 00000000000..0755056e2d4 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.js @@ -0,0 +1,23 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] +declare class Class { + x: T; +} + +declare function fn(): T; + + +type ClassAlias = typeof Class; +type FnAlias = typeof fn; + +type Wat = ClassAlias & FnAlias; + + +declare const wat: Wat; +wat as Wat; + + +//// [aliasInstantiationExpressionGenericIntersectionNoCrash2.js] +"use strict"; +wat; diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols new file mode 100644 index 00000000000..f73a051b0c7 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.symbols @@ -0,0 +1,47 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts === +declare class Class { +>Class : Symbol(Class, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 20)) + + x: T; +>x : Symbol(Class.x, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 24)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 20)) +} + +declare function fn(): T; +>fn : Symbol(fn, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 2, 1)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 20)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 20)) + + +type ClassAlias = typeof Class; +>ClassAlias : Symbol(ClassAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 28)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 16)) +>Class : Symbol(Class, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 0, 0)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 16)) + +type FnAlias = typeof fn; +>FnAlias : Symbol(FnAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 37)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 13)) +>fn : Symbol(fn, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 2, 1)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 13)) + +type Wat = ClassAlias & FnAlias; +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) +>ClassAlias : Symbol(ClassAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 4, 28)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) +>FnAlias : Symbol(FnAlias, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 7, 37)) +>T : Symbol(T, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 10, 9)) + + +declare const wat: Wat; +>wat : Symbol(wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 13, 13)) +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) + +wat as Wat; +>wat : Symbol(wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 13, 13)) +>Wat : Symbol(Wat, Decl(aliasInstantiationExpressionGenericIntersectionNoCrash2.ts, 8, 31)) + diff --git a/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types new file mode 100644 index 00000000000..8ddaf31d491 --- /dev/null +++ b/tests/baselines/reference/aliasInstantiationExpressionGenericIntersectionNoCrash2.types @@ -0,0 +1,33 @@ +//// [tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts] //// + +=== aliasInstantiationExpressionGenericIntersectionNoCrash2.ts === +declare class Class { +>Class : Class + + x: T; +>x : T +} + +declare function fn(): T; +>fn : () => T + + +type ClassAlias = typeof Class; +>ClassAlias : typeof Class +>Class : typeof Class + +type FnAlias = typeof fn; +>FnAlias : typeof fn +>fn : () => T_1 + +type Wat = ClassAlias & FnAlias; +>Wat : Wat + + +declare const wat: Wat; +>wat : Wat + +wat as Wat; +>wat as Wat : Wat +>wat : Wat + diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 3f7f78fd008..eddf759a20b 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -7049,6 +7049,7 @@ declare namespace ts { ExportEquals = "export=", Default = "default", This = "this", + InstantiationExpression = "__instantiationExpression", } /** * This represents a string whose leading underscore have been escaped by adding extra leading underscores. diff --git a/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts new file mode 100644 index 00000000000..d43bc399df8 --- /dev/null +++ b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash1.ts @@ -0,0 +1,12 @@ +// @strict: true + +class ErrImpl { + e!: E; +} + +declare const Err: typeof ErrImpl & (() => T); + +type ErrAlias = typeof Err; + +declare const e: ErrAlias; +e as ErrAlias; diff --git a/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts new file mode 100644 index 00000000000..b81740c9513 --- /dev/null +++ b/tests/cases/compiler/aliasInstantiationExpressionGenericIntersectionNoCrash2.ts @@ -0,0 +1,17 @@ +// @strict: true + +declare class Class { + x: T; +} + +declare function fn(): T; + + +type ClassAlias = typeof Class; +type FnAlias = typeof fn; + +type Wat = ClassAlias & FnAlias; + + +declare const wat: Wat; +wat as Wat;