Merge pull request #21573 from Microsoft/optimizeUnionIntersection

Optimize union and intersection types
This commit is contained in:
Anders Hejlsberg 2018-02-02 12:41:04 -08:00 committed by GitHub
commit 79d2772531
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 75 additions and 64 deletions

View File

@ -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);

View File

@ -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) {