Skip to content

Commit 39f82a8

Browse files
committed
Use denormalized type objects for origin / support 'keyof' origins
1 parent 11d2712 commit 39f82a8

File tree

2 files changed

+67
-54
lines changed

2 files changed

+67
-54
lines changed

src/compiler/checker.ts

Lines changed: 66 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4553,17 +4553,17 @@ namespace ts {
45534553
? symbolToTypeNode(type.symbol, context, SymbolFlags.Type)
45544554
: factory.createTypeReferenceNode(factory.createIdentifier("?"), /*typeArguments*/ undefined);
45554555
}
4556+
if (type.flags & TypeFlags.Union && (<UnionType>type).origin) {
4557+
type = (<UnionType>type).origin!;
4558+
}
45564559
if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
4557-
const isIntersection = type.flags & TypeFlags.Intersection || (<UnionType>type).origin?.isIntersection;
4558-
const types = type.flags & TypeFlags.Intersection ? (<IntersectionType>type).types :
4559-
isIntersection ? (<UnionType>type).origin!.types :
4560-
formatUnionTypes((<UnionType>type).origin?.types || (<UnionType>type).types);
4560+
const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).types) : (<IntersectionType>type).types;
45614561
if (length(types) === 1) {
45624562
return typeToTypeNodeHelper(types[0], context);
45634563
}
45644564
const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true);
45654565
if (typeNodes && typeNodes.length > 0) {
4566-
return isIntersection ? factory.createIntersectionTypeNode(typeNodes) : factory.createUnionTypeNode(typeNodes);
4566+
return type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes);
45674567
}
45684568
else {
45694569
if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) {
@@ -13256,7 +13256,7 @@ namespace ts {
1325613256
// expression constructs such as array literals and the || and ?: operators). Named types can
1325713257
// circularly reference themselves and therefore cannot be subtype reduced during their declaration.
1325813258
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
13259-
function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: UnionOrigin): Type {
13259+
function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type {
1326013260
if (types.length === 0) {
1326113261
return neverType;
1326213262
}
@@ -13298,14 +13298,17 @@ namespace ts {
1329813298
reducedTypes.push(t);
1329913299
}
1330013300
}
13301-
for (const t of namedUnions) {
13302-
insertType(reducedTypes, t);
13303-
}
13304-
if (!aliasSymbol && reducedTypes.length === 1) {
13305-
return reducedTypes[0];
13301+
if (!aliasSymbol && namedUnions.length === 1 && reducedTypes.length === 0) {
13302+
return namedUnions[0];
1330613303
}
13307-
if (reducedTypes.length <= typeSet.length) {
13308-
origin = { types: reducedTypes, isIntersection: false };
13304+
// We create a denormalized origin type only when the union was created from one or more named unions
13305+
// (unions with alias symbols or origins) and when there is no overlap between those named unions.
13306+
const namedTypesCount = reduceLeft(namedUnions, (sum, union) => sum + (<UnionType>union).types.length, 0);
13307+
if (namedTypesCount + reducedTypes.length === typeSet.length) {
13308+
for (const t of namedUnions) {
13309+
insertType(reducedTypes, t);
13310+
}
13311+
origin = createUnionType(reducedTypes);
1330913312
}
1331013313
}
1331113314
const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) |
@@ -13345,24 +13348,34 @@ namespace ts {
1334513348
return a.kind === b.kind && a.parameterIndex === b.parameterIndex;
1334613349
}
1334713350

13351+
function createUnionType(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type) {
13352+
const result = <UnionType>createType(TypeFlags.Union);
13353+
result.objectFlags = getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
13354+
result.types = types;
13355+
result.origin = origin;
13356+
result.aliasSymbol = aliasSymbol;
13357+
result.aliasTypeArguments = aliasTypeArguments;
13358+
return result;
13359+
}
13360+
1334813361
// This function assumes the constituent type list is sorted and deduplicated.
13349-
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: UnionOrigin): Type {
13362+
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: Type): Type {
1335013363
if (types.length === 0) {
1335113364
return neverType;
1335213365
}
1335313366
if (types.length === 1) {
1335413367
return types[0];
1335513368
}
13356-
const id = (origin ? `${origin.isIntersection ? "&" : "|"}${getTypeListId(origin.types)}` : getTypeListId(types)) + (aliasSymbol ? `@${getSymbolId(aliasSymbol)}` : "");
13369+
const typeKey = !origin ? getTypeListId(types) :
13370+
origin.flags & TypeFlags.Union ? `|${getTypeListId((<UnionType>origin).types)}` :
13371+
origin.flags & TypeFlags.Intersection ? `&${getTypeListId((<IntersectionType>origin).types)}` :
13372+
`#${(<IndexType>origin).type.id}`;
13373+
const id = typeKey + (aliasSymbol ? `@${getSymbolId(aliasSymbol)}` : "");
1335713374
let type = unionTypes.get(id);
1335813375
if (!type) {
13359-
type = <UnionType>createType(TypeFlags.Union);
13376+
type = createUnionType(types, aliasSymbol, aliasTypeArguments, origin);
13377+
type.objectFlags |= objectFlags;
1336013378
unionTypes.set(id, type);
13361-
type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
13362-
type.types = types;
13363-
type.origin = origin;
13364-
type.aliasSymbol = aliasSymbol;
13365-
type.aliasTypeArguments = aliasTypeArguments;
1336613379
}
1336713380
return type;
1336813381
}
@@ -13611,14 +13624,16 @@ namespace ts {
1361113624
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
1361213625
}
1361313626
else {
13614-
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
13615-
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
13616-
// If the estimated size of the resulting union type exceeds 100000 constituents, report an error.
13627+
// We are attempting to construct a type of the form X & (A | B) & (C | D). Transform this into a type of
13628+
// the form X & A & C | X & A & D | X & B & C | X & B & D. If the estimated size of the resulting union type
13629+
// exceeds 100000 constituents, report an error.
1361713630
if (!checkCrossProductUnion(typeSet)) {
1361813631
return errorType;
1361913632
}
1362013633
const constituents = getCrossProductIntersections(typeSet);
13621-
const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? { types: typeSet, isIntersection: true } : undefined;
13634+
// We attach a denormalized origin type when at least one constituent of the cross-product union is an
13635+
// intersection (i.e. when the intersection didn't just reduce one or more unions to smaller unions).
13636+
const origin = some(constituents, t => !!(t.flags & TypeFlags.Intersection)) ? createIntersectionType(typeSet) : undefined;
1362213637
result = getUnionType(constituents, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin);
1362313638
}
1362413639
}
@@ -13630,8 +13645,12 @@ namespace ts {
1363013645
return result;
1363113646
}
1363213647

