Skip to content

Commit 216266b

Browse files
committed
Fast path in relations and filtering of pure discriminated union types
1 parent cb2d36e commit 216266b

File tree

2 files changed

+93
-4
lines changed

2 files changed

+93
-4
lines changed

src/compiler/checker.ts

+87-4
Original file line numberDiff line numberDiff line change
@@ -11390,7 +11390,7 @@ namespace ts {
1139011390
// Flags we want to propagate to the result if they exist in all source symbols
1139111391
let optionalFlag = isUnion ? SymbolFlags.None : SymbolFlags.Optional;
1139211392
let syntheticFlag = CheckFlags.SyntheticMethod;
11393-
let checkFlags = 0;
11393+
let checkFlags = CheckFlags.OnlyUnitTypes;
1139411394
for (const current of containingType.types) {
1139511395
const type = getApparentType(current);
1139611396
if (!(type === errorType || type.flags & TypeFlags.Never)) {
@@ -11478,6 +11478,9 @@ namespace ts {
1147811478
if (type.flags & TypeFlags.Never) {
1147911479
checkFlags |= CheckFlags.HasNeverType;
1148011480
}
11481+
if (!isUnitType(type)) {
11482+
checkFlags &= ~CheckFlags.OnlyUnitTypes;
11483+
}
1148111484
propTypes.push(type);
1148211485
}
1148311486
addRange(propTypes, indexTypes);
@@ -17526,8 +17529,19 @@ namespace ts {
1752617529

1752717530
function typeRelatedToSomeType(source: Type, target: UnionOrIntersectionType, reportErrors: boolean): Ternary {
1752817531
const targetTypes = target.types;
17529-
if (target.flags & TypeFlags.Union && containsType(targetTypes, source)) {
17530-
return Ternary.True;
17532+
if (target.flags & TypeFlags.Union) {
17533+
if (containsType(targetTypes, source)) {
17534+
return Ternary.True;
17535+
}
17536+
if (targetTypes.length >= 4) {
17537+
const match = getMatchingUnionConstituentForType(<UnionType>target, source);
17538+
if (match) {
17539+
const related = isRelatedTo(source, match, /*reportErrors*/ false);
17540+
if (related) {
17541+
return related;
17542+
}
17543+
}
17544+
}
1753117545
}
1753217546
for (const type of targetTypes) {
1753317547
const related = isRelatedTo(source, type, /*reportErrors*/ false);
@@ -21364,6 +21378,71 @@ namespace ts {
2136421378
return result;
2136521379
}
2136621380

21381+
function getUnitTypeProperties(unionType: UnionType): Symbol[] {
21382+
return unionType.unitTypeProperties || (unionType.unitTypeProperties =
21383+
filter(getPropertiesOfUnionOrIntersectionType(unionType), prop => !!(
21384+
getCheckFlags(prop) & CheckFlags.SyntheticProperty &&
21385+
((<TransientSymbol>prop).checkFlags & CheckFlags.UnitDiscriminant) === CheckFlags.UnitDiscriminant)));
21386+
}
21387+
21388+
function getUnionConstituentKeyForType(unionType: UnionType, type: Type) {
21389+
const unitTypeProperties = getUnitTypeProperties(unionType);
21390+
if (unitTypeProperties.length === 0) {
21391+
return undefined;
21392+
}
21393+
const propTypes = [];
21394+
for (const prop of unitTypeProperties) {
21395+
const propType = getTypeOfPropertyOfType(type, prop.escapedName);
21396+
if (!(propType && isUnitType(propType))) {
21397+
return undefined;
21398+
}
21399+
propTypes.push(getRegularTypeOfLiteralType(propType));
21400+
}
21401+
return getTypeListId(propTypes);
21402+
}
21403+
21404+
function getUnionConstituentKeyForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) {
21405+
const unitTypeProperties = getUnitTypeProperties(unionType);
21406+
if (unitTypeProperties.length === 0) {
21407+
return undefined;
21408+
}
21409+
const propTypes = [];
21410+
for (const prop of unitTypeProperties) {
21411+
const propNode = find(node.properties, p => p.symbol && p.kind === SyntaxKind.PropertyAssignment &&
21412+
p.symbol.escapedName === prop.escapedName && isPossiblyDiscriminantValue(p.initializer));
21413+
const propType = propNode && getTypeOfExpression((<PropertyAssignment>propNode).initializer);
21414+
if (!(propType && isUnitType(propType))) {
21415+
return undefined;
21416+
}
21417+
propTypes.push(getRegularTypeOfLiteralType(propType));
21418+
}
21419+
return getTypeListId(propTypes);
21420+
}
21421+
21422+
function getUnionConstituentMap(unionType: UnionType) {
21423+
if (!unionType.constituentMap) {
21424+
const map = unionType.constituentMap = new Map<string, Type | undefined>();
21425+
for (const t of unionType.types) {
21426+
const key = getUnionConstituentKeyForType(unionType, t);
21427+
if (key) {
21428+
const duplicate = map.has(key);
21429+
map.set(key, duplicate ? undefined : t);
21430+
}
21431+
}
21432+
}
21433+
return unionType.constituentMap;
21434+
}
21435+
21436+
function getMatchingUnionConstituentForType(unionType: UnionType, type: Type) {
21437+
const key = getUnionConstituentKeyForType(unionType, type);
21438+
return key && getUnionConstituentMap(unionType).get(key);
21439+
}
21440+
21441+
function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) {
21442+
const key = getUnionConstituentKeyForObjectLiteral(unionType, node);
21443+
return key && getUnionConstituentMap(unionType).get(key);
21444+
}
21445+
2136721446
function isOrContainsMatchingReference(source: Node, target: Node) {
2136821447
return isMatchingReference(source, target) || containsMatchingReference(source, target);
2136921448
}
@@ -24609,7 +24688,7 @@ namespace ts {
2460924688
}
2461024689

2461124690
function discriminateContextualTypeByObjectMembers(node: ObjectLiteralExpression, contextualType: UnionType) {
24612-
return discriminateTypeByDiscriminableItems(contextualType,
24691+
return getMatchingUnionConstituentForObjectLiteral(contextualType, node) || discriminateTypeByDiscriminableItems(contextualType,
2461324692
map(
2461424693
filter(node.properties, p => !!p.symbol && p.kind === SyntaxKind.PropertyAssignment && isPossiblyDiscriminantValue(p.initializer) && isDiscriminantProperty(contextualType, p.symbol.escapedName)),
2461524694
prop => ([() => checkExpression((prop as PropertyAssignment).initializer), prop.symbol.escapedName] as [() => Type, __String])
@@ -41035,6 +41114,10 @@ namespace ts {
4103541114
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
4103641115
function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) {
4103741116
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
41117+
const match = getMatchingUnionConstituentForType(<UnionType>target, source);
41118+
if (match) {
41119+
return match;
41120+
}
4103841121
const sourceProperties = getPropertiesOfType(source);
4103941122
if (sourceProperties) {
4104041123
const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target);

src/compiler/types.ts

+6
Original file line numberDiff line numberDiff line change
@@ -4806,8 +4806,10 @@ namespace ts {
48064806
HasNeverType = 1 << 17, // Synthetic property with at least one never type in constituents
48074807
Mapped = 1 << 18, // Property of mapped type
48084808
StripOptional = 1 << 19, // Strip optionality in mapped property
4809+
OnlyUnitTypes = 1 << 20, // Synthetic property where property in every constituent has a unit type
48094810
Synthetic = SyntheticProperty | SyntheticMethod,
48104811
Discriminant = HasNonUniformType | HasLiteralType,
4812+
UnitDiscriminant = HasNonUniformType | OnlyUnitTypes,
48114813
Partial = ReadPartial | WritePartial
48124814
}
48134815

@@ -5303,6 +5305,10 @@ namespace ts {
53035305
regularType?: UnionType;
53045306
/* @internal */
53055307
origin?: Type; // Denormalized union, intersection, or index type in which union originates
5308+
/* @internal */
5309+
unitTypeProperties?: Symbol[] | undefined;
5310+
/* @internal */
5311+
constituentMap?: Map<Type | undefined>;
53065312
}
53075313

53085314
export interface IntersectionType extends UnionOrIntersectionType {

0 commit comments

Comments
 (0)