From 922d47e23ffa42de2a1e722f42942a150a939a18 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Thu, 9 Mar 2023 16:29:40 -0800 Subject: [PATCH 1/2] Fast path for negative case when relating to unions of primtives --- src/compiler/checker.ts | 24 +++++++++++++++++------- src/compiler/types.ts | 2 +- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5359974ff87f3..68258b090b904 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16548,7 +16548,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; @@ -16584,10 +16584,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)); @@ -16719,12 +16715,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); } @@ -20837,6 +20833,20 @@ 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 string 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. + 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 936fe425cc1e4..1ad296d38f19f 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; From 059a1f06c928ed693260773fa3af77b54b8d5634 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 10 Mar 2023 07:07:40 -0800 Subject: [PATCH 2/2] Fix comment --- src/compiler/checker.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 68258b090b904..4f40d1a8a4992 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -20837,9 +20837,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { 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 string literal type in one of its fresh/regular forms. + // 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. + // 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 :