1
0
mirror of https://github.com/golang/go synced 2024-11-11 21:20:21 -07:00

[dev.regabi] go/types: report unused packages in source order

This is a port of CL 287072 to go/types.

Change-Id: I08f56995f0323c1f238d1b44703a481d393471d5
Reviewed-on: https://go-review.googlesource.com/c/go/+/289720
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Trust: Robert Findley <rfindley@google.com>
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Rob Findley 2021-02-04 12:24:10 -05:00 committed by Robert Findley
parent 813958f13c
commit c48d1503ba
5 changed files with 54 additions and 61 deletions

View File

@ -69,6 +69,12 @@ type importKey struct {
path, dir string path, dir string
} }
// A dotImportKey describes a dot-imported object in the given scope.
type dotImportKey struct {
scope *Scope
obj Object
}
// A Checker maintains the state of the type checker. // A Checker maintains the state of the type checker.
// It must be created with NewChecker. // It must be created with NewChecker.
type Checker struct { type Checker struct {
@ -87,7 +93,8 @@ type Checker struct {
// (initialized by Files, valid only for the duration of check.Files; // (initialized by Files, valid only for the duration of check.Files;
// maps and lists are allocated on demand) // maps and lists are allocated on demand)
files []*ast.File // package files files []*ast.File // package files
unusedDotImports map[*Scope]map[*Package]*ast.ImportSpec // unused dot-imported packages imports []*PkgName // list of imported packages
dotImportMap map[dotImportKey]*PkgName // maps dot-imported objects to the package they were dot-imported through
firstErr error // first error encountered firstErr error // first error encountered
methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods methods map[*TypeName][]*Func // maps package scope type names to associated non-blank (non-interface) methods
@ -104,22 +111,6 @@ type Checker struct {
indent int // indentation for tracing indent int // indentation for tracing
} }
// addUnusedImport adds the position of a dot-imported package
// pkg to the map of dot imports for the given file scope.
func (check *Checker) addUnusedDotImport(scope *Scope, pkg *Package, spec *ast.ImportSpec) {
mm := check.unusedDotImports
if mm == nil {
mm = make(map[*Scope]map[*Package]*ast.ImportSpec)
check.unusedDotImports = mm
}
m := mm[scope]
if m == nil {
m = make(map[*Package]*ast.ImportSpec)
mm[scope] = m
}
m[pkg] = spec
}
// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists // addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists
func (check *Checker) addDeclDep(to Object) { func (check *Checker) addDeclDep(to Object) {
from := check.decl from := check.decl
@ -202,7 +193,8 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
func (check *Checker) initFiles(files []*ast.File) { func (check *Checker) initFiles(files []*ast.File) {
// start with a clean slate (check.Files may be called multiple times) // start with a clean slate (check.Files may be called multiple times)
check.files = nil check.files = nil
check.unusedDotImports = nil check.imports = nil
check.dotImportMap = nil
check.firstErr = nil check.firstErr = nil
check.methods = nil check.methods = nil
@ -272,10 +264,16 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
if !check.conf.DisableUnusedImportCheck { if !check.conf.DisableUnusedImportCheck {
check.unusedImports() check.unusedImports()
} }
// no longer needed - release memory
check.imports = nil
check.dotImportMap = nil
check.recordUntyped() check.recordUntyped()
check.pkg.complete = true check.pkg.complete = true
// TODO(rFindley) There's more memory we should release at this point.
return return
} }

View File

@ -275,21 +275,26 @@ func (check *Checker) collectObjects() {
} }
} }
obj := NewPkgName(d.spec.Pos(), pkg, name, imp) pkgName := NewPkgName(d.spec.Pos(), pkg, name, imp)
if d.spec.Name != nil { if d.spec.Name != nil {
// in a dot-import, the dot represents the package // in a dot-import, the dot represents the package
check.recordDef(d.spec.Name, obj) check.recordDef(d.spec.Name, pkgName)
} else { } else {
check.recordImplicit(d.spec, obj) check.recordImplicit(d.spec, pkgName)
} }
if path == "C" { if path == "C" {
// match cmd/compile (not prescribed by spec) // match cmd/compile (not prescribed by spec)
obj.used = true pkgName.used = true
} }
// add import to file scope // add import to file scope
check.imports = append(check.imports, pkgName)
if name == "." { if name == "." {
// dot-import
if check.dotImportMap == nil {
check.dotImportMap = make(map[dotImportKey]*PkgName)
}
// merge imported scope with file scope // merge imported scope with file scope
for _, obj := range imp.scope.elems { for _, obj := range imp.scope.elems {
// A package scope may contain non-exported objects, // A package scope may contain non-exported objects,
@ -303,16 +308,15 @@ func (check *Checker) collectObjects() {
if alt := fileScope.Insert(obj); alt != nil { if alt := fileScope.Insert(obj); alt != nil {
check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", obj.Name()) check.errorf(d.spec.Name, _DuplicateDecl, "%s redeclared in this block", obj.Name())
check.reportAltDecl(alt) check.reportAltDecl(alt)
} else {
check.dotImportMap[dotImportKey{fileScope, obj}] = pkgName
} }
} }
} }
// add position to set of dot-import positions for this file
// (this is only needed for "imported but not used" errors)
check.addUnusedDotImport(fileScope, imp, d.spec)
} else { } else {
// declare imported package object in file scope // declare imported package object in file scope
// (no need to provide s.Name since we called check.recordDef earlier) // (no need to provide s.Name since we called check.recordDef earlier)
check.declare(fileScope, nil, obj, token.NoPos) check.declare(fileScope, nil, pkgName, token.NoPos)
} }
case constDecl: case constDecl:
// declare all constants // declare all constants
@ -566,40 +570,31 @@ func (check *Checker) unusedImports() {
// any of its exported identifiers. To import a package solely for its side-effects // any of its exported identifiers. To import a package solely for its side-effects
// (initialization), use the blank identifier as explicit package name." // (initialization), use the blank identifier as explicit package name."
// check use of regular imported packages for _, obj := range check.imports {
for _, scope := range check.pkg.scope.children /* file scopes */ { if !obj.used && obj.name != "_" {
for _, obj := range scope.elems { check.errorUnusedPkg(obj)
if obj, ok := obj.(*PkgName); ok { }
// Unused "blank imports" are automatically ignored }
// since _ identifiers are not entered into scopes. }
if !obj.used {
func (check *Checker) errorUnusedPkg(obj *PkgName) {
// If the package was imported with a name other than the final
// import path element, show it explicitly in the error message.
// Note that this handles both renamed imports and imports of
// packages containing unconventional package declarations.
// Note that this uses / always, even on Windows, because Go import
// paths always use forward slashes.
path := obj.imported.path path := obj.imported.path
base := pkgName(path) elem := path
if obj.name == base { if i := strings.LastIndex(elem, "/"); i >= 0 {
elem = elem[i+1:]
}
if obj.name == "" || obj.name == "." || obj.name == elem {
check.softErrorf(obj, _UnusedImport, "%q imported but not used", path) check.softErrorf(obj, _UnusedImport, "%q imported but not used", path)
} else { } else {
check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name) check.softErrorf(obj, _UnusedImport, "%q imported but not used as %s", path, obj.name)
} }
} }
}
}
}
// check use of dot-imported packages
for _, unusedDotImports := range check.unusedDotImports {
for pkg, pos := range unusedDotImports {
check.softErrorf(pos, _UnusedImport, "%q imported but not used", pkg.path)
}
}
}
// pkgName returns the package name (last element) of an import path.
func pkgName(path string) string {
if i := strings.LastIndex(path, "/"); i >= 0 {
path = path[i+1:]
}
return path
}
// dir makes a good-faith attempt to return the directory // dir makes a good-faith attempt to return the directory
// portion of path. If path is empty, the result is ".". // portion of path. If path is empty, the result is ".".

