From 475941c2957a1d98dc333c0924baf7f6439e0af0 Mon Sep 17 00:00:00 2001 From: Ben Clayton Date: Wed, 30 Mar 2022 21:12:14 +0000 Subject: [PATCH] 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 Reviewed-by: Ryan Harrison Commit-Queue: Ryan Harrison --- .gitignore | 5 +- tools/src/container/container.go | 16 +++ tools/src/container/container_test.go | 28 +++++ tools/src/container/key.go | 24 +++++ tools/src/container/map.go | 62 +++++++++++ tools/src/container/map_test.go | 114 ++++++++++++++++++++ tools/src/container/set.go | 100 ++++++++++++++++++ tools/src/container/set_test.go | 145 ++++++++++++++++++++++++++ tools/src/go.mod | 7 ++ tools/src/go.sum | 5 + 10 files changed, 505 insertions(+), 1 deletion(-) create mode 100644 tools/src/container/container.go create mode 100644 tools/src/container/container_test.go create mode 100644 tools/src/container/key.go create mode 100644 tools/src/container/map.go create mode 100644 tools/src/container/map_test.go create mode 100644 tools/src/container/set.go create mode 100644 tools/src/container/set_test.go create mode 100644 tools/src/go.mod create mode 100644 tools/src/go.sum diff --git a/.gitignore b/.gitignore index 1ab427f6a4..a028df0cb5 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/tools/src/container/container.go b/tools/src/container/container.go new file mode 100644 index 0000000000..995875774e --- /dev/null +++ b/tools/src/container/container.go @@ -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 diff --git a/tools/src/container/container_test.go b/tools/src/container/container_test.go new file mode 100644 index 0000000000..064ae0301a --- /dev/null +++ b/tools/src/container/container_test.go @@ -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) + } +} diff --git a/tools/src/container/key.go b/tools/src/container/key.go new file mode 100644 index 0000000000..ea843b73bc --- /dev/null +++ b/tools/src/container/key.go @@ -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 +} diff --git a/tools/src/container/map.go b/tools/src/container/map.go new file mode 100644 index 0000000000..6b8acdc184 --- /dev/null +++ b/tools/src/container/map.go @@ -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 +} diff --git a/tools/src/container/map_test.go b/tools/src/container/map_test.go new file mode 100644 index 0000000000..292b428ac4 --- /dev/null +++ b/tools/src/container/map_test.go @@ -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}) +} diff --git a/tools/src/container/set.go b/tools/src/container/set.go new file mode 100644 index 0000000000..c48a649a33 --- /dev/null +++ b/tools/src/container/set.go @@ -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 +} diff --git a/tools/src/container/set_test.go b/tools/src/container/set_test.go new file mode 100644 index 0000000000..ff1e28f6ba --- /dev/null +++ b/tools/src/container/set_test.go @@ -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"}) +} diff --git a/tools/src/go.mod b/tools/src/go.mod new file mode 100644 index 0000000000..c55a6ee075 --- /dev/null +++ b/tools/src/go.mod @@ -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 diff --git a/tools/src/go.sum b/tools/src/go.sum new file mode 100644 index 0000000000..58b75a45fd --- /dev/null +++ b/tools/src/go.sum @@ -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=