mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-23 07:07:09 -05:00
Improve constraints of conditional types applied to constrained type variables (#56004)
This commit is contained in:
@@ -13679,7 +13679,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const checkType = (type as ConditionalType).checkType;
|
||||
const constraint = getLowerBoundOfKeyType(checkType);
|
||||
if (constraint !== checkType) {
|
||||
return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper));
|
||||
return getConditionalTypeInstantiation(type as ConditionalType, prependTypeMapping((type as ConditionalType).root.checkType, constraint, (type as ConditionalType).mapper), /*forConstraint*/ false);
|
||||
}
|
||||
}
|
||||
return type;
|
||||
@@ -14128,7 +14128,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
const simplified = getSimplifiedType(type.checkType, /*writing*/ false);
|
||||
const constraint = simplified === type.checkType ? getConstraintOfType(simplified) : simplified;
|
||||
if (constraint && constraint !== type.checkType) {
|
||||
const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper));
|
||||
const instantiated = getConditionalTypeInstantiation(type, prependTypeMapping(type.root.checkType, constraint, type.mapper), /*forConstraint*/ true);
|
||||
if (!(instantiated.flags & TypeFlags.Never)) {
|
||||
type.resolvedConstraintOfDistributive = instantiated;
|
||||
return instantiated;
|
||||
@@ -18353,7 +18353,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return isGenericType(type) || checkTuples && isTupleType(type) && some(getElementTypes(type), isGenericType);
|
||||
}
|
||||
|
||||
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
||||
function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
||||
let result;
|
||||
let extraTypes: Type[] | undefined;
|
||||
let tailCount = 0;
|
||||
@@ -18432,8 +18432,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// 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)))) {
|
||||
// Return union of trueType and falseType for 'any' since it matches anything
|
||||
if (checkType.flags & TypeFlags.Any) {
|
||||
// Return union of trueType and falseType for 'any' since it matches anything. Furthermore, for a
|
||||
// distributive conditional type applied to the constraint of a type variable, include trueType if
|
||||
// there are possible values of the check type that are also possible values of the extends type.
|
||||
// We use a reverse assignability check as it is less expensive than the comparable relationship
|
||||
// and avoids false positives of a non-empty intersection check.
|
||||
if (checkType.flags & TypeFlags.Any || forConstraint && !(inferredExtendsType.flags & TypeFlags.Never) && someType(getPermissiveInstantiation(inferredExtendsType), t => isTypeAssignableTo(t, getPermissiveInstantiation(checkType)))) {
|
||||
(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
|
||||
@@ -18557,7 +18561,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
aliasSymbol,
|
||||
aliasTypeArguments,
|
||||
};
|
||||
links.resolvedType = getConditionalType(root, /*mapper*/ undefined);
|
||||
links.resolvedType = getConditionalType(root, /*mapper*/ undefined, /*forConstraint*/ false);
|
||||
if (outerTypeParameters) {
|
||||
root.instantiations = new Map<string, Type>();
|
||||
root.instantiations.set(getTypeListId(outerTypeParameters), links.resolvedType);
|
||||
@@ -19565,14 +19569,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return result;
|
||||
}
|
||||
|
||||
function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
||||
function getConditionalTypeInstantiation(type: ConditionalType, mapper: TypeMapper, forConstraint: boolean, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
|
||||
const root = type.root;
|
||||
if (root.outerTypeParameters) {
|
||||
// We are instantiating a conditional type that has one or more type parameters in scope. Apply the
|
||||
// mapper to the type parameters to produce the effective list of type arguments, and compute the
|
||||
// instantiation cache key from the type IDs of the type arguments.
|
||||
const typeArguments = map(root.outerTypeParameters, t => getMappedType(t, mapper));
|
||||
const id = getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments);
|
||||
const id = (forConstraint ? "C" : "") + getTypeListId(typeArguments) + getAliasId(aliasSymbol, aliasTypeArguments);
|
||||
let result = root.instantiations!.get(id);
|
||||
if (!result) {
|
||||
const newMapper = createTypeMapper(root.outerTypeParameters, typeArguments);
|
||||
@@ -19582,8 +19586,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
// distributive conditional type T extends U ? X : Y is instantiated with A | B for T, the
|
||||
// result is (A extends U ? X : Y) | (B extends U ? X : Y).
|
||||
result = distributionType && checkType !== distributionType && distributionType.flags & (TypeFlags.Union | TypeFlags.Never) ?
|
||||
mapTypeWithAlias(getReducedType(distributionType), t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper)), aliasSymbol, aliasTypeArguments) :
|
||||
getConditionalType(root, newMapper, aliasSymbol, aliasTypeArguments);
|
||||
mapTypeWithAlias(getReducedType(distributionType), t => getConditionalType(root, prependTypeMapping(checkType, t, newMapper), forConstraint), aliasSymbol, aliasTypeArguments) :
|
||||
getConditionalType(root, newMapper, forConstraint, aliasSymbol, aliasTypeArguments);
|
||||
root.instantiations!.set(id, result);
|
||||
}
|
||||
return result;
|
||||
@@ -19665,7 +19669,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return getIndexedAccessType(instantiateType((type as IndexedAccessType).objectType, mapper), instantiateType((type as IndexedAccessType).indexType, mapper), (type as IndexedAccessType).accessFlags, /*accessNode*/ undefined, newAliasSymbol, newAliasTypeArguments);
|
||||
}
|
||||
if (flags & TypeFlags.Conditional) {
|
||||
return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), aliasSymbol, aliasTypeArguments);
|
||||
return getConditionalTypeInstantiation(type as ConditionalType, combineTypeMappers((type as ConditionalType).mapper, mapper), /*forConstraint*/ false, aliasSymbol, aliasTypeArguments);
|
||||
}
|
||||
if (flags & TypeFlags.Substitution) {
|
||||
const newBaseType = instantiateType((type as SubstitutionType).baseType, mapper);
|
||||
@@ -22414,18 +22418,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way
|
||||
// more assignments than are desirable (since it maps the source check type to its constraint, it loses information)
|
||||
const distributiveConstraint = hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined;
|
||||
if (distributiveConstraint) {
|
||||
if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// conditionals _can_ be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
|
||||
// conditionals can be related to one another via normal constraint, as, eg, `A extends B ? O : never` should be assignable to `O`
|
||||
// when `O` is a conditional (`never` is trivially assignable to `O`, as is `O`!).
|
||||
const defaultConstraint = getDefaultConstraintOfConditionalType(source as ConditionalType);
|
||||
if (defaultConstraint) {
|
||||
@@ -22433,6 +22426,15 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// conditionals aren't related to one another via distributive constraint as it is much too inaccurate and allows way
|
||||
// more assignments than are desirable (since it maps the source check type to its constraint, it loses information).
|
||||
const distributiveConstraint = !(targetFlags & TypeFlags.Conditional) && hasNonCircularBaseConstraint(source) ? getConstraintOfDistributiveConditionalType(source as ConditionalType) : undefined;
|
||||
if (distributiveConstraint) {
|
||||
resetErrorInfo(saveErrorInfo);
|
||||
if (result = isRelatedTo(distributiveConstraint, target, RecursionFlags.Source, reportErrors)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// An empty object type is related to any mapped type that includes a '?' modifier.
|
||||
|
||||
Reference in New Issue
Block a user