diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5e1933e185f9d..259714fe6f705 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7899,6 +7899,7 @@ namespace ts { } else if (flags & TypeFlags.Any) { includes |= TypeIncludes.Any; + if (type === wildcardType) includes |= TypeIncludes.Wildcard; } else if (flags & TypeFlags.Never) { includes |= TypeIncludes.Never; @@ -7950,7 +7951,7 @@ namespace ts { return neverType; } if (includes & TypeIncludes.Any) { - return anyType; + return includes & TypeIncludes.Wildcard ? wildcardType : anyType; } if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) { typeSet.push(emptyObjectType); @@ -8188,6 +8189,9 @@ namespace ts { } function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode): Type { + if (objectType === wildcardType || indexType === wildcardType) { + return wildcardType; + } // If the index type is generic, or if the object type is generic and doesn't originate in an expression, // we are performing a higher-order index access where we cannot meaningfully access the properties of the // object type. Note that for a generic T and a non-generic K, we eagerly resolve T[K] if it originates in @@ -8253,37 +8257,45 @@ namespace ts { function getConditionalType(root: ConditionalRoot, mapper: TypeMapper): Type { const checkType = instantiateType(root.checkType, mapper); const extendsType = instantiateType(root.extendsType, mapper); - // Return falseType for a definitely false extends check. We check an instantations 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 instatiations will be and we can just return the false branch type. - if (!typeMaybeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) { - return instantiateType(root.falseType, mapper); - } - // The check could be true for some instantiation - let combinedMapper: TypeMapper; - if (root.inferTypeParameters) { - const inferences = map(root.inferTypeParameters, createInferenceInfo); - // 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. - inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); - // We infer {} when there are no candidates for a type parameter - const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType); - combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes)); - } - // Return union of trueType and falseType for 'any' since it matches anything - if (checkType.flags & TypeFlags.Any) { - return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]); - } - // Instantiate the extends type including inferences for 'infer T' type parameters - const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; - // Return trueType for a definitely true extends check. The definitely assignable relation excludes - // type variable constraints from consideration. Without the definitely assignable relation, the type - // type Foo = T extends { x: string } ? string : number - // would immediately resolve to 'string' instead of being deferred. - if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) { - return instantiateType(root.trueType, combinedMapper || mapper); + if (checkType === wildcardType || extendsType === wildcardType) { + return wildcardType; + } + // If this is a distributive conditional type and the check type is generic, we need to defer + // resolution of the conditional type such that a later instantiation will properly distribute + // over union types. + if (!root.isDistributive || !maybeTypeOfKind(checkType, TypeFlags.Instantiable)) { + // Return falseType for a definitely false extends check. We check an instantations 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 instatiations will be and we can just return the false branch type. + if (!isTypeAssignableTo(getWildcardInstantiation(checkType), getWildcardInstantiation(extendsType))) { + return instantiateType(root.falseType, mapper); + } + // The check could be true for some instantiation + let combinedMapper: TypeMapper; + if (root.inferTypeParameters) { + const inferences = map(root.inferTypeParameters, createInferenceInfo); + // 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. + inferTypes(inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + // We infer {} when there are no candidates for a type parameter + const inferredTypes = map(inferences, inference => getTypeFromInference(inference) || emptyObjectType); + combinedMapper = combineTypeMappers(mapper, createTypeMapper(root.inferTypeParameters, inferredTypes)); + } + // Return union of trueType and falseType for 'any' since it matches anything + if (checkType.flags & TypeFlags.Any) { + return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]); + } + // Instantiate the extends type including inferences for 'infer T' type parameters + const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; + // Return trueType for a definitely true extends check. The definitely assignable relation excludes + // type variable constraints from consideration. Without the definitely assignable relation, the type + // type Foo = T extends { x: string } ? string : number + // would immediately resolve to 'string' instead of being deferred. + if (checkTypeRelatedTo(checkType, inferredExtendsType, definitelyAssignableRelation, /*errorNode*/ undefined)) { + return instantiateType(root.trueType, combinedMapper || mapper); + } } // Return a deferred type for a check that is neither definitely true nor definitely false const erasedCheckType = getActualTypeParameter(checkType); diff --git a/tests/baselines/reference/inferTypes1.errors.txt b/tests/baselines/reference/inferTypes1.errors.txt index b36528f721d9e..5531ec08c7f39 100644 --- a/tests/baselines/reference/inferTypes1.errors.txt +++ b/tests/baselines/reference/inferTypes1.errors.txt @@ -191,4 +191,19 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(134,40): error TS2322: type B = string extends T ? { [P in T]: void; } : T; // Error ~ !!! error TS2322: Type 'T' is not assignable to type 'string'. + + // Repro from #22302 + + type MatchingKeys = + K extends keyof T ? T[K] extends U ? K : never : never; + + type VoidKeys = MatchingKeys; + + interface test { + a: 1, + b: void + } + + type T80 = MatchingKeys; + type T81 = VoidKeys; \ No newline at end of file diff --git a/tests/baselines/reference/inferTypes1.js b/tests/baselines/reference/inferTypes1.js index bc53f23899701..6e41cfe199844 100644 --- a/tests/baselines/reference/inferTypes1.js +++ b/tests/baselines/reference/inferTypes1.js @@ -133,6 +133,21 @@ type C2 = S extends A2 ? [T, U] : never; type A = T extends string ? { [P in T]: void; } : T; type B = string extends T ? { [P in T]: void; } : T; // Error + +// Repro from #22302 + +type MatchingKeys = + K extends keyof T ? T[K] extends U ? K : never : never; + +type VoidKeys = MatchingKeys; + +interface test { + a: 1, + b: void +} + +type T80 = MatchingKeys; +type T81 = VoidKeys; //// [inferTypes1.js] diff --git a/tests/baselines/reference/inferTypes1.symbols b/tests/baselines/reference/inferTypes1.symbols index 784482e72c9e7..9e09eaea21d51 100644 --- a/tests/baselines/reference/inferTypes1.symbols +++ b/tests/baselines/reference/inferTypes1.symbols @@ -575,3 +575,47 @@ type B = string extends T ? { [P in T]: void; } : T; // Error >T : Symbol(T, Decl(inferTypes1.ts, 133, 7)) >T : Symbol(T, Decl(inferTypes1.ts, 133, 7)) +// Repro from #22302 + +type MatchingKeys = +>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55)) +>T : Symbol(T, Decl(inferTypes1.ts, 137, 18)) +>U : Symbol(U, Decl(inferTypes1.ts, 137, 20)) +>K : Symbol(K, Decl(inferTypes1.ts, 137, 23)) +>T : Symbol(T, Decl(inferTypes1.ts, 137, 18)) +>T : Symbol(T, Decl(inferTypes1.ts, 137, 18)) + + K extends keyof T ? T[K] extends U ? K : never : never; +>K : Symbol(K, Decl(inferTypes1.ts, 137, 23)) +>T : Symbol(T, Decl(inferTypes1.ts, 137, 18)) +>T : Symbol(T, Decl(inferTypes1.ts, 137, 18)) +>K : Symbol(K, Decl(inferTypes1.ts, 137, 23)) +>U : Symbol(U, Decl(inferTypes1.ts, 137, 20)) +>K : Symbol(K, Decl(inferTypes1.ts, 137, 23)) + +type VoidKeys = MatchingKeys; +>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59)) +>T : Symbol(T, Decl(inferTypes1.ts, 140, 14)) +>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55)) +>T : Symbol(T, Decl(inferTypes1.ts, 140, 14)) + +interface test { +>test : Symbol(test, Decl(inferTypes1.ts, 140, 41)) + + a: 1, +>a : Symbol(test.a, Decl(inferTypes1.ts, 142, 16)) + + b: void +>b : Symbol(test.b, Decl(inferTypes1.ts, 143, 9)) +} + +type T80 = MatchingKeys; +>T80 : Symbol(T80, Decl(inferTypes1.ts, 145, 1)) +>MatchingKeys : Symbol(MatchingKeys, Decl(inferTypes1.ts, 133, 55)) +>test : Symbol(test, Decl(inferTypes1.ts, 140, 41)) + +type T81 = VoidKeys; +>T81 : Symbol(T81, Decl(inferTypes1.ts, 147, 36)) +>VoidKeys : Symbol(VoidKeys, Decl(inferTypes1.ts, 138, 59)) +>test : Symbol(test, Decl(inferTypes1.ts, 140, 41)) + diff --git a/tests/baselines/reference/inferTypes1.types b/tests/baselines/reference/inferTypes1.types index 4ca78f650815e..523dd669e4cc1 100644 --- a/tests/baselines/reference/inferTypes1.types +++ b/tests/baselines/reference/inferTypes1.types @@ -312,7 +312,7 @@ type T60 = infer U; // Error >U : U type T61 = infer A extends infer B ? infer C : infer D; // Error ->T61 : {} +>T61 : T61 >T : T >A : A >B : B @@ -582,3 +582,47 @@ type B = string extends T ? { [P in T]: void; } : T; // Error >T : T >T : T +// Repro from #22302 + +type MatchingKeys = +>MatchingKeys : MatchingKeys +>T : T +>U : U +>K : K +>T : T +>T : T + + K extends keyof T ? T[K] extends U ? K : never : never; +>K : K +>T : T +>T : T +>K : K +>U : U +>K : K + +type VoidKeys = MatchingKeys; +>VoidKeys : MatchingKeys +>T : T +>MatchingKeys : MatchingKeys +>T : T + +interface test { +>test : test + + a: 1, +>a : 1 + + b: void +>b : void +} + +type T80 = MatchingKeys; +>T80 : "b" +>MatchingKeys : MatchingKeys +>test : test + +type T81 = VoidKeys; +>T81 : "b" +>VoidKeys : MatchingKeys +>test : test + diff --git a/tests/cases/conformance/types/conditional/inferTypes1.ts b/tests/cases/conformance/types/conditional/inferTypes1.ts index 671d68c324745..1defacc08cc5b 100644 --- a/tests/cases/conformance/types/conditional/inferTypes1.ts +++ b/tests/cases/conformance/types/conditional/inferTypes1.ts @@ -135,3 +135,18 @@ type C2 = S extends A2 ? [T, U] : never; type A = T extends string ? { [P in T]: void; } : T; type B = string extends T ? { [P in T]: void; } : T; // Error + +// Repro from #22302 + +type MatchingKeys = + K extends keyof T ? T[K] extends U ? K : never : never; + +type VoidKeys = MatchingKeys; + +interface test { + a: 1, + b: void +} + +type T80 = MatchingKeys; +type T81 = VoidKeys;