diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ff603646905d9..b20e2b296bb2f 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -14567,6 +14567,23 @@ namespace ts { return type; } + function isTypicalNondistributiveConditional(root: ConditionalRoot) { + return !root.isDistributive + && root.node.checkType.kind === SyntaxKind.TupleType + && length((root.node.checkType as TupleTypeNode).elements) === 1 + && root.node.extendsType.kind === SyntaxKind.TupleType + && length((root.node.extendsType as TupleTypeNode).elements) === 1; + } + + /** + * We syntactually check for common nondistributive conditional shapes and unwrap them into + * the intended comparison - we do this so we can check if the unwrapped types are generic or + * not and appropriately defer condition calculation + */ + function unwrapNondistributiveConditionalTuple(root: ConditionalRoot, type: Type) { + return isTypicalNondistributiveConditional(root) && isTupleType(type) ? getTypeArguments(type)[0] : type; + } + function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { let result; let extraTypes: Type[] | undefined; @@ -14574,9 +14591,9 @@ namespace ts { // types of the form 'A extends B ? X : C extends D ? Y : E extends F ? Z : ...' as a single construct for // purposes of resolution. This means such types aren't subject to the instatiation depth limiter. while (true) { - const checkType = instantiateType(root.checkType, mapper); + const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.checkType), mapper); const checkTypeInstantiable = isGenericObjectType(checkType) || isGenericIndexType(checkType); - const extendsType = instantiateType(root.extendsType, mapper); + const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } @@ -14599,9 +14616,9 @@ namespace ts { // types with type parameters mapped to the wildcard type, the most permissive instantiations // possible (the wildcard type is assignable to and from all types). If those are not related, // then no instantiations will be and we can just return the false branch type. - if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && ((checkType.flags & TypeFlags.Any && root.isDistributive) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any) { + if (checkType.flags & TypeFlags.Any && root.isDistributive) { (extraTypes || (extraTypes = [])).push(instantiateType(getTypeFromTypeNode(root.node.trueType), combinedMapper || mapper)); } // If falseType is an immediately nested conditional type that isn't distributive or has an @@ -14630,8 +14647,8 @@ namespace ts { // Return a deferred type for a check that is neither definitely true nor definitely false result = createType(TypeFlags.Conditional); result.root = root; - result.checkType = checkType; - result.extendsType = extendsType; + result.checkType = instantiateType(root.checkType, mapper); + result.extendsType = instantiateType(root.extendsType, mapper); result.mapper = mapper; result.combinedMapper = combinedMapper; result.aliasSymbol = aliasSymbol || root.aliasSymbol; diff --git a/tests/baselines/reference/inferTypes1.types b/tests/baselines/reference/inferTypes1.types index 86b0b145fb6b3..aa94569cb306a 100644 --- a/tests/baselines/reference/inferTypes1.types +++ b/tests/baselines/reference/inferTypes1.types @@ -257,7 +257,7 @@ type T61 = infer A extends infer B ? infer C : infer D; // Error >T61 : T61 type T62 = U extends (infer U)[] ? U : U; // Error ->T62 : any +>T62 : unknown type T63 = T extends (infer A extends infer B ? infer C : infer D) ? string : number; >T63 : T63 diff --git a/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.js b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.js new file mode 100644 index 0000000000000..748d607cf3b8d --- /dev/null +++ b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.js @@ -0,0 +1,10 @@ +//// [recursiveConditionalEvaluationNonInfinite.ts] +type Test = [T] extends [any[]] ? { array: Test } : { notArray: T }; +declare const x: Test; +const y: { array: { notArray: number } } = x; // Error +declare const a: Test; +const b: { notArray: number } = a; // Works + +//// [recursiveConditionalEvaluationNonInfinite.js] +var y = x; // Error +var b = a; // Works diff --git a/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.symbols b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.symbols new file mode 100644 index 0000000000000..12f1d367132d0 --- /dev/null +++ b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.symbols @@ -0,0 +1,30 @@ +=== tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts === +type Test = [T] extends [any[]] ? { array: Test } : { notArray: T }; +>Test : Symbol(Test, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 10)) +>T : Symbol(T, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 10)) +>array : Symbol(array, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 38)) +>Test : Symbol(Test, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 0)) +>T : Symbol(T, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 10)) +>notArray : Symbol(notArray, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 62)) +>T : Symbol(T, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 10)) + +declare const x: Test; +>x : Symbol(x, Decl(recursiveConditionalEvaluationNonInfinite.ts, 1, 13)) +>Test : Symbol(Test, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 0)) + +const y: { array: { notArray: number } } = x; // Error +>y : Symbol(y, Decl(recursiveConditionalEvaluationNonInfinite.ts, 2, 5)) +>array : Symbol(array, Decl(recursiveConditionalEvaluationNonInfinite.ts, 2, 10)) +>notArray : Symbol(notArray, Decl(recursiveConditionalEvaluationNonInfinite.ts, 2, 19)) +>x : Symbol(x, Decl(recursiveConditionalEvaluationNonInfinite.ts, 1, 13)) + +declare const a: Test; +>a : Symbol(a, Decl(recursiveConditionalEvaluationNonInfinite.ts, 3, 13)) +>Test : Symbol(Test, Decl(recursiveConditionalEvaluationNonInfinite.ts, 0, 0)) + +const b: { notArray: number } = a; // Works +>b : Symbol(b, Decl(recursiveConditionalEvaluationNonInfinite.ts, 4, 5)) +>notArray : Symbol(notArray, Decl(recursiveConditionalEvaluationNonInfinite.ts, 4, 10)) +>a : Symbol(a, Decl(recursiveConditionalEvaluationNonInfinite.ts, 3, 13)) + diff --git a/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.types b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.types new file mode 100644 index 0000000000000..2a302606d632d --- /dev/null +++ b/tests/baselines/reference/recursiveConditionalEvaluationNonInfinite.types @@ -0,0 +1,23 @@ +=== tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts === +type Test = [T] extends [any[]] ? { array: Test } : { notArray: T }; +>Test : Test +>array : Test +>notArray : T + +declare const x: Test; +>x : { array: { notArray: number; }; } + +const y: { array: { notArray: number } } = x; // Error +>y : { array: { notArray: number;}; } +>array : { notArray: number; } +>notArray : number +>x : { array: { notArray: number; }; } + +declare const a: Test; +>a : { notArray: number; } + +const b: { notArray: number } = a; // Works +>b : { notArray: number; } +>notArray : number +>a : { notArray: number; } + diff --git a/tests/baselines/reference/recursiveConditionalTypes.types b/tests/baselines/reference/recursiveConditionalTypes.types index 651799b01cfb8..91ef74f541808 100644 --- a/tests/baselines/reference/recursiveConditionalTypes.types +++ b/tests/baselines/reference/recursiveConditionalTypes.types @@ -105,7 +105,7 @@ type TT3 = TupleOf; >TT3 : number[] type TT4 = TupleOf; // Depth error ->TT4 : any +>TT4 : [any, ...any[]] function f22(tn: TupleOf, tm: TupleOf) { >f22 : (tn: TupleOf, tm: TupleOf) => void diff --git a/tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts b/tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts new file mode 100644 index 0000000000000..e2630c584b518 --- /dev/null +++ b/tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts @@ -0,0 +1,5 @@ +type Test = [T] extends [any[]] ? { array: Test } : { notArray: T }; +declare const x: Test; +const y: { array: { notArray: number } } = x; // Error +declare const a: Test; +const b: { notArray: number } = a; // Works \ No newline at end of file