From b318e23f06322c0333418aaeda0fc65d709c8b97 Mon Sep 17 00:00:00 2001 From: Sebastian Sebbie Silbermann Date: Sun, 29 Mar 2026 13:44:08 -0700 Subject: [PATCH] Use trie for removeStringLiteralsMatchedByTemplateLiterals Optimize removeStringLiteralsMatchedByTemplateLiterals by building a prefix trie from TemplateLiteralType patterns and using O(L) trie traversal per string literal instead of O(m) linear scan across all templates. StringMappingType templates (which cannot be trie-indexed) are checked separately. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/checker/checker.go | 52 +++++++++++++++++++++++++++---------- internal/checker/relater.go | 47 +++++++++++++++++++++++++++++++++ internal/checker/types.go | 5 ++++ 3 files changed, 90 insertions(+), 14 deletions(-) diff --git a/internal/checker/checker.go b/internal/checker/checker.go index 79171ee859f..52a6bbb0d4c 100644 --- a/internal/checker/checker.go +++ b/internal/checker/checker.go @@ -25193,26 +25193,50 @@ func (c *Checker) removeRedundantLiteralTypes(types []*Type, includes TypeFlags, func (c *Checker) removeStringLiteralsMatchedByTemplateLiterals(types []*Type) []*Type { templates := core.Filter(types, c.isPatternLiteralType) - if len(templates) != 0 { - i := len(types) - for i > 0 { - i-- - t := types[i] - if t.flags&TypeFlagsStringLiteral != 0 && core.Some(templates, func(template *Type) bool { - return c.isTypeMatchedByTemplateLiteralOrStringMapping(t, template) - }) { - types = slices.Delete(types, i, i+1) - } + if len(templates) == 0 { + return types + } + templateLiterals := core.Filter(templates, func(t *Type) bool { + return t.flags&TypeFlagsTemplateLiteral != 0 + }) + stringMappings := core.Filter(templates, func(t *Type) bool { + return t.flags&TypeFlagsStringMapping != 0 + }) + var trie *templateLiteralTrieNode + if len(templateLiterals) >= 2 { + trie = c.buildTemplateLiteralTrieFromTypes(templateLiterals) + } + i := len(types) + for i > 0 { + i-- + t := types[i] + if t.flags&TypeFlagsStringLiteral != 0 && c.isStringLiteralMatchedByTemplates(t, trie, templateLiterals, stringMappings) { + types = slices.Delete(types, i, i+1) } } return types } -func (c *Checker) isTypeMatchedByTemplateLiteralOrStringMapping(t *Type, template *Type) bool { - if template.flags&TypeFlagsTemplateLiteral != 0 { - return c.isTypeMatchedByTemplateLiteralType(t, template.AsTemplateLiteralType(), c.compareTypesAssignable) +func (c *Checker) isStringLiteralMatchedByTemplates(source *Type, trie *templateLiteralTrieNode, templateLiterals []*Type, stringMappings []*Type) bool { + if trie != nil { + if c.findMatchingTemplateLiteralInTrie(trie, source, c.compareTypesAssignable) != nil { + return true + } + } else if len(templateLiterals) > 0 { + if core.Some(templateLiterals, func(tl *Type) bool { + return c.isTypeMatchedByTemplateLiteralType(source, tl.AsTemplateLiteralType(), c.compareTypesAssignable) + }) { + return true + } } - return c.isMemberOfStringMapping(t, template) + if len(stringMappings) > 0 { + if core.Some(stringMappings, func(sm *Type) bool { + return c.isMemberOfStringMapping(source, sm) + }) { + return true + } + } + return false } func (c *Checker) removeConstrainedTypeVariables(types []*Type) []*Type { diff --git a/internal/checker/relater.go b/internal/checker/relater.go index 691bb1d24a0..b04681ae279 100644 --- a/internal/checker/relater.go +++ b/internal/checker/relater.go @@ -1105,6 +1105,53 @@ func (c *Checker) getMatchingUnionConstituentForType(unionType *Type, t *Type) * return c.getConstituentTypeForKeyType(unionType, propType) } +func (c *Checker) buildTemplateLiteralTrieFromTypes(templateTypes []*Type) *templateLiteralTrieNode { + root := &templateLiteralTrieNode{} + for _, t := range templateTypes { + prefix := t.AsTemplateLiteralType().texts[0] + node := root + for _, ch := range []byte(prefix) { + if node.children == nil { + node.children = make(map[byte]*templateLiteralTrieNode) + } + child := node.children[ch] + if child == nil { + child = &templateLiteralTrieNode{} + node.children[ch] = child + } + node = child + } + node.types = append(node.types, t) + } + return root +} + +func (c *Checker) findMatchingTemplateLiteralInTrie(trie *templateLiteralTrieNode, source *Type, compareTypes TypeComparer) *Type { + value := source.AsLiteralType().Value().(string) + node := trie + // Check root candidates (empty-prefix templates like `${string}`) + for _, t := range node.types { + if c.isTypeMatchedByTemplateLiteralType(source, t.AsTemplateLiteralType(), compareTypes) { + return t + } + } + for _, ch := range []byte(value) { + if node.children == nil { + return nil + } + node = node.children[ch] + if node == nil { + return nil + } + for _, t := range node.types { + if c.isTypeMatchedByTemplateLiteralType(source, t.AsTemplateLiteralType(), compareTypes) { + return t + } + } + } + return nil +} + // Return the name of a discriminant property for which it was possible and feasible to construct a map of // constituent types keyed by the literal types of the property by that name in each constituent type. Return // an empty string if no such discriminant property exists. diff --git a/internal/checker/types.go b/internal/checker/types.go index 84e2e3fb093..4f9bba3b237 100644 --- a/internal/checker/types.go +++ b/internal/checker/types.go @@ -1059,6 +1059,11 @@ type TemplateLiteralType struct { func (t *TemplateLiteralType) Texts() []string { return t.texts } func (t *TemplateLiteralType) Types() []*Type { return t.types } +type templateLiteralTrieNode struct { + children map[byte]*templateLiteralTrieNode + types []*Type // template literal types whose prefix ends at this node +} + type StringMappingType struct { ConstrainedType target *Type