Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer resolution of true and false branches in conditional types #31354

Merged
merged 7 commits into from May 14, 2019
Merged
47 changes: 10 additions & 37 deletions src/compiler/checker.ts
Expand Up @@ -10196,6 +10196,7 @@ namespace ts {
function getSimplifiedType(type: Type, writing: boolean): Type {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(<IndexedAccessType>type, writing) :
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(<ConditionalType>type, writing) :
type.flags & TypeFlags.Substitution ? writing ? (<SubstitutionType>type).typeVariable : (<SubstitutionType>type).substitute :
type;
}

Expand Down Expand Up @@ -10260,10 +10261,10 @@ namespace ts {
}

function getSimplifiedConditionalType(type: ConditionalType, writing: boolean) {
const falseType = getFalseTypeFromConditionalType(type);
const trueType = getTrueTypeFromConditionalType(type);
const checkType = type.checkType;
const extendsType = type.extendsType;
const trueType = getTrueTypeFromConditionalType(type);
const falseType = getFalseTypeFromConditionalType(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 (checkType.flags & TypeFlags.Any || isTypeAssignableTo(getRestrictiveInstantiation(checkType), getRestrictiveInstantiation(extendsType))) { // Always true
Expand All @@ -10281,10 +10282,16 @@ namespace ts {
return getSimplifiedType(falseType, writing);
}
}

return type;
}

/**
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
*/
function isIntersectionEmpty(type1: Type, type2: Type) {
return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never);
}

function substituteIndexedMappedType(objectType: MappedType, index: Type) {
const mapper = createTypeMapper([getTypeParameterFromMappedType(objectType)], [index]);
const templateMapper = combineTypeMappers(objectType.mapper, mapper);
Expand Down Expand Up @@ -10391,36 +10398,12 @@ namespace ts {
return type;
}

/**
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
*/
function isIntersectionEmpty(type1: Type, type2: Type) {
return !!(getUnionType([intersectTypes(type1, type2), neverType]).flags & TypeFlags.Never);
}

function getConditionalType(root: ConditionalRoot, mapper: TypeMapper | undefined): Type {
const checkType = instantiateType(root.checkType, mapper);
const extendsType = instantiateType(root.extendsType, mapper);
if (checkType === wildcardType || extendsType === wildcardType) {
return wildcardType;
}
// Simplifications for types of the form `T extends U ? T : never` and `T extends U ? never : T`.
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 checkType;
}
else if (isIntersectionEmpty(checkType, extendsType)) { // Always false
return neverType;
}
}
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 checkType;
}
}
const checkTypeInstantiable = maybeTypeOfKind(checkType, TypeFlags.Instantiable | TypeFlags.GenericMappedType);
let combinedMapper: TypeMapper | undefined;
if (root.inferTypeParameters) {
Expand Down Expand Up @@ -10461,10 +10444,6 @@ namespace ts {
}
}
// Return a deferred type for a check that is neither definitely true nor definitely false
return getDeferredConditionalType(root, mapper, combinedMapper, checkType, extendsType);
}

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;
Expand Down Expand Up @@ -12453,12 +12432,6 @@ namespace ts {
if (isFreshLiteralType(target)) {
target = (<FreshableType>target).regularType;
}
if (source.flags & TypeFlags.Substitution) {
source = (<SubstitutionType>source).substitute;
}
if (target.flags & TypeFlags.Substitution) {
target = (<SubstitutionType>target).typeVariable;
}
if (source.flags & TypeFlags.Simplifiable) {
source = getSimplifiedType(source, /*writing*/ false);
}
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Expand Up @@ -3967,7 +3967,7 @@ namespace ts {
/* @internal */
ObjectFlagsType = Nullable | Never | Object | Union | Intersection,
/* @internal */
Simplifiable = IndexedAccess | Conditional,
Simplifiable = IndexedAccess | Conditional | Substitution,
// 'Narrowable' types are types where narrowing actually narrows.
// This *should* be every type other than null, undefined, void, and never
Narrowable = Any | Unknown | StructuredOrInstantiable | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbol | UniqueESSymbol | NonPrimitive,
Expand Down