mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
[dev.typeparams] cmd/compile/internal/types2: report unused packages in source order
1) Rather than map-iterate through all file scopes and collect unused packages, collect all imports in the Checker.imports list so that errors are reported in source order. 2) From cmd/compile, borrow the idea of a "dotImportRefs" map to map dot-imported objects to the package they were dot-imported through (we call the map "dotImportMap"). 3) From cmd/compile, borrow the "pkgnotused" function (called Checker.errorUnusedPkg in this code) and clean up unused package error reporting. 4) Adjust unused package error message to match compiler message exactly. 5) Enable one more excluded test case in test/run.go. Change-Id: I4e4e55512a6043a7fd54f576c7441e3dd4077d6f Reviewed-on: https://go-review.googlesource.com/c/go/+/287072 Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
08a598f8c1
commit
217a461f56
6 changed files with 62 additions and 74 deletions
|
|
@ -74,6 +74,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 {
|
||||||
|
|
@ -92,8 +98,9 @@ type Checker struct {
|
||||||
// information collected during type-checking of a set of package files
|
// information collected during type-checking of a set of package files
|
||||||
// (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 []*syntax.File // package files
|
files []*syntax.File // list of package files
|
||||||
unusedDotImports map[*Scope]map[*Package]syntax.Pos // positions of unused dot-imported packages for each file scope
|
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
|
||||||
|
|
@ -110,22 +117,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, pos syntax.Pos) {
|
|
||||||
mm := check.unusedDotImports
|
|
||||||
if mm == nil {
|
|
||||||
mm = make(map[*Scope]map[*Package]syntax.Pos)
|
|
||||||
check.unusedDotImports = mm
|
|
||||||
}
|
|
||||||
m := mm[scope]
|
|
||||||
if m == nil {
|
|
||||||
m = make(map[*Package]syntax.Pos)
|
|
||||||
mm[scope] = m
|
|
||||||
}
|
|
||||||
m[pkg] = pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -209,7 +200,8 @@ func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
|
||||||
func (check *Checker) initFiles(files []*syntax.File) {
|
func (check *Checker) initFiles(files []*syntax.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
|
||||||
|
|
@ -291,6 +283,9 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) {
|
||||||
print("== unusedImports ==")
|
print("== unusedImports ==")
|
||||||
check.unusedImports()
|
check.unusedImports()
|
||||||
}
|
}
|
||||||
|
// no longer needed - release memory
|
||||||
|
check.imports = nil
|
||||||
|
check.dotImportMap = nil
|
||||||
|
|
||||||
print("== recordUntyped ==")
|
print("== recordUntyped ==")
|
||||||
check.recordUntyped()
|
check.recordUntyped()
|
||||||
|
|
@ -301,6 +296,9 @@ func (check *Checker) checkFiles(files []*syntax.File) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
check.pkg.complete = true
|
check.pkg.complete = true
|
||||||
|
|
||||||
|
// TODO(gri) There's more memory we should release at this point.
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -273,21 +273,26 @@ func (check *Checker) collectObjects() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
obj := NewPkgName(s.Pos(), pkg, name, imp)
|
pkgName := NewPkgName(s.Pos(), pkg, name, imp)
|
||||||
if s.LocalPkgName != nil {
|
if s.LocalPkgName != nil {
|
||||||
// in a dot-import, the dot represents the package
|
// in a dot-import, the dot represents the package
|
||||||
check.recordDef(s.LocalPkgName, obj)
|
check.recordDef(s.LocalPkgName, pkgName)
|
||||||
} else {
|
} else {
|
||||||
check.recordImplicit(s, obj)
|
check.recordImplicit(s, 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,
|
||||||
|
|
@ -301,16 +306,15 @@ func (check *Checker) collectObjects() {
|
||||||
if alt := fileScope.Insert(obj); alt != nil {
|
if alt := fileScope.Insert(obj); alt != nil {
|
||||||
check.errorf(s.LocalPkgName, "%s redeclared in this block", obj.Name())
|
check.errorf(s.LocalPkgName, "%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, s.Pos())
|
|
||||||
} else {
|
} else {
|
||||||
// declare imported package object in file scope
|
// declare imported package object in file scope
|
||||||
// (no need to provide s.LocalPkgName since we called check.recordDef earlier)
|
// (no need to provide s.LocalPkgName since we called check.recordDef earlier)
|
||||||
check.declare(fileScope, nil, obj, nopos)
|
check.declare(fileScope, nil, pkgName, nopos)
|
||||||
}
|
}
|
||||||
|
|
||||||
case *syntax.ConstDecl:
|
case *syntax.ConstDecl:
|
||||||
|
|
@ -673,51 +677,38 @@ 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 {
|
|
||||||
path := obj.imported.path
|
|
||||||
base := pkgName(path)
|
|
||||||
if obj.name == base {
|
|
||||||
if check.conf.CompilerErrorMessages {
|
|
||||||
check.softErrorf(obj.pos, "%q imported and not used", path)
|
|
||||||
} else {
|
|
||||||
check.softErrorf(obj.pos, "%q imported but not used", path)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if check.conf.CompilerErrorMessages {
|
|
||||||
check.softErrorf(obj.pos, "%q imported and not used as %s", path, obj.name)
|
|
||||||
} else {
|
|
||||||
check.softErrorf(obj.pos, "%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 {
|
|
||||||
if check.conf.CompilerErrorMessages {
|
|
||||||
check.softErrorf(pos, "%q imported and not used", pkg.path)
|
|
||||||
} else {
|
|
||||||
check.softErrorf(pos, "%q imported but not used", pkg.path)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pkgName returns the package name (last element) of an import path.
|
func (check *Checker) errorUnusedPkg(obj *PkgName) {
|
||||||
func pkgName(path string) string {
|
// If the package was imported with a name other than the final
|
||||||
if i := strings.LastIndex(path, "/"); i >= 0 {
|
// import path element, show it explicitly in the error message.
|
||||||
path = path[i+1:]
|
// 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
|
||||||
|
elem := path
|
||||||
|
if i := strings.LastIndex(elem, "/"); i >= 0 {
|
||||||
|
elem = elem[i+1:]
|
||||||
|
}
|
||||||
|
if obj.name == "" || obj.name == "." || obj.name == elem {
|
||||||
|
if check.conf.CompilerErrorMessages {
|
||||||
|
check.softErrorf(obj, "imported and not used: %q", path)
|
||||||
|
} else {
|
||||||
|
check.softErrorf(obj, "%q imported but not used", path)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if check.conf.CompilerErrorMessages {
|
||||||
|
check.softErrorf(obj, "imported and not used: %q as %s", path, obj.name)
|
||||||
|
} else {
|
||||||
|
check.softErrorf(obj, "%q imported but not used as %s", path, obj.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return path
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// dir makes a good-faith attempt to return the directory
|
// dir makes a good-faith attempt to return the directory
|
||||||
|
|
|
||||||
|
|
@ -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 (
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -56,12 +56,12 @@ func (check *Checker) ident(x *operand, e *syntax.Name, def *Named, wantType boo
|
||||||
}
|
}
|
||||||
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) {
|
||||||
|
|
|
||||||
|
|
@ -1956,7 +1956,6 @@ var excluded = map[string]bool{
|
||||||
"fixedbugs/issue18331.go": true, // missing error about misuse of //go:noescape (irgen needs code from noder)
|
"fixedbugs/issue18331.go": true, // missing error about misuse of //go:noescape (irgen needs code from noder)
|
||||||
"fixedbugs/issue18393.go": true, // types2 not run after syntax errors
|
"fixedbugs/issue18393.go": true, // types2 not run after syntax errors
|
||||||
"fixedbugs/issue19012.go": true, // multiple errors on same line
|
"fixedbugs/issue19012.go": true, // multiple errors on same line
|
||||||
"fixedbugs/issue20298.go": true, // types2 non-deterministically reports unused imports
|
|
||||||
"fixedbugs/issue20233.go": true, // types2 reports two instead of one error (pref: compiler)
|
"fixedbugs/issue20233.go": true, // types2 reports two instead of one error (pref: compiler)
|
||||||
"fixedbugs/issue20245.go": true, // types2 reports two instead of one error (pref: compiler)
|
"fixedbugs/issue20245.go": true, // types2 reports two instead of one error (pref: compiler)
|
||||||
"fixedbugs/issue20250.go": true, // correct diagnostics, but different lines (probably irgen's fault)
|
"fixedbugs/issue20250.go": true, // correct diagnostics, but different lines (probably irgen's fault)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue