mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-06-17 12:19:32 -05:00
Add missing relationship allowing a type to be assignable to a conditional when assignable to both branches (#30639)
* Finally add that missing relationship allowing a type to be assignable to both branches of a conditional * Explicitly write out Ternary.Maybe * Add slightly modified example from #25413 * fix sick sentence * Loosen check to skip false branch constraint check to consider `infer` parameters as always satisfied in the extends clause * Simplify things a bit, only instantiate once Co-authored-by: Nathan Shively-Sanders <293473+sandersn@users.noreply.github.com>
This commit is contained in:
@@ -14607,6 +14607,13 @@ 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;
|
||||
@@ -18139,6 +18146,36 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (target.flags & TypeFlags.Conditional) {
|
||||
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)
|
||||
let localResult: Ternary | undefined;
|
||||
if (skipTrue || (localResult = isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.trueType), distributionMapper) : getTrueTypeFromConditionalType(c), /*reportErrors*/ false))) {
|
||||
if (!skipFalse) {
|
||||
localResult = (localResult || Ternary.Maybe) & isRelatedTo(source, distributionMapper ? instantiateType(getTypeFromTypeNode(c.root.node.falseType), distributionMapper) : getFalseTypeFromConditionalType(c), /*reportErrors*/ false);
|
||||
}
|
||||
}
|
||||
if (localResult) {
|
||||
resetErrorInfo(saveErrorInfo);
|
||||
return localResult;
|
||||
}
|
||||
}
|
||||
else if (target.flags & TypeFlags.TemplateLiteral && source.flags & TypeFlags.StringLiteral) {
|
||||
if (isPatternLiteralType(target)) {
|
||||
// match all non-`string` segments
|
||||
|
||||
Reference in New Issue
Block a user