13648+
function getCrossProductUnionSize(types: readonly Type[]) {
13649+
return reduceLeft(types, (n, t) => t.flags & TypeFlags.Union ? n * (<UnionType>t).types.length : t.flags & TypeFlags.Never ? 0 : n, 1);
13650+
}
13651+
1363313652
function checkCrossProductUnion(types: readonly Type[]) {
13634-
const size = reduceLeft(types, (n, t) => n * (t.flags & TypeFlags.Union ? (<UnionType>t).types.length : t.flags & TypeFlags.Never ? 0 : 1), 1);
13653+
const size = getCrossProductUnionSize(types);
1363513654
if (size >= 100000) {
1363613655
tracing.instant(tracing.Phase.CheckTypes, "checkCrossProductUnion_DepthLimit", { typeIds: types.map(t => t.id), size });
1363713656
error(currentNode, Diagnostics.Expression_produces_a_union_type_that_is_too_complex_to_represent);
@@ -13640,17 +13659,17 @@ namespace ts {
1364013659
return true;
1364113660
}
1364213661

13643-
function getCrossProductIntersections(typeSet: readonly Type[]) {
13644-
const count = reduceLeft(typeSet, (n, t) => t.flags & TypeFlags.Union ? n * (<UnionType>t).types.length : n, 1);
13662+
function getCrossProductIntersections(types: readonly Type[]) {
13663+
const count = getCrossProductUnionSize(types);
1364513664
const intersections: Type[] = [];
1364613665
for (let i = 0; i < count; i++) {
13647-
const constituents = typeSet.slice();
13666+
const constituents = types.slice();
1364813667
let n = i;
13649-
for (let j = typeSet.length - 1; j >= 0; j--) {
13650-
if (typeSet[j].flags & TypeFlags.Union) {
13651-
const types = (<UnionType>typeSet[j]).types;
13652-
const length = types.length;
13653-
constituents[j] = types[n % length];
13668+
for (let j = types.length - 1; j >= 0; j--) {
13669+
if (types[j].flags & TypeFlags.Union) {
13670+
const sourceTypes = (<UnionType>types[j]).types;
13671+
const length = sourceTypes.length;
13672+
constituents[j] = sourceTypes[n % length];
1365413673
n = Math.floor(n / length);
1365513674
}
1365613675
}
@@ -13738,8 +13757,10 @@ namespace ts {
1373813757
return neverType;
1373913758
}
1374013759

13741-
function getLiteralTypeFromProperties(type: Type, include: TypeFlags) {
13742-
return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include)));
13760+
function getLiteralTypeFromProperties(type: Type, include: TypeFlags, includeOrigin: boolean) {
13761+
const origin = includeOrigin && (getObjectFlags(type) & (ObjectFlags.ClassOrInterface | ObjectFlags.Reference) || type.aliasSymbol) ? createIndexType(type, /*stringsOnly*/ false) : undefined;
13762+
return getUnionType(map(getPropertiesOfType(type), p => getLiteralTypeFromProperty(p, include)), UnionReduction.Literal,
13763+
/*aliasSymbol*/ undefined, /*aliasTypeArguments*/ undefined, origin);
1374313764
}
1374413765

1374513766
function getNonEnumNumberIndexInfo(type: Type) {
@@ -13748,6 +13769,7 @@ namespace ts {
1374813769
}
1374913770

1375013771
function getIndexType(type: Type, stringsOnly = keyofStringsOnly, noIndexSignatures?: boolean): Type {
13772+
const includeOrigin = stringsOnly === keyofStringsOnly && !noIndexSignatures;
1375113773
type = getReducedType(type);
1375213774
return type.flags & TypeFlags.Union ? getIntersectionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
1375313775
type.flags & TypeFlags.Intersection ? getUnionType(map((<IntersectionType>type).types, t => getIndexType(t, stringsOnly, noIndexSignatures))) :
@@ -13756,10 +13778,10 @@ namespace ts {
1375613778
type === wildcardType ? wildcardType :
1375713779
type.flags & TypeFlags.Unknown ? neverType :
1375813780
type.flags & (TypeFlags.Any | TypeFlags.Never) ? keyofConstraintType :
13759-
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral) :
13760-
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol)]) :
13761-
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol)]) :
13762-
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique);
13781+
stringsOnly ? !noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromProperties(type, TypeFlags.StringLiteral, includeOrigin) :
13782+
!noIndexSignatures && getIndexInfoOfType(type, IndexKind.String) ? getUnionType([stringType, numberType, getLiteralTypeFromProperties(type, TypeFlags.UniqueESSymbol, includeOrigin)]) :
13783+
getNonEnumNumberIndexInfo(type) ? getUnionType([numberType, getLiteralTypeFromProperties(type, TypeFlags.StringLiteral | TypeFlags.UniqueESSymbol, includeOrigin)]) :
13784+
getLiteralTypeFromProperties(type, TypeFlags.StringOrNumberLiteralOrUnique, includeOrigin);
1376313785
}
1376413786

