Skip to content

Commit 38b898b

Browse files
findleyrgopherbot
authored andcommitted
internal/persistent: add Set
Add a simple Set wrapper around persistent.Map, with a new test. Follow-up CLs will replace ad-hoc sets in gopls with a persistent.Set. Change-Id: Idd5fc5389719d3f59d658d8d9cb8fc0206e35797 Reviewed-on: https://go-review.googlesource.com/c/tools/+/524761 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com> Auto-Submit: Robert Findley <rfindley@google.com> gopls-CI: kokoro <noreply+kokoro@google.com>
1 parent 44f7796 commit 38b898b

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed

internal/persistent/set.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package persistent
6+
7+
import "golang.org/x/tools/internal/constraints"
8+
9+
// Set is a collection of elements of type K.
10+
//
11+
// It uses immutable data structures internally, so that sets can be cloned in
12+
// constant time.
13+
//
14+
// The zero value is a valid empty set.
15+
type Set[K constraints.Ordered] struct {
16+
impl *Map[K, struct{}]
17+
}
18+
19+
// Clone creates a copy of the receiver.
20+
func (s *Set[K]) Clone() *Set[K] {
21+
clone := new(Set[K])
22+
if s.impl != nil {
23+
clone.impl = s.impl.Clone()
24+
}
25+
return clone
26+
}
27+
28+
// Destroy destroys the set.
29+
//
30+
// After Destroy, the Set should not be used again.
31+
func (s *Set[K]) Destroy() {
32+
if s.impl != nil {
33+
s.impl.Destroy()
34+
}
35+
}
36+
37+
// Contains reports whether s contains the given key.
38+
func (s *Set[K]) Contains(key K) bool {
39+
if s.impl == nil {
40+
return false
41+
}
42+
_, ok := s.impl.Get(key)
43+
return ok
44+
}
45+
46+
// Range calls f sequentially in ascending key order for all entries in the set.
47+
func (s *Set[K]) Range(f func(key K)) {
48+
if s.impl != nil {
49+
s.impl.Range(func(key K, _ struct{}) {
50+
f(key)
51+
})
52+
}
53+
}
54+
55+
// AddAll adds all elements from other to the receiver set.
56+
func (s *Set[K]) AddAll(other *Set[K]) {
57+
if other.impl != nil {
58+
if s.impl == nil {
59+
s.impl = new(Map[K, struct{}])
60+
}
61+
s.impl.SetAll(other.impl)
62+
}
63+
}
64+
65+
// Add adds an element to the set.
66+
func (s *Set[K]) Add(key K) {
67+
if s.impl == nil {
68+
s.impl = new(Map[K, struct{}])
69+
}
70+
s.impl.Set(key, struct{}{}, nil)
71+
}
72+
73+
// Remove removes an element from the set.
74+
func (s *Set[K]) Remove(key K) {
75+
if s.impl != nil {
76+
s.impl.Delete(key)
77+
}
78+
}

internal/persistent/set_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package persistent_test
6+
7+
import (
8+
"fmt"
9+
"strings"
10+
"testing"
11+
12+
"golang.org/x/tools/internal/constraints"
13+
"golang.org/x/tools/internal/persistent"
14+
)
15+
16+
func TestSet(t *testing.T) {
17+
const (
18+
add = iota
19+
remove
20+
)
21+
type op struct {
22+
op int
23+
v int
24+
}
25+
26+
tests := []struct {
27+
label string
28+
ops []op
29+
want []int
30+
}{
31+
{"empty", nil, nil},
32+
{"singleton", []op{{add, 1}}, []int{1}},
33+
{"add and remove", []op{
34+
{add, 1},
35+
{remove, 1},
36+
}, nil},
37+
{"interleaved and remove", []op{
38+
{add, 1},
39+
{add, 2},
40+
{remove, 1},
41+
{add, 3},
42+
}, []int{2, 3}},
43+
}
44+
45+
for _, test := range tests {
46+
t.Run(test.label, func(t *testing.T) {
47+
var s persistent.Set[int]
48+
for _, op := range test.ops {
49+
switch op.op {
50+
case add:
51+
s.Add(op.v)
52+
case remove:
53+
s.Remove(op.v)
54+
}
55+
}
56+
57+
if d := diff(&s, test.want); d != "" {
58+
t.Errorf("unexpected diff:\n%s", d)
59+
}
60+
})
61+
}
62+
}
63+
64+
func TestSet_Clone(t *testing.T) {
65+
s1 := new(persistent.Set[int])
66+
s1.Add(1)
67+
s1.Add(2)
68+
s2 := s1.Clone()
69+
s1.Add(3)
70+
s2.Add(4)
71+
if d := diff(s1, []int{1, 2, 3}); d != "" {
72+
t.Errorf("s1: unexpected diff:\n%s", d)
73+
}
74+
if d := diff(s2, []int{1, 2, 4}); d != "" {
75+
t.Errorf("s2: unexpected diff:\n%s", d)
76+
}
77+
}
78+
79+
func TestSet_AddAll(t *testing.T) {
80+
s1 := new(persistent.Set[int])
81+
s1.Add(1)
82+
s1.Add(2)
83+
s2 := new(persistent.Set[int])
84+
s2.Add(2)
85+
s2.Add(3)
86+
s2.Add(4)
87+
s3 := new(persistent.Set[int])
88+
89+
s := new(persistent.Set[int])
90+
s.AddAll(s1)
91+
s.AddAll(s2)
92+
s.AddAll(s3)
93+
94+
if d := diff(s1, []int{1, 2}); d != "" {
95+
t.Errorf("s1: unexpected diff:\n%s", d)
96+
}
97+
if d := diff(s2, []int{2, 3, 4}); d != "" {
98+
t.Errorf("s2: unexpected diff:\n%s", d)
99+
}
100+
if d := diff(s3, nil); d != "" {
101+
t.Errorf("s3: unexpected diff:\n%s", d)
102+
}
103+
if d := diff(s, []int{1, 2, 3, 4}); d != "" {
104+
t.Errorf("s: unexpected diff:\n%s", d)
105+
}
106+
}
107+
108+
func diff[K constraints.Ordered](got *persistent.Set[K], want []K) string {
109+
wantSet := make(map[K]struct{})
110+
for _, w := range want {
111+
wantSet[w] = struct{}{}
112+
}
113+
var diff []string
114+
got.Range(func(key K) {
115+
if _, ok := wantSet[key]; !ok {
116+
diff = append(diff, fmt.Sprintf("+%v", key))
117+
}
118+
})
119+
for key := range wantSet {
120+
if !got.Contains(key) {
121+
diff = append(diff, fmt.Sprintf("-%v", key))
122+
}
123+
}
124+
if len(diff) > 0 {
125+
d := new(strings.Builder)
126+
for _, l := range diff {
127+
fmt.Fprintln(d, l)
128+
}
129+
return d.String()
130+
}
131+
return ""
132+
}

0 commit comments

Comments
 (0)