From adb37a5abb629c6bfa4a2132640ba1a9b3e8672d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 7 Feb 2022 15:00:13 -0500 Subject: [PATCH] Optimize and remove more redundant elaborations --- src/compiler/checker.ts | 29 +++++++++++------------------ src/compiler/types.ts | 2 ++ 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4ac2e391f1975..ddc3fd2074fac 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18292,22 +18292,17 @@ namespace ts { return Ternary.True; } - // Try to see if we're relating something like `Foo` -> `Bar | null | undefined`. - // If so, reporting the `null` and `undefined` in the type is hardly useful. - // First, see if we're even relating an object type to a union. - // Then see if the target is stripped down to a single non-union type. - // Note - // * We actually want to remove null and undefined naively here (rather than using getNonNullableType), - // since we don't want to end up with a worse error like "`Foo` is not assignable to `NonNullable`" - // when dealing with generics. - // * We also don't deal with primitive source types, since we already halt elaboration below. - if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object && - (target as UnionType).types.length <= 3 && maybeTypeOfKind(target, TypeFlags.Nullable)) { - const nullStrippedTarget = extractTypesOfKind(target, ~TypeFlags.Nullable); - if (!(nullStrippedTarget.flags & (TypeFlags.Union | TypeFlags.Never))) { - target = getNormalizedType(nullStrippedTarget, /*writing*/ true); - } - if (source === nullStrippedTarget) return Ternary.True; + // See if we're relating a definitely non-nullable type to a union that includes null and/or undefined + // plus a single non-nullable type. If so, remove null and/or undefined from the target type. + if (source.flags & TypeFlags.DefinitelyNonNullable && target.flags & TypeFlags.Union) { + const types = (target as UnionType).types; + const candidate = types.length === 2 && types[0].flags & TypeFlags.Nullable ? types[1] : + types.length === 3 && types[0].flags & TypeFlags.Nullable && types[1].flags & TypeFlags.Nullable ? types[2] : + undefined; + if (candidate && !(candidate.flags & TypeFlags.Nullable)) { + target = getNormalizedType(candidate, /*writing*/ true); + if (source === target) return Ternary.True; + } } if (relation === comparableRelation && !(target.flags & TypeFlags.Never) && isSimpleTypeRelatedTo(target, source, relation) || @@ -18949,8 +18944,6 @@ namespace ts { return result; } if (source.flags & TypeFlags.Intersection || source.flags & TypeFlags.TypeParameter && target.flags & TypeFlags.Union) { - // (T extends 1 | 2) & 1 <=> 1 - // (T extends 1 | 2) <=> T & 1 | T & 2 // The combined constraint of an intersection type is the intersection of the constraints of // the constituents. When an intersection type contains instantiable types with union type // constraints, there are situations where we need to examine the combined constraint. One is diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 8a67511208893..5cabf3edef8be 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5174,6 +5174,8 @@ namespace ts { ESSymbolLike = ESSymbol | UniqueESSymbol, VoidLike = Void | Undefined, /* @internal */ + DefinitelyNonNullable = StringLike | NumberLike | BigIntLike | BooleanLike | EnumLike | ESSymbolLike | Object | NonPrimitive, + /* @internal */ DisjointDomains = NonPrimitive | StringLike | NumberLike | BigIntLike | BooleanLike | ESSymbolLike | VoidLike | Null, UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection,