From 83b797c81a1b436d2c3a1589d96100e4f9ea35db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sun, 25 Oct 2026 19:43:13 +0100 Subject: [PATCH 1/2] Check union subsets with a forward identity scan instead of binary searches --- tsc/internal/checker/relater.go | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index 007fba2511a13..e7e3656b4048c 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -2869,10 +2869,17 @@ func (c *Checker) isTypeSubsetOf(source *Type, target *Type) bool { func (c *Checker) isTypeSubsetOfUnion(source *Type, target *Type) bool { if source.flags&TypeFlagsUnion != 0 { - for _, t := range source.Types() { - if !containsType(target.Types(), t) { + // Union types are sorted by CompareTypes, so the types of a subset appear in target in the same order. + sourceTypes, targetTypes := source.Types(), target.Types() + if len(sourceTypes) > len(targetTypes) { + return false + } + for _, t := range sourceTypes { + i := slices.Index(targetTypes, t) + if i < 0 { return false } + targetTypes = targetTypes[i+1:] } return true } From 4c35db3c1e9b256530fd3d0bf32e4e5fc9748b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Mon, 26 Oct 2026 07:29:29 +0100 Subject: [PATCH 2/2] Binary search sparse union subsets, and search each type only after the previous match --- tsc/internal/checker/relater.go | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/tsc/internal/checker/relater.go b/tsc/internal/checker/relater.go index e7e3656b4048c..562ae0451cb43 100644 --- a/tsc/internal/checker/relater.go +++ b/tsc/internal/checker/relater.go @@ -1,6 +1,7 @@ package checker import ( + "math/bits" "slices" "strconv" "strings" @@ -2869,14 +2870,26 @@ func (c *Checker) isTypeSubsetOf(source *Type, target *Type) bool { func (c *Checker) isTypeSubsetOfUnion(source *Type, target *Type) bool { if source.flags&TypeFlagsUnion != 0 { - // Union types are sorted by CompareTypes, so the types of a subset appear in target in the same order. sourceTypes, targetTypes := source.Types(), target.Types() if len(sourceTypes) > len(targetTypes) { return false } + // Scan by identity unless a few source types are looked for in a large target. A CompareTypes call costs + // roughly 8 pointer comparisons, so there a binary search is cheaper than walking the target. + if len(sourceTypes)*bits.Len(uint(len(targetTypes)))*8 >= len(targetTypes) { + for _, t := range sourceTypes { + i := slices.Index(targetTypes, t) + if i < 0 { + return false + } + // Union types are sorted by CompareTypes, so the next source type can only appear after this one. + targetTypes = targetTypes[i+1:] + } + return true + } for _, t := range sourceTypes { - i := slices.Index(targetTypes, t) - if i < 0 { + i, found := slices.BinarySearchFunc(targetTypes, t, CompareTypes) + if !found { return false } targetTypes = targetTypes[i+1:]