mirror of
https://github.com/golang/go
synced 2024-11-18 17:54:57 -07:00
go.tools/go/types: "imported but not used" checks for packages
also: - initial code for unused label errors - some cleanups, better names - additional tests TODO: Dot-imported packages are not handled yet; i.e., they are always considered used for now. R=adonovan CC=golang-dev https://golang.org/cl/13768043
This commit is contained in:
parent
34fbb29ae0
commit
86e41f819a
@ -96,7 +96,7 @@ func TestScopesInfo(t *testing.T) {
|
||||
{`package p`, []string{
|
||||
"file:",
|
||||
}},
|
||||
{`package p; import ( "fmt"; m "math"; _ "os" )`, []string{
|
||||
{`package p; import ( "fmt"; m "math"; _ "os" ); var ( _ = fmt.Println; _ = m.Pi )`, []string{
|
||||
"file:fmt m",
|
||||
}},
|
||||
{`package p; func _() {}`, []string{
|
||||
|
@ -187,6 +187,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
|
||||
if ident, ok := e.X.(*ast.Ident); ok {
|
||||
if pkg, _ := check.topScope.LookupParent(ident.Name).(*PkgName); pkg != nil {
|
||||
check.recordObject(ident, pkg)
|
||||
pkg.used = true
|
||||
exp := pkg.pkg.scope.Lookup(sel)
|
||||
if exp == nil {
|
||||
if !pkg.pkg.fake {
|
||||
@ -196,7 +197,7 @@ func (check *checker) selector(x *operand, e *ast.SelectorExpr) {
|
||||
}
|
||||
if !exp.IsExported() {
|
||||
check.errorf(e.Pos(), "%s not exported by package %s", sel, ident)
|
||||
goto Error
|
||||
// ok to continue
|
||||
}
|
||||
check.recordSelection(e, PackageObj, nil, exp, nil, false)
|
||||
// Simplified version of the code for *ast.Idents:
|
||||
|
@ -49,6 +49,7 @@ type checker struct {
|
||||
// functions
|
||||
funcList []funcInfo // list of functions/methods with correct signatures and non-empty bodies
|
||||
funcSig *Signature // signature of currently type-checked function
|
||||
labels *Scope // label scope of currently type-checked function; lazily allocated
|
||||
|
||||
// debugging
|
||||
indent int // indentation for tracing
|
||||
|
@ -45,6 +45,7 @@ var (
|
||||
|
||||
// Each tests entry is list of files belonging to the same package.
|
||||
var tests = [][]string{
|
||||
{"testdata/importdecl0a.src", "testdata/importdecl0b.src"},
|
||||
{"testdata/cycles.src"},
|
||||
{"testdata/decls0.src"},
|
||||
{"testdata/decls1.src"},
|
||||
|
@ -87,6 +87,8 @@ import m "math"
|
||||
const c = 3.0
|
||||
type T []int
|
||||
func f(a int, s string) float64 {
|
||||
fmt.Println("calling f")
|
||||
_ = m.Pi // use package math
|
||||
const d int = c + 1
|
||||
var x int
|
||||
x = a + len(s)
|
||||
|
@ -123,10 +123,12 @@ func (obj *object) sameId(pkg *Package, name string) bool {
|
||||
// A PkgName represents an imported Go package.
|
||||
type PkgName struct {
|
||||
object
|
||||
|
||||
used bool
|
||||
}
|
||||
|
||||
func NewPkgName(pos token.Pos, pkg *Package, name string) *PkgName {
|
||||
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid]}}
|
||||
return &PkgName{object: object{nil, pos, pkg, name, Typ[Invalid]}}
|
||||
}
|
||||
|
||||
func (obj *PkgName) String() string { return obj.toString("package", nil) }
|
||||
@ -236,10 +238,12 @@ func (obj *Func) String() string {
|
||||
// A Label represents a declared label.
|
||||
type Label struct {
|
||||
object
|
||||
|
||||
used bool
|
||||
}
|
||||
|
||||
func NewLabel(pos token.Pos, name string) *Label {
|
||||
return &Label{object{nil, pos, nil, name, nil}}
|
||||
return &Label{object: object{nil, pos, nil, name, nil}}
|
||||
}
|
||||
|
||||
func (obj *Label) String() string { return fmt.Sprintf("label %s", obj.Name()) }
|
||||
|
@ -114,13 +114,13 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
pkg := check.pkg
|
||||
|
||||
// Phase 1: Pre-declare all package-level objects so that they can be found
|
||||
// independent of source order. Collect methods for a later phase.
|
||||
// independent of source order. Associate methods with receiver
|
||||
// base type names.
|
||||
|
||||
var (
|
||||
scopes []*Scope // corresponding file scope per file
|
||||
scope *Scope // current file scope
|
||||
objList []Object // list of package-level objects to type-check
|
||||
objMap = make(map[Object]declInfo) // declaration info for each package-level object
|
||||
fileScope *Scope // current file scope
|
||||
objList []Object // list of package-level objects to type-check
|
||||
objMap = make(map[Object]declInfo) // declaration info for each package-level object
|
||||
)
|
||||
|
||||
// declare declares obj in the package scope, records its ident -> obj mapping,
|
||||
@ -137,7 +137,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
|
||||
check.declareObj(pkg.scope, ident, obj)
|
||||
objList = append(objList, obj)
|
||||
objMap[obj] = declInfo{scope, typ, init, nil}
|
||||
objMap[obj] = declInfo{fileScope, typ, init, nil}
|
||||
}
|
||||
|
||||
importer := check.conf.Import
|
||||
@ -148,14 +148,15 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
// only add packages to pkg.imports that have not been seen already
|
||||
seen := make(map[*Package]bool)
|
||||
|
||||
var fileScopes []*Scope // list of file scopes
|
||||
for _, file := range files {
|
||||
// The package identifier denotes the current package,
|
||||
// but there is no corresponding package object.
|
||||
check.recordObject(file.Name, nil)
|
||||
|
||||
scope = NewScope(pkg.scope)
|
||||
check.recordScope(file, scope)
|
||||
scopes = append(scopes, scope)
|
||||
fileScope = NewScope(pkg.scope)
|
||||
check.recordScope(file, fileScope)
|
||||
fileScopes = append(fileScopes, fileScope)
|
||||
|
||||
for _, decl := range file.Decls {
|
||||
switch d := decl.(type) {
|
||||
@ -223,13 +224,13 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
if obj.IsExported() {
|
||||
// Note: This will change each imported object's scope!
|
||||
// May be an issue for type aliases.
|
||||
check.declareObj(scope, nil, obj)
|
||||
check.declareObj(fileScope, nil, obj)
|
||||
check.recordImplicit(s, obj)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// declare imported package object in file scope
|
||||
check.declareObj(scope, nil, obj)
|
||||
check.declareObj(fileScope, nil, obj)
|
||||
}
|
||||
|
||||
case *ast.ValueSpec:
|
||||
@ -332,7 +333,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
}
|
||||
}
|
||||
objList = append(objList, obj)
|
||||
objMap[obj] = declInfo{scope, nil, nil, d}
|
||||
objMap[obj] = declInfo{fileScope, nil, nil, d}
|
||||
|
||||
default:
|
||||
check.invalidAST(d.Pos(), "unknown ast.Decl node %T", d)
|
||||
@ -342,7 +343,7 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
|
||||
// Phase 2: Verify that objects in package and file scopes have different names.
|
||||
|
||||
for _, scope := range scopes {
|
||||
for _, scope := range fileScopes {
|
||||
for _, obj := range scope.elems {
|
||||
if alt := pkg.scope.Lookup(obj.Name()); alt != nil {
|
||||
check.errorf(alt.Pos(), "%s already declared in this file through import of package %s", obj.Name(), obj.Pkg().Name())
|
||||
@ -384,16 +385,43 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
|
||||
check.topScope = f.sig.scope // open function scope
|
||||
check.funcSig = f.sig
|
||||
check.labels = nil // lazily allocated
|
||||
check.stmtList(f.body.List, false)
|
||||
|
||||
if f.sig.results.Len() > 0 && !check.isTerminating(f.body, "") {
|
||||
check.errorf(f.body.Rbrace, "missing return")
|
||||
}
|
||||
|
||||
// spec: "It is illegal to define a label that is never used."
|
||||
if check.labels != nil {
|
||||
for _, obj := range check.labels.elems {
|
||||
if l := obj.(*Label); !l.used {
|
||||
check.errorf(l.pos, "%s defined but not used", l.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 5: Check for declared but not used packages and variables.
|
||||
// Note: must happen after checking all functions because closures may affect outer scopes
|
||||
|
||||
// Each set of implicitly declared lhs variables of a type switch acts collectively
|
||||
// spec: "It is illegal (...) to directly import a package without referring to
|
||||
// any of its exported identifiers. To import a package solely for its side-effects
|
||||
// (initialization), use the blank identifier as explicit package name."
|
||||
for _, scope := range fileScopes {
|
||||
// Unused "blank imports" are automatically ignored
|
||||
// since _ identifiers are not entered into scopes.
|
||||
for _, obj := range scope.elems {
|
||||
if p, _ := obj.(*PkgName); p != nil && !p.used {
|
||||
check.errorf(p.pos, "%q imported but not used", p.pkg.path)
|
||||
}
|
||||
// TODO(gri) dot-imports are not handled for now since we don't
|
||||
// have mapping from Object to corresponding PkgName through
|
||||
// which the object was imported.
|
||||
}
|
||||
}
|
||||
|
||||
// Each set of implicitly declared lhs variables in a type switch acts collectively
|
||||
// as a single lhs variable. If any one was 'used', all of them are 'used'. Handle
|
||||
// them before the general analysis.
|
||||
for _, vars := range check.lhsVarsList {
|
||||
@ -411,9 +439,9 @@ func (check *checker) resolveFiles(files []*ast.File) {
|
||||
}
|
||||
}
|
||||
|
||||
// spec: "Implementation restriction: A compiler may make it illegal to
|
||||
// declare a variable inside a function body if the variable is never used."
|
||||
for _, f := range check.funcList {
|
||||
// spec: "Implementation restriction: A compiler may make it illegal to
|
||||
// declare a variable inside a function body if the variable is never used."
|
||||
check.usage(f.sig.scope)
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +116,6 @@ func TestStdfixed(t *testing.T) {
|
||||
"bug223.go", "bug413.go", "bug459.go", // TODO(gri) complete initialization checks
|
||||
"bug248.go", "bug302.go", "bug369.go", // complex test instructions - ignore
|
||||
"bug250.go", // TODO(gri) fix recursive interfaces
|
||||
"bug373.go", // TODO(gri) implement use checks
|
||||
"bug376.go", // TODO(gri) built-ins must be called (no built-in function expressions)
|
||||
"issue3924.go", // TODO(gri) && and || produce bool result (not untyped bool)
|
||||
"issue4847.go", // TODO(gri) initialization cycle error not found
|
||||
|
@ -63,6 +63,14 @@ func (check *checker) closeScope() {
|
||||
check.topScope = check.topScope.Parent()
|
||||
}
|
||||
|
||||
func assignOp(op token.Token) token.Token {
|
||||
// token_test.go verifies the token ordering this function relies on
|
||||
if token.ADD_ASSIGN <= op && op <= token.AND_NOT_ASSIGN {
|
||||
return op + (token.ADD - token.ADD_ASSIGN)
|
||||
}
|
||||
return token.ILLEGAL
|
||||
}
|
||||
|
||||
// stmt typechecks statement s.
|
||||
func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
|
||||
// statements cannot use iota in general
|
||||
@ -77,13 +85,17 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
|
||||
check.declStmt(s.Decl)
|
||||
|
||||
case *ast.LabeledStmt:
|
||||
scope := check.funcSig.labels
|
||||
scope := check.labels
|
||||
if scope == nil {
|
||||
scope = NewScope(nil) // no label scope chain
|
||||
check.funcSig.labels = scope
|
||||
check.labels = scope
|
||||
}
|
||||
label := s.Label
|
||||
check.declareObj(scope, label, NewLabel(label.Pos(), label.Name))
|
||||
l := NewLabel(label.Pos(), label.Name)
|
||||
// Labels are not resolved yet - mark them as used to avoid errors.
|
||||
// TODO(gri) fix this
|
||||
l.used = true
|
||||
check.declareObj(scope, label, l)
|
||||
check.stmt(s.Stmt, fallthroughOk)
|
||||
|
||||
case *ast.ExprStmt:
|
||||
@ -169,32 +181,8 @@ func (check *checker) stmt(s ast.Stmt, fallthroughOk bool) {
|
||||
check.errorf(s.TokPos, "assignment operation %s requires single-valued expressions", s.Tok)
|
||||
return
|
||||
}
|
||||
// TODO(gri) make this conversion more efficient
|
||||
var op token.Token
|
||||
switch s.Tok {
|
||||
case token.ADD_ASSIGN:
|
||||
op = token.ADD
|
||||
case token.SUB_ASSIGN:
|
||||
op = token.SUB
|
||||
case token.MUL_ASSIGN:
|
||||
op = token.MUL
|
||||
case token.QUO_ASSIGN:
|
||||
op = token.QUO
|
||||
case token.REM_ASSIGN:
|
||||
op = token.REM
|
||||
case token.AND_ASSIGN:
|
||||
op = token.AND
|
||||
case token.OR_ASSIGN:
|
||||
op = token.OR
|
||||
case token.XOR_ASSIGN:
|
||||
op = token.XOR
|
||||
case token.SHL_ASSIGN:
|
||||
op = token.SHL
|
||||
case token.SHR_ASSIGN:
|
||||
op = token.SHR
|
||||
case token.AND_NOT_ASSIGN:
|
||||
op = token.AND_NOT
|
||||
default:
|
||||
op := assignOp(s.Tok)
|
||||
if op == token.ILLEGAL {
|
||||
check.invalidAST(s.TokPos, "unknown assignment operation %s", s.Tok)
|
||||
return
|
||||
}
|
||||
|
18
go/types/testdata/decls0.src
vendored
18
go/types/testdata/decls0.src
vendored
@ -6,23 +6,7 @@
|
||||
|
||||
package decls0
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
// we can have multiple blank imports (was bug)
|
||||
_ "math"
|
||||
_ "net/rpc"
|
||||
// reflect defines a type "flag" which shows up in the gc export data
|
||||
"reflect"
|
||||
. "reflect"
|
||||
init /* ERROR "cannot declare init" */ "fmt"
|
||||
)
|
||||
|
||||
// reflect.flag must not be visible in this package
|
||||
type flag int
|
||||
type _ reflect /* ERROR "not exported" */ .flag
|
||||
|
||||
// dot-imported exported objects may conflict with local objects
|
||||
type Value /* ERROR "already declared in this file" */ struct{}
|
||||
import "unsafe"
|
||||
|
||||
const pi = 3.1415
|
||||
|
||||
|
43
go/types/testdata/importdecl0a.src
vendored
Normal file
43
go/types/testdata/importdecl0a.src
vendored
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2013 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 importdecl0
|
||||
|
||||
import ()
|
||||
|
||||
import (
|
||||
// we can have multiple blank imports (was bug)
|
||||
_ "math"
|
||||
_ "net/rpc"
|
||||
init /* ERROR "cannot declare init" */ "fmt"
|
||||
// reflect defines a type "flag" which shows up in the gc export data
|
||||
"reflect"
|
||||
. "reflect"
|
||||
)
|
||||
|
||||
import "math" /* ERROR "imported but not used" */
|
||||
import m /* ERROR "imported but not used" */ "math"
|
||||
import _ "math"
|
||||
|
||||
import (
|
||||
"math/big" /* ERROR "imported but not used" */
|
||||
b /* ERROR "imported but not used" */ "math/big"
|
||||
_ "math/big"
|
||||
)
|
||||
|
||||
import "fmt"
|
||||
import f "fmt"
|
||||
|
||||
// reflect.flag must not be visible in this package
|
||||
type flag int
|
||||
type _ reflect /* ERROR "not exported" */ .flag
|
||||
|
||||
// dot-imported exported objects may conflict with local objects
|
||||
type Value /* ERROR "already declared in this file" */ struct{}
|
||||
|
||||
var _ = fmt.Println // use "fmt"
|
||||
|
||||
func _() {
|
||||
f.Println() // use "fmt"
|
||||
}
|
12
go/types/testdata/importdecl0b.src
vendored
Normal file
12
go/types/testdata/importdecl0b.src
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
// Copyright 2013 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 importdecl0
|
||||
|
||||
import "math"
|
||||
import m "math"
|
||||
|
||||
// using "math" in this file doesn't affect its use in other files
|
||||
const Pi0 = math.Pi
|
||||
const Pi1 = m.Pi
|
47
go/types/token_test.go
Normal file
47
go/types/token_test.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// This file checks invariants of token.Token ordering that we rely on
|
||||
// since package go/token doesn't provide any guarantees at the moment.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"go/token"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var assignOps = map[token.Token]token.Token{
|
||||
token.ADD_ASSIGN: token.ADD,
|
||||
token.SUB_ASSIGN: token.SUB,
|
||||
token.MUL_ASSIGN: token.MUL,
|
||||
token.QUO_ASSIGN: token.QUO,
|
||||
token.REM_ASSIGN: token.REM,
|
||||
token.AND_ASSIGN: token.AND,
|
||||
token.OR_ASSIGN: token.OR,
|
||||
token.XOR_ASSIGN: token.XOR,
|
||||
token.SHL_ASSIGN: token.SHL,
|
||||
token.SHR_ASSIGN: token.SHR,
|
||||
token.AND_NOT_ASSIGN: token.AND_NOT,
|
||||
}
|
||||
|
||||
func TestZeroTok(t *testing.T) {
|
||||
// zero value for token.Token must be token.ILLEGAL
|
||||
var zero token.Token
|
||||
if token.ILLEGAL != zero {
|
||||
t.Errorf("%s == %d; want 0", token.ILLEGAL, zero)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssignOp(t *testing.T) {
|
||||
// there are fewer than 256 tokens
|
||||
for i := 0; i < 256; i++ {
|
||||
tok := token.Token(i)
|
||||
got := assignOp(tok)
|
||||
want := assignOps[tok]
|
||||
if got != want {
|
||||
t.Errorf("for assignOp(%s): got %s; want %s", tok, got, want)
|
||||
}
|
||||
}
|
||||
}
|
@ -194,7 +194,6 @@ func (t *Tuple) At(i int) *Var { return t.vars[i] }
|
||||
// A Signature represents a (non-builtin) function or method type.
|
||||
type Signature struct {
|
||||
scope *Scope // function scope, always present
|
||||
labels *Scope // label scope, or nil (lazily allocated)
|
||||
recv *Var // nil if not a method
|
||||
params *Tuple // (incoming) parameters from left to right; or nil
|
||||
results *Tuple // (outgoing) results from left to right; or nil
|
||||
@ -217,7 +216,7 @@ func NewSignature(scope *Scope, recv *Var, params, results *Tuple, isVariadic bo
|
||||
panic("types.NewSignature: variadic parameter must be of unnamed slice type")
|
||||
}
|
||||
}
|
||||
return &Signature{scope, nil, recv, params, results, isVariadic}
|
||||
return &Signature{scope, recv, params, results, isVariadic}
|
||||
}
|
||||
|
||||
// Recv returns the receiver of signature s (if a method), or nil if a
|
||||
|
Loading…
Reference in New Issue
Block a user