diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4c1ad31d43ff6..b9207bc648c2e 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -17302,25 +17302,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; } - function isTypicalNondistributiveConditional(root: ConditionalRoot) { - return !root.isDistributive && isSingletonTupleType(root.node.checkType) && isSingletonTupleType(root.node.extendsType); + function isSimpleTupleType(node: TypeNode) { + return isTupleTypeNode(node) && length(node.elements) > 0 && + !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); } - function isSingletonTupleType(node: TypeNode) { - return isTupleTypeNode(node) && - length(node.elements) === 1 && - !isOptionalTypeNode(node.elements[0]) && - !isRestTypeNode(node.elements[0]) && - !(isNamedTupleMember(node.elements[0]) && (node.elements[0].questionToken || node.elements[0].dotDotDotToken)); - } - - /** - * 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 isDeferredType(type: Type, checkTuples: boolean) { + return isGenericType(type) || checkTuples && isTupleType(type) && some(getTypeArguments(type), isGenericType); } function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type { @@ -17338,10 +17326,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { result = errorType; break; } - const isUnwrapped = isTypicalNondistributiveConditional(root); - const checkType = instantiateType(unwrapNondistributiveConditionalTuple(root, getActualTypeVariable(root.checkType)), mapper); - const checkTypeInstantiable = isGenericType(checkType); - const extendsType = instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), mapper); + // 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 checkType = instantiateType(getActualTypeVariable(root.checkType), mapper); + const checkTypeDeferred = isDeferredType(checkType, checkTuples); + const extendsType = instantiateType(root.extendsType, mapper); if (checkType === wildcardType || extendsType === wildcardType) { return wildcardType; } @@ -17375,7 +17367,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } } - if (!checkTypeInstantiable) { + if (!checkTypeDeferred) { // We don't want inferences from constraints as they may cause us to eagerly resolve the // conditional type instead of deferring resolution. Also, we always want strict function // types rules (i.e. proper contravariance) for inferences. @@ -17388,16 +17380,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; } // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(unwrapNondistributiveConditionalTuple(root, root.extendsType), combinedMapper) : extendsType; + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; // We attempt to resolve the conditional type only when the check and extends types are non-generic - if (!checkTypeInstantiable && !isGenericType(inferredExtendsType)) { + if (!checkTypeDeferred && !isDeferredType(inferredExtendsType, checkTuples)) { // Return falseType for a definitely false extends check. We check an instantiations of the two // 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 && !isUnwrapped) || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { + if (!(inferredExtendsType.flags & TypeFlags.AnyOrUnknown) && (checkType.flags & TypeFlags.Any || !isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType)))) { // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any && !isUnwrapped) { + if (checkType.flags & TypeFlags.Any) { (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 diff --git a/tests/baselines/reference/deferredConditionalTypes.js b/tests/baselines/reference/deferredConditionalTypes.js new file mode 100644 index 0000000000000..24ec4d3525217 --- /dev/null +++ b/tests/baselines/reference/deferredConditionalTypes.js @@ -0,0 +1,45 @@ +//// [deferredConditionalTypes.ts] +type A = { x: T } extends { x: 0 } ? 1 : 0; + +type T0 = A extends 0 ? 1 : 0; // Deferred +type T1 = [A] extends [0] ? 1 : 0; // Deferred +type T2 = [A, A] extends [0, 0] ? 1 : 0; // Deferred +type T3 = [A, A, A] extends [0, 0, 0] ? 1 : 0; // Deferred + +type T4 = [A] extends [0, 0] ? 1 : 0; // 0 +type T5 = [A, A] extends [0] ? 1 : 0; // 0 + +type T6 = { y: A } extends { y: 0 } ? 1 : 0; // 0, but should be deferred + +// Repro from #52068 + +type Or = [A, B] extends [false, false] ? false : true; +type And = [A, B] extends [true, true] ? true : false; +type Not = T extends true ? false : true; +type Extends = A extends B ? true : false; + +type IsNumberLiteral = And, Not>>; + +type IsLiteral = Or>; + +// Repro from #51145#issuecomment-1276804047 + +type Values = + O extends any[] + ? O[number] + : O[keyof O] + +type Equals = [A, B] extends [B, A] ? true : false; + +type FilterByStringValue = { + [K in keyof O as Equals extends true ? K : never]: any +} + +type FilteredValuesMatchNever + = Equals>, never> + +type FilteredRes1 = FilteredValuesMatchNever<[]> + + +//// [deferredConditionalTypes.js] +"use strict"; diff --git a/tests/baselines/reference/deferredConditionalTypes.symbols b/tests/baselines/reference/deferredConditionalTypes.symbols new file mode 100644 index 0000000000000..ab703e014997d --- /dev/null +++ b/tests/baselines/reference/deferredConditionalTypes.symbols @@ -0,0 +1,157 @@ +=== tests/cases/compiler/deferredConditionalTypes.ts === +type A = { x: T } extends { x: 0 } ? 1 : 0; +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 0, 7)) +>x : Symbol(x, Decl(deferredConditionalTypes.ts, 0, 13)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 0, 7)) +>x : Symbol(x, Decl(deferredConditionalTypes.ts, 0, 30)) + +type T0 = A extends 0 ? 1 : 0; // Deferred +>T0 : Symbol(T0, Decl(deferredConditionalTypes.ts, 0, 46)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 2, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 2, 8)) + +type T1 = [A] extends [0] ? 1 : 0; // Deferred +>T1 : Symbol(T1, Decl(deferredConditionalTypes.ts, 2, 36)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 3, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 3, 8)) + +type T2 = [A, A] extends [0, 0] ? 1 : 0; // Deferred +>T2 : Symbol(T2, Decl(deferredConditionalTypes.ts, 3, 40)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 4, 8)) + +type T3 = [A, A, A] extends [0, 0, 0] ? 1 : 0; // Deferred +>T3 : Symbol(T3, Decl(deferredConditionalTypes.ts, 4, 49)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 5, 8)) + +type T4 = [A] extends [0, 0] ? 1 : 0; // 0 +>T4 : Symbol(T4, Decl(deferredConditionalTypes.ts, 5, 58)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 7, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 7, 8)) + +type T5 = [A, A] extends [0] ? 1 : 0; // 0 +>T5 : Symbol(T5, Decl(deferredConditionalTypes.ts, 7, 43)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 8, 8)) + +type T6 = { y: A } extends { y: 0 } ? 1 : 0; // 0, but should be deferred +>T6 : Symbol(T6, Decl(deferredConditionalTypes.ts, 8, 46)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 10, 8)) +>y : Symbol(y, Decl(deferredConditionalTypes.ts, 10, 14)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 0, 0)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 10, 8)) +>y : Symbol(y, Decl(deferredConditionalTypes.ts, 10, 34)) + +// Repro from #52068 + +type Or = [A, B] extends [false, false] ? false : true; +>Or : Symbol(Or, Decl(deferredConditionalTypes.ts, 10, 50)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 14, 8)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 14, 26)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 14, 8)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 14, 26)) + +type And = [A, B] extends [true, true] ? true : false; +>And : Symbol(And, Decl(deferredConditionalTypes.ts, 14, 93)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 15, 9)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 15, 27)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 15, 9)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 15, 27)) + +type Not = T extends true ? false : true; +>Not : Symbol(Not, Decl(deferredConditionalTypes.ts, 15, 92)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 16, 9)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 16, 9)) + +type Extends = A extends B ? true : false; +>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 17, 13)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 17, 15)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 17, 13)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 17, 15)) + +type IsNumberLiteral = And, Not>>; +>IsNumberLiteral : Symbol(IsNumberLiteral, Decl(deferredConditionalTypes.ts, 17, 48)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21)) +>And : Symbol(And, Decl(deferredConditionalTypes.ts, 14, 93)) +>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21)) +>Not : Symbol(Not, Decl(deferredConditionalTypes.ts, 15, 92)) +>Extends : Symbol(Extends, Decl(deferredConditionalTypes.ts, 16, 60)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 19, 21)) + +type IsLiteral = Or>; +>IsLiteral : Symbol(IsLiteral, Decl(deferredConditionalTypes.ts, 19, 75)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 21, 15)) +>Or : Symbol(Or, Decl(deferredConditionalTypes.ts, 10, 50)) +>IsNumberLiteral : Symbol(IsNumberLiteral, Decl(deferredConditionalTypes.ts, 17, 48)) +>T : Symbol(T, Decl(deferredConditionalTypes.ts, 21, 15)) + +// Repro from #51145#issuecomment-1276804047 + +type Values = +>Values : Symbol(Values, Decl(deferredConditionalTypes.ts, 21, 50)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12)) + + O extends any[] +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12)) + + ? O[number] +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12)) + + : O[keyof O] +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 25, 12)) + +type Equals = [A, B] extends [B, A] ? true : false; +>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14)) +>B : Symbol(B, Decl(deferredConditionalTypes.ts, 30, 14)) +>A : Symbol(A, Decl(deferredConditionalTypes.ts, 30, 12)) + +type FilterByStringValue = { +>FilterByStringValue : Symbol(FilterByStringValue, Decl(deferredConditionalTypes.ts, 30, 57)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25)) + + [K in keyof O as Equals extends true ? K : never]: any +>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25)) +>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 32, 25)) +>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3)) +>K : Symbol(K, Decl(deferredConditionalTypes.ts, 33, 3)) +} + +type FilteredValuesMatchNever +>FilteredValuesMatchNever : Symbol(FilteredValuesMatchNever, Decl(deferredConditionalTypes.ts, 34, 1)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 36, 30)) + + = Equals>, never> +>Equals : Symbol(Equals, Decl(deferredConditionalTypes.ts, 28, 16)) +>Values : Symbol(Values, Decl(deferredConditionalTypes.ts, 21, 50)) +>FilterByStringValue : Symbol(FilterByStringValue, Decl(deferredConditionalTypes.ts, 30, 57)) +>O : Symbol(O, Decl(deferredConditionalTypes.ts, 36, 30)) + +type FilteredRes1 = FilteredValuesMatchNever<[]> +>FilteredRes1 : Symbol(FilteredRes1, Decl(deferredConditionalTypes.ts, 37, 51)) +>FilteredValuesMatchNever : Symbol(FilteredValuesMatchNever, Decl(deferredConditionalTypes.ts, 34, 1)) + diff --git a/tests/baselines/reference/deferredConditionalTypes.types b/tests/baselines/reference/deferredConditionalTypes.types new file mode 100644 index 0000000000000..1535ed406d16c --- /dev/null +++ b/tests/baselines/reference/deferredConditionalTypes.types @@ -0,0 +1,92 @@ +=== tests/cases/compiler/deferredConditionalTypes.ts === +type A = { x: T } extends { x: 0 } ? 1 : 0; +>A : A +>x : T +>x : 0 + +type T0 = A extends 0 ? 1 : 0; // Deferred +>T0 : T0 + +type T1 = [A] extends [0] ? 1 : 0; // Deferred +>T1 : T1 + +type T2 = [A, A] extends [0, 0] ? 1 : 0; // Deferred +>T2 : T2 + +type T3 = [A, A, A] extends [0, 0, 0] ? 1 : 0; // Deferred +>T3 : T3 + +type T4 = [A] extends [0, 0] ? 1 : 0; // 0 +>T4 : 0 + +type T5 = [A, A] extends [0] ? 1 : 0; // 0 +>T5 : 0 + +type T6 = { y: A } extends { y: 0 } ? 1 : 0; // 0, but should be deferred +>T6 : 0 +>y : A +>y : 0 + +// Repro from #52068 + +type Or = [A, B] extends [false, false] ? false : true; +>Or : Or +>false : false +>false : false +>false : false +>true : true + +type And = [A, B] extends [true, true] ? true : false; +>And : And +>true : true +>true : true +>true : true +>false : false + +type Not = T extends true ? false : true; +>Not : Not +>true : true +>false : false +>true : true + +type Extends = A extends B ? true : false; +>Extends : Extends +>true : true +>false : false + +type IsNumberLiteral = And, Not>>; +>IsNumberLiteral : IsNumberLiteral + +type IsLiteral = Or>; +>IsLiteral : IsLiteral +>false : false + +// Repro from #51145#issuecomment-1276804047 + +type Values = +>Values : Values + + O extends any[] + ? O[number] + : O[keyof O] + +type Equals = [A, B] extends [B, A] ? true : false; +>Equals : Equals +>true : true +>false : false + +type FilterByStringValue = { +>FilterByStringValue : FilterByStringValue + + [K in keyof O as Equals extends true ? K : never]: any +>true : true +} + +type FilteredValuesMatchNever +>FilteredValuesMatchNever : FilteredValuesMatchNever + + = Equals>, never> + +type FilteredRes1 = FilteredValuesMatchNever<[]> +>FilteredRes1 : true + diff --git a/tests/cases/compiler/deferredConditionalTypes.ts b/tests/cases/compiler/deferredConditionalTypes.ts new file mode 100644 index 0000000000000..e7512f4b5d594 --- /dev/null +++ b/tests/cases/compiler/deferredConditionalTypes.ts @@ -0,0 +1,42 @@ +// @strict: true + +type A = { x: T } extends { x: 0 } ? 1 : 0; + +type T0 = A extends 0 ? 1 : 0; // Deferred +type T1 = [A] extends [0] ? 1 : 0; // Deferred +type T2 = [A, A] extends [0, 0] ? 1 : 0; // Deferred +type T3 = [A, A, A] extends [0, 0, 0] ? 1 : 0; // Deferred + +type T4 = [A] extends [0, 0] ? 1 : 0; // 0 +type T5 = [A, A] extends [0] ? 1 : 0; // 0 + +type T6 = { y: A } extends { y: 0 } ? 1 : 0; // 0, but should be deferred + +// Repro from #52068 + +type Or = [A, B] extends [false, false] ? false : true; +type And = [A, B] extends [true, true] ? true : false; +type Not = T extends true ? false : true; +type Extends = A extends B ? true : false; + +type IsNumberLiteral = And, Not>>; + +type IsLiteral = Or>; + +// Repro from #51145#issuecomment-1276804047 + +type Values = + O extends any[] + ? O[number] + : O[keyof O] + +type Equals = [A, B] extends [B, A] ? true : false; + +type FilterByStringValue = { + [K in keyof O as Equals extends true ? K : never]: any +} + +type FilteredValuesMatchNever + = Equals>, never> + +type FilteredRes1 = FilteredValuesMatchNever<[]>