View File

@ -8,7 +8,7 @@ import "math"
import m "math" import m "math"
import . "testing" // declares T in file scope import . "testing" // declares T in file scope
import . /* ERROR "imported but not used" */ "unsafe" import . /* ERROR .unsafe. imported but not used */ "unsafe"
import . "fmt" // declares Println in file scope import . "fmt" // declares Println in file scope
import ( import (

View File

@ -4,7 +4,7 @@
package importdecl1 package importdecl1
import . /* ERROR "imported but not used" */ "unsafe" import . /* ERROR .unsafe. imported but not used */ "unsafe"
type B interface { type B interface {
A A

View File

@ -51,12 +51,12 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, wantType bool)
} }
assert(typ != nil) assert(typ != nil)
// The object may be dot-imported: If so, remove its package from // The object may have been dot-imported.
// the map of unused dot imports for the respective file scope. // If so, mark the respective package as used.
// (This code is only needed for dot-imports. Without them, // (This code is only needed for dot-imports. Without them,
// we only have to mark variables, see *Var case below). // we only have to mark variables, see *Var case below).
if pkg := obj.Pkg(); pkg != check.pkg && pkg != nil { if pkgName := check.dotImportMap[dotImportKey{scope, obj}]; pkgName != nil {
delete(check.unusedDotImports[scope], pkg) pkgName.used = true
} }
switch obj := obj.(type) { switch obj := obj.(type) {