tools: Add src/container

Contains generic Map and Set types.

Golang 1.18 added new support for generics, but has not yet added a standard library that provides generic containers. In future versions of Golang, there will almost certainly be similar implementations of these types.
Until then, use these to simplify some code.

100% test coverage.

Bug: dawn:1342
Change-Id: I2a5c7bfb26f15c2099037d3fa0f0576df641d9f6
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/85220
Auto-Submit: Ben Clayton <bclayton@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
This commit is contained in:
Ben Clayton 2022-03-30 21:12:14 +00:00 committed by Dawn LUCI CQ
parent abe784b502
commit 475941c295
10 changed files with 505 additions and 1 deletions

5
.gitignore vendored
View File

@ -27,7 +27,10 @@
/third_party/vulkan-deps
/third_party/vulkan_memory_allocator
/third_party/zlib
/tools
/tools/clang
/tools/cmake
/tools/golang
/tools/memory
/out
# Modified from https://www.gitignore.io/api/vim,macos,linux,emacs,windows,sublimetext,visualstudio,visualstudiocode

View File

@ -0,0 +1,16 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package set implements a basic generic containers
package container

View File

@ -0,0 +1,28 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_test
import (
"testing"
"github.com/google/go-cmp/cmp"
)
func expectEq(t *testing.T, name string, got, expect interface{}) {
t.Helper()
if diff := cmp.Diff(got, expect); diff != "" {
t.Errorf("%v:\n%v", name, diff)
}
}

View File

@ -0,0 +1,24 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container
// key is the constraint for container keys.
// As Map and Set sort before returning a slice, the constraint is equivalent to
// the constraints.Ordered in x/exp, instead of 'comparable':
// https://cs.opensource.google/go/x/exp/+/master:constraints/constraints.go
type key interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 |
~uint32 | ~uint64 | ~uintptr | ~float32 | ~float64 | ~string
}

View File

