From a424002d1d6c81b9b51c0a96b6721a4d3239dd4b Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Sat, 26 Sep 2026 20:10:45 -0400 Subject: [PATCH 1/4] Check intersections for never-reduction without creating every property getReducedType decided whether an intersection reduces to never by creating the combined property of every property name of every constituent (getPropertiesOfUnionOrIntersectionType). In large intersections most of those synthetic properties, and the intersections of their types, are never used again. Only a combination of distinct constituent properties where one has a literal type (a discriminant), a private property, or a constituent property that is itself such a combination can satisfy isNeverReducedProperty. isNeverReducedIntersection now collects the constituent properties of each name as createUnionOrIntersectionProperty does, including comparing instantiations of the same property, and only creates the combined property for those names. When the properties of the intersection are already resolved it checks them as before. Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 71 +++- ...sectionNeverReductionByProperty.errors.txt | 138 ++++++++ ...tersectionNeverReductionByProperty.symbols | 331 ++++++++++++++++++ ...intersectionNeverReductionByProperty.types | 315 +++++++++++++++++ .../intersectionNeverReductionByProperty.ts | 95 +++++ 5 files changed, 949 insertions(+), 1 deletion(-) create mode 100644 tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.errors.txt create mode 100644 tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.symbols create mode 100644 tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.types create mode 100644 tsc/testdata/tests/cases/compiler/intersectionNeverReductionByProperty.ts diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 845f62365bb84..9b9b49ea1f571 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -22189,7 +22189,7 @@ func (c *Checker) getReducedType(t *Type) *Type { case t.flags&TypeFlagsIntersection != 0: if t.objectFlags&ObjectFlagsIsNeverIntersectionComputed == 0 { t.objectFlags |= ObjectFlagsIsNeverIntersectionComputed - if core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) { + if c.isNeverReducedIntersection(t) { t.objectFlags |= ObjectFlagsIsNeverIntersection } } @@ -22216,6 +22216,75 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool { return c.isDiscriminantWithNeverType(prop) || isConflictingPrivateProperty(prop) } +// isNeverReducedIntersection returns +// core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) +// without creating the combined properties that can't reduce the intersection +// to never, which in large intersections are most of them. +func (c *Checker) isNeverReducedIntersection(t *Type) bool { + if props := t.AsUnionOrIntersectionType().resolvedProperties; props != nil { + return core.Some(props, c.isNeverReducedProperty) + } + var checked collections.Set[string] + for _, current := range t.Types() { + for _, prop := range c.getPropertiesOfType(current) { + if checked.Has(prop.Name) { + continue + } + checked.Add(prop.Name) + if !c.mayBeNeverReducedProperty(t, prop.Name) { + continue + } + if combined := c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/); combined != nil && c.isNeverReducedProperty(combined) { + return true + } + } + } + return false +} + +// mayBeNeverReducedProperty reports whether the property that +// createUnionOrIntersectionProperty combines for name in the intersection t +// can satisfy isNeverReducedProperty. That takes distinct constituent +// properties where one has a literal type (a discriminant), a private +// property, or a constituent property that is itself such a combination. +// Constituent properties are collected as createUnionOrIntersectionProperty +// does. +func (c *Checker) mayBeNeverReducedProperty(t *Type, name string) bool { + var singleProp *ast.Symbol + var propSet collections.OrderedSet[*ast.Symbol] + for _, current := range t.Types() { + apparent := c.getApparentType(current) + if c.isErrorType(apparent) || apparent.flags&TypeFlagsNever != 0 { + continue + } + prop := c.getPropertyOfTypeEx(apparent, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) + if prop == nil { + continue + } + if prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 || getDeclarationModifierFlagsFromSymbol(prop)&ast.ModifierFlagsPrivate != 0 { + return true + } + if singleProp == nil { + singleProp = prop + } else if prop != singleProp { + if c.getTargetSymbol(prop) == c.getTargetSymbol(singleProp) && c.compareProperties(singleProp, prop, compareTypesEqual) == TernaryTrue { + continue + } + if propSet.Size() == 0 { + propSet.Add(singleProp) + } + propSet.Add(prop) + } + } + for prop := range propSet.Values() { + propType := c.getTypeOfSymbol(prop) + if isLiteralType(propType) || c.isPatternLiteralType(propType) { + return true + } + } + return false +} + func (c *Checker) getReducedApparentType(t *Type) *Type { // Since getApparentType may return a non-reduced union or intersection type, we need to perform // type reduction both before and after obtaining the apparent type. For example, given a type parameter diff --git a/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.errors.txt b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.errors.txt new file mode 100644 index 0000000000000..1894b4ed94bd9 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.errors.txt @@ -0,0 +1,138 @@ +intersectionNeverReductionByProperty.ts(24,4): error TS2339: Property 'kind' does not exist on type 'never'. + The intersection 'A1' was reduced to 'never' because property 'kind' has conflicting types in some constituents. +intersectionNeverReductionByProperty.ts(30,4): error TS2339: Property 'flag' does not exist on type 'never'. + The intersection 'A7' was reduced to 'never' because property 'flag' has conflicting types in some constituents. +intersectionNeverReductionByProperty.ts(31,4): error TS2339: Property 'tag' does not exist on type 'never'. + The intersection 'A8' was reduced to 'never' because property 'tag' has conflicting types in some constituents. +intersectionNeverReductionByProperty.ts(58,4): error TS2339: Property 'x' does not exist on type 'never'. + The intersection 'P1 & P2' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some. +intersectionNeverReductionByProperty.ts(59,4): error TS2445: Property 'x' is protected and only accessible within class 'Q1 & Q2' and its subclasses. +intersectionNeverReductionByProperty.ts(63,4): error TS2339: Property 'kind' does not exist on type 'never'. + The intersection 'Generic<"a"> & Generic<"b">' was reduced to 'never' because property 'value' has conflicting types in some constituents. +intersectionNeverReductionByProperty.ts(70,4): error TS2339: Property 'kind' does not exist on type 'never'. + The intersection 'Tagged<"b">' was reduced to 'never' because property 'kind' has conflicting types in some constituents. +intersectionNeverReductionByProperty.ts(74,7): error TS2339: Property 'kind' does not exist on type 'never'. + Property 'kind' does not exist on type 'never'. +intersectionNeverReductionByProperty.ts(90,4): error TS2339: Property 'p1' does not exist on type 'never'. + The intersection 'Many & { kind: "other"; }' was reduced to 'never' because property 'kind' has conflicting types in some constituents. + + +==== intersectionNeverReductionByProperty.ts (9 errors) ==== + // Intersections that are and aren't reduced to never because of conflicting + // discriminants or private properties. + + type A1 = { kind: "a"; x: string } & { kind: "b"; y: string }; + type A2 = { kind: "a" | "b"; x: string } & { kind: "b"; y: string }; + type A3 = { kind?: "a" } & { kind?: "b" }; + type A4 = { kind: "a" } & { kind: string }; + type A5 = { kind: `a${string}` } & { kind: `b${string}` }; + type A6 = { kind: never } & { kind: "a" }; + type A7 = { flag: true } & { flag: false }; + type A8 = { tag: 1; a: string } & { b: number } & { tag: 2; c: boolean }; + type A9 = { x: string } & { x: number }; + + declare const a1: A1; + declare const a2: A2; + declare const a3: A3; + declare const a4: A4; + declare const a5: A5; + declare const a6: A6; + declare const a7: A7; + declare const a8: A8; + declare const a9: A9; + + a1.kind; + ~~~~ +!!! error TS2339: Property 'kind' does not exist on type 'never'. +!!! error TS2339: The intersection 'A1' was reduced to 'never' because property 'kind' has conflicting types in some constituents. + a2.kind; + a3.kind; + a4.kind; + a5.kind; + a6.kind; + a7.flag; + ~~~~ +!!! error TS2339: Property 'flag' does not exist on type 'never'. +!!! error TS2339: The intersection 'A7' was reduced to 'never' because property 'flag' has conflicting types in some constituents. + a8.tag; + ~~~ +!!! error TS2339: Property 'tag' does not exist on type 'never'. +!!! error TS2339: The intersection 'A8' was reduced to 'never' because property 'tag' has conflicting types in some constituents. + a9.x; + + class P1 { + private x = 1; + } + class P2 { + private x = 1; + } + class Q1 { + protected x = 1; + } + class Q2 { + protected x = 1; + } + class Generic { + private value!: T; + kind!: T; + } + + declare const b1: P1 & P2; + declare const b2: Q1 & Q2; + declare const b3: P1 & { y: string }; + declare const b4: Generic & Generic; + declare const b5: Generic & Generic; + declare const b6: Generic<"a"> & Generic<"b">; + + b1.x; + ~ +!!! error TS2339: Property 'x' does not exist on type 'never'. +!!! error TS2339: The intersection 'P1 & P2' was reduced to 'never' because property 'x' exists in multiple constituents and is private in some. + b2.x; + ~ +!!! error TS2445: Property 'x' is protected and only accessible within class 'Q1 & Q2' and its subclasses. + b3.y; + b4.kind; + b5.kind; + b6.kind; + ~~~~ +!!! error TS2339: Property 'kind' does not exist on type 'never'. +!!! error TS2339: The intersection 'Generic<"a"> & Generic<"b">' was reduced to 'never' because property 'value' has conflicting types in some constituents. + + type Tagged = { kind: T } & { kind: "a" }; + declare const c1: Tagged<"a">; + declare const c2: Tagged<"b">; + declare const c3: Tagged; + c1.kind; + c2.kind; + ~~~~ +!!! error TS2339: Property 'kind' does not exist on type 'never'. +!!! error TS2339: The intersection 'Tagged<"b">' was reduced to 'never' because property 'kind' has conflicting types in some constituents. + c3.kind; + + function f(x: T & { kind: "c" }, y: T & { kind: "a" }) { + x.kind; + ~~~~ +!!! error TS2339: Property 'kind' does not exist on type 'never'. +!!! error TS2339: Property 'kind' does not exist on type 'never'. + y.kind; + } + + interface Many { + p1: string; + p2: number; + p3: boolean; + p4: string[]; + p5: () => void; + kind: "many"; + } + declare const d1: Many & { kind: "many"; extra: string }; + declare const d2: Many & { kind: "other" }; + declare const d3: Many & Partial & { p1: "literal" }; + d1.extra; + d2.p1; + ~~ +!!! error TS2339: Property 'p1' does not exist on type 'never'. +!!! error TS2339: The intersection 'Many & { kind: "other"; }' was reduced to 'never' because property 'kind' has conflicting types in some constituents. + d3.p1; + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.symbols b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.symbols new file mode 100644 index 0000000000000..0da725cfab748 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.symbols @@ -0,0 +1,331 @@ +//// [tests/cases/compiler/intersectionNeverReductionByProperty.ts] //// + +=== intersectionNeverReductionByProperty.ts === +// Intersections that are and aren't reduced to never because of conflicting +// discriminants or private properties. + +type A1 = { kind: "a"; x: string } & { kind: "b"; y: string }; +>A1 : Symbol(A1, Decl(intersectionNeverReductionByProperty.ts, 0, 0)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 3, 11)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 3, 22)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 3, 38)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 3, 49)) + +type A2 = { kind: "a" | "b"; x: string } & { kind: "b"; y: string }; +>A2 : Symbol(A2, Decl(intersectionNeverReductionByProperty.ts, 3, 62)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 4, 11)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 4, 28)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 4, 44)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 4, 55)) + +type A3 = { kind?: "a" } & { kind?: "b" }; +>A3 : Symbol(A3, Decl(intersectionNeverReductionByProperty.ts, 4, 68)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 5, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 5, 28)) + +type A4 = { kind: "a" } & { kind: string }; +>A4 : Symbol(A4, Decl(intersectionNeverReductionByProperty.ts, 5, 42)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 6, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 6, 27)) + +type A5 = { kind: `a${string}` } & { kind: `b${string}` }; +>A5 : Symbol(A5, Decl(intersectionNeverReductionByProperty.ts, 6, 43)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 7, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 7, 36)) + +type A6 = { kind: never } & { kind: "a" }; +>A6 : Symbol(A6, Decl(intersectionNeverReductionByProperty.ts, 7, 58)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 8, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 8, 29)) + +type A7 = { flag: true } & { flag: false }; +>A7 : Symbol(A7, Decl(intersectionNeverReductionByProperty.ts, 8, 42)) +>flag : Symbol(flag, Decl(intersectionNeverReductionByProperty.ts, 9, 11)) +>flag : Symbol(flag, Decl(intersectionNeverReductionByProperty.ts, 9, 28)) + +type A8 = { tag: 1; a: string } & { b: number } & { tag: 2; c: boolean }; +>A8 : Symbol(A8, Decl(intersectionNeverReductionByProperty.ts, 9, 43)) +>tag : Symbol(tag, Decl(intersectionNeverReductionByProperty.ts, 10, 11)) +>a : Symbol(a, Decl(intersectionNeverReductionByProperty.ts, 10, 19)) +>b : Symbol(b, Decl(intersectionNeverReductionByProperty.ts, 10, 35)) +>tag : Symbol(tag, Decl(intersectionNeverReductionByProperty.ts, 10, 51)) +>c : Symbol(c, Decl(intersectionNeverReductionByProperty.ts, 10, 59)) + +type A9 = { x: string } & { x: number }; +>A9 : Symbol(A9, Decl(intersectionNeverReductionByProperty.ts, 10, 73)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 11, 11)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 11, 27)) + +declare const a1: A1; +>a1 : Symbol(a1, Decl(intersectionNeverReductionByProperty.ts, 13, 13)) +>A1 : Symbol(A1, Decl(intersectionNeverReductionByProperty.ts, 0, 0)) + +declare const a2: A2; +>a2 : Symbol(a2, Decl(intersectionNeverReductionByProperty.ts, 14, 13)) +>A2 : Symbol(A2, Decl(intersectionNeverReductionByProperty.ts, 3, 62)) + +declare const a3: A3; +>a3 : Symbol(a3, Decl(intersectionNeverReductionByProperty.ts, 15, 13)) +>A3 : Symbol(A3, Decl(intersectionNeverReductionByProperty.ts, 4, 68)) + +declare const a4: A4; +>a4 : Symbol(a4, Decl(intersectionNeverReductionByProperty.ts, 16, 13)) +>A4 : Symbol(A4, Decl(intersectionNeverReductionByProperty.ts, 5, 42)) + +declare const a5: A5; +>a5 : Symbol(a5, Decl(intersectionNeverReductionByProperty.ts, 17, 13)) +>A5 : Symbol(A5, Decl(intersectionNeverReductionByProperty.ts, 6, 43)) + +declare const a6: A6; +>a6 : Symbol(a6, Decl(intersectionNeverReductionByProperty.ts, 18, 13)) +>A6 : Symbol(A6, Decl(intersectionNeverReductionByProperty.ts, 7, 58)) + +declare const a7: A7; +>a7 : Symbol(a7, Decl(intersectionNeverReductionByProperty.ts, 19, 13)) +>A7 : Symbol(A7, Decl(intersectionNeverReductionByProperty.ts, 8, 42)) + +declare const a8: A8; +>a8 : Symbol(a8, Decl(intersectionNeverReductionByProperty.ts, 20, 13)) +>A8 : Symbol(A8, Decl(intersectionNeverReductionByProperty.ts, 9, 43)) + +declare const a9: A9; +>a9 : Symbol(a9, Decl(intersectionNeverReductionByProperty.ts, 21, 13)) +>A9 : Symbol(A9, Decl(intersectionNeverReductionByProperty.ts, 10, 73)) + +a1.kind; +>a1 : Symbol(a1, Decl(intersectionNeverReductionByProperty.ts, 13, 13)) + +a2.kind; +>a2.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 4, 11), Decl(intersectionNeverReductionByProperty.ts, 4, 44)) +>a2 : Symbol(a2, Decl(intersectionNeverReductionByProperty.ts, 14, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 4, 11), Decl(intersectionNeverReductionByProperty.ts, 4, 44)) + +a3.kind; +>a3.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 5, 11), Decl(intersectionNeverReductionByProperty.ts, 5, 28)) +>a3 : Symbol(a3, Decl(intersectionNeverReductionByProperty.ts, 15, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 5, 11), Decl(intersectionNeverReductionByProperty.ts, 5, 28)) + +a4.kind; +>a4.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 6, 11), Decl(intersectionNeverReductionByProperty.ts, 6, 27)) +>a4 : Symbol(a4, Decl(intersectionNeverReductionByProperty.ts, 16, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 6, 11), Decl(intersectionNeverReductionByProperty.ts, 6, 27)) + +a5.kind; +>a5.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 7, 11), Decl(intersectionNeverReductionByProperty.ts, 7, 36)) +>a5 : Symbol(a5, Decl(intersectionNeverReductionByProperty.ts, 17, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 7, 11), Decl(intersectionNeverReductionByProperty.ts, 7, 36)) + +a6.kind; +>a6.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 8, 11), Decl(intersectionNeverReductionByProperty.ts, 8, 29)) +>a6 : Symbol(a6, Decl(intersectionNeverReductionByProperty.ts, 18, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 8, 11), Decl(intersectionNeverReductionByProperty.ts, 8, 29)) + +a7.flag; +>a7 : Symbol(a7, Decl(intersectionNeverReductionByProperty.ts, 19, 13)) + +a8.tag; +>a8 : Symbol(a8, Decl(intersectionNeverReductionByProperty.ts, 20, 13)) + +a9.x; +>a9.x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 11, 11), Decl(intersectionNeverReductionByProperty.ts, 11, 27)) +>a9 : Symbol(a9, Decl(intersectionNeverReductionByProperty.ts, 21, 13)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 11, 11), Decl(intersectionNeverReductionByProperty.ts, 11, 27)) + +class P1 { +>P1 : Symbol(P1, Decl(intersectionNeverReductionByProperty.ts, 31, 5)) + + private x = 1; +>x : Symbol(P1.x, Decl(intersectionNeverReductionByProperty.ts, 33, 10)) +} +class P2 { +>P2 : Symbol(P2, Decl(intersectionNeverReductionByProperty.ts, 35, 1)) + + private x = 1; +>x : Symbol(P2.x, Decl(intersectionNeverReductionByProperty.ts, 36, 10)) +} +class Q1 { +>Q1 : Symbol(Q1, Decl(intersectionNeverReductionByProperty.ts, 38, 1)) + + protected x = 1; +>x : Symbol(Q1.x, Decl(intersectionNeverReductionByProperty.ts, 39, 10)) +} +class Q2 { +>Q2 : Symbol(Q2, Decl(intersectionNeverReductionByProperty.ts, 41, 1)) + + protected x = 1; +>x : Symbol(Q2.x, Decl(intersectionNeverReductionByProperty.ts, 42, 10)) +} +class Generic { +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 45, 14)) + + private value!: T; +>value : Symbol(Generic.value, Decl(intersectionNeverReductionByProperty.ts, 45, 18)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 45, 14)) + + kind!: T; +>kind : Symbol(Generic.kind, Decl(intersectionNeverReductionByProperty.ts, 46, 22)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 45, 14)) +} + +declare const b1: P1 & P2; +>b1 : Symbol(b1, Decl(intersectionNeverReductionByProperty.ts, 50, 13)) +>P1 : Symbol(P1, Decl(intersectionNeverReductionByProperty.ts, 31, 5)) +>P2 : Symbol(P2, Decl(intersectionNeverReductionByProperty.ts, 35, 1)) + +declare const b2: Q1 & Q2; +>b2 : Symbol(b2, Decl(intersectionNeverReductionByProperty.ts, 51, 13)) +>Q1 : Symbol(Q1, Decl(intersectionNeverReductionByProperty.ts, 38, 1)) +>Q2 : Symbol(Q2, Decl(intersectionNeverReductionByProperty.ts, 41, 1)) + +declare const b3: P1 & { y: string }; +>b3 : Symbol(b3, Decl(intersectionNeverReductionByProperty.ts, 52, 13)) +>P1 : Symbol(P1, Decl(intersectionNeverReductionByProperty.ts, 31, 5)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 52, 24)) + +declare const b4: Generic & Generic; +>b4 : Symbol(b4, Decl(intersectionNeverReductionByProperty.ts, 53, 13)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) + +declare const b5: Generic & Generic; +>b5 : Symbol(b5, Decl(intersectionNeverReductionByProperty.ts, 54, 13)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) + +declare const b6: Generic<"a"> & Generic<"b">; +>b6 : Symbol(b6, Decl(intersectionNeverReductionByProperty.ts, 55, 13)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) +>Generic : Symbol(Generic, Decl(intersectionNeverReductionByProperty.ts, 44, 1)) + +b1.x; +>b1 : Symbol(b1, Decl(intersectionNeverReductionByProperty.ts, 50, 13)) + +b2.x; +>b2.x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 39, 10), Decl(intersectionNeverReductionByProperty.ts, 42, 10)) +>b2 : Symbol(b2, Decl(intersectionNeverReductionByProperty.ts, 51, 13)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 39, 10), Decl(intersectionNeverReductionByProperty.ts, 42, 10)) + +b3.y; +>b3.y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 52, 24)) +>b3 : Symbol(b3, Decl(intersectionNeverReductionByProperty.ts, 52, 13)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 52, 24)) + +b4.kind; +>b4.kind : Symbol(Generic.kind, Decl(intersectionNeverReductionByProperty.ts, 46, 22)) +>b4 : Symbol(b4, Decl(intersectionNeverReductionByProperty.ts, 53, 13)) +>kind : Symbol(Generic.kind, Decl(intersectionNeverReductionByProperty.ts, 46, 22)) + +b5.kind; +>b5.kind : Symbol(Generic.kind, Decl(intersectionNeverReductionByProperty.ts, 46, 22)) +>b5 : Symbol(b5, Decl(intersectionNeverReductionByProperty.ts, 54, 13)) +>kind : Symbol(Generic.kind, Decl(intersectionNeverReductionByProperty.ts, 46, 22)) + +b6.kind; +>b6 : Symbol(b6, Decl(intersectionNeverReductionByProperty.ts, 55, 13)) + +type Tagged = { kind: T } & { kind: "a" }; +>Tagged : Symbol(Tagged, Decl(intersectionNeverReductionByProperty.ts, 62, 8)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 64, 12)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 18)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 64, 12)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 32)) + +declare const c1: Tagged<"a">; +>c1 : Symbol(c1, Decl(intersectionNeverReductionByProperty.ts, 65, 13)) +>Tagged : Symbol(Tagged, Decl(intersectionNeverReductionByProperty.ts, 62, 8)) + +declare const c2: Tagged<"b">; +>c2 : Symbol(c2, Decl(intersectionNeverReductionByProperty.ts, 66, 13)) +>Tagged : Symbol(Tagged, Decl(intersectionNeverReductionByProperty.ts, 62, 8)) + +declare const c3: Tagged; +>c3 : Symbol(c3, Decl(intersectionNeverReductionByProperty.ts, 67, 13)) +>Tagged : Symbol(Tagged, Decl(intersectionNeverReductionByProperty.ts, 62, 8)) + +c1.kind; +>c1.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 18), Decl(intersectionNeverReductionByProperty.ts, 64, 32)) +>c1 : Symbol(c1, Decl(intersectionNeverReductionByProperty.ts, 65, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 18), Decl(intersectionNeverReductionByProperty.ts, 64, 32)) + +c2.kind; +>c2 : Symbol(c2, Decl(intersectionNeverReductionByProperty.ts, 66, 13)) + +c3.kind; +>c3.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 18), Decl(intersectionNeverReductionByProperty.ts, 64, 32)) +>c3 : Symbol(c3, Decl(intersectionNeverReductionByProperty.ts, 67, 13)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 64, 18), Decl(intersectionNeverReductionByProperty.ts, 64, 32)) + +function f(x: T & { kind: "c" }, y: T & { kind: "a" }) { +>f : Symbol(f, Decl(intersectionNeverReductionByProperty.ts, 70, 8)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 72, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 22)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 38)) +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 72, 52)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 72, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 60)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 72, 73)) +>T : Symbol(T, Decl(intersectionNeverReductionByProperty.ts, 72, 11)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 82)) + + x.kind; +>x : Symbol(x, Decl(intersectionNeverReductionByProperty.ts, 72, 52)) + + y.kind; +>y.kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 22), Decl(intersectionNeverReductionByProperty.ts, 72, 82)) +>y : Symbol(y, Decl(intersectionNeverReductionByProperty.ts, 72, 73)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 72, 22), Decl(intersectionNeverReductionByProperty.ts, 72, 82)) +} + +interface Many { +>Many : Symbol(Many, Decl(intersectionNeverReductionByProperty.ts, 75, 1)) + + p1: string; +>p1 : Symbol(Many.p1, Decl(intersectionNeverReductionByProperty.ts, 77, 16)) + + p2: number; +>p2 : Symbol(Many.p2, Decl(intersectionNeverReductionByProperty.ts, 78, 15)) + + p3: boolean; +>p3 : Symbol(Many.p3, Decl(intersectionNeverReductionByProperty.ts, 79, 15)) + + p4: string[]; +>p4 : Symbol(Many.p4, Decl(intersectionNeverReductionByProperty.ts, 80, 16)) + + p5: () => void; +>p5 : Symbol(Many.p5, Decl(intersectionNeverReductionByProperty.ts, 81, 17)) + + kind: "many"; +>kind : Symbol(Many.kind, Decl(intersectionNeverReductionByProperty.ts, 82, 19)) +} +declare const d1: Many & { kind: "many"; extra: string }; +>d1 : Symbol(d1, Decl(intersectionNeverReductionByProperty.ts, 85, 13)) +>Many : Symbol(Many, Decl(intersectionNeverReductionByProperty.ts, 75, 1)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 85, 26)) +>extra : Symbol(extra, Decl(intersectionNeverReductionByProperty.ts, 85, 40)) + +declare const d2: Many & { kind: "other" }; +>d2 : Symbol(d2, Decl(intersectionNeverReductionByProperty.ts, 86, 13)) +>Many : Symbol(Many, Decl(intersectionNeverReductionByProperty.ts, 75, 1)) +>kind : Symbol(kind, Decl(intersectionNeverReductionByProperty.ts, 86, 26)) + +declare const d3: Many & Partial & { p1: "literal" }; +>d3 : Symbol(d3, Decl(intersectionNeverReductionByProperty.ts, 87, 13)) +>Many : Symbol(Many, Decl(intersectionNeverReductionByProperty.ts, 75, 1)) +>Partial : Symbol(Partial, Decl(lib.es5.d.ts, --, --)) +>Many : Symbol(Many, Decl(intersectionNeverReductionByProperty.ts, 75, 1)) +>p1 : Symbol(p1, Decl(intersectionNeverReductionByProperty.ts, 87, 42)) + +d1.extra; +>d1.extra : Symbol(extra, Decl(intersectionNeverReductionByProperty.ts, 85, 40)) +>d1 : Symbol(d1, Decl(intersectionNeverReductionByProperty.ts, 85, 13)) +>extra : Symbol(extra, Decl(intersectionNeverReductionByProperty.ts, 85, 40)) + +d2.p1; +>d2 : Symbol(d2, Decl(intersectionNeverReductionByProperty.ts, 86, 13)) + +d3.p1; +>d3.p1 : Symbol(p1, Decl(intersectionNeverReductionByProperty.ts, 77, 16), Decl(intersectionNeverReductionByProperty.ts, 87, 42)) +>d3 : Symbol(d3, Decl(intersectionNeverReductionByProperty.ts, 87, 13)) +>p1 : Symbol(p1, Decl(intersectionNeverReductionByProperty.ts, 77, 16), Decl(intersectionNeverReductionByProperty.ts, 87, 42)) + diff --git a/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.types b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.types new file mode 100644 index 0000000000000..962507c1efa68 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/intersectionNeverReductionByProperty.types @@ -0,0 +1,315 @@ +//// [tests/cases/compiler/intersectionNeverReductionByProperty.ts] //// + +=== intersectionNeverReductionByProperty.ts === +// Intersections that are and aren't reduced to never because of conflicting +// discriminants or private properties. + +type A1 = { kind: "a"; x: string } & { kind: "b"; y: string }; +>A1 : never +>kind : "a" +>x : string +>kind : "b" +>y : string + +type A2 = { kind: "a" | "b"; x: string } & { kind: "b"; y: string }; +>A2 : A2 +>kind : "a" | "b" +>x : string +>kind : "b" +>y : string + +type A3 = { kind?: "a" } & { kind?: "b" }; +>A3 : A3 +>kind : "a" | undefined +>kind : "b" | undefined + +type A4 = { kind: "a" } & { kind: string }; +>A4 : A4 +>kind : "a" +>kind : string + +type A5 = { kind: `a${string}` } & { kind: `b${string}` }; +>A5 : A5 +>kind : `a${string}` +>kind : `b${string}` + +type A6 = { kind: never } & { kind: "a" }; +>A6 : A6 +>kind : never +>kind : "a" + +type A7 = { flag: true } & { flag: false }; +>A7 : never +>flag : true +>true : true +>flag : false +>false : false + +type A8 = { tag: 1; a: string } & { b: number } & { tag: 2; c: boolean }; +>A8 : never +>tag : 1 +>a : string +>b : number +>tag : 2 +>c : boolean + +type A9 = { x: string } & { x: number }; +>A9 : A9 +>x : string +>x : number + +declare const a1: A1; +>a1 : never + +declare const a2: A2; +>a2 : A2 + +declare const a3: A3; +>a3 : A3 + +declare const a4: A4; +>a4 : A4 + +declare const a5: A5; +>a5 : A5 + +declare const a6: A6; +>a6 : A6 + +declare const a7: A7; +>a7 : never + +declare const a8: A8; +>a8 : never + +declare const a9: A9; +>a9 : A9 + +a1.kind; +>a1.kind : any +>a1 : never +>kind : any + +a2.kind; +>a2.kind : "b" +>a2 : A2 +>kind : "b" + +a3.kind; +>a3.kind : undefined +>a3 : A3 +>kind : undefined + +a4.kind; +>a4.kind : "a" +>a4 : A4 +>kind : "a" + +a5.kind; +>a5.kind : `a${string}` & `b${string}` +>a5 : A5 +>kind : `a${string}` & `b${string}` + +a6.kind; +>a6.kind : never +>a6 : A6 +>kind : never + +a7.flag; +>a7.flag : any +>a7 : never +>flag : any + +a8.tag; +>a8.tag : any +>a8 : never +>tag : any + +a9.x; +>a9.x : never +>a9 : A9 +>x : never + +class P1 { +>P1 : P1 + + private x = 1; +>x : number +>1 : 1 +} +class P2 { +>P2 : P2 + + private x = 1; +>x : number +>1 : 1 +} +class Q1 { +>Q1 : Q1 + + protected x = 1; +>x : number +>1 : 1 +} +class Q2 { +>Q2 : Q2 + + protected x = 1; +>x : number +>1 : 1 +} +class Generic { +>Generic : Generic + + private value!: T; +>value : T + + kind!: T; +>kind : T +} + +declare const b1: P1 & P2; +>b1 : never + +declare const b2: Q1 & Q2; +>b2 : Q1 & Q2 + +declare const b3: P1 & { y: string }; +>b3 : P1 & { y: string; } +>y : string + +declare const b4: Generic & Generic; +>b4 : Generic + +declare const b5: Generic & Generic; +>b5 : Generic & Generic + +declare const b6: Generic<"a"> & Generic<"b">; +>b6 : never + +b1.x; +>b1.x : any +>b1 : never +>x : any + +b2.x; +>b2.x : number +>b2 : Q1 & Q2 +>x : number + +b3.y; +>b3.y : string +>b3 : P1 & { y: string; } +>y : string + +b4.kind; +>b4.kind : string +>b4 : Generic +>kind : string + +b5.kind; +>b5.kind : never +>b5 : Generic & Generic +>kind : never + +b6.kind; +>b6.kind : any +>b6 : never +>kind : any + +type Tagged = { kind: T } & { kind: "a" }; +>Tagged : Tagged +>kind : T +>kind : "a" + +declare const c1: Tagged<"a">; +>c1 : Tagged<"a"> + +declare const c2: Tagged<"b">; +>c2 : never + +declare const c3: Tagged; +>c3 : Tagged + +c1.kind; +>c1.kind : "a" +>c1 : Tagged<"a"> +>kind : "a" + +c2.kind; +>c2.kind : any +>c2 : never +>kind : any + +c3.kind; +>c3.kind : "a" +>c3 : Tagged +>kind : "a" + +function f(x: T & { kind: "c" }, y: T & { kind: "a" }) { +>f : (x: T & { kind: "c"; }, y: T & { kind: "a"; }) => void +>kind : "a" +>kind : "b" +>x : never +>kind : "c" +>y : T & { kind: "a"; } +>kind : "a" + + x.kind; +>x.kind : any +>x : never +>kind : any + + y.kind; +>y.kind : "a" +>y : { kind: "a"; } & { kind: "a"; } +>kind : "a" +} + +interface Many { + p1: string; +>p1 : string + + p2: number; +>p2 : number + + p3: boolean; +>p3 : boolean + + p4: string[]; +>p4 : string[] + + p5: () => void; +>p5 : () => void + + kind: "many"; +>kind : "many" +} +declare const d1: Many & { kind: "many"; extra: string }; +>d1 : Many & { kind: "many"; extra: string; } +>kind : "many" +>extra : string + +declare const d2: Many & { kind: "other" }; +>d2 : never +>kind : "other" + +declare const d3: Many & Partial & { p1: "literal" }; +>d3 : Many & Partial & { p1: "literal"; } +>p1 : "literal" + +d1.extra; +>d1.extra : string +>d1 : Many & { kind: "many"; extra: string; } +>extra : string + +d2.p1; +>d2.p1 : any +>d2 : never +>p1 : any + +d3.p1; +>d3.p1 : "literal" +>d3 : Many & Partial & { p1: "literal"; } +>p1 : "literal" + diff --git a/tsc/testdata/tests/cases/compiler/intersectionNeverReductionByProperty.ts b/tsc/testdata/tests/cases/compiler/intersectionNeverReductionByProperty.ts new file mode 100644 index 0000000000000..05455cc49799d --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/intersectionNeverReductionByProperty.ts @@ -0,0 +1,95 @@ +// @strict: true +// @target: esnext +// @noEmit: true + +// Intersections that are and aren't reduced to never because of conflicting +// discriminants or private properties. + +type A1 = { kind: "a"; x: string } & { kind: "b"; y: string }; +type A2 = { kind: "a" | "b"; x: string } & { kind: "b"; y: string }; +type A3 = { kind?: "a" } & { kind?: "b" }; +type A4 = { kind: "a" } & { kind: string }; +type A5 = { kind: `a${string}` } & { kind: `b${string}` }; +type A6 = { kind: never } & { kind: "a" }; +type A7 = { flag: true } & { flag: false }; +type A8 = { tag: 1; a: string } & { b: number } & { tag: 2; c: boolean }; +type A9 = { x: string } & { x: number }; + +declare const a1: A1; +declare const a2: A2; +declare const a3: A3; +declare const a4: A4; +declare const a5: A5; +declare const a6: A6; +declare const a7: A7; +declare const a8: A8; +declare const a9: A9; + +a1.kind; +a2.kind; +a3.kind; +a4.kind; +a5.kind; +a6.kind; +a7.flag; +a8.tag; +a9.x; + +class P1 { + private x = 1; +} +class P2 { + private x = 1; +} +class Q1 { + protected x = 1; +} +class Q2 { + protected x = 1; +} +class Generic { + private value!: T; + kind!: T; +} + +declare const b1: P1 & P2; +declare const b2: Q1 & Q2; +declare const b3: P1 & { y: string }; +declare const b4: Generic & Generic; +declare const b5: Generic & Generic; +declare const b6: Generic<"a"> & Generic<"b">; + +b1.x; +b2.x; +b3.y; +b4.kind; +b5.kind; +b6.kind; + +type Tagged = { kind: T } & { kind: "a" }; +declare const c1: Tagged<"a">; +declare const c2: Tagged<"b">; +declare const c3: Tagged; +c1.kind; +c2.kind; +c3.kind; + +function f(x: T & { kind: "c" }, y: T & { kind: "a" }) { + x.kind; + y.kind; +} + +interface Many { + p1: string; + p2: number; + p3: boolean; + p4: string[]; + p5: () => void; + kind: "many"; +} +declare const d1: Many & { kind: "many"; extra: string }; +declare const d2: Many & { kind: "other" }; +declare const d3: Many & Partial & { p1: "literal" }; +d1.extra; +d2.p1; +d3.p1; From d33ab853572c91d58f4655849c0883cf08b36029 Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Sun, 27 Sep 2026 00:10:47 -0400 Subject: [PATCH 2/4] Only combine properties that more than one constituent has Instead of collecting the constituent properties of every name the way createUnionOrIntersectionProperty does, create the combined property for any name that more than one constituent has. The combined property of a name only one constituent has is that constituent's own property, which can only reduce the intersection if it already combines the properties of a union. This creates more combined properties than before, but is much simpler and saves the per-name lookups in every constituent. Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 67 ++++++++------------------------- 1 file changed, 15 insertions(+), 52 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9b9b49ea1f571..af491588aa06d 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -22218,68 +22218,31 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool { // isNeverReducedIntersection returns // core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) -// without creating the combined properties that can't reduce the intersection -// to never, which in large intersections are most of them. +// without creating combined properties for names that only one constituent +// has, which in large intersections are most of them. The combined property +// for such a name is the constituent's own, which can only reduce the +// intersection if it already combines the properties of a union. func (c *Checker) isNeverReducedIntersection(t *Type) bool { if props := t.AsUnionOrIntersectionType().resolvedProperties; props != nil { return core.Some(props, c.isNeverReducedProperty) } - var checked collections.Set[string] + var names []string + mayReduce := make(map[string]bool) for _, current := range t.Types() { for _, prop := range c.getPropertiesOfType(current) { - if checked.Has(prop.Name) { - continue - } - checked.Add(prop.Name) - if !c.mayBeNeverReducedProperty(t, prop.Name) { - continue - } - if combined := c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/); combined != nil && c.isNeverReducedProperty(combined) { - return true + if _, seen := mayReduce[prop.Name]; seen { + mayReduce[prop.Name] = true + } else { + names = append(names, prop.Name) + mayReduce[prop.Name] = prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 } } } - return false -} - -// mayBeNeverReducedProperty reports whether the property that -// createUnionOrIntersectionProperty combines for name in the intersection t -// can satisfy isNeverReducedProperty. That takes distinct constituent -// properties where one has a literal type (a discriminant), a private -// property, or a constituent property that is itself such a combination. -// Constituent properties are collected as createUnionOrIntersectionProperty -// does. -func (c *Checker) mayBeNeverReducedProperty(t *Type, name string) bool { - var singleProp *ast.Symbol - var propSet collections.OrderedSet[*ast.Symbol] - for _, current := range t.Types() { - apparent := c.getApparentType(current) - if c.isErrorType(apparent) || apparent.flags&TypeFlagsNever != 0 { - continue - } - prop := c.getPropertyOfTypeEx(apparent, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) - if prop == nil { - continue - } - if prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 || getDeclarationModifierFlagsFromSymbol(prop)&ast.ModifierFlagsPrivate != 0 { - return true - } - if singleProp == nil { - singleProp = prop - } else if prop != singleProp { - if c.getTargetSymbol(prop) == c.getTargetSymbol(singleProp) && c.compareProperties(singleProp, prop, compareTypesEqual) == TernaryTrue { - continue - } - if propSet.Size() == 0 { - propSet.Add(singleProp) + for _, name := range names { + if mayReduce[name] { + if prop := c.getPropertyOfUnionOrIntersectionType(t, name, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) { + return true } - propSet.Add(prop) - } - } - for prop := range propSet.Values() { - propType := c.getTypeOfSymbol(prop) - if isLiteralType(propType) || c.isPatternLiteralType(propType) { - return true } } return false From 5974e8f4b0838dffa66392156bd74086745dedde Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Sun, 27 Sep 2026 10:06:05 -0400 Subject: [PATCH 3/4] Go back to the precise never-reduction check This reverts d33ab8535. The simpler check was faster on the typescript-benchmarking projects, but on a 37k-file program it created 356k more symbols and held 0.28 GiB more heap (about 1.3 GiB more peak RSS with 4 checkers), which is the problem this change is for. Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 67 +++++++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index af491588aa06d..9b9b49ea1f571 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -22218,31 +22218,68 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool { // isNeverReducedIntersection returns // core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) -// without creating combined properties for names that only one constituent -// has, which in large intersections are most of them. The combined property -// for such a name is the constituent's own, which can only reduce the -// intersection if it already combines the properties of a union. +// without creating the combined properties that can't reduce the intersection +// to never, which in large intersections are most of them. func (c *Checker) isNeverReducedIntersection(t *Type) bool { if props := t.AsUnionOrIntersectionType().resolvedProperties; props != nil { return core.Some(props, c.isNeverReducedProperty) } - var names []string - mayReduce := make(map[string]bool) + var checked collections.Set[string] for _, current := range t.Types() { for _, prop := range c.getPropertiesOfType(current) { - if _, seen := mayReduce[prop.Name]; seen { - mayReduce[prop.Name] = true - } else { - names = append(names, prop.Name) - mayReduce[prop.Name] = prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 + if checked.Has(prop.Name) { + continue + } + checked.Add(prop.Name) + if !c.mayBeNeverReducedProperty(t, prop.Name) { + continue + } + if combined := c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/); combined != nil && c.isNeverReducedProperty(combined) { + return true } } } - for _, name := range names { - if mayReduce[name] { - if prop := c.getPropertyOfUnionOrIntersectionType(t, name, true /*skipObjectFunctionPropertyAugment*/); prop != nil && c.isNeverReducedProperty(prop) { - return true + return false +} + +// mayBeNeverReducedProperty reports whether the property that +// createUnionOrIntersectionProperty combines for name in the intersection t +// can satisfy isNeverReducedProperty. That takes distinct constituent +// properties where one has a literal type (a discriminant), a private +// property, or a constituent property that is itself such a combination. +// Constituent properties are collected as createUnionOrIntersectionProperty +// does. +func (c *Checker) mayBeNeverReducedProperty(t *Type, name string) bool { + var singleProp *ast.Symbol + var propSet collections.OrderedSet[*ast.Symbol] + for _, current := range t.Types() { + apparent := c.getApparentType(current) + if c.isErrorType(apparent) || apparent.flags&TypeFlagsNever != 0 { + continue + } + prop := c.getPropertyOfTypeEx(apparent, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) + if prop == nil { + continue + } + if prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 || getDeclarationModifierFlagsFromSymbol(prop)&ast.ModifierFlagsPrivate != 0 { + return true + } + if singleProp == nil { + singleProp = prop + } else if prop != singleProp { + if c.getTargetSymbol(prop) == c.getTargetSymbol(singleProp) && c.compareProperties(singleProp, prop, compareTypesEqual) == TernaryTrue { + continue + } + if propSet.Size() == 0 { + propSet.Add(singleProp) } + propSet.Add(prop) + } + } + for prop := range propSet.Values() { + propType := c.getTypeOfSymbol(prop) + if isLiteralType(propType) || c.isPatternLiteralType(propType) { + return true } } return false From 120bd57e7f5cfa5eb461e19ea33dbaf8ef92ba14 Mon Sep 17 00:00:00 2001 From: Max schwenk Date: Sun, 27 Sep 2026 12:05:09 -0400 Subject: [PATCH 4/4] Share property collection with createUnionOrIntersectionProperty isNeverReducedIntersection re-implemented how createUnionOrIntersectionProperty collects each constituent's property of a name. That loop is now collectUnionOrIntersectionPropertyParts, and both use it. From what it collects, isNeverReducedIntersection checks whether the combined property could satisfy isNeverReducedProperty (it needs a literal-typed or a private constituent property) before creating it. Co-Authored-By: Claude Opus 5.5 --- tsc/internal/checker/checker.go | 105 +++++++++++++++++--------------- 1 file changed, 56 insertions(+), 49 deletions(-) diff --git a/tsc/internal/checker/checker.go b/tsc/internal/checker/checker.go index 9b9b49ea1f571..7fb9e41a508e8 100644 --- a/tsc/internal/checker/checker.go +++ b/tsc/internal/checker/checker.go @@ -21787,10 +21787,23 @@ func (c *Checker) getUnionOrIntersectionProperty(t *Type, name string, skipObjec return prop } -func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol { +// unionOrIntersectionPropertyParts is what the constituents of a union or +// intersection contribute to its property of a given name. +type unionOrIntersectionPropertyParts struct { + singleProp *ast.Symbol + propSet collections.OrderedSet[*ast.Symbol] // distinct constituent properties, if more than one + indexTypes []*Type + propFlags ast.SymbolFlags + checkFlags ast.CheckFlags + optionalFlag ast.SymbolFlags + syntheticFlag ast.CheckFlags + mergedInstantiations bool +} + +func (c *Checker) collectUnionOrIntersectionPropertyParts(containingType *Type, name string, skipObjectFunctionPropertyAugment bool, parts *unionOrIntersectionPropertyParts) { propFlags := ast.SymbolFlagsNone var singleProp *ast.Symbol - var propSet collections.OrderedSet[*ast.Symbol] + propSet := &parts.propSet var indexTypes []*Type isUnion := containingType.flags&TypeFlagsUnion != 0 // Flags we want to propagate to the result if they exist in all source symbols @@ -21895,6 +21908,16 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s } } } + parts.singleProp, parts.indexTypes, parts.mergedInstantiations = singleProp, indexTypes, mergedInstantiations + parts.propFlags, parts.checkFlags, parts.optionalFlag, parts.syntheticFlag = propFlags, checkFlags, optionalFlag, syntheticFlag +} + +func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name string, skipObjectFunctionPropertyAugment bool) *ast.Symbol { + var parts unionOrIntersectionPropertyParts + c.collectUnionOrIntersectionPropertyParts(containingType, name, skipObjectFunctionPropertyAugment, &parts) + singleProp, propSet, indexTypes := parts.singleProp, &parts.propSet, parts.indexTypes + propFlags, checkFlags, optionalFlag, syntheticFlag := parts.propFlags, parts.checkFlags, parts.optionalFlag, parts.syntheticFlag + isUnion := containingType.flags&TypeFlagsUnion != 0 if singleProp == nil { // No property was found return nil @@ -21902,7 +21925,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s if isUnion && (propSet.Size() != 0 || checkFlags&ast.CheckFlagsPartial != 0) && checkFlags&(ast.CheckFlagsContainsPrivate|ast.CheckFlagsContainsProtected|ast.CheckFlagsContainsWritePrivate|ast.CheckFlagsContainsWriteProtected) != 0 && - !(propSet.Size() != 0 && c.hasCommonDeclaration(&propSet)) { + !(propSet.Size() != 0 && c.hasCommonDeclaration(propSet)) { // A property in a union has a private or protected declaration in one constituent, but is missing // or has a different declaration in another constituent. If the private or protected declaration is // for reading, we don't create a property. @@ -21918,7 +21941,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s } } if propSet.Size() == 0 && checkFlags&ast.CheckFlagsReadPartial == 0 && len(indexTypes) == 0 { - if !mergedInstantiations { + if !parts.mergedInstantiations { return singleProp } // No symbol from a union/intersection should have a `.parent` set (since unions/intersections don't act as symbol parents) @@ -21975,7 +21998,7 @@ func (c *Checker) createUnionOrIntersectionProperty(containingType *Type, name s if t != firstType { checkFlags |= ast.CheckFlagsHasNonUniformType } - if isLiteralType(t) || c.isPatternLiteralType(t) { + if c.isLiteralOrPatternLiteralType(t) { checkFlags |= ast.CheckFlagsHasLiteralType } if t.flags&TypeFlagsNever != 0 && t != c.uniqueLiteralType { @@ -22218,8 +22241,8 @@ func (c *Checker) isNeverReducedProperty(prop *ast.Symbol) bool { // isNeverReducedIntersection returns // core.Some(c.getPropertiesOfUnionOrIntersectionType(t), c.isNeverReducedProperty) -// without creating the combined properties that can't reduce the intersection -// to never, which in large intersections are most of them. +// without creating the combined properties that can't satisfy +// isNeverReducedProperty, which in large intersections are most of them. func (c *Checker) isNeverReducedIntersection(t *Type) bool { if props := t.AsUnionOrIntersectionType().resolvedProperties; props != nil { return core.Some(props, c.isNeverReducedProperty) @@ -22227,14 +22250,21 @@ func (c *Checker) isNeverReducedIntersection(t *Type) bool { var checked collections.Set[string] for _, current := range t.Types() { for _, prop := range c.getPropertiesOfType(current) { - if checked.Has(prop.Name) { - continue - } - checked.Add(prop.Name) - if !c.mayBeNeverReducedProperty(t, prop.Name) { + if !checked.AddIfAbsent(prop.Name) { continue } - if combined := c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/); combined != nil && c.isNeverReducedProperty(combined) { + var parts unionOrIntersectionPropertyParts + c.collectUnionOrIntersectionPropertyParts(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/, &parts) + var combined *ast.Symbol + switch { + case parts.propSet.Size() == 0 && !parts.mergedInstantiations: + // What createUnionOrIntersectionProperty returns. Merged instantiations + // get a copy without check flags, which can't reduce. + combined = parts.singleProp + case parts.propSet.Size() != 0 && c.mayBeNeverReducedProperty(&parts): + combined = c.getPropertyOfUnionOrIntersectionType(t, prop.Name, true /*skipObjectFunctionPropertyAugment*/) + } + if combined != nil && c.isNeverReducedProperty(combined) { return true } } @@ -22242,49 +22272,26 @@ func (c *Checker) isNeverReducedIntersection(t *Type) bool { return false } -// mayBeNeverReducedProperty reports whether the property that -// createUnionOrIntersectionProperty combines for name in the intersection t -// can satisfy isNeverReducedProperty. That takes distinct constituent -// properties where one has a literal type (a discriminant), a private -// property, or a constituent property that is itself such a combination. -// Constituent properties are collected as createUnionOrIntersectionProperty -// does. -func (c *Checker) mayBeNeverReducedProperty(t *Type, name string) bool { - var singleProp *ast.Symbol - var propSet collections.OrderedSet[*ast.Symbol] - for _, current := range t.Types() { - apparent := c.getApparentType(current) - if c.isErrorType(apparent) || apparent.flags&TypeFlagsNever != 0 { - continue - } - prop := c.getPropertyOfTypeEx(apparent, name, true /*skipObjectFunctionPropertyAugment*/, false /*includeTypeOnlyMembers*/) - if prop == nil { - continue - } - if prop.CheckFlags&(ast.CheckFlagsNonUniformAndLiteral|ast.CheckFlagsContainsPrivate) != 0 || getDeclarationModifierFlagsFromSymbol(prop)&ast.ModifierFlagsPrivate != 0 { - return true - } - if singleProp == nil { - singleProp = prop - } else if prop != singleProp { - if c.getTargetSymbol(prop) == c.getTargetSymbol(singleProp) && c.compareProperties(singleProp, prop, compareTypesEqual) == TernaryTrue { - continue - } - if propSet.Size() == 0 { - propSet.Add(singleProp) - } - propSet.Add(prop) - } +// mayBeNeverReducedProperty reports whether the property combined from parts +// with more than one distinct constituent property can satisfy +// isNeverReducedProperty, which requires CheckFlagsHasLiteralType or +// CheckFlagsContainsPrivate. +func (c *Checker) mayBeNeverReducedProperty(parts *unionOrIntersectionPropertyParts) bool { + if parts.checkFlags&ast.CheckFlagsContainsPrivate != 0 { + return true } - for prop := range propSet.Values() { - propType := c.getTypeOfSymbol(prop) - if isLiteralType(propType) || c.isPatternLiteralType(propType) { + for prop := range parts.propSet.Values() { + if c.isLiteralOrPatternLiteralType(c.getTypeOfSymbol(prop)) { return true } } return false } +func (c *Checker) isLiteralOrPatternLiteralType(t *Type) bool { + return isLiteralType(t) || c.isPatternLiteralType(t) +} + func (c *Checker) getReducedApparentType(t *Type) *Type { // Since getApparentType may return a non-reduced union or intersection type, we need to perform // type reduction both before and after obtaining the apparent type. For example, given a type parameter