mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-07 05:41:22 -06:00
Merge pull request #21573 from Microsoft/optimizeUnionIntersection
Optimize union and intersection types
This commit is contained in:
commit
79d2772531
@ -580,6 +580,21 @@ namespace ts {
|
||||
Both = Source | Target,
|
||||
}
|
||||
|
||||
const enum TypeIncludes {
|
||||
Any = 1 << 0,
|
||||
Undefined = 1 << 1,
|
||||
Null = 1 << 2,
|
||||
Never = 1 << 3,
|
||||
NonWideningType = 1 << 4,
|
||||
String = 1 << 5,
|
||||
Number = 1 << 6,
|
||||
ESSymbol = 1 << 7,
|
||||
LiteralOrUniqueESSymbol = 1 << 8,
|
||||
ObjectType = 1 << 9,
|
||||
EmptyObject = 1 << 10,
|
||||
Union = 1 << 11,
|
||||
}
|
||||
|
||||
const enum MembersOrExportsResolutionKind {
|
||||
resolvedExports = "resolvedExports",
|
||||
resolvedMembers = "resolvedMembers"
|
||||
@ -5540,6 +5555,8 @@ namespace ts {
|
||||
sig.minArgumentCount = minArgumentCount;
|
||||
sig.hasRestParameter = hasRestParameter;
|
||||
sig.hasLiteralTypes = hasLiteralTypes;
|
||||
sig.target = undefined;
|
||||
sig.mapper = undefined;
|
||||
return sig;
|
||||
}
|
||||
|
||||
@ -7443,21 +7460,6 @@ namespace ts {
|
||||
return links.resolvedType;
|
||||
}
|
||||
|
||||
interface TypeSet extends Array<Type> {
|
||||
containsAny?: boolean;
|
||||
containsUndefined?: boolean;
|
||||
containsNull?: boolean;
|
||||
containsNever?: boolean;
|
||||
containsNonWideningType?: boolean;
|
||||
containsString?: boolean;
|
||||
containsNumber?: boolean;
|
||||
containsESSymbol?: boolean;
|
||||
containsLiteralOrUniqueESSymbol?: boolean;
|
||||
containsObjectType?: boolean;
|
||||
containsEmptyObject?: boolean;
|
||||
unionIndex?: number;
|
||||
}
|
||||
|
||||
function getTypeId(type: Type) {
|
||||
return type.id;
|
||||
}
|
||||
@ -7482,28 +7484,28 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function addTypeToUnion(typeSet: TypeSet, type: Type) {
|
||||
function addTypeToUnion(typeSet: Type[], includes: TypeIncludes, type: Type) {
|
||||
const flags = type.flags;
|
||||
if (flags & TypeFlags.Union) {
|
||||
addTypesToUnion(typeSet, (<UnionType>type).types);
|
||||
includes = addTypesToUnion(typeSet, includes, (<UnionType>type).types);
|
||||
}
|
||||
else if (flags & TypeFlags.Any) {
|
||||
typeSet.containsAny = true;
|
||||
includes |= TypeIncludes.Any;
|
||||
}
|
||||
else if (!strictNullChecks && flags & TypeFlags.Nullable) {
|
||||
if (flags & TypeFlags.Undefined) typeSet.containsUndefined = true;
|
||||
if (flags & TypeFlags.Null) typeSet.containsNull = true;
|
||||
if (!(flags & TypeFlags.ContainsWideningType)) typeSet.containsNonWideningType = true;
|
||||
if (flags & TypeFlags.Undefined) includes |= TypeIncludes.Undefined;
|
||||
if (flags & TypeFlags.Null) includes |= TypeIncludes.Null;
|
||||
if (!(flags & TypeFlags.ContainsWideningType)) includes |= TypeIncludes.NonWideningType;
|
||||
}
|
||||
else if (!(flags & TypeFlags.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(<IntersectionType>type))) {
|
||||
// We ignore 'never' types in unions. Likewise, we ignore intersections of unit types as they are
|
||||
// another form of 'never' (in that they have an empty value domain). We could in theory turn
|
||||
// intersections of unit types into 'never' upon construction, but deferring the reduction makes it
|
||||
// easier to reason about their origin.
|
||||
if (flags & TypeFlags.String) typeSet.containsString = true;
|
||||
if (flags & TypeFlags.Number) typeSet.containsNumber = true;
|
||||
if (flags & TypeFlags.ESSymbol) typeSet.containsESSymbol = true;
|
||||
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) typeSet.containsLiteralOrUniqueESSymbol = true;
|
||||
if (flags & TypeFlags.String) includes |= TypeIncludes.String;
|
||||
if (flags & TypeFlags.Number) includes |= TypeIncludes.Number;
|
||||
if (flags & TypeFlags.ESSymbol) includes |= TypeIncludes.ESSymbol;
|
||||
if (flags & TypeFlags.StringOrNumberLiteralOrUnique) includes |= TypeIncludes.LiteralOrUniqueESSymbol;
|
||||
const len = typeSet.length;
|
||||
const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues);
|
||||
if (index < 0) {
|
||||
@ -7513,14 +7515,16 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
}
|
||||
return includes;
|
||||
}
|
||||
|
||||
// Add the given types to the given type set. Order is preserved, duplicates are removed,
|
||||
// and nested types of the given kind are flattened into the set.
|
||||
function addTypesToUnion(typeSet: TypeSet, types: Type[]) {
|
||||
function addTypesToUnion(typeSet: Type[], includes: TypeIncludes, types: Type[]): TypeIncludes {
|
||||
for (const type of types) {
|
||||
addTypeToUnion(typeSet, type);
|
||||
includes = addTypeToUnion(typeSet, includes, type);
|
||||
}
|
||||
return includes;
|
||||
}
|
||||
|
||||
function containsIdenticalType(types: Type[], type: Type) {
|
||||
@ -7544,7 +7548,7 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function isSetOfLiteralsFromSameEnum(types: TypeSet): boolean {
|
||||
function isSetOfLiteralsFromSameEnum(types: Type[]): boolean {
|
||||
const first = types[0];
|
||||
if (first.flags & TypeFlags.EnumLiteral) {
|
||||
const firstEnum = getParentOfSymbol(first.symbol);
|
||||
@ -7560,7 +7564,7 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
function removeSubtypes(types: TypeSet) {
|
||||
function removeSubtypes(types: Type[]) {
|
||||
if (types.length === 0 || isSetOfLiteralsFromSameEnum(types)) {
|
||||
return;
|
||||
}
|
||||
@ -7573,15 +7577,15 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function removeRedundantLiteralTypes(types: TypeSet) {
|
||||
function removeRedundantLiteralTypes(types: Type[], includes: TypeIncludes) {
|
||||
let i = types.length;
|
||||
while (i > 0) {
|
||||
i--;
|
||||
const t = types[i];
|
||||
const remove =
|
||||
t.flags & TypeFlags.StringLiteral && types.containsString ||
|
||||
t.flags & TypeFlags.NumberLiteral && types.containsNumber ||
|
||||
t.flags & TypeFlags.UniqueESSymbol && types.containsESSymbol ||
|
||||
t.flags & TypeFlags.StringLiteral && includes & TypeIncludes.String ||
|
||||
t.flags & TypeFlags.NumberLiteral && includes & TypeIncludes.Number ||
|
||||
t.flags & TypeFlags.UniqueESSymbol && includes & TypeIncludes.ESSymbol ||
|
||||
t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (<LiteralType>t).regularType);
|
||||
if (remove) {
|
||||
orderedRemoveItemAt(types, i);
|
||||
@ -7603,15 +7607,15 @@ namespace ts {
|
||||
if (types.length === 1) {
|
||||
return types[0];
|
||||
}
|
||||
const typeSet = [] as TypeSet;
|
||||
addTypesToUnion(typeSet, types);
|
||||
if (typeSet.containsAny) {
|
||||
const typeSet: Type[] = [];
|
||||
const includes = addTypesToUnion(typeSet, 0, types);
|
||||
if (includes & TypeIncludes.Any) {
|
||||
return anyType;
|
||||
}
|
||||
switch (unionReduction) {
|
||||
case UnionReduction.Literal:
|
||||
if (typeSet.containsLiteralOrUniqueESSymbol) {
|
||||
removeRedundantLiteralTypes(typeSet);
|
||||
if (includes & TypeIncludes.LiteralOrUniqueESSymbol) {
|
||||
removeRedundantLiteralTypes(typeSet, includes);
|
||||
}
|
||||
break;
|
||||
case UnionReduction.Subtype:
|
||||
@ -7619,8 +7623,8 @@ namespace ts {
|
||||
break;
|
||||
}
|
||||
if (typeSet.length === 0) {
|
||||
return typeSet.containsNull ? typeSet.containsNonWideningType ? nullType : nullWideningType :
|
||||
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
|
||||
return includes & TypeIncludes.Null ? includes & TypeIncludes.NonWideningType ? nullType : nullWideningType :
|
||||
includes & TypeIncludes.Undefined ? includes & TypeIncludes.NonWideningType ? undefinedType : undefinedWideningType :
|
||||
neverType;
|
||||
}
|
||||
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
|
||||
@ -7698,39 +7702,42 @@ namespace ts {
|
||||
return links.resolvedType;
|
||||
}
|
||||
|
||||
function addTypeToIntersection(typeSet: TypeSet, type: Type) {
|
||||
if (type.flags & TypeFlags.Intersection) {
|
||||
addTypesToIntersection(typeSet, (<IntersectionType>type).types);
|
||||
function addTypeToIntersection(typeSet: Type[], includes: TypeIncludes, type: Type) {
|
||||
const flags = type.flags;
|
||||
if (flags & TypeFlags.Intersection) {
|
||||
includes = addTypesToIntersection(typeSet, includes, (<IntersectionType>type).types);
|
||||
}
|
||||
else if (type.flags & TypeFlags.Any) {
|
||||
typeSet.containsAny = true;
|
||||
else if (flags & TypeFlags.Any) {
|
||||
includes |= TypeIncludes.Any;
|
||||
}
|
||||
else if (type.flags & TypeFlags.Never) {
|
||||
typeSet.containsNever = true;
|
||||
else if (flags & TypeFlags.Never) {
|
||||
includes |= TypeIncludes.Never;
|
||||
}
|
||||
else if (getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type)) {
|
||||
typeSet.containsEmptyObject = true;
|
||||
includes |= TypeIncludes.EmptyObject;
|
||||
}
|
||||
else if ((strictNullChecks || !(type.flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
|
||||
if (type.flags & TypeFlags.Object) {
|
||||
typeSet.containsObjectType = true;
|
||||
else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) {
|
||||
if (flags & TypeFlags.Object) {
|
||||
includes |= TypeIncludes.ObjectType;
|
||||
}
|
||||
if (type.flags & TypeFlags.Union && typeSet.unionIndex === undefined) {
|
||||
typeSet.unionIndex = typeSet.length;
|
||||
if (flags & TypeFlags.Union) {
|
||||
includes |= TypeIncludes.Union;
|
||||
}
|
||||
if (!(type.flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
|
||||
if (!(flags & TypeFlags.Object && (<ObjectType>type).objectFlags & ObjectFlags.Anonymous &&
|
||||
type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) {
|
||||
typeSet.push(type);
|
||||
}
|
||||
}
|
||||
return includes;
|
||||
}
|
||||
|
||||
// Add the given types to the given type set. Order is preserved, freshness is removed from literal
|
||||
// types, duplicates are removed, and nested types of the given kind are flattened into the set.
|
||||
function addTypesToIntersection(typeSet: TypeSet, types: Type[]) {
|
||||
function addTypesToIntersection(typeSet: Type[], includes: TypeIncludes, types: Type[]) {
|
||||
for (const type of types) {
|
||||
addTypeToIntersection(typeSet, getRegularTypeOfLiteralType(type));
|
||||
includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type));
|
||||
}
|
||||
return includes;
|
||||
}
|
||||
|
||||
// We normalize combinations of intersection and union types based on the distributive property of the '&'
|
||||
@ -7747,27 +7754,27 @@ namespace ts {
|
||||
if (types.length === 0) {
|
||||
return emptyObjectType;
|
||||
}
|
||||
const typeSet = [] as TypeSet;
|
||||
addTypesToIntersection(typeSet, types);
|
||||
if (typeSet.containsNever) {
|
||||
const typeSet: Type[] = [];
|
||||
const includes = addTypesToIntersection(typeSet, 0, types);
|
||||
if (includes & TypeIncludes.Never) {
|
||||
return neverType;
|
||||
}
|
||||
if (typeSet.containsAny) {
|
||||
if (includes & TypeIncludes.Any) {
|
||||
return anyType;
|
||||
}
|
||||
if (typeSet.containsEmptyObject && !typeSet.containsObjectType) {
|
||||
if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) {
|
||||
typeSet.push(emptyObjectType);
|
||||
}
|
||||
if (typeSet.length === 1) {
|
||||
return typeSet[0];
|
||||
}
|
||||
const unionIndex = typeSet.unionIndex;
|
||||
if (unionIndex !== undefined) {
|
||||
if (includes & TypeIncludes.Union) {
|
||||
// We are attempting to construct a type of the form X & (A | B) & Y. Transform this into a type of
|
||||
// the form X & A & Y | X & B & Y and recursively reduce until no union type constituents remain.
|
||||
const unionIndex = findIndex(typeSet, t => (t.flags & TypeFlags.Union) !== 0);
|
||||
const unionType = <UnionType>typeSet[unionIndex];
|
||||
return getUnionType(map(unionType.types, t => getIntersectionType(replaceElement(typeSet, unionIndex, t))),
|
||||
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
|
||||
UnionReduction.Literal, aliasSymbol, aliasTypeArguments);
|
||||
}
|
||||
const id = getTypeListId(typeSet);
|
||||
let type = intersectionTypes.get(id);
|
||||
|
||||
@ -2778,6 +2778,10 @@ namespace ts {
|
||||
this.flags = flags;
|
||||
this.escapedName = name;
|
||||
this.declarations = undefined;
|
||||
this.valueDeclaration = undefined;
|
||||
this.id = undefined;
|
||||
this.mergeId = undefined;
|
||||
this.parent = undefined;
|
||||
}
|
||||
|
||||
function Type(this: Type, checker: TypeChecker, flags: TypeFlags) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user