diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index aa11e9bf3c..36cf9aa947 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -53,7 +53,8 @@ namespace ts { let typeCount = 0; let symbolCount = 0; let enumCount = 0; - let symbolInstantiationDepth = 0; + let instantiationDepth = 0; + let constraintDepth = 0; const emptySymbols = createSymbolTable(); const identityMapper: (type: Type) => Type = identity; @@ -5303,22 +5304,14 @@ namespace ts { function getTypeOfInstantiatedSymbol(symbol: Symbol): Type { const links = getSymbolLinks(symbol); if (!links.type) { - if (symbolInstantiationDepth === 100) { - error(symbol.valueDeclaration, Diagnostics.Generic_type_instantiation_is_excessively_deep_and_possibly_infinite); - links.type = errorType; + if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { + return links.type = errorType; } - else { - if (!pushTypeResolution(symbol, TypeSystemPropertyName.Type)) { - return links.type = errorType; - } - symbolInstantiationDepth++; - let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!); - symbolInstantiationDepth--; - if (!popTypeResolution()) { - type = reportCircularityError(symbol); - } - links.type = type; + let type = instantiateType(getTypeOfSymbol(links.target!), links.mapper!); + if (!popTypeResolution()) { + type = reportCircularityError(symbol); } + links.type = type; } return links.type; } @@ -6928,7 +6921,8 @@ namespace ts { // over the conditional type and possibly reduced. For example, 'T extends undefined ? never : T' // removes 'undefined' from T. if (type.root.isDistributive) { - const constraint = getConstraintOfType(getSimplifiedType(type.checkType)); + const simplified = getSimplifiedType(type.checkType); + const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified; if (constraint) { const mapper = makeUnaryTypeMapper(type.root.checkType, constraint); const instantiated = getConditionalTypeInstantiation(type, combineTypeMappers(mapper, type.mapper)); @@ -7011,6 +7005,7 @@ namespace ts { * circularly references the type variable. */ function getResolvedBaseConstraint(type: InstantiableType | UnionOrIntersectionType): Type { + let nonTerminating = false; return type.resolvedBaseConstraint || (type.resolvedBaseConstraint = getTypeWithThisArgument(getImmediateBaseConstraint(type), type)); @@ -7019,8 +7014,18 @@ namespace ts { if (!pushTypeResolution(t, TypeSystemPropertyName.ImmediateBaseConstraint)) { return circularConstraintType; } + if (constraintDepth === 50) { + // We have reached 50 recursive invocations of getImmediateBaseConstraint and there is a + // very high likelyhood we're dealing with an infinite generic type that perpetually generates + // new type identities as we descend into it. We stop the recursion here and mark this type + // and the outer types as having circular constraints. + nonTerminating = true; + return t.immediateBaseConstraint = noConstraintType; + } + constraintDepth++; let result = computeBaseConstraint(getSimplifiedType(t)); - if (!popTypeResolution()) { + constraintDepth--; + if (!popTypeResolution() || nonTerminating) { result = circularConstraintType; } t.immediateBaseConstraint = result || noConstraintType; @@ -10293,49 +10298,66 @@ namespace ts { function instantiateType(type: Type, mapper: TypeMapper | undefined): Type; function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined; function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined { - if (type && mapper && mapper !== identityMapper) { - if (type.flags & TypeFlags.TypeParameter) { - return mapper(type); + if (!type || !mapper || mapper === identityMapper) { + return type; + } + if (instantiationDepth === 50) { + // We have reached 50 recursive type instantiations and there is a very high likelyhood we're dealing + // with a combination of infinite generic types that perpetually generate new type identities. We stop + // the recursion here by yielding the error type. + return errorType; + } + instantiationDepth++; + const result = instantiateTypeWorker(type, mapper); + instantiationDepth--; + return result; + } + + function instantiateTypeWorker(type: Type, mapper: TypeMapper): Type { + const flags = type.flags; + if (flags & TypeFlags.TypeParameter) { + return mapper(type); + } + if (flags & TypeFlags.Object) { + const objectFlags = (type).objectFlags; + if (objectFlags & ObjectFlags.Anonymous) { + // If the anonymous type originates in a declaration of a function, method, class, or + // interface, in an object type literal, or in an object literal expression, we may need + // to instantiate the type because it might reference a type parameter. + return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ? + getAnonymousTypeInstantiation(type, mapper) : type; } - if (type.flags & TypeFlags.Object) { - if ((type).objectFlags & ObjectFlags.Anonymous) { - // If the anonymous type originates in a declaration of a function, method, class, or - // interface, in an object type literal, or in an object literal expression, we may need - // to instantiate the type because it might reference a type parameter. - return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ? - getAnonymousTypeInstantiation(type, mapper) : type; - } - if ((type).objectFlags & ObjectFlags.Mapped) { - return getAnonymousTypeInstantiation(type, mapper); - } - if ((type).objectFlags & ObjectFlags.Reference) { - const typeArguments = (type).typeArguments; - const newTypeArguments = instantiateTypes(typeArguments, mapper); - return newTypeArguments !== typeArguments ? createTypeReference((type).target, newTypeArguments) : type; - } + if (objectFlags & ObjectFlags.Mapped) { + return getAnonymousTypeInstantiation(type, mapper); } - if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) { - const types = (type).types; - const newTypes = instantiateTypes(types, mapper); - return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; - } - if (type.flags & TypeFlags.Intersection) { - const types = (type).types; - const newTypes = instantiateTypes(types, mapper); - return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; - } - if (type.flags & TypeFlags.Index) { - return getIndexType(instantiateType((type).type, mapper)); - } - if (type.flags & TypeFlags.IndexedAccess) { - return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); - } - if (type.flags & TypeFlags.Conditional) { - return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); - } - if (type.flags & TypeFlags.Substitution) { - return instantiateType((type).typeVariable, mapper); + if (objectFlags & ObjectFlags.Reference) { + const typeArguments = (type).typeArguments; + const newTypeArguments = instantiateTypes(typeArguments, mapper); + return newTypeArguments !== typeArguments ? createTypeReference((type).target, newTypeArguments) : type; } + return type; + } + if (flags & TypeFlags.Union && !(flags & TypeFlags.Primitive)) { + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; + } + if (flags & TypeFlags.Intersection) { + const types = (type).types; + const newTypes = instantiateTypes(types, mapper); + return newTypes !== types ? getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) : type; + } + if (flags & TypeFlags.Index) { + return getIndexType(instantiateType((type).type, mapper)); + } + if (flags & TypeFlags.IndexedAccess) { + return getIndexedAccessType(instantiateType((type).objectType, mapper), instantiateType((type).indexType, mapper)); + } + if (flags & TypeFlags.Conditional) { + return getConditionalTypeInstantiation(type, combineTypeMappers((type).mapper, mapper)); + } + if (flags & TypeFlags.Substitution) { + return instantiateType((type).typeVariable, mapper); } return type; } diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index 6000c48de4..32a07c0818 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -1964,10 +1964,6 @@ "category": "Error", "code": 2549 }, - "Generic type instantiation is excessively deep and possibly infinite.": { - "category": "Error", - "code": 2550 - }, "Property '{0}' does not exist on type '{1}'. Did you mean '{2}'?": { "category": "Error", "code": 2551 diff --git a/tests/baselines/reference/infiniteConstraints.errors.txt b/tests/baselines/reference/infiniteConstraints.errors.txt new file mode 100644 index 0000000000..387b0daac6 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.errors.txt @@ -0,0 +1,53 @@ +tests/cases/compiler/infiniteConstraints.ts(4,37): error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. +tests/cases/compiler/infiniteConstraints.ts(31,42): error TS2345: Argument of type '{ main: Record<"val", "dup">; alternate: Record<"val", "dup">; }' is not assignable to parameter of type '{ main: never; alternate: never; }'. + Types of property 'main' are incompatible. + Type 'Record<"val", "dup">' is not assignable to type 'never'. +tests/cases/compiler/infiniteConstraints.ts(36,71): error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. + + +==== tests/cases/compiler/infiniteConstraints.ts (3 errors) ==== + // Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + + type T1], { val: string }>["val"] }> = B; + type T2]["val"] }> = B; + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '"val"' cannot be used to index type 'B[Exclude]'. + + // Repros from #22950 + + type AProp = T + + declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } + >(arg: T): T + + const out = myBug({obj1: {a: "test"}}) + + type Value = Record<"val", V>; + declare function value(val: V): Value; + + declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] + ? never + : any + } + >(vals: T): void; + + const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); + + const shouldBeNoError = ensureNoDuplicates({main: value("test")}); + + const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2345: Argument of type '{ main: Record<"val", "dup">; alternate: Record<"val", "dup">; }' is not assignable to parameter of type '{ main: never; alternate: never; }'. +!!! error TS2345: Types of property 'main' are incompatible. +!!! error TS2345: Type 'Record<"val", "dup">' is not assignable to type 'never'. + + // Repro from #26448 + + type Cond = T extends number ? number : never; + declare function function1}>(): T[keyof T]["foo"]; + ~~~~~~~~~~~~~~~~~ +!!! error TS2536: Type '"foo"' cannot be used to index type 'T[keyof T]'. + \ No newline at end of file diff --git a/tests/baselines/reference/infiniteConstraints.js b/tests/baselines/reference/infiniteConstraints.js new file mode 100644 index 0000000000..03a4d70e00 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.js @@ -0,0 +1,46 @@ +//// [infiniteConstraints.ts] +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +type T2]["val"] }> = B; + +// Repros from #22950 + +type AProp = T + +declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T + +const out = myBug({obj1: {a: "test"}}) + +type Value = Record<"val", V>; +declare function value(val: V): Value; + +declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] + ? never + : any + } +>(vals: T): void; + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); + +// Repro from #26448 + +type Cond = T extends number ? number : never; +declare function function1}>(): T[keyof T]["foo"]; + + +//// [infiniteConstraints.js] +"use strict"; +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint +var out = myBug({ obj1: { a: "test" } }); +var noError = ensureNoDuplicates({ main: value("test"), alternate: value("test2") }); +var shouldBeNoError = ensureNoDuplicates({ main: value("test") }); +var shouldBeError = ensureNoDuplicates({ main: value("dup"), alternate: value("dup") }); diff --git a/tests/baselines/reference/infiniteConstraints.symbols b/tests/baselines/reference/infiniteConstraints.symbols new file mode 100644 index 0000000000..6306203a7e --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.symbols @@ -0,0 +1,140 @@ +=== tests/cases/compiler/infiniteConstraints.ts === +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +>T1 : Symbol(T1, Decl(infiniteConstraints.ts, 0, 0)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 2, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 2, 21)) +>val : Symbol(val, Decl(infiniteConstraints.ts, 2, 69)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 2, 8)) + +type T2]["val"] }> = B; +>T2 : Symbol(T2, Decl(infiniteConstraints.ts, 2, 99)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 3, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 3, 21)) +>B : Symbol(B, Decl(infiniteConstraints.ts, 3, 8)) + +// Repros from #22950 + +type AProp = T +>AProp : Symbol(AProp, Decl(infiniteConstraints.ts, 3, 73)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 7, 11)) +>a : Symbol(a, Decl(infiniteConstraints.ts, 7, 22)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 7, 11)) + +declare function myBug< +>myBug : Symbol(myBug, Decl(infiniteConstraints.ts, 7, 39)) + + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 10, 15)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 10, 15)) +>AProp : Symbol(AProp, Decl(infiniteConstraints.ts, 3, 73)) +>U : Symbol(U, Decl(infiniteConstraints.ts, 10, 54)) +>U : Symbol(U, Decl(infiniteConstraints.ts, 10, 54)) + +>(arg: T): T +>arg : Symbol(arg, Decl(infiniteConstraints.ts, 11, 2)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 9, 23)) + +const out = myBug({obj1: {a: "test"}}) +>out : Symbol(out, Decl(infiniteConstraints.ts, 13, 5)) +>myBug : Symbol(myBug, Decl(infiniteConstraints.ts, 7, 39)) +>obj1 : Symbol(obj1, Decl(infiniteConstraints.ts, 13, 19)) +>a : Symbol(a, Decl(infiniteConstraints.ts, 13, 26)) + +type Value = Record<"val", V>; +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 15, 11)) +>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 15, 11)) + +declare function value(val: V): Value; +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) +>val : Symbol(val, Decl(infiniteConstraints.ts, 16, 41)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>V : Symbol(V, Decl(infiniteConstraints.ts, 16, 23)) + +declare function ensureNoDuplicates< +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) + + T extends { +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) + + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) +>Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>Exclude : Symbol(Exclude, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 20, 5)) +>Value : Symbol(Value, Decl(infiniteConstraints.ts, 13, 38)) + + ? never + : any + } +>(vals: T): void; +>vals : Symbol(vals, Decl(infiniteConstraints.ts, 24, 2)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 18, 36)) + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); +>noError : Symbol(noError, Decl(infiniteConstraints.ts, 26, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 26, 36)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>alternate : Symbol(alternate, Decl(infiniteConstraints.ts, 26, 56)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); +>shouldBeNoError : Symbol(shouldBeNoError, Decl(infiniteConstraints.ts, 28, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 28, 44)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); +>shouldBeError : Symbol(shouldBeError, Decl(infiniteConstraints.ts, 30, 5)) +>ensureNoDuplicates : Symbol(ensureNoDuplicates, Decl(infiniteConstraints.ts, 16, 59)) +>main : Symbol(main, Decl(infiniteConstraints.ts, 30, 42)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) +>alternate : Symbol(alternate, Decl(infiniteConstraints.ts, 30, 61)) +>value : Symbol(value, Decl(infiniteConstraints.ts, 15, 57)) + +// Repro from #26448 + +type Cond = T extends number ? number : never; +>Cond : Symbol(Cond, Decl(infiniteConstraints.ts, 30, 88)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 34, 10)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 34, 10)) + +declare function function1}>(): T[keyof T]["foo"]; +>function1 : Symbol(function1, Decl(infiniteConstraints.ts, 34, 49)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 35, 39)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>Cond : Symbol(Cond, Decl(infiniteConstraints.ts, 30, 88)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>K : Symbol(K, Decl(infiniteConstraints.ts, 35, 39)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) +>T : Symbol(T, Decl(infiniteConstraints.ts, 35, 27)) + diff --git a/tests/baselines/reference/infiniteConstraints.types b/tests/baselines/reference/infiniteConstraints.types new file mode 100644 index 0000000000..e23e138c82 --- /dev/null +++ b/tests/baselines/reference/infiniteConstraints.types @@ -0,0 +1,97 @@ +=== tests/cases/compiler/infiniteConstraints.ts === +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +>T1 : B +>val : string + +type T2]["val"] }> = B; +>T2 : B + +// Repros from #22950 + +type AProp = T +>AProp : T +>a : string + +declare function myBug< +>myBug : (arg: T) => T + + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T +>arg : T + +const out = myBug({obj1: {a: "test"}}) +>out : { obj1: { a: string; }; } +>myBug({obj1: {a: "test"}}) : { obj1: { a: string; }; } +>myBug : (arg: T) => T +>{obj1: {a: "test"}} : { obj1: { a: string; }; } +>obj1 : { a: string; } +>{a: "test"} : { a: string; } +>a : string +>"test" : "test" + +type Value = Record<"val", V>; +>Value : Record<"val", V> + +declare function value(val: V): Value; +>value : (val: V) => Record<"val", V> +>val : V + +declare function ensureNoDuplicates< +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void + + T extends { + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] + ? never + : any + } +>(vals: T): void; +>vals : T + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); +>noError : void +>ensureNoDuplicates({main: value("test"), alternate: value("test2")}) : void +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("test"), alternate: value("test2")} : { main: Record<"val", "test">; alternate: Record<"val", "test2">; } +>main : Record<"val", "test"> +>value("test") : Record<"val", "test"> +>value : (val: V) => Record<"val", V> +>"test" : "test" +>alternate : Record<"val", "test2"> +>value("test2") : Record<"val", "test2"> +>value : (val: V) => Record<"val", V> +>"test2" : "test2" + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); +>shouldBeNoError : void +>ensureNoDuplicates({main: value("test")}) : void +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("test")} : { main: Record<"val", "test">; } +>main : Record<"val", "test"> +>value("test") : Record<"val", "test"> +>value : (val: V) => Record<"val", V> +>"test" : "test" + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); +>shouldBeError : any +>ensureNoDuplicates({main: value("dup"), alternate: value("dup")}) : any +>ensureNoDuplicates : >["val"] extends Extract], Record<"val", string>>["val"] ? never : any; }>(vals: T) => void +>{main: value("dup"), alternate: value("dup")} : { main: Record<"val", "dup">; alternate: Record<"val", "dup">; } +>main : Record<"val", "dup"> +>value("dup") : Record<"val", "dup"> +>value : (val: V) => Record<"val", V> +>"dup" : "dup" +>alternate : Record<"val", "dup"> +>value("dup") : Record<"val", "dup"> +>value : (val: V) => Record<"val", V> +>"dup" : "dup" + +// Repro from #26448 + +type Cond = T extends number ? number : never; +>Cond : Cond + +declare function function1}>(): T[keyof T]["foo"]; +>function1 : ; }>() => T[keyof T]["foo"] + diff --git a/tests/cases/compiler/infiniteConstraints.ts b/tests/cases/compiler/infiniteConstraints.ts new file mode 100644 index 0000000000..e914b738ef --- /dev/null +++ b/tests/cases/compiler/infiniteConstraints.ts @@ -0,0 +1,38 @@ +// @strict: true + +// Both of the following types trigger the recursion limiter in getImmediateBaseConstraint + +type T1], { val: string }>["val"] }> = B; +type T2]["val"] }> = B; + +// Repros from #22950 + +type AProp = T + +declare function myBug< + T extends { [K in keyof T]: T[K] extends AProp ? U : never } +>(arg: T): T + +const out = myBug({obj1: {a: "test"}}) + +type Value = Record<"val", V>; +declare function value(val: V): Value; + +declare function ensureNoDuplicates< + T extends { + [K in keyof T]: Extract["val"] extends Extract], Value>["val"] + ? never + : any + } +>(vals: T): void; + +const noError = ensureNoDuplicates({main: value("test"), alternate: value("test2")}); + +const shouldBeNoError = ensureNoDuplicates({main: value("test")}); + +const shouldBeError = ensureNoDuplicates({main: value("dup"), alternate: value("dup")}); + +// Repro from #26448 + +type Cond = T extends number ? number : never; +declare function function1}>(): T[keyof T]["foo"];