Skip to content

Commit 15105dd

Browse files
committed
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 <[email protected]> Trust: Robert Griesemer <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 40254ec commit 15105dd

File tree

4 files changed

+97
-12
lines changed

4 files changed

+97
-12
lines changed

src/go/types/check.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,16 @@ type Checker struct {
9191
impMap map[importKey]*Package // maps (import path, source directory) to (complete or fake) package
9292
posMap map[*Interface][]token.Pos // maps interface types to lists of embedded interface positions
9393
typMap map[string]*Named // maps an instantiated named type hash to a *Named type
94-
pkgCnt map[string]int // counts number of imported packages with a given name (for better error messages)
94+
95+
// pkgPathMap maps package names to the set of distinct import paths we've
96+
// seen for that name, anywhere in the import graph. It is used for
97+
// disambiguating package names in error messages.
98+
//
99+
// pkgPathMap is allocated lazily, so that we don't pay the price of building
100+
// it on the happy path. seenPkgMap tracks the packages that we've already
101+
// walked.
102+
pkgPathMap map[string]map[string]bool
103+
seenPkgMap map[*Package]bool
95104

96105
// information collected during type-checking of a set of package files
97106
// (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
187196
impMap: make(map[importKey]*Package),
188197
posMap: make(map[*Interface][]token.Pos),
189198
typMap: make(map[string]*Named),
190-
pkgCnt: make(map[string]int),
191199
}
192200
}
193201

@@ -265,9 +273,6 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
265273
if !check.conf.DisableUnusedImportCheck {
266274
check.unusedImports()
267275
}
268-
// no longer needed - release memory
269-
check.imports = nil
270-
check.dotImportMap = nil
271276

272277
check.recordUntyped()
273278

@@ -277,6 +282,12 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
277282

278283
check.pkg.complete = true
279284

285+
// no longer needed - release memory
286+
check.imports = nil
287+
check.dotImportMap = nil
288+
check.pkgPathMap = nil
289+
check.seenPkgMap = nil
290+
280291
// TODO(rFindley) There's more memory we should release at this point.
281292

282293
return