1376513787
function getExtractStringType(type: Type) {
@@ -15520,10 +15542,6 @@ namespace ts {
1552015542
return getConditionalType(root, mapper);
1552115543
}
1552215544

15523-
function instantiateUnionOrigin(origin: UnionOrigin | undefined, mapper: TypeMapper) {
15524-
return origin && { types: instantiateTypes(origin.types, mapper), isIntersection: origin.isIntersection };
15525-
}
15526-
1552715545
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
1552815546
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
1552915547
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
@@ -15564,12 +15582,13 @@ namespace ts {
1556415582
return type;
1556515583
}
1556615584
if (flags & TypeFlags.UnionOrIntersection) {
15567-
const types = (<UnionOrIntersectionType>type).types;
15585+
const origin = type.flags & TypeFlags.Union ? (<UnionType>type).origin : undefined;
15586+
const types = origin && origin.flags & TypeFlags.UnionOrIntersection ? (<UnionOrIntersectionType>origin).types : (<UnionOrIntersectionType>type).types;
1556815587
const newTypes = instantiateTypes(types, mapper);
1556915588
return newTypes === types ? type :
15570-
flags & TypeFlags.Intersection ?
15589+
flags & TypeFlags.Intersection || origin && origin.flags & TypeFlags.Intersection ?
1557115590
getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) :
15572-
getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper), instantiateUnionOrigin((<UnionType>type).origin, mapper));
15591+
getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
1557315592
}
1557415593
if (flags & TypeFlags.Index) {
1557515594
return getIndexType(instantiateType((<IndexType>type).type, mapper));

src/compiler/types.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5296,13 +5296,7 @@ namespace ts {
52965296
/* @internal */
52975297
regularType?: UnionType;
52985298
/* @internal */
5299-
origin?: UnionOrigin;
5300-
}
5301-
5302-
/* @internal */
5303-
export interface UnionOrigin {
5304-
types: readonly Type[];
5305-
isIntersection: boolean;
5299+
origin?: Type; // Denormalized union, intersection, or index type in which union originates
53065300
}
53075301

53085302
export interface IntersectionType extends UnionOrIntersectionType {

0 commit comments

Comments
 (0)