From 9ea685b942e3a5d4e8285e7e3f5d76ab3a2c6659 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Oct 2021 09:21:21 -0700 Subject: [PATCH 1/5] Simplify relationship check for conditional type on target side --- src/compiler/checker.ts | 41 +++++++---------------------------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index bcf3a78f052c5..1a2d609356e98 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15315,13 +15315,6 @@ namespace ts { return type[cache] = type; } - function isConditionalTypeAlwaysTrueDisregardingInferTypes(type: ConditionalType) { - const extendsInferParamMapper = type.root.inferTypeParameters && createTypeMapper(type.root.inferTypeParameters, map(type.root.inferTypeParameters, () => wildcardType)); - const checkType = type.checkType; - const extendsType = type.extendsType; - return isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(instantiateType(extendsType, extendsInferParamMapper))); - } - function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) { const checkType = type.checkType; const extendsType = type.extendsType; @@ -19030,35 +19023,15 @@ namespace ts { resetErrorInfo(saveErrorInfo); return Ternary.Maybe; } - const c = target as ConditionalType; - // Check if the conditional is always true or always false but still deferred for distribution purposes - const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); - const skipFalse = !skipTrue && isConditionalTypeAlwaysTrueDisregardingInferTypes(c); - - // Instantiate with a replacement mapper if the conditional is distributive, replacing the check type with a clone of itself, - // this way {x: string | number, y: string | number} -> (T extends T ? { x: T, y: T } : never) appropriately _fails_ when - // T = string | number (since that will end up distributing and producing `{x: string, y: string} | {x: number, y: number}`, - // to which `{x: string | number, y: string | number}` isn't assignable) - let distributionMapper: TypeMapper | undefined; - const checkVar = getActualTypeVariable(c.root.checkType); - if (c.root.isDistributive && checkVar.flags & TypeFlags.TypeParameter) { - const newParam = cloneTypeParameter(checkVar); - distributionMapper = prependTypeMapping(checkVar, newParam, c.mapper); - newParam.mapper = distributionMapper; - } - - // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) - const expanding = isDeeplyNestedType(target, targetStack, targetDepth); - let localResult: Ternary | undefined = expanding ? Ternary.Maybe : undefined; - if (skipTrue || expanding || (localResult = isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.trueType), distributionMapper) : getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false))) { - if (!skipFalse && !expanding) { - localResult = (localResult || Ternary.Maybe) & isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.falseType), distributionMapper) : getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false); + if (!(target as ConditionalType).root.inferTypeParameters) { + if (result = isRelatedTo(source, getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Target, /*reportErrors*/ false)) { + result &= isRelatedTo(source, getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Target, /*reportErrors*/ false); + if (result) { + resetErrorInfo(saveErrorInfo); + return result; + } } } - if (localResult) { - resetErrorInfo(saveErrorInfo); - return localResult; - } } else if (target.flags & TypeFlags.TemplateLiteral) { if (source.flags & TypeFlags.TemplateLiteral) { From 75ab347bfb17aed15102412fae0b06bc4da138df Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 19 Oct 2021 09:21:42 -0700 Subject: [PATCH 2/5] Accept new baselines --- ...tionalTypeAssignabilityWhenDeferred.errors.txt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt index acb5354e1f076..156624abf785c 100644 --- a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt +++ b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt @@ -1,5 +1,10 @@ +tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(12,20): error TS2345: Argument of type '"value"' is not assignable to parameter of type 'XX extends XX ? "value" : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(28,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(29,21): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. +tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(33,22): error TS2345: Argument of type 'T | null' is not assignable to parameter of type 'null extends T | null ? any : never'. + Type 'null' is not assignable to type 'null extends T | null ? any : never'. +tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(34,23): error TS2345: Argument of type 'T | null' is not assignable to parameter of type '[null] extends [T | null] ? any : never'. + Type 'null' is not assignable to type '[null] extends [T | null] ? any : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(39,3): error TS2322: Type '{ x: T; y: T; }' is not assignable to type 'T extends T ? { x: T; y: T; } : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(46,3): error TS2322: Type 'string' is not assignable to type 'Foo'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(63,9): error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'. @@ -10,7 +15,7 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive'. -==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (8 errors) ==== +==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (11 errors) ==== export type FilterPropsByType = { [K in keyof T]: T[K] extends TT ? K : never }[keyof T]; @@ -23,6 +28,8 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T export function func(x: XX, tipos: { value: XX }[]) { select(x, tipos, "value"); + ~~~~~~~ +!!! error TS2345: Argument of type '"value"' is not assignable to parameter of type 'XX extends XX ? "value" : never'. } declare function onlyNullablePlease( @@ -48,7 +55,13 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T function f(t: T) { var x: T | null = Math.random() > 0.5 ? null : t; onlyNullablePlease(x); // should work + ~ +!!! error TS2345: Argument of type 'T | null' is not assignable to parameter of type 'null extends T | null ? any : never'. +!!! error TS2345: Type 'null' is not assignable to type 'null extends T | null ? any : never'. onlyNullablePlease2(x); // should work + ~ +!!! error TS2345: Argument of type 'T | null' is not assignable to parameter of type '[null] extends [T | null] ? any : never'. +!!! error TS2345: Type 'null' is not assignable to type '[null] extends [T | null] ? any : never'. } function f2(t1: { x: T; y: T }, t2: T extends T ? { x: T; y: T } : never) { From 3012d3563ad0a8c1e37b9ee8cc624584f5fc6248 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Oct 2021 11:05:32 -0700 Subject: [PATCH 3/5] Better support for non-distribution-dependent types --- src/compiler/checker.ts | 21 +++++++++++++++++---- src/compiler/types.ts | 1 + 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 1a2d609356e98..10755cecfe111 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -15642,6 +15642,10 @@ namespace ts { const links = getNodeLinks(node); if (!links.resolvedType) { const checkType = getTypeFromTypeNode(node.checkType); + const isDistributive = !!(checkType.flags & TypeFlags.TypeParameter); + const isDistributionDependent = isDistributive && ( + isTypeParameterPossiblyReferenced(checkType as TypeParameter, node.trueType) || + isTypeParameterPossiblyReferenced(checkType as TypeParameter, node.falseType)); const aliasSymbol = getAliasSymbolForTypeNode(node); const aliasTypeArguments = getTypeArgumentsForAliasSymbol(aliasSymbol); const allOuterTypeParameters = getOuterTypeParameters(node, /*includeThisTypes*/ true); @@ -15650,7 +15654,8 @@ namespace ts { node, checkType, extendsType: getTypeFromTypeNode(node.extendsType), - isDistributive: !!(checkType.flags & TypeFlags.TypeParameter), + isDistributive, + isDistributionDependent, inferTypeParameters: getInferTypeParameters(node), outerTypeParameters, instantiations: undefined, @@ -19023,9 +19028,17 @@ namespace ts { resetErrorInfo(saveErrorInfo); return Ternary.Maybe; } - if (!(target as ConditionalType).root.inferTypeParameters) { - if (result = isRelatedTo(source, getTrueTypeFromConditionalType(target as ConditionalType), RecursionFlags.Target, /*reportErrors*/ false)) { - result &= isRelatedTo(source, getFalseTypeFromConditionalType(target as ConditionalType), RecursionFlags.Target, /*reportErrors*/ false); + const c = target as ConditionalType; + // We check for a relationship to a conditional type target only when the conditional type has no + // 'infer' positions and is not distributive or is distributive but doesn't reference the check type + // parameter in either of the result types. + if (!c.root.inferTypeParameters && !c.root.isDistributionDependent) { + // Check if the conditional is always true or always false but still deferred for distribution purposes. + const skipTrue = !isTypeAssignableTo(getPermissiveInstantiation(c.checkType), getPermissiveInstantiation(c.extendsType)); + const skipFalse = !skipTrue && isTypeAssignableTo(getRestrictiveInstantiation(c.checkType), getRestrictiveInstantiation(c.extendsType)); + // TODO: Find a nice way to include potential conditional type breakdowns in error output, if they seem good (they usually don't) + if (result = skipTrue ? Ternary.True : isRelatedTo(source, getTrueTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false)) { + result &= skipFalse ? Ternary.True : isRelatedTo(source, getFalseTypeFromConditionalType(c), RecursionFlags.Target, /*reportErrors*/ false); if (result) { resetErrorInfo(saveErrorInfo); return result; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index a5b143e06f3bd..ae2db50075a6b 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5648,6 +5648,7 @@ namespace ts { checkType: Type; extendsType: Type; isDistributive: boolean; + isDistributionDependent: boolean; inferTypeParameters?: TypeParameter[]; outerTypeParameters?: TypeParameter[]; instantiations?: Map; From 45a0113d503ed0caf4ea53e2c058577617645519 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Oct 2021 11:06:16 -0700 Subject: [PATCH 4/5] Accept new API baselines --- tests/baselines/reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 116be4316f918..e1512586cff46 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2747,6 +2747,7 @@ declare namespace ts { checkType: Type; extendsType: Type; isDistributive: boolean; + isDistributionDependent: boolean; inferTypeParameters?: TypeParameter[]; outerTypeParameters?: TypeParameter[]; instantiations?: Map; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 56d764c664c5d..f93ca902bbf07 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2747,6 +2747,7 @@ declare namespace ts { checkType: Type; extendsType: Type; isDistributive: boolean; + isDistributionDependent: boolean; inferTypeParameters?: TypeParameter[]; outerTypeParameters?: TypeParameter[]; instantiations?: Map; From dc9da805710ab207dbbcf7160698594c3bbc6663 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 21 Oct 2021 11:27:41 -0700 Subject: [PATCH 5/5] Accept new baselines --- ...tionalTypeAssignabilityWhenDeferred.errors.txt | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt index 156624abf785c..acb5354e1f076 100644 --- a/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt +++ b/tests/baselines/reference/conditionalTypeAssignabilityWhenDeferred.errors.txt @@ -1,10 +1,5 @@ -tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(12,20): error TS2345: Argument of type '"value"' is not assignable to parameter of type 'XX extends XX ? "value" : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(28,20): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(29,21): error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'. -tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(33,22): error TS2345: Argument of type 'T | null' is not assignable to parameter of type 'null extends T | null ? any : never'. - Type 'null' is not assignable to type 'null extends T | null ? any : never'. -tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(34,23): error TS2345: Argument of type 'T | null' is not assignable to parameter of type '[null] extends [T | null] ? any : never'. - Type 'null' is not assignable to type '[null] extends [T | null] ? any : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(39,3): error TS2322: Type '{ x: T; y: T; }' is not assignable to type 'T extends T ? { x: T; y: T; } : never'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(46,3): error TS2322: Type 'string' is not assignable to type 'Foo'. tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(63,9): error TS2322: Type '{ a: number; b: number; }' is not assignable to type '[T] extends [[infer U]] ? U : { b: number; }'. @@ -15,7 +10,7 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T Type '(arg: any) => any' is not assignable to type 'InferBecauseWhyNotDistributive'. -==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (11 errors) ==== +==== tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts (8 errors) ==== export type FilterPropsByType = { [K in keyof T]: T[K] extends TT ? K : never }[keyof T]; @@ -28,8 +23,6 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T export function func(x: XX, tipos: { value: XX }[]) { select(x, tipos, "value"); - ~~~~~~~ -!!! error TS2345: Argument of type '"value"' is not assignable to parameter of type 'XX extends XX ? "value" : never'. } declare function onlyNullablePlease( @@ -55,13 +48,7 @@ tests/cases/compiler/conditionalTypeAssignabilityWhenDeferred.ts(116,3): error T function f(t: T) { var x: T | null = Math.random() > 0.5 ? null : t; onlyNullablePlease(x); // should work - ~ -!!! error TS2345: Argument of type 'T | null' is not assignable to parameter of type 'null extends T | null ? any : never'. -!!! error TS2345: Type 'null' is not assignable to type 'null extends T | null ? any : never'. onlyNullablePlease2(x); // should work - ~ -!!! error TS2345: Argument of type 'T | null' is not assignable to parameter of type '[null] extends [T | null] ? any : never'. -!!! error TS2345: Type 'null' is not assignable to type '[null] extends [T | null] ? any : never'. } function f2(t1: { x: T; y: T }, t2: T extends T ? { x: T; y: T } : never) {