From a70c4095dbf9a6f089738778487352afa1205d30 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 17 Mar 2023 08:10:48 -0700 Subject: [PATCH] Fast path for negative case when relating to unions of primtives (#53192) --- src/compiler/checker.ts | 25 ++++++++++++++++++------- src/compiler/types.ts | 2 +- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 57d15bdac2b..0d4c8c7459d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16582,7 +16582,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { for (const u of unionTypes) { if (!containsType(u.types, type)) { const primitive = type.flags & TypeFlags.StringLiteral ? stringType : - type.flags & TypeFlags.NumberLiteral ? numberType : + type.flags & (TypeFlags.Enum | TypeFlags.NumberLiteral) ? numberType : type.flags & TypeFlags.BigIntLiteral ? bigintType : type.flags & TypeFlags.UniqueESSymbol ? esSymbolType : undefined; @@ -16618,10 +16618,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return false; } - function eachIsUnionContaining(types: Type[], flag: TypeFlags) { - return every(types, t => !!(t.flags & TypeFlags.Union) && some((t as UnionType).types, tt => !!(tt.flags & flag))); - } - function removeFromEach(types: Type[], flag: TypeFlags) { for (let i = 0; i < types.length; i++) { types[i] = filterType(types[i], t => !(t.flags & flag)); @@ -16753,12 +16749,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // reduced we'll never reduce again, so this occurs at most once. result = getIntersectionType(typeSet, aliasSymbol, aliasTypeArguments); } - else if (eachIsUnionContaining(typeSet, TypeFlags.Undefined)) { + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && (t as UnionType).types[0].flags & TypeFlags.Undefined))) { const containedUndefinedType = some(typeSet, containsMissingType) ? missingType : undefinedType; removeFromEach(typeSet, TypeFlags.Undefined); result = getUnionType([getIntersectionType(typeSet), containedUndefinedType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } - else if (eachIsUnionContaining(typeSet, TypeFlags.Null)) { + else if (every(typeSet, t => !!(t.flags & TypeFlags.Union && ((t as UnionType).types[0].flags & TypeFlags.Null || (t as UnionType).types[1].flags & TypeFlags.Null)))) { removeFromEach(typeSet, TypeFlags.Null); result = getUnionType([getIntersectionType(typeSet), nullType], UnionReduction.Literal, aliasSymbol, aliasTypeArguments); } @@ -20855,6 +20851,21 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { if (containsType(targetTypes, source)) { return Ternary.True; } + if (getObjectFlags(target) & ObjectFlags.PrimitiveUnion && !(source.flags & TypeFlags.EnumLiteral) && ( + source.flags & (TypeFlags.StringLiteral | TypeFlags.BooleanLiteral | TypeFlags.BigIntLiteral) || + (relation === subtypeRelation || relation === strictSubtypeRelation) && source.flags & TypeFlags.NumberLiteral)) { + // When relating a literal type to a union of primitive types, we know the relation is false unless + // the union contains the base primitive type or the literal type in one of its fresh/regular forms. + // We exclude numeric literals for non-subtype relations because numeric literals are assignable to + // numeric enum literals with the same value. Similarly, we exclude enum literal types because + // identically named enum types are related (see isEmumTypeRelatedTo). + const alternateForm = source === (source as StringLiteralType).regularType ? (source as StringLiteralType).freshType : (source as StringLiteralType).regularType; + const primitive = source.flags & TypeFlags.StringLiteral ? stringType : + source.flags & TypeFlags.NumberLiteral ? numberType : + source.flags & TypeFlags.BigIntLiteral ? bigintType : + undefined; + return primitive && containsType(targetTypes, primitive) || alternateForm && containsType(targetTypes, alternateForm) ? Ternary.True : Ternary.False; + } const match = getMatchingUnionConstituentForType(target as UnionType, source); if (match) { const related = isRelatedTo(source, match, RecursionFlags.Target, /*reportErrors*/ false); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b84d13a0642..68caffea128 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -6130,7 +6130,7 @@ export const enum TypeFlags { /** @internal */ IncludesInstantiable = Substitution, /** @internal */ - NotPrimitiveUnion = Any | Unknown | Enum | Void | Never | Object | Intersection | IncludesInstantiable, + NotPrimitiveUnion = Any | Unknown | Void | Never | Object | Intersection | IncludesInstantiable, } export type DestructuringPattern = BindingPattern | ObjectLiteralExpression | ArrayLiteralExpression;