@ -0,0 +1,62 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container
import "sort"
// Map is a generic unordered map, which wrap's go's builtin 'map'.
// K is the map key, which must match the 'key' constraint.
// V is the map value, which can be any type.
type Map[K key, V any] map[K]V
// Returns a new empty map
func NewMap[K key, V any]() Map[K, V] {
return make(Map[K, V])
}
// Add adds an item to the map.
func (m Map[K, V]) Add(k K, v V) {
m[k] = v
}
// Remove removes an item from the map
func (m Map[K, V]) Remove(item K) {
delete(m, item)
}
// Contains returns true if the map contains the given item
func (m Map[K, V]) Contains(item K) bool {
_, found := m[item]
return found
}
// Keys returns the sorted keys of the map as a slice
func (m Map[K, V]) Keys() []K {
out := make([]K, 0, len(m))
for v := range m {
out = append(out, v)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
}
// Values returns the values of the map sorted by key
func (m Map[K, V]) Values() []V {
out := make([]V, 0, len(m))
for _, k := range m.Keys() {
out = append(out, m[k])
}
return out
}

View File

@ -0,0 +1,114 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_test
import (
"testing"
"dawn.googlesource.com/dawn/tools/src/container"
)
func TestNewMap(t *testing.T) {
m := container.NewMap[string, int]()
expectEq(t, "len(m)", len(m), 0)
}
func TestMapAdd(t *testing.T) {
m := container.NewMap[string, int]()
m.Add("c", 3)
expectEq(t, "len(m)", len(m), 1)
expectEq(t, `m["a"]`, m["a"], 0)
expectEq(t, `m["b"]`, m["b"], 0)
expectEq(t, `m["c"]`, m["c"], 3)
m.Add("a", 1)
expectEq(t, "len(m)", len(m), 2)
expectEq(t, `m["a"]`, m["a"], 1)
expectEq(t, `m["b"]`, m["b"], 0)
expectEq(t, `m["c"]`, m["c"], 3)
m.Add("b", 2)
expectEq(t, "len(m)", len(m), 3)
expectEq(t, `m["a"]`, m["a"], 1)
expectEq(t, `m["b"]`, m["b"], 2)
expectEq(t, `m["c"]`, m["c"], 3)
}
func TestMapRemove(t *testing.T) {
m := container.NewMap[string, int]()
m.Add("a", 1)
m.Add("b", 2)
m.Add("c", 3)
m.Remove("c")
expectEq(t, "len(m)", len(m), 2)
expectEq(t, `m["a"]`, m["a"], 1)
expectEq(t, `m["b"]`, m["b"], 2)
expectEq(t, `m["c"]`, m["c"], 0)
m.Remove("a")
expectEq(t, "len(m)", len(m), 1)
expectEq(t, `m["a"]`, m["a"], 0)
expectEq(t, `m["b"]`, m["b"], 2)
expectEq(t, `m["c"]`, m["c"], 0)
m.Remove("b")
expectEq(t, "len(m)", len(m), 0)
expectEq(t, `m["a"]`, m["a"], 0)
expectEq(t, `m["b"]`, m["b"], 0)
expectEq(t, `m["c"]`, m["c"], 0)
}
func TestMapContains(t *testing.T) {
m := container.NewMap[string, int]()
m.Add("c", 3)
expectEq(t, `m.Contains("a")`, m.Contains("a"), false)
expectEq(t, `m.Contains("b")`, m.Contains("b"), false)
expectEq(t, `m.Contains("c")`, m.Contains("c"), true)
m.Add("a", 1)
expectEq(t, `m.Contains("a")`, m.Contains("a"), true)
expectEq(t, `m.Contains("b")`, m.Contains("b"), false)
expectEq(t, `m.Contains("c")`, m.Contains("c"), true)
m.Add("b", 2)
expectEq(t, `m.Contains("a")`, m.Contains("a"), true)
expectEq(t, `m.Contains("b")`, m.Contains("b"), true)
expectEq(t, `m.Contains("c")`, m.Contains("c"), true)
}
func TestMapKeys(t *testing.T) {
m := container.NewMap[string, int]()
m.Add("c", 3)
expectEq(t, `m.Keys()`, m.Keys(), []string{"c"})
m.Add("a", 1)
expectEq(t, `m.Keys()`, m.Keys(), []string{"a", "c"})
m.Add("b", 2)
expectEq(t, `m.Keys()`, m.Keys(), []string{"a", "b", "c"})
}
func TestMapValues(t *testing.T) {
m := container.NewMap[string, int]()
m.Add("c", 1)
expectEq(t, `m.Values()`, m.Values(), []int{1})
m.Add("a", 2)
expectEq(t, `m.Values()`, m.Values(), []int{2, 1})
m.Add("b", 3)
expectEq(t, `m.Values()`, m.Values(), []int{2, 3, 1})
}

100
tools/src/container/set.go Normal file
View File

@ -0,0 +1,100 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container
import "sort"
// Set is a generic unordered set, which wrap's go's builtin 'map'.
// T is the set key, which must match the 'key' constraint.
type Set[T key] map[T]struct{}
// Returns a new set with the give items
func NewSet[T key](items ...T) Set[T] {
out := make(Set[T])
for _, item := range items {
out.Add(item)
}
return out
}
// Clone returns a new Set populated with s
func (s Set[T]) Clone() Set[T] {
out := make(Set[T], len(s))
for item := range s {
out.Add(item)
}
return out
}
// Add adds an item to the set.
func (s Set[T]) Add(item T) {
s[item] = struct{}{}
}
// AddAll adds all the items of o to the set.
func (s Set[T]) AddAll(o Set[T]) {
for item := range o {
s.Add(item)
}
}
// Remove removes an item from the set
func (s Set[T]) Remove(item T) {
delete(s, item)
}
// RemoveAll removes all the items of o from the set.
func (s Set[T]) RemoveAll(o Set[T]) {
for item := range o {
s.Remove(item)
}
}
// Contains returns true if the set contains the given item
func (s Set[T]) Contains(item T) bool {
_, found := s[item]
return found
}
// Contains returns true if the set contains all the items in o
func (s Set[T]) ContainsAll(o Set[T]) bool {
for item := range o {
if !s.Contains(item) {
return false
}
}
return true
}
// Intersection returns true if the set contains all the items in o
func (s Set[T]) Intersection(o Set[T]) Set[T] {
out := NewSet[T]()
for item := range o {
if s.Contains(item) {
out.Add(item)
}
}
return out
}
// List returns the sorted entries of the set as a slice
func (s Set[T]) List() []T {
out := make([]T, 0, len(s))
for v := range s {
out = append(out, v)
}
sort.Slice(out, func(i, j int) bool { return out[i] < out[j] })
return out
}

View File

@ -0,0 +1,145 @@
// Copyright 2022 The Dawn Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package container_test
import (
"testing"
"dawn.googlesource.com/dawn/tools/src/container"
)
func TestNewEmptySet(t *testing.T) {
s := container.NewSet[string]()
expectEq(t, "len(s)", len(s), 0)
}
func TestNewSet(t *testing.T) {
s := container.NewSet("c", "a", "b")
expectEq(t, "len(s)", len(s), 3)
}
func TestSetList(t *testing.T) {
s := container.NewSet("c", "a", "b")
expectEq(t, "s.List()", s.List(), []string{"a", "b", "c"})
}
func TestSetClone(t *testing.T) {
a := container.NewSet("c", "a", "b")
b := a.Clone()
a.Remove("a")
expectEq(t, "b.List()", b.List(), []string{"a", "b", "c"})
}
func TestSetAdd(t *testing.T) {
s := container.NewSet[string]()
s.Add("c")
expectEq(t, "len(s)", len(s), 1)
expectEq(t, "s.List()", s.List(), []string{"c"})
s.Add("a")
expectEq(t, "len(s)", len(s), 2)
expectEq(t, "s.List()", s.List(), []string{"a", "c"})
s.Add("b")
expectEq(t, "len(s)", len(s), 3)
expectEq(t, "s.List()", s.List(), []string{"a", "b", "c"})
}
func TestSetRemove(t *testing.T) {
s := container.NewSet("c", "a", "b")
s.Remove("c")
expectEq(t, "len(s)", len(s), 2)
expectEq(t, "s.List()", s.List(), []string{"a", "b"})
s.Remove("a")
expectEq(t, "len(s)", len(s), 1)
expectEq(t, "s.List()", s.List(), []string{"b"})
s.Remove("b")
expectEq(t, "len(s)", len(s), 0)
expectEq(t, "s.List()", s.List(), []string{})
}
func TestSetContains(t *testing.T) {
s := container.NewSet[string]()
s.Add("c")
expectEq(t, `m.Contains("a")`, s.Contains("a"), false)
expectEq(t, `s.Contains("b")`, s.Contains("b"), false)
expectEq(t, `s.Contains("c")`, s.Contains("c"), true)
s.Add("a")
expectEq(t, `s.Contains("a")`, s.Contains("a"), true)
expectEq(t, `s.Contains("b")`, s.Contains("b"), false)
expectEq(t, `s.Contains("c")`, s.Contains("c"), true)
s.Add("b")
expectEq(t, `s.Contains("a")`, s.Contains("a"), true)
expectEq(t, `s.Contains("b")`, s.Contains("b"), true)
expectEq(t, `s.Contains("c")`, s.Contains("c"), true)
}
func TestSetContainsAll(t *testing.T) {
S := container.NewSet[string]
s := container.NewSet[string]()
s.Add("c")
expectEq(t, `s.ContainsAll("a")`, s.ContainsAll(S("a")), false)
expectEq(t, `s.ContainsAll("b")`, s.ContainsAll(S("b")), false)
expectEq(t, `s.ContainsAll("c")`, s.ContainsAll(S("c")), true)
expectEq(t, `s.ContainsAll("a", "b")`, s.ContainsAll(S("a", "b")), false)
expectEq(t, `s.ContainsAll("b", "c")`, s.ContainsAll(S("b", "c")), false)
expectEq(t, `s.ContainsAll("c", "a")`, s.ContainsAll(S("c", "a")), false)
expectEq(t, `s.ContainsAll("c", "a", "b")`, s.ContainsAll(S("c", "a", "b")), false)
s.Add("a")
expectEq(t, `s.ContainsAll("a")`, s.ContainsAll(S("a")), true)
expectEq(t, `s.ContainsAll("b")`, s.ContainsAll(S("b")), false)
expectEq(t, `s.ContainsAll("c")`, s.ContainsAll(S("c")), true)
expectEq(t, `s.ContainsAll("a", "b")`, s.ContainsAll(S("a", "b")), false)
expectEq(t, `s.ContainsAll("b", "c")`, s.ContainsAll(S("b", "c")), false)
expectEq(t, `s.ContainsAll("c", "a")`, s.ContainsAll(S("c", "a")), true)
expectEq(t, `s.ContainsAll("c", "a", "b")`, s.ContainsAll(S("c", "a", "b")), false)
s.Add("b")
expectEq(t, `s.ContainsAll("a")`, s.ContainsAll(S("a")), true)
expectEq(t, `s.ContainsAll("b")`, s.ContainsAll(S("b")), true)
expectEq(t, `s.ContainsAll("c")`, s.ContainsAll(S("c")), true)
expectEq(t, `s.ContainsAll("a", "b")`, s.ContainsAll(S("a", "b")), true)
expectEq(t, `s.ContainsAll("b", "c")`, s.ContainsAll(S("b", "c")), true)
expectEq(t, `s.ContainsAll("c", "a")`, s.ContainsAll(S("c", "a")), true)
expectEq(t, `s.ContainsAll("c", "a", "b")`, s.ContainsAll(S("c", "a", "b")), true)
}
func TestSetIntersection(t *testing.T) {
a := container.NewSet(1, 3, 4, 6)
b := container.NewSet(2, 3, 4, 5)
i := a.Intersection(b)
expectEq(t, `i.List()`, i.List(), []int{3, 4})
}
func TestSetAddAll(t *testing.T) {
s := container.NewSet[string]()
s.AddAll(container.NewSet("c", "a"))
expectEq(t, "len(s)", len(s), 2)
expectEq(t, "s.List()", s.List(), []string{"a", "c"})
}
func TestSetRemoveAll(t *testing.T) {
s := container.NewSet("c", "a", "b")
s.RemoveAll(container.NewSet("c", "a"))
expectEq(t, "len(s)", len(s), 1)
expectEq(t, "s.List()", s.List(), []string{"b"})
}

7
tools/src/go.mod Normal file
View File

@ -0,0 +1,7 @@
module dawn.googlesource.com/dawn/tools/src
go 1.18
require github.com/google/go-cmp v0.5.6
require golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect

5
tools/src/go.sum Normal file
View File

@ -0,0 +1,5 @@
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=