Skip to content

Commit 9ceb1e5

Browse files
committed
cmd/go: avoid needing to manipulate ImportStack when constructing error
Simplify the printing of PackageErrors by pushing and popping packages from the import stack when creating the error, rather than when printing the error. In some cases, we don't have the same amount of information to recreate the exact error, so we'll print the name of the package the error is for, even when it's redundant. In the case of import cycle errors, this change results in the addition of the position information of the error. This change supercedes CLs 220718 and 217106. It introduces a simpler way to format errors. Fixes #36173 Change-Id: Ie27011eb71f82e165ed4f9567bba6890a3849fc1 Reviewed-on: https://go-review.googlesource.com/c/go/+/224660 Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]>
1 parent 3335727 commit 9ceb1e5

File tree

6 files changed

+81
-62
lines changed

6 files changed

+81
-62
lines changed

src/cmd/go/go_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2662,7 +2662,7 @@ func TestBadCommandLines(t *testing.T) {
26622662
tg.tempFile("src/-x/x.go", "package x\n")
26632663
tg.setenv("GOPATH", tg.path("."))
26642664
tg.runFail("build", "--", "-x")
2665-
tg.grepStderr("invalid input directory name \"-x\"", "did not reject -x directory")
2665+
tg.grepStderr("invalid import path \"-x\"", "did not reject -x import path")
26662666

26672667
tg.tempFile("src/-x/y/y.go", "package y\n")
26682668
tg.setenv("GOPATH", tg.path("."))

src/cmd/go/internal/load/pkg.go

Lines changed: 69 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -318,16 +318,16 @@ func (p *Package) copyBuild(pp *build.Package) {
318318

319319
// A PackageError describes an error loading information about a package.
320320
type PackageError struct {
321-
ImportStack []string // shortest path from package named on command line to this one
322-
Pos string // position of error
323-
Err error // the error itself
324-
IsImportCycle bool // the error is an import cycle
325-
Hard bool // whether the error is soft or hard; soft errors are ignored in some places
321+
ImportStack []string // shortest path from package named on command line to this one
322+
Pos string // position of error
323+
Err error // the error itself
324+
IsImportCycle bool // the error is an import cycle
325+
Hard bool // whether the error is soft or hard; soft errors are ignored in some places
326+
alwaysPrintStack bool // whether to always print the ImportStack
326327
}
327328

328329
func (p *PackageError) Error() string {
329-
// Import cycles deserve special treatment.
330-
if p.Pos != "" && !p.IsImportCycle {
330+
if p.Pos != "" && (len(p.ImportStack) == 0 || !p.alwaysPrintStack) {
331331
// Omit import stack. The full path to the file where the error
332332
// is the most important thing.
333333
return p.Pos + ": " + p.Err.Error()
@@ -339,15 +339,14 @@ func (p *PackageError) Error() string {
339339
// last path on the stack, we don't omit the path. An error like
340340
// "package A imports B: error loading C caused by B" would not be clearer
341341
// if "imports B" were omitted.
342-
stack := p.ImportStack
343-
var ierr ImportPathError
344-
if len(stack) > 0 && errors.As(p.Err, &ierr) && ierr.ImportPath() == stack[len(stack)-1] {
345-
stack = stack[:len(stack)-1]
346-
}
347-
if len(stack) == 0 {
342+
if len(p.ImportStack) == 0 {
348343
return p.Err.Error()
349344
}
350-
return "package " + strings.Join(stack, "\n\timports ") + ": " + p.Err.Error()
345+
var optpos string
346+
if p.Pos != "" {
347+
optpos = "\n\t" + p.Pos
348+
}
349+
return "package " + strings.Join(p.ImportStack, "\n\timports ") + optpos + ": " + p.Err.Error()
351350
}
352351

353352
func (p *PackageError) Unwrap() error { return p.Err }
@@ -549,9 +548,6 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
549548
panic("LoadImport called with empty package path")
550549
}
551550

552-
stk.Push(path)
553-
defer stk.Pop()
554-
555551
var parentPath, parentRoot string
556552
parentIsStd := false
557553
if parent != nil {
@@ -564,6 +560,11 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
564560
pre.preloadImports(bp.Imports, bp)
565561
}
566562
if bp == nil {
563+
if importErr, ok := err.(ImportPathError); !ok || importErr.ImportPath() != path {
564+
// Only add path to the error's import stack if it's not already present on the error.
565+
stk.Push(path)
566+
defer stk.Pop()
567+
}
567568
return &Package{
568569
PackagePublic: PackagePublic{
569570
ImportPath: path,
@@ -578,7 +579,9 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
578579
importPath := bp.ImportPath
579580
p := packageCache[importPath]
580581
if p != nil {
582+
stk.Push(path)
581583
p = reusePackage(p, stk)
584+
stk.Pop()
582585
} else {
583586
p = new(Package)
584587
p.Internal.Local = build.IsLocalImport(path)
@@ -588,8 +591,11 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
588591
// Load package.
589592
// loadPackageData may return bp != nil even if an error occurs,
590593
// in order to return partial information.
591-
p.load(stk, bp, err)
592-
if p.Error != nil && p.Error.Pos == "" {
594+
p.load(path, stk, bp, err)
595+
// Add position information unless this is a NoGoError or an ImportCycle error.
596+
// Import cycles deserve special treatment.
597+
var g *build.NoGoError
598+
if p.Error != nil && p.Error.Pos == "" && !errors.As(err, &g) && !p.Error.IsImportCycle {
593599
p = setErrorPos(p, importPos)
594600
}
595601

@@ -608,7 +614,7 @@ func loadImport(pre *preload, path, srcDir string, parent *Package, stk *ImportS
608614
return setErrorPos(perr, importPos)
609615
}
610616
if mode&ResolveImport != 0 {
611-
if perr := disallowVendor(srcDir, path, p, stk); perr != p {
617+
if perr := disallowVendor(srcDir, path, parentPath, p, stk); perr != p {
612618
return setErrorPos(perr, importPos)
613619
}
614620
}
@@ -1246,7 +1252,7 @@ func disallowInternal(srcDir string, importer *Package, importerPath string, p *
12461252
// as if it were generated into the testing directory tree
12471253
// (it's actually in a temporary directory outside any Go tree).
12481254
// This cleans up a former kludge in passing functionality to the testing package.
1249-
if strings.HasPrefix(p.ImportPath, "testing/internal") && len(*stk) >= 2 && (*stk)[len(*stk)-2] == "testmain" {
1255+
if str.HasPathPrefix(p.ImportPath, "testing/internal") && importerPath == "testmain" {
12501256
return p
12511257
}
12521258

@@ -1262,11 +1268,10 @@ func disallowInternal(srcDir string, importer *Package, importerPath string, p *
12621268
return p
12631269
}
12641270

1265-
// The stack includes p.ImportPath.
1266-
// If that's the only thing on the stack, we started
1271+
// importerPath is empty: we started
12671272
// with a name given on the command line, not an
12681273
// import. Anything listed on the command line is fine.
1269-
if len(*stk) == 1 {
1274+
if importerPath == "" {
12701275
return p
12711276
}
12721277

@@ -1315,8 +1320,9 @@ func disallowInternal(srcDir string, importer *Package, importerPath string, p *
13151320
// Internal is present, and srcDir is outside parent's tree. Not allowed.
13161321
perr := *p
13171322
perr.Error = &PackageError{
1318-
ImportStack: stk.Copy(),
1319-
Err: ImportErrorf(p.ImportPath, "use of internal package "+p.ImportPath+" not allowed"),
1323+
alwaysPrintStack: true,
1324+
ImportStack: stk.Copy(),
1325+
Err: ImportErrorf(p.ImportPath, "use of internal package "+p.ImportPath+" not allowed"),
13201326
}
13211327
perr.Incomplete = true
13221328
return &perr
@@ -1344,16 +1350,15 @@ func findInternal(path string) (index int, ok bool) {
13441350
// disallowVendor checks that srcDir is allowed to import p as path.
13451351
// If the import is allowed, disallowVendor returns the original package p.
13461352
// If not, it returns a new package containing just an appropriate error.
1347-
func disallowVendor(srcDir string, path string, p *Package, stk *ImportStack) *Package {
1348-
// The stack includes p.ImportPath.
1349-
// If that's the only thing on the stack, we started
1353+
func disallowVendor(srcDir string, path string, importerPath string, p *Package, stk *ImportStack) *Package {
1354+
// If the importerPath is empty, we started
13501355
// with a name given on the command line, not an
13511356
// import. Anything listed on the command line is fine.
1352-
if len(*stk) == 1 {
1357+
if importerPath == "" {
13531358
return p
13541359
}
13551360

1356-
if perr := disallowVendorVisibility(srcDir, p, stk); perr != p {
1361+
if perr := disallowVendorVisibility(srcDir, p, importerPath, stk); perr != p {
13571362
return perr
13581363
}
13591364

@@ -1376,12 +1381,12 @@ func disallowVendor(srcDir string, path string, p *Package, stk *ImportStack) *P
13761381
// is not subject to the rules, only subdirectories of vendor.
13771382
// This allows people to have packages and commands named vendor,
13781383
// for maximal compatibility with existing source trees.
1379-
func disallowVendorVisibility(srcDir string, p *Package, stk *ImportStack) *Package {
1380-
// The stack includes p.ImportPath.
1381-
// If that's the only thing on the stack, we started
1384+
func disallowVendorVisibility(srcDir string, p *Package, importerPath string, stk *ImportStack) *Package {
1385+
// The stack does not include p.ImportPath.
1386+
// If there's nothing on the stack, we started
13821387
// with a name given on the command line, not an
13831388
// import. Anything listed on the command line is fine.
1384-
if len(*stk) == 1 {
1389+
if importerPath == "" {
13851390
return p
13861391
}
13871392

@@ -1525,7 +1530,8 @@ func (p *Package) DefaultExecName() string {
15251530

15261531
// load populates p using information from bp, err, which should
15271532
// be the result of calling build.Context.Import.
1528-
func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
1533+
// stk contains the import stack, not including path itself.
1534+
func (p *Package) load(path string, stk *ImportStack, bp *build.Package, err error) {
15291535
p.copyBuild(bp)
15301536

15311537
// The localPrefix is the path we interpret ./ imports relative to.
@@ -1548,7 +1554,16 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
15481554

15491555
if err != nil {
15501556
p.Incomplete = true
1557+
// Report path in error stack unless err is an ImportPathError with path already set.
1558+
pushed := false
1559+
if e, ok := err.(ImportPathError); !ok || e.ImportPath() != path {
1560+
stk.Push(path)
1561+
pushed = true // Remember to pop after setError.
1562+
}
15511563
setError(base.ExpandScanner(p.rewordError(err)))
1564+
if pushed {
1565+
stk.Pop()
1566+
}
15521567
if _, isScanErr := err.(scanner.ErrorList); !isScanErr {
15531568
return
15541569
}
@@ -1675,6 +1690,23 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
16751690
}
16761691
}
16771692

1693+
// Check for case-insensitive collisions of import paths.
1694+
fold := str.ToFold(p.ImportPath)
1695+
if other := foldPath[fold]; other == "" {
1696+
foldPath[fold] = p.ImportPath
1697+
} else if other != p.ImportPath {
1698+
setError(ImportErrorf(p.ImportPath, "case-insensitive import collision: %q and %q", p.ImportPath, other))
1699+
return
1700+
}
1701+
1702+
if !SafeArg(p.ImportPath) {
1703+
setError(ImportErrorf(p.ImportPath, "invalid import path %q", p.ImportPath))
1704+
return
1705+
}
1706+
1707+
stk.Push(path)
1708+
defer stk.Pop()
1709+
16781710
// Check for case-insensitive collision of input files.
16791711
// To avoid problems on case-insensitive files, we reject any package
16801712
// where two different input files have equal names under a case-insensitive
@@ -1703,10 +1735,6 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
17031735
setError(fmt.Errorf("invalid input directory name %q", name))
17041736
return
17051737
}
1706-
if !SafeArg(p.ImportPath) {
1707-
setError(ImportErrorf(p.ImportPath, "invalid import path %q", p.ImportPath))
1708-
return
1709-
}
17101738

17111739
// Build list of imported packages and full dependency list.
17121740
imports := make([]*Package, 0, len(p.Imports))
@@ -1770,15 +1798,6 @@ func (p *Package) load(stk *ImportStack, bp *build.Package, err error) {
17701798
return
17711799
}
17721800

1773-
// Check for case-insensitive collisions of import paths.
1774-
fold := str.ToFold(p.ImportPath)
1775-
if other := foldPath[fold]; other == "" {
1776-
foldPath[fold] = p.ImportPath
1777-
} else if other != p.ImportPath {
1778-
setError(ImportErrorf(p.ImportPath, "case-insensitive import collision: %q and %q", p.ImportPath, other))
1779-
return
1780-
}
1781-
17821801
if cfg.ModulesEnabled && p.Error == nil {
17831802
mainPath := p.ImportPath
17841803
if p.Internal.CmdlineFiles {
@@ -2266,9 +2285,7 @@ func GoFilesPackage(gofiles []string) *Package {
22662285
pkg := new(Package)
22672286
pkg.Internal.Local = true
22682287
pkg.Internal.CmdlineFiles = true
2269-
stk.Push("main")
2270-
pkg.load(&stk, bp, err)
2271-
stk.Pop()
2288+
pkg.load("command-line-arguments", &stk, bp, err)
22722289
pkg.Internal.LocalPrefix = dirToImportPath(dir)
22732290
pkg.ImportPath = "command-line-arguments"
22742291
pkg.Target = ""

src/cmd/go/internal/load/test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ func TestPackagesFor(p *Package, cover *TestCover) (pmain, ptest, pxtest *Packag
5656
}
5757
if len(p1.DepsErrors) > 0 {
5858
perr := p1.DepsErrors[0]
59-
perr.Pos = "" // show full import stack
6059
err = perr
6160
break
6261
}

src/cmd/go/testdata/script/mod_empty_err.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ go list -e -f {{.Error}} ./empty
1010
stdout 'no Go files in \$WORK[/\\]empty'
1111

1212
go list -e -f {{.Error}} ./exclude
13-
stdout 'package example.com/m/exclude: build constraints exclude all Go files in \$WORK[/\\]exclude'
13+
stdout 'build constraints exclude all Go files in \$WORK[/\\]exclude'
1414

1515
go list -e -f {{.Error}} ./missing
1616
stdout 'stat '$WORK'[/\\]missing: directory not found'

src/cmd/go/testdata/script/test_import_error_stack.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
! go test testdep/p1
22
stderr 'package testdep/p1 \(test\)\n\timports testdep/p2\n\timports testdep/p3: build constraints exclude all Go files ' # check for full import stack
33

4+
! go vet testdep/p1
5+
stderr 'package testdep/p1 \(test\)\n\timports testdep/p2\n\timports testdep/p3: build constraints exclude all Go files ' # check for full import stack
6+
47
-- testdep/p1/p1.go --
58
package p1
69
-- testdep/p1/p1_test.go --

src/cmd/go/testdata/script/vet_internal.txt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,28 @@ env GO111MODULE=off
33
# Issue 36173. Verify that "go vet" prints line numbers on load errors.
44

55
! go vet a/a.go
6-
stderr '^a[/\\]a.go:5:3: use of internal package'
6+
stderr '^package command-line-arguments\n\ta[/\\]a.go:5:3: use of internal package'
77

88
! go vet a/a_test.go
9-
stderr '^package command-line-arguments \(test\): use of internal package' # BUG
9+
stderr '^package command-line-arguments \(test\)\n\ta[/\\]a_test.go:4:3: use of internal package'
1010

1111
! go vet a
12-
stderr '^a[/\\]a.go:5:3: use of internal package'
12+
stderr '^package a\n\ta[/\\]a.go:5:3: use of internal package'
1313

1414
go vet b/b.go
1515
! stderr 'use of internal package'
1616

1717
! go vet b/b_test.go
18-
stderr '^package command-line-arguments \(test\): use of internal package' # BUG
18+
stderr '^package command-line-arguments \(test\)\n\tb[/\\]b_test.go:4:3: use of internal package'
1919

2020
! go vet depends-on-a/depends-on-a.go
21-
stderr '^a[/\\]a.go:5:3: use of internal package'
21+
stderr '^package command-line-arguments\n\timports a\n\ta[/\\]a.go:5:3: use of internal package'
2222

2323
! go vet depends-on-a/depends-on-a_test.go
24-
stderr '^package command-line-arguments \(test\)\n\timports a: use of internal package a/x/internal/y not allowed$' # BUG
24+
stderr '^package command-line-arguments \(test\)\n\timports a\n\ta[/\\]a.go:5:3: use of internal package a/x/internal/y not allowed'
2525

2626
! go vet depends-on-a
27-
stderr '^a[/\\]a.go:5:3: use of internal package'
27+
stderr '^package depends-on-a\n\timports a\n\ta[/\\]a.go:5:3: use of internal package'
2828

2929
-- a/a.go --
3030
// A package with bad imports in both src and test

0 commit comments

Comments
 (0)