Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 38 additions & 14 deletions internal/checker/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
47 changes: 47 additions & 0 deletions internal/checker/relater.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this, you will probably benefit from using core.Arena to avoid a bunch of small allocs of these nodes, given they're all generated once in this func. Can just write var arena core.Arena[templateLiteralTrieNode] and then do arena.New() instead of &templateLiteralTrieNode{}.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, so, I never hit send on this :(

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.
Expand Down
5 changes: 5 additions & 0 deletions internal/checker/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading