diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 16b2091733a..d2ae5b4cea8 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -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" @@ -5516,6 +5531,8 @@ namespace ts { sig.minArgumentCount = minArgumentCount; sig.hasRestParameter = hasRestParameter; sig.hasLiteralTypes = hasLiteralTypes; + sig.target = undefined; + sig.mapper = undefined; return sig; } @@ -7419,21 +7436,6 @@ namespace ts { return links.resolvedType; } - interface TypeSet extends Array { - 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; } @@ -7458,28 +7460,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, (type).types); + includes = addTypesToUnion(typeSet, includes, (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(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) { @@ -7489,14 +7491,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) { @@ -7520,7 +7524,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); @@ -7536,7 +7540,7 @@ namespace ts { return false; } - function removeSubtypes(types: TypeSet) { + function removeSubtypes(types: Type[]) { if (types.length === 0 || isSetOfLiteralsFromSameEnum(types)) { return; } @@ -7549,15 +7553,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, (t).regularType); if (remove) { orderedRemoveItemAt(types, i); @@ -7579,15 +7583,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: @@ -7595,8 +7599,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); @@ -7674,39 +7678,42 @@ namespace ts { return links.resolvedType; } - function addTypeToIntersection(typeSet: TypeSet, type: Type) { - if (type.flags & TypeFlags.Intersection) { - addTypesToIntersection(typeSet, (type).types); + function addTypeToIntersection(typeSet: Type[], includes: TypeIncludes, type: Type) { + const flags = type.flags; + if (flags & TypeFlags.Intersection) { + includes = addTypesToIntersection(typeSet, includes, (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 && (type).objectFlags & ObjectFlags.Anonymous && + if (!(flags & TypeFlags.Object && (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 '&' @@ -7723,27 +7730,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 = 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);