mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-09 02:30:15 -06:00
Defer resolution of the true and false branches of conditional types
This commit is contained in:
parent
ae3d1d45c1
commit
71fe8e824e
@ -394,7 +394,6 @@ namespace ts {
|
||||
const intersectionTypes = createMap<IntersectionType>();
|
||||
const literalTypes = createMap<LiteralType>();
|
||||
const indexedAccessTypes = createMap<IndexedAccessType>();
|
||||
const conditionalTypes = createMap<Type>();
|
||||
const substitutionTypes = createMap<SubstitutionType>();
|
||||
const evolvingArrayTypes: EvolvingArrayType[] = [];
|
||||
const undefinedProperties = createMap<Symbol>() as UnderscoreEscapedMap<Symbol>;
|
||||
@ -3648,8 +3647,8 @@ namespace ts {
|
||||
context.inferTypeParameters = (<ConditionalType>type).root.inferTypeParameters;
|
||||
const extendsTypeNode = typeToTypeNodeHelper((<ConditionalType>type).extendsType, context);
|
||||
context.inferTypeParameters = saveInferTypeParameters;
|
||||
const trueTypeNode = typeToTypeNodeHelper((<ConditionalType>type).trueType, context);
|
||||
const falseTypeNode = typeToTypeNodeHelper((<ConditionalType>type).falseType, context);
|
||||
const trueTypeNode = typeToTypeNodeHelper(getTrueTypeFromConditionalType(<ConditionalType>type), context);
|
||||
const falseTypeNode = typeToTypeNodeHelper(getFalseTypeFromConditionalType(<ConditionalType>type), context);
|
||||
context.approximateLength += 15;
|
||||
return createConditionalTypeNode(checkTypeNode, extendsTypeNode, trueTypeNode, falseTypeNode);
|
||||
}
|
||||
@ -7674,7 +7673,7 @@ namespace ts {
|
||||
// just `any`. This result is _usually_ unwanted - so instead here we elide an `any` branch from the constraint type,
|
||||
// in effect treating `any` like `never` rather than `unknown` in this location.
|
||||
const trueConstraint = getInferredTrueTypeFromConditionalType(type);
|
||||
const falseConstraint = type.falseType;
|
||||
const falseConstraint = getFalseTypeFromConditionalType(type);
|
||||
type.resolvedDefaultConstraint = isTypeAny(trueConstraint) ? falseConstraint : isTypeAny(falseConstraint) ? trueConstraint : getUnionType([trueConstraint, falseConstraint]);
|
||||
}
|
||||
return type.resolvedDefaultConstraint;
|
||||
@ -10377,37 +10376,23 @@ namespace ts {
|
||||
if (checkType === wildcardType || extendsType === wildcardType) {
|
||||
return wildcardType;
|
||||
}
|
||||
const trueType = instantiateType(root.trueType, mapper);
|
||||
const falseType = instantiateType(root.falseType, mapper);
|
||||
const instantiationId = `${root.isDistributive ? "d" : ""}${getTypeId(checkType)}>${getTypeId(extendsType)}?${getTypeId(trueType)}:${getTypeId(falseType)}`;
|
||||
const result = conditionalTypes.get(instantiationId);
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
const newResult = getConditionalTypeWorker(root, mapper, checkType, extendsType, trueType, falseType);
|
||||
conditionalTypes.set(instantiationId, newResult);
|
||||
return newResult;
|
||||
}
|
||||
|
||||
function getConditionalTypeWorker(root: ConditionalRoot, mapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
|
||||
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
|
||||
if (falseType.flags & TypeFlags.Never && getActualTypeVariable(trueType) === getActualTypeVariable(checkType)) {
|
||||
if (root.falseType.flags & TypeFlags.Never && getActualTypeVariable(root.trueType) === getActualTypeVariable(root.checkType)) {
|
||||
if (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
|
||||
return trueType;
|
||||
return instantiateType(root.trueType, mapper);
|
||||
}
|
||||
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
|
||||
return neverType;
|
||||
}
|
||||
}
|
||||
else if (trueType.flags & TypeFlags.Never && getActualTypeVariable(falseType) === getActualTypeVariable(checkType)) {
|
||||
else if (root.trueType.flags & TypeFlags.Never && getActualTypeVariable(root.falseType) === getActualTypeVariable(root.checkType)) {
|
||||
if (!(checkType.flags & TypeFlags.Any) && isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
|
||||
return neverType;
|
||||
}
|
||||
else if (checkType.flags & TypeFlags.Any || isIntersectionEmpty(checkType, extendsType)) { // Always false
|
||||
return falseType;
|
||||
return instantiateType(root.falseType, mapper);
|
||||
}
|
||||
}
|
||||
|
||||
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType);
|
||||
let combinedMapper: TypeMapper | undefined;
|
||||
if (root.inferTypeParameters) {
|
||||
@ -10425,18 +10410,18 @@ namespace ts {
|
||||
// We attempt to resolve the conditional type only when the check and extends types are non-generic
|
||||
if (!checkTypeInstantiable && !maybeTypeOfKind(inferredExtendsType, TypeFlags.Instantiable | TypeFlags.GenericMappedType)) {
|
||||
if (inferredExtendsType.flags & TypeFlags.AnyOrUnknown) {
|
||||
return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType;
|
||||
return instantiateType(root.trueType, combinedMapper || mapper);
|
||||
}
|
||||
// Return union of trueType and falseType for 'any' since it matches anything
|
||||
if (checkType.flags & TypeFlags.Any) {
|
||||
return getUnionType([combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType, falseType]);
|
||||
return getUnionType([instantiateType(root.trueType, combinedMapper || mapper), instantiateType(root.falseType, mapper)]);
|
||||
}
|
||||
// Return falseType for a definitely false extends check. We check an instantiations of the two
|
||||
// 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 (!isTypeAssignableTo(getPermissiveInstantiation(checkType), getPermissiveInstantiation(inferredExtendsType))) {
|
||||
return falseType;
|
||||
return instantiateType(root.falseType, mapper);
|
||||
}
|
||||
// Return trueType for a definitely true extends check. We check instantiations of the two
|
||||
// types with type parameters mapped to their restrictive form, i.e. a form of the type parameter
|
||||
@ -10444,14 +10429,14 @@ namespace ts {
|
||||
// type Foo<T extends { x: any }> = T extends { x: string } ? string : number
|
||||
// doesn't immediately resolve to 'string' instead of being deferred.
|
||||
if (isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(inferredExtendsType))) {
|
||||
return combinedMapper ? instantiateType(root.trueType, combinedMapper) : trueType;
|
||||
return instantiateType(root.trueType, combinedMapper || mapper);
|
||||
}
|
||||
}
|
||||
// Return a deferred type for a check that is neither definitely true nor definitely false
|
||||
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType, trueType, falseType);
|
||||
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType);
|
||||
}
|
||||
|
||||
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type, trueType: Type, falseType: Type) {
|
||||
function getDeferredConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined, combinedMapper: TypeMapper | undefined, checkType: Type, extendsType: Type) {
|
||||
const erasedCheckType = getActualTypeVariable(checkType);
|
||||
const result = <ConditionalType>createType(TypeFlags.Conditional);
|
||||
result.root = root;
|
||||
@ -10459,15 +10444,21 @@ namespace ts {
|
||||
result.extendsType = extendsType;
|
||||
result.mapper = mapper;
|
||||
result.combinedMapper = combinedMapper;
|
||||
result.trueType = trueType;
|
||||
result.falseType = falseType;
|
||||
result.aliasSymbol = root.aliasSymbol;
|
||||
result.aliasTypeArguments = instantiateTypes(root.aliasTypeArguments, mapper!); // TODO: GH#18217
|
||||
return result;
|
||||
}
|
||||
|
||||
function getTrueTypeFromConditionalType(type: ConditionalType) {
|
||||
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(type.root.trueType, type.mapper));
|
||||
}
|
||||
|
||||
function getFalseTypeFromConditionalType(type: ConditionalType) {
|
||||
return type.resolvedFalseType || (type.resolvedFalseType = instantiateType(type.root.falseType, type.mapper));
|
||||
}
|
||||
|
||||
function getInferredTrueTypeFromConditionalType(type: ConditionalType) {
|
||||
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = instantiateType(type.root.trueType, type.combinedMapper || type.mapper));
|
||||
return type.resolvedInferredTrueType || (type.resolvedInferredTrueType = type.combinedMapper ? instantiateType(type.root.trueType, type.combinedMapper) : getTrueTypeFromConditionalType(type));
|
||||
}
|
||||
|
||||
function getInferTypeParameters(node: ConditionalTypeNode): TypeParameter[] | undefined {
|
||||
@ -12987,8 +12978,8 @@ namespace ts {
|
||||
if ((<ConditionalType>source).root.isDistributive === (<ConditionalType>target).root.isDistributive) {
|
||||
if (result = isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType, /*reportErrors*/ false)) {
|
||||
if (result &= isRelatedTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType, /*reportErrors*/ false)) {
|
||||
if (result &= isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, /*reportErrors*/ false)) {
|
||||
if (result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, /*reportErrors*/ false)) {
|
||||
if (result &= isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
|
||||
if (result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), /*reportErrors*/ false)) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@ -13147,8 +13138,8 @@ namespace ts {
|
||||
// and Y1 is related to Y2.
|
||||
if (isTypeIdenticalTo((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType) &&
|
||||
(isRelatedTo((<ConditionalType>source).checkType, (<ConditionalType>target).checkType) || isRelatedTo((<ConditionalType>target).checkType, (<ConditionalType>source).checkType))) {
|
||||
if (result = isRelatedTo((<ConditionalType>source).trueType, (<ConditionalType>target).trueType, reportErrors)) {
|
||||
result &= isRelatedTo((<ConditionalType>source).falseType, (<ConditionalType>target).falseType, reportErrors);
|
||||
if (result = isRelatedTo(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target), reportErrors)) {
|
||||
result &= isRelatedTo(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target), reportErrors);
|
||||
}
|
||||
if (result) {
|
||||
errorInfo = saveErrorInfo;
|
||||
@ -15181,12 +15172,12 @@ namespace ts {
|
||||
else if (source.flags & TypeFlags.Conditional && target.flags & TypeFlags.Conditional) {
|
||||
inferFromTypes((<ConditionalType>source).checkType, (<ConditionalType>target).checkType);
|
||||
inferFromTypes((<ConditionalType>source).extendsType, (<ConditionalType>target).extendsType);
|
||||
inferFromTypes((<ConditionalType>source).trueType, (<ConditionalType>target).trueType);
|
||||
inferFromTypes((<ConditionalType>source).falseType, (<ConditionalType>target).falseType);
|
||||
inferFromTypes(getTrueTypeFromConditionalType(<ConditionalType>source), getTrueTypeFromConditionalType(<ConditionalType>target));
|
||||
inferFromTypes(getFalseTypeFromConditionalType(<ConditionalType>source), getFalseTypeFromConditionalType(<ConditionalType>target));
|
||||
}
|
||||
else if (target.flags & TypeFlags.Conditional && !contravariant) {
|
||||
inferFromTypes(source, (<ConditionalType>target).trueType);
|
||||
inferFromTypes(source, (<ConditionalType>target).falseType);
|
||||
inferFromTypes(source, getTrueTypeFromConditionalType(<ConditionalType>target));
|
||||
inferFromTypes(source, getFalseTypeFromConditionalType(<ConditionalType>target));
|
||||
}
|
||||
else if (target.flags & TypeFlags.UnionOrIntersection) {
|
||||
// We infer from types that are not naked type variables first so that inferences we
|
||||
|
||||
@ -4338,8 +4338,8 @@ namespace ts {
|
||||
root: ConditionalRoot;
|
||||
checkType: Type;
|
||||
extendsType: Type;
|
||||
trueType: Type;
|
||||
falseType: Type;
|
||||
resolvedTrueType: Type;
|
||||
resolvedFalseType: Type;
|
||||
/* @internal */
|
||||
resolvedInferredTrueType?: Type; // The `trueType` instantiated with the `combinedMapper`, if present
|
||||
/* @internal */
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user