Preserve intersection origin for union types

This commit is contained in:
Anders Hejlsberg committed 2020-12-28 17:05:44 -10:00
1 parent 2e2048cacc
commit 44231c80c3
2 files changed
+51 -34

No files matched your search

+43 -32
View File
@@ -4554,14 +4554,16 @@ namespace ts {
: factory.createTypeReferenceNode(factory.createIdentifier("?"), /*typeArguments*/ undefined);
}
if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).originalTypes || (<UnionType>type).types) : (<IntersectionType>type).types;
const isIntersection = type.flags & TypeFlags.Intersection || (<UnionType>type).origin?.isIntersection;
const types = type.flags & TypeFlags.Intersection ? (<IntersectionType>type).types :
isIntersection ? (<UnionType>type).origin!.types :
formatUnionTypes((<UnionType>type).origin?.types || (<UnionType>type).types);
if (length(types) === 1) {
return typeToTypeNodeHelper(types[0], context);
}
const typeNodes = mapToTypeNodes(types, context, /*isBareList*/ true);
if (typeNodes && typeNodes.length > 0) {
const unionOrIntersectionTypeNode = type.flags & TypeFlags.Union ? factory.createUnionTypeNode(typeNodes) : factory.createIntersectionTypeNode(typeNodes);
return unionOrIntersectionTypeNode;
return isIntersection ? factory.createIntersectionTypeNode(typeNodes) : factory.createUnionTypeNode(typeNodes);
}
else {
if (!context.encounteredError && !(context.flags & NodeBuilderFlags.AllowEmptyUnionOrIntersection)) {
@@ -11444,7 +11446,7 @@ namespace ts {
}
function getUnaliasedUnionType(type: Type) {
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.EnumLiteral) && (<UnionType>type).aliasSymbol) {
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.EnumLiteral) && ((<UnionType>type).aliasSymbol || (<UnionType>type).origin)) {
return (<UnionType>type).unaliasedType || ((<UnionType>type).unaliasedType = getUnionTypeFromSortedList((<UnionType>type).types, (<UnionType>type).objectFlags));
}
return type;
@@ -13249,20 +13251,6 @@ namespace ts {
}
}
function reduceOriginalTypes(types: readonly Type[], typeSet: readonly Type[]) {
const aliasedUnions = filter(types, t => !!(t.flags & TypeFlags.Union && t.aliasSymbol));
const result: Type[] = [];
for (const t of typeSet) {
if (!(t.flags & TypeFlags.Union && t.aliasSymbol) && !some(aliasedUnions, union => containsType((<UnionType>union).types, t))) {
result.push(t);
}
}
for (const t of aliasedUnions) {
insertType(result, t);
}
return result;
}
// We sort and deduplicate the constituent types based on object identity. If the subtypeReduction
// flag is specified we also reduce the constituent type set to only include types that aren't subtypes
// of other types. Subtype reduction is expensive for large union types and is possible only when union
@@ -13270,7 +13258,7 @@ namespace ts {
// expression constructs such as array literals and the || and ?: operators). Named types can
// circularly reference themselves and therefore cannot be subtype reduced during their declaration.
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
function getUnionType(types: readonly Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: UnionOrigin): Type {
if (types.length === 0) {
return neverType;
}
@@ -13279,10 +13267,6 @@ namespace ts {
}
const typeSet: Type[] = [];
const includes = addTypesToUnion(typeSet, 0, types);
const originalTypes = includes & TypeFlags.Union ? reduceOriginalTypes(types, typeSet) : undefined;
if (!aliasSymbol && originalTypes && originalTypes.length === 1) {
return originalTypes[0];
}
if (unionReduction !== UnionReduction.None) {
if (includes & TypeFlags.AnyOrUnknown) {
return includes & TypeFlags.Any ? includes & TypeFlags.IncludesWildcard ? wildcardType : anyType : unknownType;
@@ -13308,9 +13292,27 @@ namespace ts {
neverType;
}
}
if (!origin && includes & TypeFlags.Union) {
const aliasedUnions = filter(types, t => !!(t.flags & TypeFlags.Union && t.aliasSymbol));
const reducedTypes: Type[] = [];
for (const t of typeSet) {
if (!(t.flags & TypeFlags.Union && t.aliasSymbol) && !some(aliasedUnions, union => containsType((<UnionType>union).types, t))) {
reducedTypes.push(t);
}
}
for (const t of aliasedUnions) {
insertType(reducedTypes, t);
}
if (!aliasSymbol && reducedTypes.length === 1) {
return reducedTypes[0];
}
if (reducedTypes.length <= typeSet.length) {
origin = { types: reducedTypes, isIntersection: false };
}
}
const objectFlags = (includes & TypeFlags.NotPrimitiveUnion ? 0 : ObjectFlags.PrimitiveUnion) |
(includes & TypeFlags.Intersection ? ObjectFlags.ContainsIntersections : 0);
return getUnionTypeFromSortedList(typeSet, objectFlags, originalTypes && originalTypes.length <= typeSet.length ? originalTypes : undefined, aliasSymbol, aliasTypeArguments);
return getUnionTypeFromSortedList(typeSet, objectFlags, aliasSymbol, aliasTypeArguments, origin);
}
function getUnionTypePredicate(signatures: readonly Signature[]): TypePredicate | undefined {
@@ -13346,21 +13348,21 @@ namespace ts {
}
// This function assumes the constituent type list is sorted and deduplicated.
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, originalTypes?: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[]): Type {
function getUnionTypeFromSortedList(types: Type[], objectFlags: ObjectFlags, aliasSymbol?: Symbol, aliasTypeArguments?: readonly Type[], origin?: UnionOrigin): Type {
if (types.length === 0) {
return neverType;
}
if (types.length === 1) {
return types[0];
}
const id = getTypeListId(types) + (originalTypes ? `#${getTypeListId(originalTypes)}` : "") + (aliasSymbol ? `@${getSymbolId(aliasSymbol)}` : "");
const id = getTypeListId(types) + (origin ? `${origin.isIntersection ? '&' : '|'}${getTypeListId(origin.types)}` : "") + (aliasSymbol ? `@${getSymbolId(aliasSymbol)}` : "");
let type = unionTypes.get(id);
if (!type) {
type = <UnionType>createType(TypeFlags.Union);
unionTypes.set(id, type);
type.objectFlags = objectFlags | getPropagatingFlagsOfTypes(types, /*excludeKinds*/ TypeFlags.Nullable);
type.types = types;
type.originalTypes = originalTypes;
type.origin = origin;
/*
Note: This is the alias symbol (or lack thereof) that we see when we first encounter this union type.
For aliases of identical unions, eg `type T = A | B; type U = A | B`, the symbol of the first alias encountered is the aliasSymbol.
@@ -13611,10 +13613,10 @@ namespace ts {
result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments);
}
else if (extractIrreducible(typeSet, TypeFlags.Undefined)) {
result = getUnionType([getIntersectionType(typeSet), undefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
result = getUnionTypeFromIntersection([getIntersectionType(typeSet), undefinedType], aliasSymbol, aliasTypeArguments, typeSet);
}
else if (extractIrreducible(typeSet, TypeFlags.Null)) {
result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
result = getUnionTypeFromIntersection([getIntersectionType(typeSet), nullType], aliasSymbol, aliasTypeArguments, typeSet);
}
else {
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
@@ -13625,8 +13627,8 @@ namespace ts {
}
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
const unionType = <UnionType>typeSet[unionIndex];
result = getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
result = getUnionTypeFromIntersection(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
aliasSymbol, aliasTypeArguments, typeSet);
}
}
else {
@@ -13637,6 +13639,11 @@ namespace ts {
return result;
}
function getUnionTypeFromIntersection(types: readonly Type[], aliasSymbol: Symbol | undefined, aliasTypeArguments: readonly Type[] | undefined, originalTypes: readonly Type[]) {
const origin = some(types, t => !!(t.flags & TypeFlags.Intersection)) ? { types: originalTypes, isIntersection: true } : undefined;
return getUnionType(types, UnionReduction.Literal, aliasSymbol, aliasTypeArguments, origin);
}
function checkCrossProductUnion(types: readonly Type[]) {
const size = reduceLeft(types, (n, t) => n * (t.flags & TypeFlags.Union ? (<UnionType>t).types.length : t.flags & TypeFlags.Never ? 0 : 1), 1);
if (size >= 100000) {
@@ -15507,6 +15514,10 @@ namespace ts {
return getConditionalType(root, mapper);
}
function instantiateUnionOrigin(origin: UnionOrigin | undefined, mapper: TypeMapper) {
return origin && { types: instantiateTypes(origin.types, mapper), isIntersection: origin.isIntersection };
}
function instantiateType(type: Type, mapper: TypeMapper | undefined): Type;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined;
function instantiateType(type: Type | undefined, mapper: TypeMapper | undefined): Type | undefined {
@@ -15552,7 +15563,7 @@ namespace ts {
return newTypes === types ? type :
flags & TypeFlags.Intersection ?
getIntersectionType(newTypes, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper)) :
getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
getUnionType(newTypes, UnionReduction.Literal, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper), instantiateUnionOrigin((<UnionType>type).origin, mapper));
}
if (flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
+8 -2
View File
@@ -5288,8 +5288,6 @@ namespace ts {
resolvedStringIndexType: IndexType;
/* @internal */
resolvedBaseConstraint: Type;
/* @internal */
originalTypes?: Type[];
}
export interface UnionType extends UnionOrIntersectionType {
@@ -5299,6 +5297,14 @@ namespace ts {
regularType?: UnionType;
/* @internal */
unaliasedType?: Type;
/* @internal */
origin?: UnionOrigin;
}
/* @internal */
export interface UnionOrigin {
types: readonly Type[];
isIntersection: boolean;
}
export interface IntersectionType extends UnionOrIntersectionType {