From 9680b434791dd7af8abb706f69b31c2270ac8c3a Mon Sep 17 00:00:00 2001 From: Dominik Honnef Date: Fri, 5 Mar 2021 09:56:27 +0100 Subject: [PATCH] unused: revert to using a typeutil.Map In 41bedbdbf9e6154d6edae49010d46bd375b076f2 we incorrectly decided that unused no longer needed to deduplicate types. For the most part, this was correct. However, it failed to account for pointer types. go/types does not guarantee that types.NewPointer(T) == types.NewPointer(T). This caused parts of the graph to be incorrectly unreachable. This commit reverts the aforementioned commit, but puts our custom Map in its own package, only to be used by the 'unused' check. Closes gh-942 --- unused/testdata/src/pointers/pointers.go | 17 ++ unused/typemap/example_test.go | 67 +++++ unused/typemap/identical.go | 149 +++++++++++ unused/typemap/map.go | 318 +++++++++++++++++++++++ unused/typemap/map_test.go | 174 +++++++++++++ unused/unused.go | 38 +-- unused/unused_test.go | 1 + 7 files changed, 745 insertions(+), 19 deletions(-) create mode 100644 unused/testdata/src/pointers/pointers.go create mode 100644 unused/typemap/example_test.go create mode 100644 unused/typemap/identical.go create mode 100644 unused/typemap/map.go create mode 100644 unused/typemap/map_test.go diff --git a/unused/testdata/src/pointers/pointers.go b/unused/testdata/src/pointers/pointers.go new file mode 100644 index 00000000..104ebcce --- /dev/null +++ b/unused/testdata/src/pointers/pointers.go @@ -0,0 +1,17 @@ +package baz + +import "fmt" + +type Foo interface { // used + bar() // used +} + +func Bar(f Foo) { // used + f.bar() +} + +type Buzz struct{} // used + +func (b *Buzz) bar() { // used + fmt.Println("foo bar buzz") +} diff --git a/unused/typemap/example_test.go b/unused/typemap/example_test.go new file mode 100644 index 00000000..1627ef78 --- /dev/null +++ b/unused/typemap/example_test.go @@ -0,0 +1,67 @@ +// Copyright 2014 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 typemap_test + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "go/types" + "sort" + + "honnef.co/go/tools/go/types/typeutil" +) + +func ExampleMap() { + const source = `package P + +var X []string +var Y []string + +const p, q = 1.0, 2.0 + +func f(offset int32) (value byte, ok bool) +func g(rune) (uint8, bool) +` + + // Parse and type-check the package. + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, "P.go", source, 0) + if err != nil { + panic(err) + } + pkg, err := new(types.Config).Check("P", fset, []*ast.File{f}, nil) + if err != nil { + panic(err) + } + + scope := pkg.Scope() + + // Group names of package-level objects by their type. + var namesByType typeutil.Map // value is []string + for _, name := range scope.Names() { + T := scope.Lookup(name).Type() + + names, _ := namesByType.At(T).([]string) + names = append(names, name) + namesByType.Set(T, names) + } + + // Format, sort, and print the map entries. + var lines []string + namesByType.Iterate(func(T types.Type, names interface{}) { + lines = append(lines, fmt.Sprintf("%s %s", names, T)) + }) + sort.Strings(lines) + for _, line := range lines { + fmt.Println(line) + } + + // Output: + // [X Y] []string + // [f g] func(offset int32) (value byte, ok bool) + // [p q] untyped float +} diff --git a/unused/typemap/identical.go b/unused/typemap/identical.go new file mode 100644 index 00000000..248f3e17 --- /dev/null +++ b/unused/typemap/identical.go @@ -0,0 +1,149 @@ +package typemap + +import ( + "go/types" +) + +// Unlike types.Identical, receivers of Signature types are not ignored. +// Unlike types.Identical, interfaces are compared via pointer equality (except for the empty interface, which gets deduplicated). +// Unlike types.Identical, structs are compared via pointer equality. +func identical0(x, y types.Type) bool { + if x == y { + return true + } + + switch x := x.(type) { + case *types.Basic: + // Basic types are singletons except for the rune and byte + // aliases, thus we cannot solely rely on the x == y check + // above. See also comment in TypeName.IsAlias. + if y, ok := y.(*types.Basic); ok { + return x.Kind() == y.Kind() + } + + case *types.Array: + // Two array types are identical if they have identical element types + // and the same array length. + if y, ok := y.(*types.Array); ok { + // If one or both array lengths are unknown (< 0) due to some error, + // assume they are the same to avoid spurious follow-on errors. + return (x.Len() < 0 || y.Len() < 0 || x.Len() == y.Len()) && identical0(x.Elem(), y.Elem()) + } + + case *types.Slice: + // Two slice types are identical if they have identical element types. + if y, ok := y.(*types.Slice); ok { + return identical0(x.Elem(), y.Elem()) + } + + case *types.Struct: + if y, ok := y.(*types.Struct); ok { + return x == y + } + + case *types.Pointer: + // Two pointer types are identical if they have identical base types. + if y, ok := y.(*types.Pointer); ok { + return identical0(x.Elem(), y.Elem()) + } + + case *types.Tuple: + // Two tuples types are identical if they have the same number of elements + // and corresponding elements have identical types. + if y, ok := y.(*types.Tuple); ok { + if x.Len() == y.Len() { + if x != nil { + for i := 0; i < x.Len(); i++ { + v := x.At(i) + w := y.At(i) + if !identical0(v.Type(), w.Type()) { + return false + } + } + } + return true + } + } + + case *types.Signature: + // Two function types are identical if they have the same number of parameters + // and result values, corresponding parameter and result types are identical, + // and either both functions are variadic or neither is. Parameter and result + // names are not required to match. + if y, ok := y.(*types.Signature); ok { + + return x.Variadic() == y.Variadic() && + identical0(x.Params(), y.Params()) && + identical0(x.Results(), y.Results()) && + (x.Recv() != nil && y.Recv() != nil && identical0(x.Recv().Type(), y.Recv().Type()) || x.Recv() == nil && y.Recv() == nil) + } + + case *types.Interface: + // The issue with interfaces, typeutil.Map and types.Identical + // + // types.Identical, when comparing two interfaces, only looks at the set + // of all methods, not differentiating between implicit (embedded) and + // explicit methods. + // + // When we see the following two types, in source order + // + // type I1 interface { foo() } + // type I2 interface { I1 } + // + // then we will first correctly process I1 and its underlying type. When + // we get to I2, we will see that its underlying type is identical to + // that of I1 and not process it again. This, however, means that we will + // not record the fact that I2 embeds I1. If only I2 is reachable via the + // graph root, then I1 will not be considered used. + // + // We choose to be lazy and compare interfaces by their + // pointers. This will obviously miss identical interfaces, + // but this only has a runtime cost, it doesn't affect + // correctness. + if y, ok := y.(*types.Interface); ok { + if x.NumEmbeddeds() == 0 && + y.NumEmbeddeds() == 0 && + x.NumMethods() == 0 && + y.NumMethods() == 0 { + // all truly empty interfaces are the same + return true + } + return x == y + } + + case *types.Map: + // Two map types are identical if they have identical key and value types. + if y, ok := y.(*types.Map); ok { + return identical0(x.Key(), y.Key()) && identical0(x.Elem(), y.Elem()) + } + + case *types.Chan: + // Two channel types are identical if they have identical value types + // and the same direction. + if y, ok := y.(*types.Chan); ok { + return x.Dir() == y.Dir() && identical0(x.Elem(), y.Elem()) + } + + case *types.Named: + // Two named types are identical if their type names originate + // in the same type declaration. + if y, ok := y.(*types.Named); ok { + return x.Obj() == y.Obj() + } + + case nil: + + default: + panic("unreachable") + } + + return false +} + +// Identical reports whether x and y are identical types. +// Unlike types.Identical, receivers of Signature types are not ignored. +// Unlike types.Identical, interfaces are compared via pointer equality (except for the empty interface, which gets deduplicated). +// Unlike types.Identical, structs are compared via pointer equality. +func Identical(x, y types.Type) (ret bool) { + return identical0(x, y) +} diff --git a/unused/typemap/map.go b/unused/typemap/map.go new file mode 100644 index 00000000..d9961cab --- /dev/null +++ b/unused/typemap/map.go @@ -0,0 +1,318 @@ +// Copyright 2014 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 typemap defines Map, a mapping from types.Type to interface{} values. +package typemap + +import ( + "bytes" + "fmt" + "go/types" + "reflect" +) + +// Map is a hash-table-based mapping from types (types.Type) to +// arbitrary interface{} values. The concrete types that implement +// the Type interface are pointers. Since they are not canonicalized, +// == cannot be used to check for equivalence, and thus we cannot +// simply use a Go map. +// +// Just as with map[K]V, a nil *Map is a valid empty map. +// +// Not thread-safe. +// +// This fork handles Signatures correctly, respecting method +// receivers. Furthermore, it doesn't deduplicate interfaces or +// structs. Interfaces aren't deduplicated as not to conflate implicit +// and explicit methods. Structs aren't deduplicated because we track +// fields of each type separately. +// +type Map struct { + hasher Hasher // shared by many Maps + table map[uint32][]entry // maps hash to bucket; entry.key==nil means unused + length int // number of map entries +} + +// entry is an entry (key/value association) in a hash bucket. +type entry struct { + key types.Type + value interface{} +} + +// SetHasher sets the hasher used by Map. +// +// All Hashers are functionally equivalent but contain internal state +// used to cache the results of hashing previously seen types. +// +// A single Hasher created by MakeHasher() may be shared among many +// Maps. This is recommended if the instances have many keys in +// common, as it will amortize the cost of hash computation. +// +// A Hasher may grow without bound as new types are seen. Even when a +// type is deleted from the map, the Hasher never shrinks, since other +// types in the map may reference the deleted type indirectly. +// +// Hashers are not thread-safe, and read-only operations such as +// Map.Lookup require updates to the hasher, so a full Mutex lock (not a +// read-lock) is require around all Map operations if a shared +// hasher is accessed from multiple threads. +// +// If SetHasher is not called, the Map will create a private hasher at +// the first call to Insert. +// +func (m *Map) SetHasher(hasher Hasher) { + m.hasher = hasher +} + +// Delete removes the entry with the given key, if any. +// It returns true if the entry was found. +// +func (m *Map) Delete(key types.Type) bool { + if m != nil && m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + for i, e := range bucket { + if e.key != nil && Identical(key, e.key) { + // We can't compact the bucket as it + // would disturb iterators. + bucket[i] = entry{} + m.length-- + return true + } + } + } + return false +} + +// At returns the map entry for the given key. +// The result is nil if the entry is not present. +// +func (m *Map) At(key types.Type) interface{} { + if m != nil && m.table != nil { + for _, e := range m.table[m.hasher.Hash(key)] { + if e.key != nil && Identical(key, e.key) { + return e.value + } + } + } + return nil +} + +// Set sets the map entry for key to val, +// and returns the previous entry, if any. +func (m *Map) Set(key types.Type, value interface{}) (prev interface{}) { + if m.table != nil { + hash := m.hasher.Hash(key) + bucket := m.table[hash] + var hole *entry + for i, e := range bucket { + if e.key == nil { + hole = &bucket[i] + } else if Identical(key, e.key) { + prev = e.value + bucket[i].value = value + return + } + } + + if hole != nil { + *hole = entry{key, value} // overwrite deleted entry + } else { + m.table[hash] = append(bucket, entry{key, value}) + } + } else { + if m.hasher.memo == nil { + m.hasher = MakeHasher() + } + hash := m.hasher.Hash(key) + m.table = map[uint32][]entry{hash: {entry{key, value}}} + } + + m.length++ + return +} + +// Len returns the number of map entries. +func (m *Map) Len() int { + if m != nil { + return m.length + } + return 0 +} + +// Iterate calls function f on each entry in the map in unspecified order. +// +// If f should mutate the map, Iterate provides the same guarantees as +// Go maps: if f deletes a map entry that Iterate has not yet reached, +// f will not be invoked for it, but if f inserts a map entry that +// Iterate has not yet reached, whether or not f will be invoked for +// it is unspecified. +// +func (m *Map) Iterate(f func(key types.Type, value interface{})) { + if m != nil { + for _, bucket := range m.table { + for _, e := range bucket { + if e.key != nil { + f(e.key, e.value) + } + } + } + } +} + +// Keys returns a new slice containing the set of map keys. +// The order is unspecified. +func (m *Map) Keys() []types.Type { + keys := make([]types.Type, 0, m.Len()) + m.Iterate(func(key types.Type, _ interface{}) { + keys = append(keys, key) + }) + return keys +} + +func (m *Map) toString(values bool) string { + if m == nil { + return "{}" + } + var buf bytes.Buffer + fmt.Fprint(&buf, "{") + sep := "" + m.Iterate(func(key types.Type, value interface{}) { + fmt.Fprint(&buf, sep) + sep = ", " + fmt.Fprint(&buf, key) + if values { + fmt.Fprintf(&buf, ": %q", value) + } + }) + fmt.Fprint(&buf, "}") + return buf.String() +} + +// String returns a string representation of the map's entries. +// Values are printed using fmt.Sprintf("%v", v). +// Order is unspecified. +// +func (m *Map) String() string { + return m.toString(true) +} + +// KeysString returns a string representation of the map's key set. +// Order is unspecified. +// +func (m *Map) KeysString() string { + return m.toString(false) +} + +//////////////////////////////////////////////////////////////////////// +// Hasher + +// A Hasher maps each type to its hash value. +// For efficiency, a hasher uses memoization; thus its memory +// footprint grows monotonically over time. +// Hashers are not thread-safe. +// Hashers have reference semantics. +// Call MakeHasher to create a Hasher. +type Hasher struct { + memo map[types.Type]uint32 +} + +// MakeHasher returns a new Hasher instance. +func MakeHasher() Hasher { + return Hasher{make(map[types.Type]uint32)} +} + +// Hash computes a hash value for the given type t such that +// Identical(t, t') => Hash(t) == Hash(t'). +func (h Hasher) Hash(t types.Type) uint32 { + hash, ok := h.memo[t] + if !ok { + hash = h.hashFor(t) + h.memo[t] = hash + } + return hash +} + +// hashString computes the Fowler–Noll–Vo hash of s. +func hashString(s string) uint32 { + var h uint32 + for i := 0; i < len(s); i++ { + h ^= uint32(s[i]) + h *= 16777619 + } + return h +} + +// hashFor computes the hash of t. +func (h Hasher) hashFor(t types.Type) uint32 { + // See Identical for rationale. + switch t := t.(type) { + case *types.Basic: + return uint32(t.Kind()) + + case *types.Array: + return 9043 + 2*uint32(t.Len()) + 3*h.Hash(t.Elem()) + + case *types.Slice: + return 9049 + 2*h.Hash(t.Elem()) + + case *types.Struct: + var hash uint32 = 9059 + for i, n := 0, t.NumFields(); i < n; i++ { + f := t.Field(i) + if f.Anonymous() { + hash += 8861 + } + hash += hashString(t.Tag(i)) + hash += hashString(f.Name()) // (ignore f.Pkg) + hash += h.Hash(f.Type()) + } + return hash + + case *types.Pointer: + return 9067 + 2*h.Hash(t.Elem()) + + case *types.Signature: + var hash uint32 = 9091 + if t.Variadic() { + hash *= 8863 + } + return hash + 3*h.hashTuple(t.Params()) + 5*h.hashTuple(t.Results()) + + case *types.Interface: + var hash uint32 = 9103 + for i, n := 0, t.NumMethods(); i < n; i++ { + // See go/types.identicalMethods for rationale. + // Method order is not significant. + // Ignore m.Pkg(). + m := t.Method(i) + hash += 3*hashString(m.Name()) + 5*h.Hash(m.Type()) + } + return hash + + case *types.Map: + return 9109 + 2*h.Hash(t.Key()) + 3*h.Hash(t.Elem()) + + case *types.Chan: + return 9127 + 2*uint32(t.Dir()) + 3*h.Hash(t.Elem()) + + case *types.Named: + // Not safe with a copying GC; objects may move. + return uint32(reflect.ValueOf(t.Obj()).Pointer()) + + case *types.Tuple: + return h.hashTuple(t) + } + panic(t) +} + +func (h Hasher) hashTuple(tuple *types.Tuple) uint32 { + // See go/types.identicalTypes for rationale. + n := tuple.Len() + var hash uint32 = 9137 + 2*uint32(n) + for i := 0; i < n; i++ { + hash += 3 * h.Hash(tuple.At(i).Type()) + } + return hash +} diff --git a/unused/typemap/map_test.go b/unused/typemap/map_test.go new file mode 100644 index 00000000..8ab48e3e --- /dev/null +++ b/unused/typemap/map_test.go @@ -0,0 +1,174 @@ +// Copyright 2014 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 typemap_test + +// TODO(adonovan): +// - test use of explicit hasher across two maps. +// - test hashcodes are consistent with equals for a range of types +// (e.g. all types generated by type-checking some body of real code). + +import ( + "go/types" + "testing" + + "honnef.co/go/tools/unused/typemap" +) + +var ( + tStr = types.Typ[types.String] // string + tPStr1 = types.NewPointer(tStr) // *string + tPStr2 = types.NewPointer(tStr) // *string, again + tInt = types.Typ[types.Int] // int + tChanInt1 = types.NewChan(types.RecvOnly, tInt) // <-chan int + tChanInt2 = types.NewChan(types.RecvOnly, tInt) // <-chan int, again +) + +func checkEqualButNotIdentical(t *testing.T, x, y types.Type, comment string) { + if !types.Identical(x, y) { + t.Errorf("%s: not equal: %s, %s", comment, x, y) + } + if x == y { + t.Errorf("%s: identical: %v, %v", comment, x, y) + } +} + +func TestAxioms(t *testing.T) { + checkEqualButNotIdentical(t, tPStr1, tPStr2, "tPstr{1,2}") + checkEqualButNotIdentical(t, tChanInt1, tChanInt2, "tChanInt{1,2}") +} + +func TestMap(t *testing.T) { + var tmap *typemap.Map + + // All methods but Set are safe on on (*T)(nil). + _ = tmap.Len() + _ = tmap.At(tPStr1) + _ = tmap.Delete(tPStr1) + _ = tmap.KeysString() + _ = tmap.String() + + tmap = new(typemap.Map) + + // Length of empty map. + if l := tmap.Len(); l != 0 { + t.Errorf("Len() on empty Map: got %d, want 0", l) + } + // At of missing key. + if v := tmap.At(tPStr1); v != nil { + t.Errorf("At() on empty Map: got %v, want nil", v) + } + // Deletion of missing key. + if tmap.Delete(tPStr1) { + t.Errorf("Delete() on empty Map: got true, want false") + } + // Set of new key. + if prev := tmap.Set(tPStr1, "*string"); prev != nil { + t.Errorf("Set() on empty Map returned non-nil previous value %s", prev) + } + + // Now: {*string: "*string"} + + // Length of non-empty map. + if l := tmap.Len(); l != 1 { + t.Errorf("Len(): got %d, want 1", l) + } + // At via insertion key. + if v := tmap.At(tPStr1); v != "*string" { + t.Errorf("At(): got %q, want \"*string\"", v) + } + // At via equal key. + if v := tmap.At(tPStr2); v != "*string" { + t.Errorf("At(): got %q, want \"*string\"", v) + } + // Iteration over sole entry. + tmap.Iterate(func(key types.Type, value interface{}) { + if key != tPStr1 { + t.Errorf("Iterate: key: got %s, want %s", key, tPStr1) + } + if want := "*string"; value != want { + t.Errorf("Iterate: value: got %s, want %s", value, want) + } + }) + + // Setion with key equal to present one. + if prev := tmap.Set(tPStr2, "*string again"); prev != "*string" { + t.Errorf("Set() previous value: got %s, want \"*string\"", prev) + } + + // Setion of another association. + if prev := tmap.Set(tChanInt1, "<-chan int"); prev != nil { + t.Errorf("Set() previous value: got %s, want nil", prev) + } + + // Now: {*string: "*string again", <-chan int: "<-chan int"} + + want1 := "{*string: \"*string again\", <-chan int: \"<-chan int\"}" + want2 := "{<-chan int: \"<-chan int\", *string: \"*string again\"}" + if s := tmap.String(); s != want1 && s != want2 { + t.Errorf("String(): got %s, want %s", s, want1) + } + + want1 = "{*string, <-chan int}" + want2 = "{<-chan int, *string}" + if s := tmap.KeysString(); s != want1 && s != want2 { + t.Errorf("KeysString(): got %s, want %s", s, want1) + } + + // Keys(). + I := types.Identical + switch k := tmap.Keys(); { + case I(k[0], tChanInt1) && I(k[1], tPStr1): // ok + case I(k[1], tChanInt1) && I(k[0], tPStr1): // ok + default: + t.Errorf("Keys(): got %v, want %s", k, want2) + } + + if l := tmap.Len(); l != 2 { + t.Errorf("Len(): got %d, want 1", l) + } + // At via original key. + if v := tmap.At(tPStr1); v != "*string again" { + t.Errorf("At(): got %q, want \"*string again\"", v) + } + hamming := 1 + tmap.Iterate(func(key types.Type, value interface{}) { + switch { + case I(key, tChanInt1): + hamming *= 2 // ok + case I(key, tPStr1): + hamming *= 3 // ok + } + }) + if hamming != 6 { + t.Errorf("Iterate: hamming: got %d, want %d", hamming, 6) + } + + if v := tmap.At(tChanInt2); v != "<-chan int" { + t.Errorf("At(): got %q, want \"<-chan int\"", v) + } + // Deletion with key equal to present one. + if !tmap.Delete(tChanInt2) { + t.Errorf("Delete() of existing key: got false, want true") + } + + // Now: {*string: "*string again"} + + if l := tmap.Len(); l != 1 { + t.Errorf("Len(): got %d, want 1", l) + } + // Deletion again. + if !tmap.Delete(tPStr2) { + t.Errorf("Delete() of existing key: got false, want true") + } + + // Now: {} + + if l := tmap.Len(); l != 0 { + t.Errorf("Len(): got %d, want %d", l, 0) + } + if s := tmap.String(); s != "{}" { + t.Errorf("Len(): got %q, want %q", s, "") + } +} diff --git a/unused/unused.go b/unused/unused.go index 79769771..b1312bef 100644 --- a/unused/unused.go +++ b/unused/unused.go @@ -17,6 +17,7 @@ import ( "honnef.co/go/tools/go/ir" "honnef.co/go/tools/go/types/typeutil" "honnef.co/go/tools/internal/passes/buildir" + "honnef.co/go/tools/unused/typemap" "golang.org/x/tools/go/analysis" ) @@ -542,9 +543,9 @@ func run(pass *analysis.Pass) (interface{}, error) { for _, v := range g.Nodes { debugNode(v) } - for _, node := range g.TypeNodes { - debugNode(node) - } + g.TypeNodes.Iterate(func(key types.Type, value interface{}) { + debugNode(value.(*node)) + }) debugf("}\n") } @@ -554,9 +555,10 @@ func run(pass *analysis.Pass) (interface{}, error) { func results(g *graph) (used, unused []types.Object) { g.color(g.Root) - for _, node := range g.TypeNodes { + g.TypeNodes.Iterate(func(_ types.Type, value interface{}) { + node := value.(*node) if node.seen { - continue + return } switch obj := node.obj.(type) { case *types.Struct: @@ -573,7 +575,7 @@ func results(g *graph) (used, unused []types.Object) { } } } - } + }) // OPT(dh): can we find meaningful initial capacities for the used and unused slices? @@ -609,9 +611,9 @@ func results(g *graph) (used, unused []types.Object) { type graph struct { Root *node - seenTypes map[types.Type]struct{} + seenTypes typemap.Map - TypeNodes map[types.Type]*node + TypeNodes typemap.Map Nodes map[interface{}]*node // context @@ -622,10 +624,8 @@ type graph struct { func newGraph() *graph { g := &graph{ - Nodes: map[interface{}]*node{}, - seenFns: map[*ir.Function]struct{}{}, - seenTypes: map[types.Type]struct{}{}, - TypeNodes: map[types.Type]*node{}, + Nodes: map[interface{}]*node{}, + seenFns: map[*ir.Function]struct{}{}, } g.Root = g.newNode(nil) return g @@ -676,11 +676,11 @@ func (g *graph) nodeMaybe(obj types.Object) (*node, bool) { func (g *graph) node(obj interface{}) (n *node, new bool) { switch obj := obj.(type) { case types.Type: - if v := g.TypeNodes[obj]; v != nil { - return v, false + if v := g.TypeNodes.At(obj); v != nil { + return v.(*node), false } n = g.newNode(obj) - g.TypeNodes[obj] = n + g.TypeNodes.Set(obj, n) return n, true case types.Object: // OPT(dh): the types.Object and default cases are identical @@ -1120,7 +1120,7 @@ func (g *graph) entry(pkg *pkg) { var ifaces []*types.Interface var notIfaces []types.Type - for t := range g.seenTypes { + g.seenTypes.Iterate(func(t types.Type, _ interface{}) { switch t := t.(type) { case *types.Interface: // OPT(dh): (8.1) we only need interfaces that have unexported methods @@ -1130,7 +1130,7 @@ func (g *graph) entry(pkg *pkg) { notIfaces = append(notIfaces, t) } } - } + }) // (8.0) handle interfaces for _, t := range notIfaces { @@ -1269,7 +1269,7 @@ func (g *graph) function(fn *ir.Function) { } func (g *graph) typ(t types.Type, parent types.Type) { - if _, ok := g.seenTypes[t]; ok { + if g.seenTypes.At(t) != nil { return } @@ -1279,7 +1279,7 @@ func (g *graph) typ(t types.Type, parent types.Type) { } } - g.seenTypes[t] = struct{}{} + g.seenTypes.Set(t, struct{}{}) if isIrrelevant(t) { return } diff --git a/unused/unused_test.go b/unused/unused_test.go index 4b500abf..dfc15e71 100644 --- a/unused/unused_test.go +++ b/unused/unused_test.go @@ -155,6 +155,7 @@ func TestAll(t *testing.T) { "nocopy", "nocopy-main", "pointer-type-embedding", + "pointers", "quiet", "selectors", "switch_interface",