From bc15c3827f87db53d0035defaa55b82f84bf4d10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 09:49:08 +0100 Subject: [PATCH 1/3] Defer conditional types with parenthesized multi-element tuple types in `extends` clause --- src/compiler/checker.ts | 9 +- .../deferredConditionalTypes2.symbols | 117 ++++++++++++++++++ .../reference/deferredConditionalTypes2.types | 62 ++++++++++ .../compiler/deferredConditionalTypes2.ts | 38 ++++++ 4 files changed, 223 insertions(+), 3 deletions(-) create mode 100644 tests/baselines/reference/deferredConditionalTypes2.symbols create mode 100644 tests/baselines/reference/deferredConditionalTypes2.types create mode 100644 tests/cases/compiler/deferredConditionalTypes2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3ac1038a4cdbe..f8e9c4f8d1742 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -965,6 +965,7 @@ import { skipParentheses, skipTrivia, skipTypeChecking, + skipTypeParentheses, some, SourceFile, SpreadAssignment, @@ -18312,7 +18313,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; } - function isSimpleTupleType(node: TypeNode) { + function isSimpleTupleType(node: TypeNode): node is TupleTypeNode { return isTupleTypeNode(node) && length(node.elements) > 0 && !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); } @@ -18343,11 +18344,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } + const checkTypeNode = skipTypeParentheses(root.node.checkType); + const extendsTypeNode = skipTypeParentheses(root.node.extendsType); // When the check and extends types are simple tuple types of the same arity, we defer resolution of the // conditional type when any tuple elements are generic. This is such that non-distributable conditional // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. - const checkTuples = isSimpleTupleType(root.node.checkType) && isSimpleTupleType(root.node.extendsType) && - length((root.node.checkType as TupleTypeNode).elements) === length((root.node.extendsType as TupleTypeNode).elements); + const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && + length(checkTypeNode.elements) === length(extendsTypeNode.elements); const checkTypeDeferred = isDeferredType(checkType, checkTuples); let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { diff --git a/tests/baselines/reference/deferredConditionalTypes2.symbols b/tests/baselines/reference/deferredConditionalTypes2.symbols new file mode 100644 index 0000000000000..b3abb1b74ba1a --- /dev/null +++ b/tests/baselines/reference/deferredConditionalTypes2.symbols @@ -0,0 +1,117 @@ +//// [tests/cases/compiler/deferredConditionalTypes2.ts] //// + +=== deferredConditionalTypes2.ts === +// https://github.com/microsoft/TypeScript/issues/56270 + +type PositiveInfinity = 1e999; +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + +type NegativeInfinity = -1e999; +>NegativeInfinity : Symbol(NegativeInfinity, Decl(deferredConditionalTypes2.ts, 2, 30)) + +export type IsEqual = (() => G extends A ? 1 : 2) extends < +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 5, 20)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 5, 22)) +>G : Symbol(G, Decl(deferredConditionalTypes2.ts, 5, 30)) +>G : Symbol(G, Decl(deferredConditionalTypes2.ts, 5, 30)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 5, 20)) + + G, +>G : Symbol(G, Decl(deferredConditionalTypes2.ts, 5, 68)) + +>() => G extends B ? 1 : 2 +>G : Symbol(G, Decl(deferredConditionalTypes2.ts, 5, 68)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 5, 22)) + + ? true + : false; + +export type Add = [ +>Add : Symbol(Add, Decl(deferredConditionalTypes2.ts, 9, 10)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 11, 16)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 11, 33)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 11, 16)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 11, 16)) +>NegativeInfinity : Symbol(NegativeInfinity, Decl(deferredConditionalTypes2.ts, 2, 30)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 11, 33)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 11, 33)) +>NegativeInfinity : Symbol(NegativeInfinity, Decl(deferredConditionalTypes2.ts, 2, 30)) + +] extends infer R extends [boolean, boolean, boolean, boolean] +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 16, 15)) + + ? [true, false] extends ([R[0], R[3]]) +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 16, 15)) +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 16, 15)) + + ? PositiveInfinity +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + : "failed" + : never; + +export type AddWithoutParentheses = [ +>AddWithoutParentheses : Symbol(AddWithoutParentheses, Decl(deferredConditionalTypes2.ts, 20, 10)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 22, 34)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 22, 51)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 22, 34)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>A : Symbol(A, Decl(deferredConditionalTypes2.ts, 22, 34)) +>NegativeInfinity : Symbol(NegativeInfinity, Decl(deferredConditionalTypes2.ts, 2, 30)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 22, 51)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + IsEqual, +>IsEqual : Symbol(IsEqual, Decl(deferredConditionalTypes2.ts, 3, 31)) +>B : Symbol(B, Decl(deferredConditionalTypes2.ts, 22, 51)) +>NegativeInfinity : Symbol(NegativeInfinity, Decl(deferredConditionalTypes2.ts, 2, 30)) + +] extends infer R extends [boolean, boolean, boolean, boolean] +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 27, 15)) + + ? [true, false] extends [R[0], R[3]] +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 27, 15)) +>R : Symbol(R, Decl(deferredConditionalTypes2.ts, 27, 15)) + + ? PositiveInfinity +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + + : "failed" + : never; + +type AddTest0 = Add; +>AddTest0 : Symbol(AddTest0, Decl(deferredConditionalTypes2.ts, 31, 10)) +>Add : Symbol(Add, Decl(deferredConditionalTypes2.ts, 9, 10)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + +type AddTest1 = AddWithoutParentheses; +>AddTest1 : Symbol(AddTest1, Decl(deferredConditionalTypes2.ts, 33, 56)) +>AddWithoutParentheses : Symbol(AddWithoutParentheses, Decl(deferredConditionalTypes2.ts, 20, 10)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) +>PositiveInfinity : Symbol(PositiveInfinity, Decl(deferredConditionalTypes2.ts, 0, 0)) + diff --git a/tests/baselines/reference/deferredConditionalTypes2.types b/tests/baselines/reference/deferredConditionalTypes2.types new file mode 100644 index 0000000000000..e2d2ec6d67f08 --- /dev/null +++ b/tests/baselines/reference/deferredConditionalTypes2.types @@ -0,0 +1,62 @@ +//// [tests/cases/compiler/deferredConditionalTypes2.ts] //// + +=== deferredConditionalTypes2.ts === +// https://github.com/microsoft/TypeScript/issues/56270 + +type PositiveInfinity = 1e999; +>PositiveInfinity : number + +type NegativeInfinity = -1e999; +>NegativeInfinity : -Infinity +>-1e999 : -Infinity +>1e999 : number + +export type IsEqual = (() => G extends A ? 1 : 2) extends < +>IsEqual : IsEqual + + G, +>() => G extends B ? 1 : 2 + ? true +>true : true + + : false; +>false : false + +export type Add = [ +>Add : [true, false] extends [IsEqual, IsEqual] ? number : "failed" + + IsEqual, + IsEqual, + IsEqual, + IsEqual, +] extends infer R extends [boolean, boolean, boolean, boolean] + ? [true, false] extends ([R[0], R[3]]) +>true : true +>false : false + + ? PositiveInfinity + : "failed" + : never; + +export type AddWithoutParentheses = [ +>AddWithoutParentheses : [true, false] extends [IsEqual, IsEqual] ? number : "failed" + + IsEqual, + IsEqual, + IsEqual, + IsEqual, +] extends infer R extends [boolean, boolean, boolean, boolean] + ? [true, false] extends [R[0], R[3]] +>true : true +>false : false + + ? PositiveInfinity + : "failed" + : never; + +type AddTest0 = Add; +>AddTest0 : number + +type AddTest1 = AddWithoutParentheses; +>AddTest1 : number + diff --git a/tests/cases/compiler/deferredConditionalTypes2.ts b/tests/cases/compiler/deferredConditionalTypes2.ts new file mode 100644 index 0000000000000..e1f4f57a2673b --- /dev/null +++ b/tests/cases/compiler/deferredConditionalTypes2.ts @@ -0,0 +1,38 @@ +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/56270 + +type PositiveInfinity = 1e999; +type NegativeInfinity = -1e999; + +export type IsEqual = (() => G extends A ? 1 : 2) extends < + G, +>() => G extends B ? 1 : 2 + ? true + : false; + +export type Add = [ + IsEqual, + IsEqual, + IsEqual, + IsEqual, +] extends infer R extends [boolean, boolean, boolean, boolean] + ? [true, false] extends ([R[0], R[3]]) + ? PositiveInfinity + : "failed" + : never; + +export type AddWithoutParentheses = [ + IsEqual, + IsEqual, + IsEqual, + IsEqual, +] extends infer R extends [boolean, boolean, boolean, boolean] + ? [true, false] extends [R[0], R[3]] + ? PositiveInfinity + : "failed" + : never; + +type AddTest0 = Add; +type AddTest1 = AddWithoutParentheses; From ec113c046f63a77e8b93e41e5f72328177c088d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Tue, 31 Oct 2023 11:00:45 +0100 Subject: [PATCH 2/3] revert predicate change --- src/compiler/checker.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f8e9c4f8d1742..9aafbcf640623 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18313,7 +18313,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; } - function isSimpleTupleType(node: TypeNode): node is TupleTypeNode { + function isSimpleTupleType(node: TypeNode): boolean { return isTupleTypeNode(node) && length(node.elements) > 0 && !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); } @@ -18350,7 +18350,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // conditional type when any tuple elements are generic. This is such that non-distributable conditional // types can be written `[X] extends [Y] ? ...` and be deferred similarly to `X extends Y ? ...`. const checkTuples = isSimpleTupleType(checkTypeNode) && isSimpleTupleType(extendsTypeNode) && - length(checkTypeNode.elements) === length(extendsTypeNode.elements); + length((checkTypeNode as TupleTypeNode).elements) === length((extendsTypeNode as TupleTypeNode).elements); const checkTypeDeferred = isDeferredType(checkType, checkTuples); let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { From 55e93bf3f9fc8d28b3274865922aaa1679d8edb7 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Fri, 8 Dec 2023 11:37:46 -0800 Subject: [PATCH 3/3] Update baseline --- .../reference/deferredConditionalTypes2.types | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/baselines/reference/deferredConditionalTypes2.types b/tests/baselines/reference/deferredConditionalTypes2.types index e2d2ec6d67f08..a14eb9574aa16 100644 --- a/tests/baselines/reference/deferredConditionalTypes2.types +++ b/tests/baselines/reference/deferredConditionalTypes2.types @@ -4,12 +4,12 @@ // https://github.com/microsoft/TypeScript/issues/56270 type PositiveInfinity = 1e999; ->PositiveInfinity : number +>PositiveInfinity : Infinity type NegativeInfinity = -1e999; >NegativeInfinity : -Infinity >-1e999 : -Infinity ->1e999 : number +>1e999 : Infinity export type IsEqual = (() => G extends A ? 1 : 2) extends < >IsEqual : IsEqual @@ -23,7 +23,7 @@ export type IsEqual = (() => G extends A ? 1 : 2) extends < >false : false export type Add = [ ->Add : [true, false] extends [IsEqual, IsEqual] ? number : "failed" +>Add : [true, false] extends [IsEqual, IsEqual] ? Infinity : "failed" IsEqual, IsEqual, @@ -39,7 +39,7 @@ export type Add = [ : never; export type AddWithoutParentheses = [ ->AddWithoutParentheses : [true, false] extends [IsEqual, IsEqual] ? number : "failed" +>AddWithoutParentheses : [true, false] extends [IsEqual, IsEqual] ? Infinity : "failed" IsEqual, IsEqual, @@ -55,8 +55,8 @@ export type AddWithoutParentheses = [ : never; type AddTest0 = Add; ->AddTest0 : number +>AddTest0 : Infinity type AddTest1 = AddWithoutParentheses; ->AddTest1 : number +>AddTest1 : Infinity