mirror of
https://github.com/golang/go.git
synced 2026-04-03 17:59:55 +09:00
go/types, types2: implement simple generic trie
Will be used to detect overlapping field selectors in struct literals. Change-Id: I6f939171ba1491251489698d40123f5283602458 Reviewed-on: https://go-review.googlesource.com/c/go/+/752601 Reviewed-by: Mark Freeman <markfreeman@google.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Griesemer <gri@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
committed by
Gopher Robot
parent
2a5890cd46
commit
710014bcc3
90
src/cmd/compile/internal/types2/trie.go
Normal file
90
src/cmd/compile/internal/types2/trie.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package types2
|
||||
|
||||
import "sort"
|
||||
|
||||
// A trie[V] maps keys to values V, similar to a map.
|
||||
// A key is a list of integers; two keys collide if one of them is a prefix of the other.
|
||||
// For instance, [1, 2] and [1, 2, 3] collide, but [1, 2, 3] and [1, 2, 4] do not.
|
||||
// If all keys have length 1, a trie degenerates into an ordinary map[int]V.
|
||||
type trie[V any] map[int]any // map value is either trie[V] (non-leaf node), or V (leaf node)
|
||||
|
||||
// insert inserts a value with a key into the trie; key must not be empty.
|
||||
// If key doesn't collide with any other key in the trie, insert succeeds
|
||||
// and returns (val, 0). Otherwise, insert fails and returns (alt, n) where
|
||||
// alt is an unspecified but deterministically chosen value with a colliding
|
||||
// key and n is the length > 0 of the common key prefix. The trie is not
|
||||
// changed in this case.
|
||||
func (tr trie[V]) insert(key []int, val V) (V, int) {
|
||||
// A key serves as the path from the trie root to its corresponding value.
|
||||
for l, index := range key {
|
||||
if v, exists := tr[index]; exists {
|
||||
// An entry already exists for this index; check its type to determine collision.
|
||||
switch v := v.(type) {
|
||||
case trie[V]:
|
||||
// Path continues.
|
||||
// Must check this case first in case V is any which would act as catch-all.
|
||||
tr = v
|
||||
case V:
|
||||
// Collision: An existing key ends here.
|
||||
// This means the existing key is a prefix of (or exactly equal to) key.
|
||||
return v, l + 1
|
||||
case nil:
|
||||
// Handle esoteric case where V is any and val is nil.
|
||||
var zero V
|
||||
return zero, l + 1
|
||||
default:
|
||||
panic("trie.insert: invalid entry")
|
||||
}
|
||||
} else {
|
||||
// Path doesn't exist yet, we need to build it.
|
||||
if l == len(key)-1 {
|
||||
// No prefix collision detected; insert val as a new leaf node.
|
||||
tr[index] = val
|
||||
return val, 0
|
||||
}
|
||||
node := make(trie[V])
|
||||
tr[index] = node
|
||||
tr = node
|
||||
}
|
||||
}
|
||||
|
||||
if len(key) == 0 {
|
||||
panic("trie.insert: key must not be empty")
|
||||
}
|
||||
|
||||
// Collision: path ends here, but the trie continues.
|
||||
// This means key is a prefix of an existing key.
|
||||
// Return a value from the subtrie.
|
||||
for len(tr) != 0 {
|
||||
// see switch above
|
||||
switch v := tr.pickValue().(type) {
|
||||
case trie[V]:
|
||||
tr = v
|
||||
case V:
|
||||
return v, len(key)
|
||||
case nil:
|
||||
var zero V
|
||||
return zero, len(key)
|
||||
default:
|
||||
panic("trie.insert: invalid entry")
|
||||
}
|
||||
}
|
||||
|
||||
panic("trie.insert: unreachable")
|
||||
}
|
||||
|
||||
// pickValue deterministically picks a value of the trie and returns it.
|
||||
// Only called in case of a collision.
|
||||
// The trie must not be empty.
|
||||
func (tr trie[V]) pickValue() any {
|
||||
var keys []int
|
||||
for k := range tr {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys) // guarantee deterministic element pick
|
||||
return tr[keys[0]]
|
||||
}
|
||||
79
src/cmd/compile/internal/types2/trie_test.go
Normal file
79
src/cmd/compile/internal/types2/trie_test.go
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package types2
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrie(t *testing.T) {
|
||||
strie := make(trie[string])
|
||||
|
||||
okay := func(index ...int) {
|
||||
s := fmt.Sprintf("%x", index)
|
||||
if p, n := strie.insert(index, s); n != 0 {
|
||||
t.Errorf("%s collided with %s (n = %d)", s, p, n)
|
||||
}
|
||||
}
|
||||
|
||||
fail := func(collision string, index ...int) {
|
||||
s := fmt.Sprintf("%x", index)
|
||||
if p, n := strie.insert(index, s); n == 0 {
|
||||
t.Errorf("%s did not collide", s)
|
||||
} else if p != collision {
|
||||
t.Errorf("%s collided with %s (n == %d), want %s", s, p, n, collision)
|
||||
}
|
||||
}
|
||||
|
||||
clear(strie)
|
||||
okay(0)
|
||||
fail("[0]", 0)
|
||||
|
||||
clear(strie)
|
||||
okay(0)
|
||||
fail("[0]", 0, 1, 2, 3, 4, 5)
|
||||
|
||||
clear(strie)
|
||||
okay(1, 2)
|
||||
okay(1, 3)
|
||||
okay(1, 4, 5)
|
||||
okay(1, 4, 2)
|
||||
fail("[1 4 2]", 1, 4)
|
||||
fail("[1 4 5]", 1, 4, 5)
|
||||
okay(1, 4, 3)
|
||||
okay(2, 1)
|
||||
okay(2, 2)
|
||||
fail("[2 2]", 2, 2, 3)
|
||||
|
||||
clear(strie)
|
||||
okay(0, 1, 2, 3, 4, 5)
|
||||
okay(0, 1, 2, 3, 4, 6)
|
||||
okay(0, 1, 2, 3, 4, 7)
|
||||
okay(0, 1, 2, 3, 4, 8, 1)
|
||||
okay(0, 1, 2, 3, 4, 4)
|
||||
fail("[0 1 2 3 4 4]", 0, 1, 2, 3)
|
||||
}
|
||||
|
||||
func TestAnyValue(t *testing.T) {
|
||||
atrie := make(trie[any]) // allow values of any type
|
||||
|
||||
val := new(42)
|
||||
alt, n := atrie.insert([]int{0}, val)
|
||||
if n != 0 {
|
||||
t.Errorf("unexpected collision (n = %d)", n)
|
||||
}
|
||||
if alt != val {
|
||||
t.Errorf("unexpected result (alt = %#x, val = %#x)", alt, val)
|
||||
}
|
||||
|
||||
alt, n = atrie.insert([]int{0}, val) // nil is a valid value
|
||||
if n == 0 {
|
||||
t.Errorf("expected collision")
|
||||
}
|
||||
if alt != val {
|
||||
t.Errorf("unexpected result (alt = %#x, val = %#x)", alt, val)
|
||||
}
|
||||
}
|
||||
@@ -194,6 +194,8 @@ var filemap = map[string]action{
|
||||
"subst.go": func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
|
||||
"termlist.go": nil,
|
||||
"termlist_test.go": nil,
|
||||
"trie.go": nil,
|
||||
"trie_test.go": nil,
|
||||
"tuple.go": nil,
|
||||
"typelists.go": nil,
|
||||
"typeset.go": func(f *ast.File) { fixTokenPos(f); renameSelectors(f, "Trace->_Trace") },
|
||||
|
||||
93
src/go/types/trie.go
Normal file
93
src/go/types/trie.go
Normal file
@@ -0,0 +1,93 @@
|
||||
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
|
||||
// Source: ../../cmd/compile/internal/types2/trie.go
|
||||
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package types
|
||||
|
||||
import "sort"
|
||||
|
||||
// A trie[V] maps keys to values V, similar to a map.
|
||||
// A key is a list of integers; two keys collide if one of them is a prefix of the other.
|
||||
// For instance, [1, 2] and [1, 2, 3] collide, but [1, 2, 3] and [1, 2, 4] do not.
|
||||
// If all keys have length 1, a trie degenerates into an ordinary map[int]V.
|
||||
type trie[V any] map[int]any // map value is either trie[V] (non-leaf node), or V (leaf node)
|
||||
|
||||
// insert inserts a value with a key into the trie; key must not be empty.
|
||||
// If key doesn't collide with any other key in the trie, insert succeeds
|
||||
// and returns (val, 0). Otherwise, insert fails and returns (alt, n) where
|
||||
// alt is an unspecified but deterministically chosen value with a colliding
|
||||
// key and n is the length > 0 of the common key prefix. The trie is not
|
||||
// changed in this case.
|
||||
func (tr trie[V]) insert(key []int, val V) (V, int) {
|
||||
// A key serves as the path from the trie root to its corresponding value.
|
||||
for l, index := range key {
|
||||
if v, exists := tr[index]; exists {
|
||||
// An entry already exists for this index; check its type to determine collision.
|
||||
switch v := v.(type) {
|
||||
case trie[V]:
|
||||
// Path continues.
|
||||
// Must check this case first in case V is any which would act as catch-all.
|
||||
tr = v
|
||||
case V:
|
||||
// Collision: An existing key ends here.
|
||||
// This means the existing key is a prefix of (or exactly equal to) key.
|
||||
return v, l + 1
|
||||
case nil:
|
||||
// Handle esoteric case where V is any and val is nil.
|
||||
var zero V
|
||||
return zero, l + 1
|
||||
default:
|
||||
panic("trie.insert: invalid entry")
|
||||
}
|
||||
} else {
|
||||
// Path doesn't exist yet, we need to build it.
|
||||
if l == len(key)-1 {
|
||||
// No prefix collision detected; insert val as a new leaf node.
|
||||
tr[index] = val
|
||||
return val, 0
|
||||
}
|
||||
node := make(trie[V])
|
||||
tr[index] = node
|
||||
tr = node
|
||||
}
|
||||
}
|
||||
|
||||
if len(key) == 0 {
|
||||
panic("trie.insert: key must not be empty")
|
||||
}
|
||||
|
||||
// Collision: path ends here, but the trie continues.
|
||||
// This means key is a prefix of an existing key.
|
||||
// Return a value from the subtrie.
|
||||
for len(tr) != 0 {
|
||||
// see switch above
|
||||
switch v := tr.pickValue().(type) {
|
||||
case trie[V]:
|
||||
tr = v
|
||||
case V:
|
||||
return v, len(key)
|
||||
case nil:
|
||||
var zero V
|
||||
return zero, len(key)
|
||||
default:
|
||||
panic("trie.insert: invalid entry")
|
||||
}
|
||||
}
|
||||
|
||||
panic("trie.insert: unreachable")
|
||||
}
|
||||
|
||||
// pickValue deterministically picks a value of the trie and returns it.
|
||||
// Only called in case of a collision.
|
||||
// The trie must not be empty.
|
||||
func (tr trie[V]) pickValue() any {
|
||||
var keys []int
|
||||
for k := range tr {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Ints(keys) // guarantee deterministic element pick
|
||||
return tr[keys[0]]
|
||||
}
|
||||
82
src/go/types/trie_test.go
Normal file
82
src/go/types/trie_test.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Code generated by "go test -run=Generate -write=all"; DO NOT EDIT.
|
||||
// Source: ../../cmd/compile/internal/types2/trie_test.go
|
||||
|
||||
// Copyright 2026 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTrie(t *testing.T) {
|
||||
strie := make(trie[string])
|
||||
|
||||
okay := func(index ...int) {
|
||||
s := fmt.Sprintf("%x", index)
|
||||
if p, n := strie.insert(index, s); n != 0 {
|
||||
t.Errorf("%s collided with %s (n = %d)", s, p, n)
|
||||
}
|
||||
}
|
||||
|
||||
fail := func(collision string, index ...int) {
|
||||
s := fmt.Sprintf("%x", index)
|
||||
if p, n := strie.insert(index, s); n == 0 {
|
||||
t.Errorf("%s did not collide", s)
|
||||
} else if p != collision {
|
||||
t.Errorf("%s collided with %s (n == %d), want %s", s, p, n, collision)
|
||||
}
|
||||
}
|
||||
|
||||
clear(strie)
|
||||
okay(0)
|
||||
fail("[0]", 0)
|
||||
|
||||
clear(strie)
|
||||
okay(0)
|
||||
fail("[0]", 0, 1, 2, 3, 4, 5)
|
||||
|
||||
clear(strie)
|
||||
okay(1, 2)
|
||||
okay(1, 3)
|
||||
okay(1, 4, 5)
|
||||
okay(1, 4, 2)
|
||||
fail("[1 4 2]", 1, 4)
|
||||
fail("[1 4 5]", 1, 4, 5)
|
||||
okay(1, 4, 3)
|
||||
okay(2, 1)
|
||||
okay(2, 2)
|
||||
fail("[2 2]", 2, 2, 3)
|
||||
|
||||
clear(strie)
|
||||
okay(0, 1, 2, 3, 4, 5)
|
||||
okay(0, 1, 2, 3, 4, 6)
|
||||
okay(0, 1, 2, 3, 4, 7)
|
||||
okay(0, 1, 2, 3, 4, 8, 1)
|
||||
okay(0, 1, 2, 3, 4, 4)
|
||||
fail("[0 1 2 3 4 4]", 0, 1, 2, 3)
|
||||
}
|
||||
|
||||
func TestAnyValue(t *testing.T) {
|
||||
atrie := make(trie[any]) // allow values of any type
|
||||
|
||||
val := new(42)
|
||||
alt, n := atrie.insert([]int{0}, val)
|
||||
if n != 0 {
|
||||
t.Errorf("unexpected collision (n = %d)", n)
|
||||
}
|
||||
if alt != val {
|
||||
t.Errorf("unexpected result (alt = %#x, val = %#x)", alt, val)
|
||||
}
|
||||
|
||||
alt, n = atrie.insert([]int{0}, val) // nil is a valid value
|
||||
if n == 0 {
|
||||
t.Errorf("expected collision")
|
||||
}
|
||||
if alt != val {
|
||||
t.Errorf("unexpected result (alt = %#x, val = %#x)", alt, val)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user