diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index f3be433aec1..374e27f9e77 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -617,22 +617,6 @@ 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, - Wildcard = 1 << 12, - } - const enum MembersOrExportsResolutionKind { resolvedExports = "resolvedExports", resolvedMembers = "resolvedMembers" @@ -7926,35 +7910,31 @@ namespace ts { return false; } - function addTypeToUnion(typeSet: Type[], includes: TypeIncludes, type: Type) { + function addTypeToUnion(typeSet: Type[], includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Union) { - includes = addTypesToUnion(typeSet, includes, (type).types); + return addTypesToUnion(typeSet, includes, (type).types); } - else if (flags & TypeFlags.Any) { - includes |= TypeIncludes.Any; - if (type === wildcardType) includes |= TypeIncludes.Wildcard; - } - else if (!strictNullChecks && flags & TypeFlags.Nullable) { - 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(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) 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) { - if (!(flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous && - type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) { - typeSet.splice(~index, 0, 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.Never || flags & TypeFlags.Intersection && isEmptyIntersectionType(type))) { + includes |= flags & ~TypeFlags.ConstructionFlags; + if (flags & TypeFlags.Any) { + if (type === wildcardType) includes |= TypeFlags.Wildcard; + } + else if (!strictNullChecks && flags & TypeFlags.Nullable) { + if (!(flags & TypeFlags.ContainsWideningType)) includes |= TypeFlags.NonWideningType; + } + else { + const len = typeSet.length; + const index = len && type.id > typeSet[len - 1].id ? ~len : binarySearch(typeSet, type, getTypeId, compareValues); + if (index < 0) { + if (!(flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous && + type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) { + typeSet.splice(~index, 0, type); + } } } } @@ -7963,7 +7943,7 @@ namespace ts { // 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: Type[], includes: TypeIncludes, types: Type[]): TypeIncludes { + function addTypesToUnion(typeSet: Type[], includes: TypeFlags, types: Type[]): TypeFlags { for (const type of types) { includes = addTypeToUnion(typeSet, includes, type); } @@ -8020,15 +8000,15 @@ namespace ts { } } - function removeRedundantLiteralTypes(types: Type[], includes: TypeIncludes) { + function removeRedundantLiteralTypes(types: Type[], includes: TypeFlags) { let i = types.length; while (i > 0) { i--; const t = types[i]; const remove = - t.flags & TypeFlags.StringLiteral && includes & TypeIncludes.String || - t.flags & TypeFlags.NumberLiteral && includes & TypeIncludes.Number || - t.flags & TypeFlags.UniqueESSymbol && includes & TypeIncludes.ESSymbol || + t.flags & TypeFlags.StringLiteral && includes & TypeFlags.String || + t.flags & TypeFlags.NumberLiteral && includes & TypeFlags.Number || + t.flags & TypeFlags.UniqueESSymbol && includes & TypeFlags.ESSymbol || t.flags & TypeFlags.StringOrNumberLiteral && t.flags & TypeFlags.FreshLiteral && containsType(types, (t).regularType); if (remove) { orderedRemoveItemAt(types, i); @@ -8052,12 +8032,12 @@ namespace ts { } const typeSet: Type[] = []; const includes = addTypesToUnion(typeSet, 0, types); - if (includes & TypeIncludes.Any) { - return includes & TypeIncludes.Wildcard ? wildcardType : anyType; + if (includes & TypeFlags.Any) { + return includes & TypeFlags.Wildcard ? wildcardType : anyType; } switch (unionReduction) { case UnionReduction.Literal: - if (includes & TypeIncludes.LiteralOrUniqueESSymbol) { + if (includes & TypeFlags.StringOrNumberLiteralOrUnique) { removeRedundantLiteralTypes(typeSet, includes); } break; @@ -8066,8 +8046,8 @@ namespace ts { break; } if (typeSet.length === 0) { - return includes & TypeIncludes.Null ? includes & TypeIncludes.NonWideningType ? nullType : nullWideningType : - includes & TypeIncludes.Undefined ? includes & TypeIncludes.NonWideningType ? undefinedType : undefinedWideningType : + return includes & TypeFlags.Null ? includes & TypeFlags.NonWideningType ? nullType : nullWideningType : + includes & TypeFlags.Undefined ? includes & TypeFlags.NonWideningType ? undefinedType : undefinedWideningType : neverType; } return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments); @@ -8145,30 +8125,23 @@ namespace ts { return links.resolvedType; } - function addTypeToIntersection(typeSet: Type[], includes: TypeIncludes, type: Type) { + function addTypeToIntersection(typeSet: Type[], includes: TypeFlags, type: Type) { const flags = type.flags; if (flags & TypeFlags.Intersection) { - includes = addTypesToIntersection(typeSet, includes, (type).types); + return addTypesToIntersection(typeSet, includes, (type).types); } - else if (flags & TypeFlags.Any) { - includes |= TypeIncludes.Any; - if (type === wildcardType) includes |= TypeIncludes.Wildcard; + if (getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type)) { + includes |= TypeFlags.EmptyObject; } - else if (flags & TypeFlags.Never) { - includes |= TypeIncludes.Never; - } - else if (getObjectFlags(type) & ObjectFlags.Anonymous && isEmptyObjectType(type)) { - includes |= TypeIncludes.EmptyObject; - } - else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type)) { - if (flags & TypeFlags.Object) { - includes |= TypeIncludes.ObjectType; + else { + includes |= flags & ~TypeFlags.ConstructionFlags; + if (flags & TypeFlags.Any) { + if (type === wildcardType) includes |= TypeFlags.Wildcard; } - if (flags & TypeFlags.Union) { - includes |= TypeIncludes.Union; - } - if (!(flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous && - type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && containsIdenticalType(typeSet, type))) { + else if ((strictNullChecks || !(flags & TypeFlags.Nullable)) && !contains(typeSet, type) && + !(flags & TypeFlags.Object && (type).objectFlags & ObjectFlags.Anonymous && + type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method) && + containsIdenticalType(typeSet, type))) { typeSet.push(type); } } @@ -8177,13 +8150,27 @@ namespace ts { // 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: Type[], includes: TypeIncludes, types: Type[]) { + function addTypesToIntersection(typeSet: Type[], includes: TypeFlags, types: Type[]) { for (const type of types) { includes = addTypeToIntersection(typeSet, includes, getRegularTypeOfLiteralType(type)); } return includes; } + function removeRedundantPrimtiveTypes(types: Type[], includes: TypeFlags) { + let i = types.length; + while (i > 0) { + i--; + const t = types[i]; + const remove = + t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || + t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; + if (remove) { + orderedRemoveItemAt(types, i); + } + } + } // We normalize combinations of intersection and union types based on the distributive property of the '&' // operator. Specifically, because X & (A | B) is equivalent to X & A | X & B, we can transform intersection // types with union type constituents into equivalent union types with intersection type constituents and @@ -8200,19 +8187,24 @@ namespace ts { } const typeSet: Type[] = []; const includes = addTypesToIntersection(typeSet, 0, types); - if (includes & TypeIncludes.Never) { + if (includes & TypeFlags.Never) { return neverType; } - if (includes & TypeIncludes.Any) { - return includes & TypeIncludes.Wildcard ? wildcardType : anyType; + if (includes & TypeFlags.Any) { + return includes & TypeFlags.Wildcard ? wildcardType : anyType; } - if (includes & TypeIncludes.EmptyObject && !(includes & TypeIncludes.ObjectType)) { + if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || + includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || + includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { + removeRedundantPrimtiveTypes(typeSet, includes); + } + if (includes & TypeFlags.EmptyObject && !(includes & TypeFlags.Object)) { typeSet.push(emptyObjectType); } if (typeSet.length === 1) { return typeSet[0]; } - if (includes & TypeIncludes.Union) { + if (includes & TypeFlags.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); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 2aea2e29742..c0d9b0ed9af 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3636,7 +3636,15 @@ namespace ts { RequiresWidening = ContainsWideningType | ContainsObjectLiteral, /* @internal */ PropagatingFlags = ContainsWideningType | ContainsObjectLiteral | ContainsAnyFunctionType, + // The following flags are used for different purposes during union and intersection type construction /* @internal */ + NonWideningType = ContainsWideningType, + /* @internal */ + Wildcard = ContainsObjectLiteral, + /* @internal */ + EmptyObject = ContainsAnyFunctionType, + /* @internal */ + ConstructionFlags = NonWideningType | Wildcard | EmptyObject } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;