src/go/types/errors.go

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,40 @@ func unreachable() {
2828
func (check *Checker) qualifier(pkg *Package) string {
2929
// Qualify the package unless it's the package being type-checked.
3030
if pkg != check.pkg {
31+
if check.pkgPathMap == nil {
32+
check.pkgPathMap = make(map[string]map[string]bool)
33+
check.seenPkgMap = make(map[*Package]bool)
34+
check.markImports(pkg)
35+
}
3136
// If the same package name was used by multiple packages, display the full path.
32-
if check.pkgCnt[pkg.name] > 1 {
37+
if len(check.pkgPathMap[pkg.name]) > 1 {
3338
return strconv.Quote(pkg.path)
3439
}
3540
return pkg.name
3641
}
3742
return ""
3843
}
3944

45+
// markImports recursively walks pkg and its imports, to record unique import
46+
// paths in pkgPathMap.
47+
func (check *Checker) markImports(pkg *Package) {
48+
if check.seenPkgMap[pkg] {
49+
return
50+
}
51+
check.seenPkgMap[pkg] = true
52+
53+
forName, ok := check.pkgPathMap[pkg.name]
54+
if !ok {
55+
forName = make(map[string]bool)
56+
check.pkgPathMap[pkg.name] = forName
57+
}
58+
forName[pkg.path] = true
59+
60+
for _, imp := range pkg.imports {
61+
check.markImports(imp)
62+
}
63+
}
64+
4065
func (check *Checker) sprintf(format string, args ...interface{}) string {
4166
for i, arg := range args {
4267
switch a := arg.(type) {

src/go/types/issues_test.go

Lines changed: 50 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -477,22 +477,26 @@ func TestIssue34151(t *testing.T) {
477477
}
478478

479479
bast := mustParse(t, bsrc)
480-
conf := Config{Importer: importHelper{a}}
480+
conf := Config{Importer: importHelper{pkg: a}}
481481
b, err := conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil)
482482
if err != nil {
483483
t.Errorf("package %s failed to typecheck: %v", b.Name(), err)
484484
}
485485
}
486486

487487
type importHelper struct {
488-
pkg *Package
488+
pkg *Package
489+
fallback Importer
489490
}
490491

491492
func (h importHelper) Import(path string) (*Package, error) {
492-
if path != h.pkg.Path() {
493+
if path == h.pkg.Path() {
494+
return h.pkg, nil
495+
}
496+
if h.fallback == nil {
493497
return nil, fmt.Errorf("got package path %q; want %q", path, h.pkg.Path())
494498
}
495-
return h.pkg, nil
499+
return h.fallback.Import(path)
496500
}
497501

498502
// TestIssue34921 verifies that we don't update an imported type's underlying
@@ -516,7 +520,7 @@ func TestIssue34921(t *testing.T) {
516520
var pkg *Package
517521
for _, src := range sources {
518522
f := mustParse(t, src)
519-
conf := Config{Importer: importHelper{pkg}}
523+
conf := Config{Importer: importHelper{pkg: pkg}}
520524
res, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
521525
if err != nil {
522526
t.Errorf("%q failed to typecheck: %v", src, err)
@@ -571,3 +575,44 @@ func TestIssue44515(t *testing.T) {
571575
t.Errorf("got %q; want %q", got, want)
572576
}
573577
}
578+
579+
func TestIssue43124(t *testing.T) {
580+
// TODO(rFindley) enhance the testdata tests to be able to express this type
581+
// of setup.
582+
583+
// All involved packages have the same name (template). Error messages should
584+
// disambiguate between text/template and html/template by printing the full
585+
// path.
586+
const (
587+
asrc = `package a; import "text/template"; func F(template.Template) {}; func G(int) {}`
588+
bsrc = `package b; import ("a"; "html/template"); func _() { a.F(template.Template{}) }`
589+
csrc = `package c; import ("a"; "html/template"); func _() { a.G(template.Template{}) }`
590+
)
591+
592+
a, err := pkgFor("a", asrc, nil)
593+
if err != nil {
594+
t.Fatalf("package a failed to typecheck: %v", err)
595+
}
596+
conf := Config{Importer: importHelper{pkg: a, fallback: importer.Default()}}
597+
598+
// Packages should be fully qualified when there is ambiguity within the
599+
// error string itself.
600+
bast := mustParse(t, bsrc)
601+
_, err = conf.Check(bast.Name.Name, fset, []*ast.File{bast}, nil)
602+
if err == nil {
603+
t.Fatal("package b had no errors")
604+
}
605+
if !strings.Contains(err.Error(), "text/template") || !strings.Contains(err.Error(), "html/template") {
606+
t.Errorf("type checking error for b does not disambiguate package template: %q", err)
607+
}
608+
609+
// ...and also when there is any ambiguity in reachable packages.
610+
cast := mustParse(t, csrc)
611+
_, err = conf.Check(cast.Name.Name, fset, []*ast.File{cast}, nil)
612+
if err == nil {
613+
t.Fatal("package c had no errors")
614+
}
615+
if !strings.Contains(err.Error(), "html/template") {
616+
t.Errorf("type checking error for c does not disambiguate package template: %q", err)
617+
}
618+
}

src/go/types/resolver.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,11 @@ func (check *Checker) importPackage(at positioner, path, dir string) *Package {
192192
// package should be complete or marked fake, but be cautious
193193
if imp.complete || imp.fake {
194194
check.impMap[key] = imp
195-
check.pkgCnt[imp.name]++
195+
// Once we've formatted an error message once, keep the pkgPathMap
196+
// up-to-date on subsequent imports.
197+
if check.pkgPathMap != nil {
198+
check.markImports(imp)
199+
}
196200
return imp
197201
}
198202

0 commit comments

Comments
 (0)