mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
go/types: walk all imports when determining package name ambiguity
CL 209578 disambiguated paths among imported packages, but as demonstrated in #43119, formatted types may reference packages that are not directly imported. Fix this by recursively walking all imports to determine whether there is any ambiguity in the import graph. This might result in over-qualification of names, but it is straightforward and should eliminate any ambiguity. In general this should be fine, but might introduce risk of infinite recursion in the case of an importer bug, or performance problems for very large import graphs. Mitigate the former by tracking seen packages, and the latter by only walking the import graph once an error has been produced. Fixes #43119 Change-Id: If874f050ad0e808db8e354c2ffc88bc6d64fd277 Reviewed-on: https://go-review.googlesource.com/c/go/+/313035 Trust: Robert Findley <rfindley@google.com> Trust: Robert Griesemer <gri@golang.org> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
40254ec0db
commit
15105dd4b5
4 changed files with 97 additions and 12 deletions
|
|
@ -91,7 +91,16 @@ type Checker struct {
|
||||||
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
|
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
|
||||||
posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
|
posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
|
||||||
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
|
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
|
||||||
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
|
|
||||||
|
// pkgPathMap maps package names to the set of distinct import paths we've
|
||||||
|
// seen for that name, anywhere in the import graph. It is used for
|
||||||
|
// disambiguating package names in error messages.
|
||||||
|
//
|
||||||
|
// pkgPathMap is allocated lazily, so that we don't pay the price of building
|
||||||
|
// it on the happy path. seenPkgMap tracks the packages that we've already
|
||||||
|
// walked.
|
||||||
|
pkgPathMap map[string]map[string]bool
|
||||||
|
seenPkgMap map[*Package]bool
|
||||||
|
|
||||||
// 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;
|
||||||
|
|
@ -187,7 +196,6 @@ func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Ch
|
||||||
impMap: make(map[importKey]*Package),
|
impMap: make(map[importKey]*Package),
|
||||||
posMap: make(map[*Interface][]token.Pos),
|
posMap: make(map[*Interface][]token.Pos),
|
||||||
typMap: make(map[string]*Named),
|
typMap: make(map[string]*Named),
|
||||||
pkgCnt: make(map[string]int),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,9 +273,6 @@ 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()
|
||||||
|
|
||||||
|
|
@ -277,6 +282,12 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
|
||||||
|
|
||||||
check.pkg.complete = true
|
check.pkg.complete = true
|
||||||
|
|
||||||
|
// no longer needed - release memory
|
||||||
|
check.imports = nil
|
||||||
|
check.dotImportMap = nil
|
||||||
|
check.pkgPathMap = nil
|
||||||
|
check.seenPkgMap = nil
|
||||||
|
|
||||||
// TODO(rFindley) There's more memory we should release at this point.
|
// TODO(rFindley) There's more memory we should release at this point.
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,13 @@ func unreachable() {
|
||||||
func (check *Checker) qualifier(pkg *Package) string {
|
func (check *Checker) qualifier(pkg *Package) string {
|
||||||
// Qualify the package unless it's the package being type-checked.
|
// Qualify the package unless it's the package being type-checked.
|
||||||
if pkg != check.pkg {
|
if pkg != check.pkg {
|
||||||
|
if check.pkgPathMap == nil {
|
||||||
|
check.pkgPathMap = make(map[string]map[string]bool)
|
||||||
|
check.seenPkgMap = make(map[*Package]bool)
|
||||||
|
check.markImports(pkg)
|
||||||
|
}
|
||||||
// If the same package name was used by multiple packages, display the full path.
|
// If the same package name was used by multiple packages, display the full path.
|
||||||
if check.pkgCnt[pkg.name] > 1 {
|
if len(check.pkgPathMap[pkg.name]) > 1 {
|
||||||
return strconv.Quote(pkg.path)
|
return strconv.Quote(pkg.path)
|
||||||
}
|
}
|
||||||
return pkg.name
|
return pkg.name
|
||||||
|
|
@ -37,6 +42,26 @@ func (check *Checker) qualifier(pkg *Package) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// markImports recursively walks pkg and its imports, to record unique import
|
||||||
|
// paths in pkgPathMap.
|
||||||
|
func (check *Checker) markImports(pkg *Package) {
|
||||||
|
if check.seenPkgMap[pkg] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
check.seenPkgMap[pkg] = true
|
||||||
|
|
||||||
|
forName, ok := check.pkgPathMap[pkg.name]
|
||||||
|
if !ok {
|
||||||
|
forName = make(map[string]bool)
|
||||||
|
check.pkgPathMap[pkg.name] = forName
|
||||||
|
}
|
||||||
|
forName[pkg.path] = true
|
||||||
|
|
||||||
|
for _, imp := range pkg.imports {
|
||||||
|
check.markImports(imp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (check *Checker) sprintf(format string, args ...interface{}) string {
|
func (check *Checker) sprintf(format string, args ...interface{}) string {
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
switch a := arg.(type) {
|
switch a := arg.(type) {
|
||||||
|
|
|
||||||
|
|
@ -477,7 +477,7 @@ func TestIssue34151(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bast := mustParse(t, bsrc)
|
bast := mustParse(t, bsrc)
|
||||||
conf := Config{Importer: importHelper{a}}
|
conf := Config{Importer: importHelper{pkg: a}}
|
||||||
b, err := conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil)
|
b, err := conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("package %s failed to typecheck: %v", b.Name(), err)
|
t.Errorf("package %s failed to typecheck: %v", b.Name(), err)
|
||||||
|
|
@ -485,14 +485,18 @@ func TestIssue34151(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type importHelper struct {
|
type importHelper struct {
|
||||||
pkg *Package
|
pkg *Package
|
||||||
|
fallback Importer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h importHelper) Import(path string) (*Package, error) {
|
func (h importHelper) Import(path string) (*Package, error) {
|
||||||
if path != h.pkg.Path() {
|
if path == h.pkg.Path() {
|
||||||
|
return h.pkg, nil
|
||||||
|
}
|
||||||
|
if h.fallback == nil {
|
||||||
return nil, fmt.Errorf("got package path %q; want %q", path, h.pkg.Path())
|
return nil, fmt.Errorf("got package path %q; want %q", path, h.pkg.Path())
|
||||||
}
|
}
|
||||||
return h.pkg, nil
|
return h.fallback.Import(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestIssue34921 verifies that we don't update an imported type's underlying
|
// TestIssue34921 verifies that we don't update an imported type's underlying
|
||||||
|
|
@ -516,7 +520,7 @@ func TestIssue34921(t *testing.T) {
|
||||||
var pkg *Package
|
var pkg *Package
|
||||||
for _, src := range sources {
|
for _, src := range sources {
|
||||||
f := mustParse(t, src)
|
f := mustParse(t, src)
|
||||||
conf := Config{Importer: importHelper{pkg}}
|
conf := Config{Importer: importHelper{pkg: pkg}}
|
||||||
res, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
|
res, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("%q failed to typecheck: %v", src, err)
|
t.Errorf("%q failed to typecheck: %v", src, err)
|
||||||
|
|
@ -571,3 +575,44 @@ func TestIssue44515(t *testing.T) {
|
||||||
t.Errorf("got %q; want %q", got, want)
|
t.Errorf("got %q; want %q", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestIssue43124(t *testing.T) {
|
||||||
|
// TODO(rFindley) enhance the testdata tests to be able to express this type
|
||||||
|
// of setup.
|
||||||
|
|
||||||
|
// All involved packages have the same name (template). Error messages should
|
||||||
|
// disambiguate between text/template and html/template by printing the full
|
||||||
|
// path.
|
||||||
|
const (
|
||||||
|
asrc = `package a; import "text/template"; func F(template.Template) {}; func G(int) {}`
|
||||||
|
bsrc = `package b; import ("a"; "html/template"); func _() { a.F(template.Template{}) }`
|
||||||
|
csrc = `package c; import ("a"; "html/template"); func _() { a.G(template.Template{}) }`
|
||||||
|
)
|
||||||
|
|
||||||
|
a, err := pkgFor("a", asrc, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("package a failed to typecheck: %v", err)
|
||||||
|
}
|
||||||
|
conf := Config{Importer: importHelper{pkg: a, fallback: importer.Default()}}
|
||||||
|
|
||||||
|
// Packages should be fully qualified when there is ambiguity within the
|
||||||
|
// error string itself.
|
||||||
|
bast := mustParse(t, bsrc)
|
||||||
|
_, err = conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("package b had no errors")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "text/template") || !strings.Contains(err.Error(), "html/template") {
|
||||||
|
t.Errorf("type checking error for b does not disambiguate package template: %q", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...and also when there is any ambiguity in reachable packages.
|
||||||
|
cast := mustParse(t, csrc)
|
||||||
|
_, err = conf.Check(cast.Name.Name, fset, []*ast.File{cast}, nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("package c had no errors")
|
||||||
|
}
|
||||||
|
if !strings.Contains(err.Error(), "html/template") {
|
||||||
|
t.Errorf("type checking error for c does not disambiguate package template: %q", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,11 @@ func (check *Checker) importPackage(at positioner, path, dir string) *Package {
|
||||||
// package should be complete or marked fake, but be cautious
|
// package should be complete or marked fake, but be cautious
|
||||||
if imp.complete || imp.fake {
|
if imp.complete || imp.fake {
|
||||||
check.impMap[key] = imp
|
check.impMap[key] = imp
|
||||||
check.pkgCnt[imp.name]++
|
// Once we've formatted an error message once, keep the pkgPathMap
|
||||||
|
// up-to-date on subsequent imports.
|
||||||
|
if check.pkgPathMap != nil {
|
||||||
|
check.markImports(imp)
|
||||||
|
}
|
||||||
return imp
|
return imp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue