Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
unused: revert to using a typeutil.Map
In 41bedbd 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
- Loading branch information
Showing
7 changed files
with
745 additions
and
19 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
} |
Oops, something went wrong.