mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-10 18:04:18 -05:00
Better detect when typical nondistributive conditionals need to be defered by unwrapping their check and extends types (#42248)
This commit is contained in:
@@ -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 = <ConditionalType>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;
|
||||
|
||||
@@ -257,7 +257,7 @@ type T61<T> = infer A extends infer B ? infer C : infer D; // Error
|
||||
>T61 : T61<T>
|
||||
|
||||
type T62<T> = U extends (infer U)[] ? U : U; // Error
|
||||
>T62 : any
|
||||
>T62 : unknown
|
||||
|
||||
type T63<T> = T extends (infer A extends infer B ? infer C : infer D) ? string : number;
|
||||
>T63 : T63<T>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
//// [recursiveConditionalEvaluationNonInfinite.ts]
|
||||
type Test<T> = [T] extends [any[]] ? { array: Test<T[0]> } : { notArray: T };
|
||||
declare const x: Test<number[]>;
|
||||
const y: { array: { notArray: number } } = x; // Error
|
||||
declare const a: Test<number>;
|
||||
const b: { notArray: number } = a; // Works
|
||||
|
||||
//// [recursiveConditionalEvaluationNonInfinite.js]
|
||||
var y = x; // Error
|
||||
var b = a; // Works
|
||||
@@ -0,0 +1,30 @@
|
||||
=== tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts ===
|
||||
type Test<T> = [T] extends [any[]] ? { array: Test<T[0]> } : { 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<number[]>;
|
||||
>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<number>;
|
||||
>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))
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
=== tests/cases/compiler/recursiveConditionalEvaluationNonInfinite.ts ===
|
||||
type Test<T> = [T] extends [any[]] ? { array: Test<T[0]> } : { notArray: T };
|
||||
>Test : Test<T>
|
||||
>array : Test<T[0]>
|
||||
>notArray : T
|
||||
|
||||
declare const x: Test<number[]>;
|
||||
>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<number>;
|
||||
>a : { notArray: number; }
|
||||
|
||||
const b: { notArray: number } = a; // Works
|
||||
>b : { notArray: number; }
|
||||
>notArray : number
|
||||
>a : { notArray: number; }
|
||||
|
||||
@@ -105,7 +105,7 @@ type TT3 = TupleOf<number, any>;
|
||||
>TT3 : number[]
|
||||
|
||||
type TT4 = TupleOf<number, 100>; // Depth error
|
||||
>TT4 : any
|
||||
>TT4 : [any, ...any[]]
|
||||
|
||||
function f22<N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>) {
|
||||
>f22 : <N extends number, M extends N>(tn: TupleOf<number, N>, tm: TupleOf<number, M>) => void
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
type Test<T> = [T] extends [any[]] ? { array: Test<T[0]> } : { notArray: T };
|
||||
declare const x: Test<number[]>;
|
||||
const y: { array: { notArray: number } } = x; // Error
|
||||
declare const a: Test<number>;
|
||||
const b: { notArray: number } = a; // Works
|
||||
Reference in New Issue
Block a user