]> Cypherpunks.ru repositories - gostls13.git/blobdiff - src/cmd/go/internal/modload/load.go
cmd/go/internal/modload: avoid calling strings.HasPrefix twice in *MainModuleSet...
[gostls13.git] / src / cmd / go / internal / modload / load.go
index 7def3c2625a3ca336f717af6938cefdd96d2ccd3..51eb141d4b38b827d1233c33b26619d51d15181f 100644 (file)
@@ -40,9 +40,10 @@ package modload
 //     - the main module specifies a go version ≤ 1.15, and the package is imported
 //       by a *test of* another package in "all".
 //
-// When we implement lazy loading, we will record the modules providing packages
-// in "all" even when we are only loading individual packages, so we set the
-// pkgInAll flag regardless of the whether the "all" pattern is a root.
+// When graph pruning is in effect, we want to spot-check the graph-pruning
+// invariants — which depend on which packages are known to be in "all" — even
+// when we are only loading individual packages, so we set the pkgInAll flag
+// regardless of the whether the "all" pattern is a root.
 // (This is necessary to maintain the “import invariant” described in
 // https://golang.org/design/36460-lazy-module-loading.)
 //
@@ -93,7 +94,6 @@ package modload
 // if those packages are not found in existing dependencies of the main module.
 
 import (
-       "bytes"
        "context"
        "errors"
        "fmt"
@@ -113,15 +113,16 @@ import (
        "cmd/go/internal/base"
        "cmd/go/internal/cfg"
        "cmd/go/internal/fsys"
+       "cmd/go/internal/gover"
        "cmd/go/internal/imports"
        "cmd/go/internal/modfetch"
+       "cmd/go/internal/modindex"
        "cmd/go/internal/mvs"
        "cmd/go/internal/par"
        "cmd/go/internal/search"
-       "cmd/internal/str"
+       "cmd/go/internal/str"
 
        "golang.org/x/mod/module"
-       "golang.org/x/mod/semver"
 )
 
 // loaded is the most recently-used package loader.
@@ -134,13 +135,13 @@ var loaded *loader
 
 // PackageOpts control the behavior of the LoadPackages function.
 type PackageOpts struct {
-       // GoVersion is the Go version to which the go.mod file should be updated
+       // TidyGoVersion is the Go version to which the go.mod file should be updated
        // after packages have been loaded.
        //
-       // An empty GoVersion means to use the Go version already specified in the
+       // An empty TidyGoVersion means to use the Go version already specified in the
        // main module's go.mod file, or the latest Go version if there is no main
        // module.
-       GoVersion string
+       TidyGoVersion string
 
        // Tags are the build tags in effect (as interpreted by the
        // cmd/go/internal/imports package).
@@ -148,7 +149,7 @@ type PackageOpts struct {
        Tags map[string]bool
 
        // Tidy, if true, requests that the build list and go.sum file be reduced to
-       // the minimial dependencies needed to reproducibly reload the requested
+       // the minimal dependencies needed to reproducibly reload the requested
        // packages.
        Tidy bool
 
@@ -230,6 +231,13 @@ type PackageOpts struct {
        // SilenceUnmatchedWarnings suppresses the warnings normally emitted for
        // patterns that did not match any packages.
        SilenceUnmatchedWarnings bool
+
+       // Resolve the query against this module.
+       MainModule module.Version
+
+       // If Switcher is non-nil, then LoadPackages passes all encountered errors
+       // to Switcher.Error and tries Switcher.Switch before base.ExitIfErrors.
+       Switcher gover.Switcher
 }
 
 // LoadPackages identifies the set of packages matching the given patterns and
@@ -255,7 +263,11 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                        case m.IsLocal():
                                // Evaluate list of file system directories on first iteration.
                                if m.Dirs == nil {
-                                       matchLocalDirs(ctx, m, rs)
+                                       matchModRoots := modRoots
+                                       if opts.MainModule != (module.Version{}) {
+                                               matchModRoots = []string{MainModules.ModRoot(opts.MainModule)}
+                                       }
+                                       matchLocalDirs(ctx, matchModRoots, m, rs)
                                }
 
                                // Make a copy of the directory list and translate to import paths.
@@ -308,7 +320,11 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                                        // The initial roots are the packages in the main module.
                                        // loadFromRoots will expand that to "all".
                                        m.Errs = m.Errs[:0]
-                                       matchPackages(ctx, m, opts.Tags, omitStd, MainModules.Versions())
+                                       matchModules := MainModules.Versions()
+                                       if opts.MainModule != (module.Version{}) {
+                                               matchModules = []module.Version{opts.MainModule}
+                                       }
+                                       matchPackages(ctx, m, opts.Tags, omitStd, matchModules)
                                } else {
                                        // Starting with the packages in the main module,
                                        // enumerate the full list of "all".
@@ -326,7 +342,10 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                }
        }
 
-       initialRS, _ := loadModFile(ctx) // Ignore needCommit — we're going to commit at the end regardless.
+       initialRS, err := loadModFile(ctx, &opts)
+       if err != nil {
+               base.Fatal(err)
+       }
 
        ld := loadFromRoots(ctx, loaderParams{
                PackageOpts:  opts,
@@ -351,11 +370,11 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
        if !ld.SilencePackageErrors {
                for _, match := range matches {
                        for _, err := range match.Errs {
-                               ld.errorf("%v\n", err)
+                               ld.error(err)
                        }
                }
        }
-       base.ExitIfErrors()
+       ld.exitIfErrors(ctx)
 
        if !opts.SilenceUnmatchedWarnings {
                search.WarnUnmatched(matches)
@@ -364,10 +383,9 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
        if opts.Tidy {
                if cfg.BuildV {
                        mg, _ := ld.requirements.Graph(ctx)
-
                        for _, m := range initialRS.rootModules {
                                var unused bool
-                               if ld.requirements.depth == eager {
+                               if ld.requirements.pruning == unpruned {
                                        // m is unused if it was dropped from the module graph entirely. If it
                                        // was only demoted from direct to indirect, it may still be in use via
                                        // a transitive import.
@@ -386,16 +404,33 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                }
 
                keep := keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly)
-               if compatDepth := modDepthFromGoVersion(ld.TidyCompatibleVersion); compatDepth != ld.requirements.depth {
-                       compatRS := newRequirements(compatDepth, ld.requirements.rootModules, ld.requirements.direct)
-                       ld.checkTidyCompatibility(ctx, compatRS)
+               compatVersion := ld.TidyCompatibleVersion
+               goVersion := ld.requirements.GoVersion()
+               if compatVersion == "" {
+                       if gover.Compare(goVersion, gover.GoStrictVersion) < 0 {
+                               compatVersion = gover.Prev(goVersion)
+                       } else {
+                               // Starting at GoStrictVersion, we no longer maintain compatibility with
+                               // versions older than what is listed in the go.mod file.
+                               compatVersion = goVersion
+                       }
+               }
+               if gover.Compare(compatVersion, goVersion) > 0 {
+                       // Each version of the Go toolchain knows how to interpret go.mod and
+                       // go.sum files produced by all previous versions, so a compatibility
+                       // version higher than the go.mod version adds nothing.
+                       compatVersion = goVersion
+               }
+               if compatPruning := pruningForGoVersion(compatVersion); compatPruning != ld.requirements.pruning {
+                       compatRS := newRequirements(compatPruning, ld.requirements.rootModules, ld.requirements.direct)
+                       ld.checkTidyCompatibility(ctx, compatRS, compatVersion)
 
                        for m := range keepSums(ctx, ld, compatRS, loadedZipSumsOnly) {
                                keep[m] = true
                        }
                }
 
-               if allowWriteGoMod {
+               if !ExplicitWriteGoMod {
                        modfetch.TrimGoSum(keep)
 
                        // commitRequirements below will also call WriteGoSum, but the "keep" map
@@ -403,15 +438,18 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                        // loaded.requirements, but here we may have also loaded (and want to
                        // preserve checksums for) additional entities from compatRS, which are
                        // only needed for compatibility with ld.TidyCompatibleVersion.
-                       if err := modfetch.WriteGoSum(keep, mustHaveCompleteRequirements()); err != nil {
-                               base.Fatalf("go: %v", err)
+                       if err := modfetch.WriteGoSum(ctx, keep, mustHaveCompleteRequirements()); err != nil {
+                               base.Fatal(err)
                        }
                }
        }
 
        // Success! Update go.mod and go.sum (if needed) and return the results.
+       // We'll skip updating if ExplicitWriteGoMod is true (the caller has opted
+       // to call WriteGoMod itself) or if ResolveMissingImports is false (the
+       // command wants to examine the package graph as-is).
        loaded = ld
-       commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+       requirements = loaded.requirements
 
        for _, pkg := range ld.pkgs {
                if !pkg.isTest() {
@@ -419,12 +457,19 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                }
        }
        sort.Strings(loadedPackages)
+
+       if !ExplicitWriteGoMod && opts.ResolveMissingImports {
+               if err := commitRequirements(ctx, WriteOpts{}); err != nil {
+                       base.Fatal(err)
+               }
+       }
+
        return matches, loadedPackages
 }
 
 // matchLocalDirs is like m.MatchDirs, but tries to avoid scanning directories
 // outside of the standard library and active modules.
-func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
+func matchLocalDirs(ctx context.Context, modRoots []string, m *search.Match, rs *Requirements) {
        if !m.IsLocal() {
                panic(fmt.Sprintf("internal error: resolveLocalDirs on non-local pattern %s", m.Pattern()))
        }
@@ -440,9 +485,22 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
                if !filepath.IsAbs(dir) {
                        absDir = filepath.Join(base.Cwd(), dir)
                }
-               if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
+
+               modRoot := findModuleRoot(absDir)
+               found := false
+               for _, mainModuleRoot := range modRoots {
+                       if mainModuleRoot == modRoot {
+                               found = true
+                               break
+                       }
+               }
+               if !found && search.InDir(absDir, cfg.GOROOTsrc) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
                        m.Dirs = []string{}
-                       m.AddError(fmt.Errorf("directory prefix %s outside available modules", base.ShortPath(absDir)))
+                       scope := "main module or its selected dependencies"
+                       if inWorkspaceMode() {
+                               scope = "modules listed in go.work or their selected dependencies"
+                       }
+                       m.AddError(fmt.Errorf("directory prefix %s does not contain %s", base.ShortPath(absDir), scope))
                        return
                }
        }
@@ -506,15 +564,14 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
        pkgNotFoundLongestPrefix := ""
        for _, mainModule := range MainModules.Versions() {
                modRoot := MainModules.ModRoot(mainModule)
-               if modRoot != "" && strings.HasPrefix(absDir, modRoot+string(filepath.Separator)) && !strings.Contains(absDir[len(modRoot):], "@") {
-                       suffix := filepath.ToSlash(absDir[len(modRoot):])
-                       if strings.HasPrefix(suffix, "/vendor/") {
+               if modRoot != "" && str.HasFilePathPrefix(absDir, modRoot) && !strings.Contains(absDir[len(modRoot):], "@") {
+                       suffix := filepath.ToSlash(str.TrimFilePathPrefix(absDir, modRoot))
+                       if pkg, found := strings.CutPrefix(suffix, "vendor/"); found {
                                if cfg.BuildMod != "vendor" {
                                        return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
                                }
 
-                               readVendorList()
-                               pkg := strings.TrimPrefix(suffix, "/vendor/")
+                               readVendorList(VendorDir())
                                if _, ok := vendorPkgModule[pkg]; !ok {
                                        return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
                                }
@@ -523,7 +580,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
 
                        mainModulePrefix := MainModules.PathPrefix(mainModule)
                        if mainModulePrefix == "" {
-                               pkg := strings.TrimPrefix(suffix, "/")
+                               pkg := suffix
                                if pkg == "builtin" {
                                        // "builtin" is a pseudo-package with a real source file.
                                        // It's not included in "std", so it shouldn't resolve from "."
@@ -533,7 +590,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
                                return pkg, nil
                        }
 
-                       pkg := mainModulePrefix + suffix
+                       pkg := pathpkg.Join(mainModulePrefix, suffix)
                        if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
                                return "", err
                        } else if !ok {
@@ -543,8 +600,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
                                // return an error.
                                if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
                                        pkgNotFoundLongestPrefix = mainModulePrefix
-                                       pkgNotFoundErr = &PackageNotInModuleError{Mod: mainModule, Pattern: pkg}
-
+                                       pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
                                }
                                continue
                        }
@@ -565,7 +621,17 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
 
        pkg := pathInModuleCache(ctx, absDir, rs)
        if pkg == "" {
-               return "", fmt.Errorf("directory %s outside available modules", base.ShortPath(absDir))
+               dirstr := fmt.Sprintf("directory %s", base.ShortPath(absDir))
+               if dirstr == "directory ." {
+                       dirstr = "current directory"
+               }
+               if inWorkspaceMode() {
+                       if mr := findModuleRoot(absDir); mr != "" {
+                               return "", fmt.Errorf("%s is contained in a module that is not one of the workspace modules listed in go.work. You can add the module to the workspace using:\n\tgo work use %s", dirstr, base.ShortPath(mr))
+                       }
+                       return "", fmt.Errorf("%s outside modules listed in go.work or their selected dependencies", dirstr)
+               }
+               return "", fmt.Errorf("%s outside main module or its selected dependencies", dirstr)
        }
        return pkg, nil
 }
@@ -580,17 +646,20 @@ var (
 // if dir is in the module cache copy of a module in our build list.
 func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string {
        tryMod := func(m module.Version) (string, bool) {
+               if gover.IsToolchain(m.Path) {
+                       return "", false
+               }
                var root string
                var err error
                if repl := Replacement(m); repl.Path != "" && repl.Version == "" {
                        root = repl.Path
                        if !filepath.IsAbs(root) {
-                               root = filepath.Join(ModRoot(), root)
+                               root = filepath.Join(replaceRelativeTo(), root)
                        }
                } else if repl.Path != "" {
-                       root, err = modfetch.DownloadDir(repl)
+                       root, err = modfetch.DownloadDir(ctx, repl)
                } else {
-                       root, err = modfetch.DownloadDir(m)
+                       root, err = modfetch.DownloadDir(ctx, m)
                }
                if err != nil {
                        return "", false
@@ -608,7 +677,7 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
                return path.Join(m.Path, filepath.ToSlash(sub)), true
        }
 
-       if rs.depth == lazy {
+       if rs.pruning == pruned {
                for _, m := range rs.rootModules {
                        if v, _ := rs.rootSelected(m.Path); v != m.Version {
                                continue // m is a root, but we have a higher root for the same path.
@@ -621,9 +690,9 @@ func pathInModuleCache(ctx context.Context, dir string, rs *Requirements) string
                }
        }
 
-       // None of the roots contained dir, or we're in eager mode and want to load
-       // the full module graph more aggressively. Either way, check the full graph
-       // to see if the directory is a non-root dependency.
+       // None of the roots contained dir, or the graph is unpruned (so we don't want
+       // to distinguish between roots and transitive dependencies). Either way,
+       // check the full graph to see if the directory is a non-root dependency.
        //
        // If the roots are not consistent with the full module graph, the selected
        // versions of root modules may differ from what we already checked above.
@@ -654,7 +723,7 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
        tags := imports.Tags()
        imports, testImports, err := imports.ScanFiles(gofiles, tags)
        if err != nil {
-               base.Fatalf("go: %v", err)
+               base.Fatal(err)
        }
 
        loaded = loadFromRoots(ctx, loaderParams{
@@ -670,7 +739,13 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
                        return roots
                },
        })
-       commitRequirements(ctx, loaded.GoVersion, loaded.requirements)
+       requirements = loaded.requirements
+
+       if !ExplicitWriteGoMod {
+               if err := commitRequirements(ctx, WriteOpts{}); err != nil {
+                       base.Fatal(err)
+               }
+       }
 }
 
 // DirImportPath returns the effective import path for dir,
@@ -695,16 +770,17 @@ func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path s
                if dir == modRoot {
                        return mms.PathPrefix(v), v
                }
-               if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
+               if str.HasFilePathPrefix(dir, modRoot) {
                        pathPrefix := MainModules.PathPrefix(v)
                        if pathPrefix > longestPrefix {
                                longestPrefix = pathPrefix
                                longestPrefixVersion = v
-                               suffix := filepath.ToSlash(dir[len(modRoot):])
-                               if strings.HasPrefix(suffix, "/vendor/") {
-                                       longestPrefixPath = strings.TrimPrefix(suffix, "/vendor/")
+                               suffix := filepath.ToSlash(str.TrimFilePathPrefix(dir, modRoot))
+                               if strings.HasPrefix(suffix, "vendor/") {
+                                       longestPrefixPath = suffix[len("vendor/"):]
+                                       continue
                                }
-                               longestPrefixPath = mms.PathPrefix(v) + suffix
+                               longestPrefixPath = pathpkg.Join(mms.PathPrefix(v), suffix)
                        }
                }
        }
@@ -715,31 +791,9 @@ func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path s
        return ".", module.Version{}
 }
 
-// ImportMap returns the actual package import path
-// for an import path found in source code.
-// If the given import path does not appear in the source code
-// for the packages that have been loaded, ImportMap returns the empty string.
-func ImportMap(path string) string {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
-       if !ok {
-               return ""
-       }
-       return pkg.path
-}
-
-// PackageDir returns the directory containing the source code
-// for the package named by the import path.
-func PackageDir(path string) string {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
-       if !ok {
-               return ""
-       }
-       return pkg.dir
-}
-
 // PackageModule returns the module providing the package named by the import path.
 func PackageModule(path string) module.Version {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                return module.Version{}
        }
@@ -758,7 +812,7 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
        if parentIsStd {
                path = loaded.stdVendor(parentPath, path)
        }
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                // The loader should have found all the relevant paths.
                // There are a few exceptions, though:
@@ -790,11 +844,15 @@ type loader struct {
        // transitively *imported by* the packages and tests in the main module.)
        allClosesOverTests bool
 
+       // skipImportModFiles indicates whether we may skip loading go.mod files
+       // for imported packages (as in 'go mod tidy' in Go 1.17–1.20).
+       skipImportModFiles bool
+
        work *par.Queue
 
        // reset on each iteration
        roots    []*loadPkg
-       pkgCache *par.Cache // package path (string) → *loadPkg
+       pkgCache *par.Cache[string, *loadPkg]
        pkgs     []*loadPkg // transitive closure of loaded packages and tests; populated in buildStacks
 }
 
@@ -817,18 +875,44 @@ func (ld *loader) reset() {
        }
 
        ld.roots = nil
-       ld.pkgCache = new(par.Cache)
+       ld.pkgCache = new(par.Cache[string, *loadPkg])
        ld.pkgs = nil
 }
 
-// errorf reports an error via either os.Stderr or base.Errorf,
+// error reports an error via either os.Stderr or base.Error,
 // according to whether ld.AllowErrors is set.
-func (ld *loader) errorf(format string, args ...interface{}) {
+func (ld *loader) error(err error) {
        if ld.AllowErrors {
-               fmt.Fprintf(os.Stderr, format, args...)
+               fmt.Fprintf(os.Stderr, "go: %v\n", err)
+       } else if ld.Switcher != nil {
+               ld.Switcher.Error(err)
        } else {
-               base.Errorf(format, args...)
+               base.Error(err)
+       }
+}
+
+// switchIfErrors switches toolchains if a switch is needed.
+func (ld *loader) switchIfErrors(ctx context.Context) {
+       if ld.Switcher != nil {
+               ld.Switcher.Switch(ctx)
+       }
+}
+
+// exitIfErrors switches toolchains if a switch is needed
+// or else exits if any errors have been reported.
+func (ld *loader) exitIfErrors(ctx context.Context) {
+       ld.switchIfErrors(ctx)
+       base.ExitIfErrors()
+}
+
+// goVersion reports the Go version that should be used for the loader's
+// requirements: ld.TidyGoVersion if set, or ld.requirements.GoVersion()
+// otherwise.
+func (ld *loader) goVersion() string {
+       if ld.TidyGoVersion != "" {
+               return ld.TidyGoVersion
        }
+       return ld.requirements.GoVersion()
 }
 
 // A loadPkg records information about a single loaded package.
@@ -847,6 +931,7 @@ type loadPkg struct {
        imports     []*loadPkg     // packages imported by this one
        testImports []string       // test-only imports, saved for use by pkg.test.
        inStd       bool
+       altMods     []module.Version // modules that could have contained the package but did not
 
        // Populated by (*loader).pkgTest:
        testOnce sync.Once
@@ -901,7 +986,7 @@ func (f loadPkgFlags) has(cond loadPkgFlags) bool {
 // An atomicLoadPkgFlags stores a loadPkgFlags for which individual flags can be
 // added atomically.
 type atomicLoadPkgFlags struct {
-       bits int32
+       bits atomic.Int32
 }
 
 // update sets the given flags in af (in addition to any flags already set).
@@ -910,9 +995,9 @@ type atomicLoadPkgFlags struct {
 // flags were newly-set.
 func (af *atomicLoadPkgFlags) update(flags loadPkgFlags) (old loadPkgFlags) {
        for {
-               old := atomic.LoadInt32(&af.bits)
+               old := af.bits.Load()
                new := old | int32(flags)
-               if new == old || atomic.CompareAndSwapInt32(&af.bits, old, new) {
+               if new == old || af.bits.CompareAndSwap(old, new) {
                        return loadPkgFlags(old)
                }
        }
@@ -920,7 +1005,7 @@ func (af *atomicLoadPkgFlags) update(flags loadPkgFlags) (old loadPkgFlags) {
 
 // has reports whether all of the flags in cond are set in af.
 func (af *atomicLoadPkgFlags) has(cond loadPkgFlags) bool {
-       return loadPkgFlags(atomic.LoadInt32(&af.bits))&cond == cond
+       return loadPkgFlags(af.bits.Load())&cond == cond
 }
 
 // isTest reports whether pkg is a test of another package.
@@ -951,48 +1036,48 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                work:         par.NewQueue(runtime.GOMAXPROCS(0)),
        }
 
-       if ld.GoVersion == "" {
-               ld.GoVersion = modFileGoVersion()
-
-               if ld.Tidy && semver.Compare("v"+ld.GoVersion, "v"+LatestGoVersion()) > 0 {
-                       ld.errorf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", ld.GoVersion, LatestGoVersion())
-                       base.ExitIfErrors()
+       if ld.requirements.pruning == unpruned {
+               // If the module graph does not support pruning, we assume that we will need
+               // the full module graph in order to load package dependencies.
+               //
+               // This might not be strictly necessary, but it matches the historical
+               // behavior of the 'go' command and keeps the go.mod file more consistent in
+               // case of erroneous hand-edits — which are less likely to be detected by
+               // spot-checks in modules that do not maintain the expanded go.mod
+               // requirements needed for graph pruning.
+               var err error
+               ld.requirements, _, err = expandGraph(ctx, ld.requirements)
+               if err != nil {
+                       ld.error(err)
                }
        }
+       ld.exitIfErrors(ctx)
 
-       if ld.Tidy {
-               if ld.TidyCompatibleVersion == "" {
-                       ld.TidyCompatibleVersion = priorGoVersion(ld.GoVersion)
-               } else if semver.Compare("v"+ld.TidyCompatibleVersion, "v"+ld.GoVersion) > 0 {
-                       // Each version of the Go toolchain knows how to interpret go.mod and
-                       // go.sum files produced by all previous versions, so a compatibility
-                       // version higher than the go.mod version adds nothing.
-                       ld.TidyCompatibleVersion = ld.GoVersion
+       updateGoVersion := func() {
+               goVersion := ld.goVersion()
+
+               if ld.requirements.pruning != workspace {
+                       var err error
+                       ld.requirements, err = convertPruning(ctx, ld.requirements, pruningForGoVersion(goVersion))
+                       if err != nil {
+                               ld.error(err)
+                               ld.exitIfErrors(ctx)
+                       }
                }
-       }
 
-       if semver.Compare("v"+ld.GoVersion, narrowAllVersionV) < 0 && !ld.UseVendorAll {
-               // The module's go version explicitly predates the change in "all" for lazy
-               // loading, so continue to use the older interpretation.
-               ld.allClosesOverTests = true
-       }
+               // If the module's Go version omits go.sum entries for go.mod files for test
+               // dependencies of external packages, avoid loading those files in the first
+               // place.
+               ld.skipImportModFiles = ld.Tidy && gover.Compare(goVersion, gover.TidyGoModSumVersion) < 0
 
-       var err error
-       ld.requirements, err = convertDepth(ctx, ld.requirements, modDepthFromGoVersion(ld.GoVersion))
-       if err != nil {
-               ld.errorf("go: %v\n", err)
-       }
-
-       if ld.requirements.depth == eager {
-               var err error
-               ld.requirements, _, err = expandGraph(ctx, ld.requirements)
-               if err != nil {
-                       ld.errorf("go: %v\n", err)
-               }
+               // If the module's go version explicitly predates the change in "all" for
+               // graph pruning, continue to use the older interpretation.
+               ld.allClosesOverTests = gover.Compare(goVersion, gover.NarrowAllVersion) < 0 && !ld.UseVendorAll
        }
 
        for {
                ld.reset()
+               updateGoVersion()
 
                // Load the root packages and their imports.
                // Note: the returned roots can change on each iteration,
@@ -1000,7 +1085,7 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                // build list we're using.
                rootPkgs := ld.listRoots(ld.requirements)
 
-               if ld.requirements.depth == lazy && cfg.BuildMod == "mod" {
+               if ld.requirements.pruning == pruned && cfg.BuildMod == "mod" {
                        // Before we start loading transitive imports of packages, locate all of
                        // the root packages and promote their containing modules to root modules
                        // dependencies. If their go.mod files are tidy (the common case) and the
@@ -1038,11 +1123,11 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
 
                changed, err := ld.updateRequirements(ctx)
                if err != nil {
-                       ld.errorf("go: %v\n", err)
+                       ld.error(err)
                        break
                }
                if changed {
-                       // Don't resolve missing imports until the module graph have stabilized.
+                       // Don't resolve missing imports until the module graph has stabilized.
                        // If the roots are still changing, they may turn out to specify a
                        // requirement on the missing package(s), and we would rather use a
                        // version specified by a new root than add a new dependency on an
@@ -1055,7 +1140,11 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                        break
                }
 
-               modAddedBy := ld.resolveMissingImports(ctx)
+               modAddedBy, err := ld.resolveMissingImports(ctx)
+               if err != nil {
+                       ld.error(err)
+                       break
+               }
                if len(modAddedBy) == 0 {
                        // The roots are stable, and we've resolved all of the missing packages
                        // that we can.
@@ -1063,10 +1152,10 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                }
 
                toAdd := make([]module.Version, 0, len(modAddedBy))
-               for m, _ := range modAddedBy {
+               for m := range modAddedBy {
                        toAdd = append(toAdd, m)
                }
-               module.Sort(toAdd) // to make errors deterministic
+               gover.ModSort(toAdd) // to make errors deterministic
 
                // We ran updateRequirements before resolving missing imports and it didn't
                // make any changes, so we know that the requirement graph is already
@@ -1085,11 +1174,11 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                        // are more descriptive.
                        if err, ok := err.(*mvs.BuildListError); ok {
                                if pkg := modAddedBy[err.Module()]; pkg != nil {
-                                       ld.errorf("go: %s: %v\n", pkg.stackText(), err.Err)
+                                       ld.error(fmt.Errorf("%s: %w", pkg.stackText(), err.Err))
                                        break
                                }
                        }
-                       ld.errorf("go: %v\n", err)
+                       ld.error(err)
                        break
                }
                if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
@@ -1101,30 +1190,62 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                }
                ld.requirements = rs
        }
-       base.ExitIfErrors() // TODO(bcmills): Is this actually needed?
+       ld.exitIfErrors(ctx)
 
        // Tidy the build list, if applicable, before we report errors.
        // (The process of tidying may remove errors from irrelevant dependencies.)
        if ld.Tidy {
                rs, err := tidyRoots(ctx, ld.requirements, ld.pkgs)
                if err != nil {
-                       ld.errorf("go: %v\n", err)
-               }
-
-               if ld.requirements.depth == lazy {
-                       // We continuously add tidy roots to ld.requirements during loading, so at
-                       // this point the tidy roots should be a subset of the roots of
-                       // ld.requirements, ensuring that no new dependencies are brought inside
-                       // the lazy-loading horizon.
-                       // If that is not the case, there is a bug in the loading loop above.
-                       for _, m := range rs.rootModules {
-                               if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
-                                       ld.errorf("go mod tidy: internal error: a requirement on %v is needed but was not added during package loading\n", m)
-                                       base.ExitIfErrors()
+                       ld.error(err)
+               } else {
+                       if ld.TidyGoVersion != "" {
+                               // Attempt to switch to the requested Go version. We have been using its
+                               // pruning and semantics all along, but there may have been — and may
+                               // still be — requirements on higher versions in the graph.
+                               tidy := overrideRoots(ctx, rs, []module.Version{{Path: "go", Version: ld.TidyGoVersion}})
+                               mg, err := tidy.Graph(ctx)
+                               if err != nil {
+                                       ld.error(err)
+                               }
+                               if v := mg.Selected("go"); v == ld.TidyGoVersion {
+                                       rs = tidy
+                               } else {
+                                       conflict := Conflict{
+                                               Path: mg.g.FindPath(func(m module.Version) bool {
+                                                       return m.Path == "go" && m.Version == v
+                                               })[1:],
+                                               Constraint: module.Version{Path: "go", Version: ld.TidyGoVersion},
+                                       }
+                                       msg := conflict.Summary()
+                                       if cfg.BuildV {
+                                               msg = conflict.String()
+                                       }
+                                       ld.error(errors.New(msg))
                                }
                        }
+
+                       if ld.requirements.pruning == pruned {
+                               // We continuously add tidy roots to ld.requirements during loading, so
+                               // at this point the tidy roots (other than possibly the "go" version
+                               // edited above) should be a subset of the roots of ld.requirements,
+                               // ensuring that no new dependencies are brought inside the
+                               // graph-pruning horizon.
+                               // If that is not the case, there is a bug in the loading loop above.
+                               for _, m := range rs.rootModules {
+                                       if m.Path == "go" && ld.TidyGoVersion != "" {
+                                               continue
+                                       }
+                                       if v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
+                                               ld.error(fmt.Errorf("internal error: a requirement on %v is needed but was not added during package loading (selected %s)", m, v))
+                                       }
+                               }
+                       }
+
+                       ld.requirements = rs
                }
-               ld.requirements = rs
+
+               ld.exitIfErrors(ctx)
        }
 
        // Report errors, if any.
@@ -1142,18 +1263,26 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                        }
                }
 
-               if ld.SilencePackageErrors {
-                       continue
+               if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) && stdErr.isStd {
+                       // Add importer go version information to import errors of standard
+                       // library packages arising from newer releases.
+                       if importer := pkg.stack; importer != nil {
+                               if v, ok := rawGoVersion.Load(importer.mod); ok && gover.Compare(gover.Local(), v.(string)) < 0 {
+                                       stdErr.importerGoVersion = v.(string)
+                               }
+                       }
+                       if ld.SilenceMissingStdImports {
+                               continue
+                       }
                }
-               if stdErr := (*ImportMissingError)(nil); errors.As(pkg.err, &stdErr) &&
-                       stdErr.isStd && ld.SilenceMissingStdImports {
+               if ld.SilencePackageErrors {
                        continue
                }
                if ld.SilenceNoGoErrors && errors.Is(pkg.err, imports.ErrNoGo) {
                        continue
                }
 
-               ld.errorf("%s: %v\n", pkg.stackText(), pkg.err)
+               ld.error(fmt.Errorf("%s: %w", pkg.stackText(), pkg.err))
        }
 
        ld.checkMultiplePaths()
@@ -1161,21 +1290,20 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
 }
 
 // updateRequirements ensures that ld.requirements is consistent with the
-// information gained from ld.pkgs and includes the modules in add as roots at
-// at least the given versions.
+// information gained from ld.pkgs.
 //
 // In particular:
 //
-//     - Modules that provide packages directly imported from the main module are
-//       marked as direct, and are promoted to explicit roots. If a needed root
-//       cannot be promoted due to -mod=readonly or -mod=vendor, the importing
-//       package is marked with an error.
+//   - Modules that provide packages directly imported from the main module are
+//     marked as direct, and are promoted to explicit roots. If a needed root
+//     cannot be promoted due to -mod=readonly or -mod=vendor, the importing
+//     package is marked with an error.
 //
-//     - If ld scanned the "all" pattern independent of build constraints, it is
-//       guaranteed to have seen every direct import. Module dependencies that did
-//       not provide any directly-imported package are then marked as indirect.
+//   - If ld scanned the "all" pattern independent of build constraints, it is
+//     guaranteed to have seen every direct import. Module dependencies that did
+//     not provide any directly-imported package are then marked as indirect.
 //
-//     - Root dependencies are updated to their selected versions.
+//   - Root dependencies are updated to their selected versions.
 //
 // The "changed" return value reports whether the update changed the selected
 // version of any module that either provided a loaded package or may now
@@ -1204,15 +1332,51 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
                }
        }
 
+       var maxTooNew *gover.TooNewError
        for _, pkg := range ld.pkgs {
+               if pkg.err != nil {
+                       if tooNew := (*gover.TooNewError)(nil); errors.As(pkg.err, &tooNew) {
+                               if maxTooNew == nil || gover.Compare(tooNew.GoVersion, maxTooNew.GoVersion) > 0 {
+                                       maxTooNew = tooNew
+                               }
+                       }
+               }
                if pkg.mod.Version != "" || !MainModules.Contains(pkg.mod.Path) {
                        continue
                }
+
                for _, dep := range pkg.imports {
                        if !dep.fromExternalModule() {
                                continue
                        }
 
+                       if inWorkspaceMode() {
+                               // In workspace mode / workspace pruning mode, the roots are the main modules
+                               // rather than the main module's direct dependencies. The check below on the selected
+                               // roots does not apply.
+                               if cfg.BuildMod == "vendor" {
+                                       // In workspace vendor mode, we don't need to load the requirements of the workspace
+                                       // modules' dependencies so the check below doesn't work. But that's okay, because
+                                       // checking whether modules are required directly for the purposes of pruning is
+                                       // less important in vendor mode: if we were able to load the package, we have
+                                       // everything we need  to build the package, and dependencies' tests are pruned out
+                                       // of the vendor directory anyway.
+                                       continue
+                               }
+                               if mg, err := rs.Graph(ctx); err != nil {
+                                       return false, err
+                               } else if _, ok := mg.RequiredBy(dep.mod); !ok {
+                                       // dep.mod is not an explicit dependency, but needs to be.
+                                       // See comment on error returned below.
+                                       pkg.err = &DirectImportFromImplicitDependencyError{
+                                               ImporterPath: pkg.path,
+                                               ImportedPath: dep.path,
+                                               Module:       dep.mod,
+                                       }
+                               }
+                               continue
+                       }
+
                        if pkg.err == nil && cfg.BuildMod != "mod" {
                                if v, ok := rs.rootSelected(dep.mod.Path); !ok || v != dep.mod.Version {
                                        // dep.mod is not an explicit dependency, but needs to be.
@@ -1240,17 +1404,20 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
                        direct[dep.mod.Path] = true
                }
        }
+       if maxTooNew != nil {
+               return false, maxTooNew
+       }
 
        var addRoots []module.Version
        if ld.Tidy {
-               // When we are tidying a lazy module, we may need to add roots to preserve
-               // the versions of indirect, test-only dependencies that are upgraded
-               // above or otherwise missing from the go.mod files of direct
-               // dependencies. (For example, the direct dependency might be a very
+               // When we are tidying a module with a pruned dependency graph, we may need
+               // to add roots to preserve the versions of indirect, test-only dependencies
+               // that are upgraded above or otherwise missing from the go.mod files of
+               // direct dependencies. (For example, the direct dependency might be a very
                // stable codebase that predates modules and thus lacks a go.mod file, or
-               // the author of the direct dependency may have forgotten to commit a
-               // change to the go.mod file, or may have made an erroneous hand-edit that
-               // causes it to be untidy.)
+               // the author of the direct dependency may have forgotten to commit a change
+               // to the go.mod file, or may have made an erroneous hand-edit that causes
+               // it to be untidy.)
                //
                // Promoting an indirect dependency to a root adds the next layer of its
                // dependencies to the module graph, which may increase the selected
@@ -1291,7 +1458,14 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
                return false, err
        }
 
-       if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
+       if rs.GoVersion() != ld.requirements.GoVersion() {
+               // A change in the selected Go version may or may not affect the set of
+               // loaded packages, but in some cases it can change the meaning of the "all"
+               // pattern, the level of pruning in the module graph, and even the set of
+               // packages present in the standard library. If it has changed, it's best to
+               // reload packages once more to be sure everything is stable.
+               changed = true
+       } else if rs != ld.requirements && !reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
                // The roots of the module graph have changed in some way (not just the
                // "direct" markings). Check whether the changes affected any of the loaded
                // packages.
@@ -1320,7 +1494,7 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
                                //
                                // In some sense, we can think of this as ‘upgraded the module providing
                                // pkg.path from "none" to a version higher than "none"’.
-                               if _, _, err = importFromModules(ctx, pkg.path, rs, nil); err == nil {
+                               if _, _, _, _, err = importFromModules(ctx, pkg.path, rs, nil, ld.skipImportModFiles); err == nil {
                                        changed = true
                                        break
                                }
@@ -1338,7 +1512,7 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
 // The newly-resolved packages are added to the addedModuleFor map, and
 // resolveMissingImports returns a map from each new module version to
 // the first missing package that module would resolve.
-func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg) {
+func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[module.Version]*loadPkg, err error) {
        type pkgMod struct {
                pkg *loadPkg
                mod *module.Version
@@ -1399,6 +1573,24 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
        <-ld.work.Idle()
 
        modAddedBy = map[module.Version]*loadPkg{}
+
+       var (
+               maxTooNew    *gover.TooNewError
+               maxTooNewPkg *loadPkg
+       )
+       for _, pm := range pkgMods {
+               if tooNew := (*gover.TooNewError)(nil); errors.As(pm.pkg.err, &tooNew) {
+                       if maxTooNew == nil || gover.Compare(tooNew.GoVersion, maxTooNew.GoVersion) > 0 {
+                               maxTooNew = tooNew
+                               maxTooNewPkg = pm.pkg
+                       }
+               }
+       }
+       if maxTooNew != nil {
+               fmt.Fprintf(os.Stderr, "go: toolchain upgrade needed to resolve %s\n", maxTooNewPkg.path)
+               return nil, maxTooNew
+       }
+
        for _, pm := range pkgMods {
                pkg, mod := pm.pkg, *pm.mod
                if mod.Path == "" {
@@ -1411,7 +1603,7 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
                }
        }
 
-       return modAddedBy
+       return modAddedBy, nil
 }
 
 // pkg locates the *loadPkg for path, creating and queuing it for loading if
@@ -1426,7 +1618,7 @@ func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loa
                panic("internal error: (*loader).pkg called with pkgImportsLoaded flag set")
        }
 
-       pkg := ld.pkgCache.Do(path, func() interface{} {
+       pkg := ld.pkgCache.Do(path, func() *loadPkg {
                pkg := &loadPkg{
                        path: path,
                }
@@ -1434,7 +1626,7 @@ func (ld *loader) pkg(ctx context.Context, path string, flags loadPkgFlags) *loa
 
                ld.work.Add(func() { ld.load(ctx, pkg) })
                return pkg
-       }).(*loadPkg)
+       })
 
        ld.applyPkgFlags(ctx, pkg, flags)
        return pkg
@@ -1531,7 +1723,7 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch
                        // If the main module is tidy and the package is in "all" — or if we're
                        // lucky — we can identify all of its imports without actually loading the
                        // full module graph.
-                       m, _, err := importFromModules(ctx, path, ld.requirements, nil)
+                       m, _, _, _, err := importFromModules(ctx, path, ld.requirements, nil, ld.skipImportModFiles)
                        if err != nil {
                                var missing *ImportMissingError
                                if errors.As(err, &missing) && ld.ResolveMissingImports {
@@ -1557,7 +1749,8 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch
                                // module to a root to ensure that any other packages this package
                                // imports are resolved from correct dependency versions.
                                //
-                               // (This is the “argument invariant” from the lazy loading design.)
+                               // (This is the “argument invariant” from
+                               // https://golang.org/design/36460-lazy-module-loading.)
                                need := <-needc
                                need[m] = true
                                needc <- need
@@ -1575,15 +1768,15 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch
        for m := range need {
                toAdd = append(toAdd, m)
        }
-       module.Sort(toAdd)
+       gover.ModSort(toAdd)
 
        rs, err := updateRoots(ctx, ld.requirements.direct, ld.requirements, nil, toAdd, ld.AssumeRootsImported)
        if err != nil {
                // We are missing some root dependency, and for some reason we can't load
                // enough of the module dependency graph to add the missing root. Package
                // loading is doomed to fail, so fail quickly.
-               ld.errorf("go: %v\n", err)
-               base.ExitIfErrors()
+               ld.error(err)
+               ld.exitIfErrors(ctx)
                return false
        }
        if reflect.DeepEqual(rs.rootModules, ld.requirements.rootModules) {
@@ -1600,26 +1793,8 @@ func (ld *loader) preloadRootModules(ctx context.Context, rootPkgs []string) (ch
 
 // load loads an individual package.
 func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
-       if strings.Contains(pkg.path, "@") {
-               // Leave for error during load.
-               return
-       }
-       if build.IsLocalImport(pkg.path) || filepath.IsAbs(pkg.path) {
-               // Leave for error during load.
-               // (Module mode does not allow local imports.)
-               return
-       }
-
-       if search.IsMetaPackage(pkg.path) {
-               pkg.err = &invalidImportError{
-                       importPath: pkg.path,
-                       err:        fmt.Errorf("%q is not an importable package; see 'go help packages'", pkg.path),
-               }
-               return
-       }
-
        var mg *ModuleGraph
-       if ld.requirements.depth == eager {
+       if ld.requirements.pruning == unpruned {
                var err error
                mg, err = ld.requirements.Graph(ctx)
                if err != nil {
@@ -1635,7 +1810,8 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
                }
        }
 
-       pkg.mod, pkg.dir, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg)
+       var modroot string
+       pkg.mod, modroot, pkg.dir, pkg.altMods, pkg.err = importFromModules(ctx, pkg.path, ld.requirements, mg, ld.skipImportModFiles)
        if pkg.dir == "" {
                return
        }
@@ -1665,7 +1841,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
                // We can't scan standard packages for gccgo.
        } else {
                var err error
-               imports, testImports, err = scanDir(pkg.dir, ld.Tags)
+               imports, testImports, err = scanDir(modroot, pkg.dir, ld.Tags)
                if err != nil {
                        pkg.err = err
                        return
@@ -1793,7 +1969,7 @@ func (ld *loader) computePatternAll() (all []string) {
 func (ld *loader) checkMultiplePaths() {
        mods := ld.requirements.rootModules
        if cached := ld.requirements.graph.Load(); cached != nil {
-               if mg := cached.(cachedGraph).mg; mg != nil {
+               if mg := cached.mg; mg != nil {
                        mods = mg.BuildList()
                }
        }
@@ -1804,14 +1980,15 @@ func (ld *loader) checkMultiplePaths() {
                if prev, ok := firstPath[src]; !ok {
                        firstPath[src] = mod.Path
                } else if prev != mod.Path {
-                       ld.errorf("go: %s@%s used for two different module paths (%s and %s)\n", src.Path, src.Version, prev, mod.Path)
+                       ld.error(fmt.Errorf("%s@%s used for two different module paths (%s and %s)", src.Path, src.Version, prev, mod.Path))
                }
        }
 }
 
 // checkTidyCompatibility emits an error if any package would be loaded from a
 // different module under rs than under ld.requirements.
-func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements) {
+func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements, compatVersion string) {
+       goVersion := rs.GoVersion()
        suggestUpgrade := false
        suggestEFlag := false
        suggestFixes := func() {
@@ -1828,13 +2005,13 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                fmt.Fprintln(os.Stderr)
 
                goFlag := ""
-               if ld.GoVersion != modFileGoVersion() {
-                       goFlag = " -go=" + ld.GoVersion
+               if goVersion != MainModules.GoVersion() {
+                       goFlag = " -go=" + goVersion
                }
 
                compatFlag := ""
-               if ld.TidyCompatibleVersion != priorGoVersion(ld.GoVersion) {
-                       compatFlag = " -compat=" + ld.TidyCompatibleVersion
+               if compatVersion != gover.Prev(goVersion) {
+                       compatFlag = " -compat=" + compatVersion
                }
                if suggestUpgrade {
                        eDesc := ""
@@ -1843,16 +2020,16 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                                eDesc = ", leaving some packages unresolved"
                                eFlag = " -e"
                        }
-                       fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", ld.TidyCompatibleVersion, eDesc, eFlag, ld.TidyCompatibleVersion, eFlag, ld.GoVersion, compatFlag)
+                       fmt.Fprintf(os.Stderr, "To upgrade to the versions selected by go %s%s:\n\tgo mod tidy%s -go=%s && go mod tidy%s -go=%s%s\n", compatVersion, eDesc, eFlag, compatVersion, eFlag, goVersion, compatFlag)
                } else if suggestEFlag {
                        // If some packages are missing but no package is upgraded, then we
                        // shouldn't suggest upgrading to the Go 1.16 versions explicitly — that
                        // wouldn't actually fix anything for Go 1.16 users, and *would* break
                        // something for Go 1.17 users.
-                       fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", ld.TidyCompatibleVersion, goFlag, compatFlag)
+                       fmt.Fprintf(os.Stderr, "To proceed despite packages unresolved in go %s:\n\tgo mod tidy -e%s%s\n", compatVersion, goFlag, compatFlag)
                }
 
-               fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", ld.TidyCompatibleVersion, goFlag, ld.GoVersion)
+               fmt.Fprintf(os.Stderr, "If reproducibility with go %s is not needed:\n\tgo mod tidy%s -compat=%s\n", compatVersion, goFlag, goVersion)
 
                // TODO(#46141): Populate the linked wiki page.
                fmt.Fprintf(os.Stderr, "For other options, see:\n\thttps://golang.org/doc/modules/pruning\n")
@@ -1860,8 +2037,10 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
 
        mg, err := rs.Graph(ctx)
        if err != nil {
-               ld.errorf("go mod tidy: error loading go %s module graph: %v\n", ld.TidyCompatibleVersion, err)
+               ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err))
+               ld.switchIfErrors(ctx)
                suggestFixes()
+               ld.exitIfErrors(ctx)
                return
        }
 
@@ -1894,7 +2073,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
 
                pkg := pkg
                ld.work.Add(func() {
-                       mod, _, err := importFromModules(ctx, pkg.path, rs, mg)
+                       mod, _, _, _, err := importFromModules(ctx, pkg.path, rs, mg, ld.skipImportModFiles)
                        if mod != pkg.mod {
                                mismatches := <-mismatchMu
                                mismatches[pkg] = mismatch{mod: mod, err: err}
@@ -1920,7 +2099,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                for _, m := range ld.requirements.rootModules {
                        if v := mg.Selected(m.Path); v != m.Version {
                                fmt.Fprintln(os.Stderr)
-                               base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https://golang.org/issue.", m.Path, ld.GoVersion, m.Version, ld.TidyCompatibleVersion, v)
+                               base.Fatalf("go: internal error: failed to diagnose selected-version mismatch for module %s: go %s selects %s, but go %s selects %s\n\tPlease report this at https://golang.org/issue.", m.Path, goVersion, m.Version, compatVersion, v)
                        }
                }
                return
@@ -1936,7 +2115,7 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
 
                if pkg.isTest() {
                        // We already did (or will) report an error for the package itself,
-                       // so don't report a duplicate (and more vebose) error for its test.
+                       // so don't report a duplicate (and more verbose) error for its test.
                        if _, ok := mismatches[pkg.testOf]; !ok {
                                base.Fatalf("go: internal error: mismatch recorded for test %s, but not its non-test package", pkg.path)
                        }
@@ -1947,9 +2126,10 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                case mismatch.err != nil:
                        // pkg resolved successfully, but errors out using the requirements in rs.
                        //
-                       // This could occur because the import is provided by a single lazy root
-                       // (and is thus unambiguous in lazy mode) and also one or more
-                       // transitive dependencies (and is ambiguous in eager mode).
+                       // This could occur because the import is provided by a single root (and
+                       // is thus unambiguous in a main module with a pruned module graph) and
+                       // also one or more transitive dependencies (and is ambiguous with an
+                       // unpruned graph).
                        //
                        // It could also occur because some transitive dependency upgrades the
                        // module that previously provided the package to a version that no
@@ -1960,12 +2140,12 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                                        Path:    pkg.mod.Path,
                                        Version: mg.Selected(pkg.mod.Path),
                                }
-                               ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, selected)
+                               ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it in %s", pkg.stackText(), pkg.mod, compatVersion, selected))
                        } else {
                                if ambiguous := (*AmbiguousImportError)(nil); errors.As(mismatch.err, &ambiguous) {
                                        // TODO: Is this check needed?
                                }
-                               ld.errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.err)
+                               ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would fail to locate it:\n\t%v", pkg.stackText(), pkg.mod, compatVersion, mismatch.err))
                        }
 
                        suggestEFlag = true
@@ -1987,23 +2167,23 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                        }
 
                case pkg.err != nil:
-                       // pkg had an error in lazy mode (presumably suppressed with the -e flag),
-                       // but not in eager mode.
+                       // pkg had an error in with a pruned module graph (presumably suppressed
+                       // with the -e flag), but the error went away using an unpruned graph.
                        //
-                       // This is possible, if, say, the import is unresolved in lazy mode
+                       // This is possible, if, say, the import is unresolved in the pruned graph
                        // (because the "latest" version of each candidate module either is
-                       // unavailable or does not contain the package), but is resolved in
-                       // eager mode due to a newer-than-latest dependency that is normally
-                       // runed out of the module graph.
+                       // unavailable or does not contain the package), but is resolved in the
+                       // unpruned graph due to a newer-than-latest dependency that is normally
+                       // pruned out.
                        //
                        // This could also occur if the source code for the module providing the
-                       // package in lazy mode has a checksum error, but eager mode upgrades
-                       // that module to a version with a correct checksum.
+                       // package in the pruned graph has a checksum error, but the unpruned
+                       // graph upgrades that module to a version with a correct checksum.
                        //
                        // pkg.err should have already been logged elsewhere — along with a
                        // stack trace — so log only the import path and non-error info here.
                        suggestUpgrade = true
-                       ld.errorf("%s failed to load from any module,\n\tbut go %s would load it from %v\n", pkg.path, ld.TidyCompatibleVersion, mismatch.mod)
+                       ld.error(fmt.Errorf("%s failed to load from any module,\n\tbut go %s would load it from %v", pkg.path, compatVersion, mismatch.mod))
 
                case pkg.mod != mismatch.mod:
                        // The package is loaded successfully by both Go versions, but from a
@@ -2011,15 +2191,16 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
                        // unnoticed!) variations in behavior between builds with different
                        // toolchains.
                        suggestUpgrade = true
-                       ld.errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, ld.TidyCompatibleVersion, mismatch.mod.Version)
+                       ld.error(fmt.Errorf("%s loaded from %v,\n\tbut go %s would select %v\n", pkg.stackText(), pkg.mod, compatVersion, mismatch.mod.Version))
 
                default:
                        base.Fatalf("go: internal error: mismatch recorded for package %s, but no differences found", pkg.path)
                }
        }
 
+       ld.switchIfErrors(ctx)
        suggestFixes()
-       base.ExitIfErrors()
+       ld.exitIfErrors(ctx)
 }
 
 // scanDir is like imports.ScanDir but elides known magic imports from the list,
@@ -2034,8 +2215,16 @@ func (ld *loader) checkTidyCompatibility(ctx context.Context, rs *Requirements)
 // during "go vendor", we look into "// +build appengine" files and
 // may see these legacy imports. We drop them so that the module
 // search does not look for modules to try to satisfy them.
-func scanDir(dir string, tags map[string]bool) (imports_, testImports []string, err error) {
+func scanDir(modroot string, dir string, tags map[string]bool) (imports_, testImports []string, err error) {
+       if ip, mierr := modindex.GetPackage(modroot, dir); mierr == nil {
+               imports_, testImports, err = ip.ScanDir(tags)
+               goto Happy
+       } else if !errors.Is(mierr, modindex.ErrNotIndexed) {
+               return nil, nil, mierr
+       }
+
        imports_, testImports, err = imports.ScanDir(dir, tags)
+Happy:
 
        filter := func(x []string) []string {
                w := 0
@@ -2093,14 +2282,13 @@ func (ld *loader) buildStacks() {
 //             other2 tested by
 //             other2.test imports
 //             pkg
-//
 func (pkg *loadPkg) stackText() string {
        var stack []*loadPkg
        for p := pkg; p != nil; p = p.stack {
                stack = append(stack, p)
        }
 
-       var buf bytes.Buffer
+       var buf strings.Builder
        for i := len(stack) - 1; i >= 0; i-- {
                p := stack[i]
                fmt.Fprint(&buf, p.path)
@@ -2144,7 +2332,7 @@ func (pkg *loadPkg) why() string {
 // If there is no reason for the package to be in the current build,
 // Why returns an empty string.
 func Why(path string) string {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, ok := loaded.pkgCache.Get(path)
        if !ok {
                return ""
        }
@@ -2156,7 +2344,7 @@ func Why(path string) string {
 // WhyDepth returns 0.
 func WhyDepth(path string) int {
        n := 0
-       pkg, _ := loaded.pkgCache.Get(path).(*loadPkg)
+       pkg, _ := loaded.pkgCache.Get(path)
        for p := pkg; p != nil; p = p.stack {
                n++
        }