Better detect when typical nondistributive conditionals need to be defered by unwrapping their check and extends types (#42248)

This commit is contained in:
Wesley Wigham
2021-01-12 12:59:52 -08:00
committed by GitHub
parent a276a6dce7
commit c3dd845923
7 changed files with 93 additions and 8 deletions

View File

@@ -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;

View File

@@ -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>

View File

@@ -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

View File

@@ -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))

View File

@@ -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; }

View File

@@ -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

View File

@@ -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