Skip to content

Commit eb8d5f1

Browse files
eps1lonclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 7881fe5 commit eb8d5f1

2 files changed

Lines changed: 74 additions & 5 deletions

File tree

src/compiler/checker.ts

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,6 +1058,7 @@ import {
10581058
SyntheticExpression,
10591059
TaggedTemplateExpression,
10601060
TemplateExpression,
1061+
TemplateLiteralTrieNode,
10611062
TemplateLiteralType,
10621063
TemplateLiteralTypeNode,
10631064
Ternary,
@@ -18219,21 +18220,31 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1821918220
function removeStringLiteralsMatchedByTemplateLiterals(types: Type[]) {
1822018221
const templates = filter(types, isPatternLiteralType) as (TemplateLiteralType | StringMappingType)[];
1822118222
if (templates.length) {
18223+
const templateLiterals = filter(templates, t => !!(t.flags & TypeFlags.TemplateLiteral)) as TemplateLiteralType[];
18224+
const stringMappings = filter(templates, t => !!(t.flags & TypeFlags.StringMapping)) as StringMappingType[];
18225+
const trie = templateLiterals.length >= 2 ? buildTemplateLiteralTrieFromTypes(templateLiterals) : undefined;
1822218226
let i = types.length;
1822318227
while (i > 0) {
1822418228
i--;
1822518229
const t = types[i];
18226-
if (t.flags & TypeFlags.StringLiteral && some(templates, template => isTypeMatchedByTemplateLiteralOrStringMapping(t, template))) {
18230+
if (t.flags & TypeFlags.StringLiteral && isStringLiteralMatchedByTemplates(t as StringLiteralType, trie, templateLiterals, stringMappings)) {
1822718231
orderedRemoveItemAt(types, i);
1822818232
}
1822918233
}
1823018234
}
1823118235
}
1823218236

18233-
function isTypeMatchedByTemplateLiteralOrStringMapping(type: Type, template: TemplateLiteralType | StringMappingType) {
18234-
return template.flags & TypeFlags.TemplateLiteral ?
18235-
isTypeMatchedByTemplateLiteralType(type, template as TemplateLiteralType) :
18236-
isMemberOfStringMapping(type, template);
18237+
function isStringLiteralMatchedByTemplates(source: StringLiteralType, trie: TemplateLiteralTrieNode | undefined, templateLiterals: readonly TemplateLiteralType[], stringMappings: readonly StringMappingType[]): boolean {
18238+
if (trie) {
18239+
if (findMatchingTemplateLiteralInTrie(trie, source)) return true;
18240+
}
18241+
else if (templateLiterals.length) {
18242+
if (some(templateLiterals, tl => isTypeMatchedByTemplateLiteralType(source, tl))) return true;
18243+
}
18244+
if (stringMappings.length) {
18245+
if (some(stringMappings, sm => isMemberOfStringMapping(source, sm))) return true;
18246+
}
18247+
return false;
1823718248
}
1823818249

1823918250
function removeConstrainedTypeVariables(types: Type[]) {
@@ -28062,6 +28073,58 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2806228073
return propType && getConstituentTypeForKeyType(unionType, propType);
2806328074
}
2806428075

28076+
function buildTemplateLiteralTrieFromTypes(templateTypes: readonly TemplateLiteralType[]): TemplateLiteralTrieNode {
28077+
const root: TemplateLiteralTrieNode = {};
28078+
for (const templateType of templateTypes) {
28079+
const prefix = templateType.texts[0];
28080+
let node = root;
28081+
for (let i = 0; i < prefix.length; i++) {
28082+
const ch = prefix.charCodeAt(i);
28083+
if (!node.children) {
28084+
node.children = new Map();
28085+
}
28086+
let child = node.children.get(ch);
28087+
if (!child) {
28088+
child = {};
28089+
node.children.set(ch, child);
28090+
}
28091+
node = child;
28092+
}
28093+
if (!node.types) {
28094+
node.types = [];
28095+
}
28096+
node.types.push(templateType);
28097+
}
28098+
return root;
28099+
}
28100+
28101+
function findMatchingTemplateLiteralInTrie(trie: TemplateLiteralTrieNode, source: StringLiteralType): TemplateLiteralType | undefined {
28102+
const value = source.value;
28103+
let node: TemplateLiteralTrieNode | undefined = trie;
28104+
// Check root candidates (empty-prefix templates like `${string}`)
28105+
if (node.types) {
28106+
for (const type of node.types) {
28107+
if (isTypeMatchedByTemplateLiteralType(source, type)) {
28108+
return type;
28109+
}
28110+
}
28111+
}
28112+
for (let i = 0; i < value.length; i++) {
28113+
node = node.children?.get(value.charCodeAt(i));
28114+
if (!node) {
28115+
return undefined;
28116+
}
28117+
if (node.types) {
28118+
for (const type of node.types) {
28119+
if (isTypeMatchedByTemplateLiteralType(source, type)) {
28120+
return type;
28121+
}
28122+
}
28123+
}
28124+
}
28125+
return undefined;
28126+
}
28127+
2806528128
function getMatchingUnionConstituentForObjectLiteral(unionType: UnionType, node: ObjectLiteralExpression) {
2806628129
const keyPropertyName = getKeyPropertyName(unionType);
2806728130
const propNode = keyPropertyName && find(node.properties, p =>

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6960,6 +6960,12 @@ export interface TemplateLiteralType extends InstantiableType {
69606960
types: readonly Type[]; // Always at least one element
69616961
}
69626962

6963+
/** @internal */
6964+
export interface TemplateLiteralTrieNode {
6965+
children?: Map<number, TemplateLiteralTrieNode>; // char code -> child
6966+
types?: TemplateLiteralType[]; // template literals whose prefix ends at this node
6967+
}
6968+
69636969
export interface StringMappingType extends InstantiableType {
69646970
symbol: Symbol;
69656971
type: Type;

0 commit comments

Comments
 (0)