]> 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 b822e74eb560de8566166adfd9f1ca17b36fdc1b..51eb141d4b38b827d1233c33b26619d51d15181f 100644 (file)
@@ -40,16 +40,17 @@ 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.)
 //
 // Because "go mod vendor" prunes out the tests of vendored packages, the
 // behavior of the "all" pattern with -mod=vendor in Go 1.11–1.15 is the same
 // as the "all" pattern (regardless of the -mod flag) in 1.16+.
-// The allClosesOverTests parameter to the loader indicates whether the "all"
+// The loader uses the GoVersion parameter to determine whether the "all"
 // pattern should close over tests (as in Go 1.11–1.15) or stop at only those
 // packages transitively imported by the packages and tests in the main module
 // ("all" in Go 1.16+ and "go mod vendor" in Go 1.11+).
@@ -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,8 +113,10 @@ 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"
@@ -133,16 +135,31 @@ var loaded *loader
 
 // PackageOpts control the behavior of the LoadPackages function.
 type PackageOpts struct {
+       // TidyGoVersion is the Go version to which the go.mod file should be updated
+       // after packages have been loaded.
+       //
+       // 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.
+       TidyGoVersion string
+
        // Tags are the build tags in effect (as interpreted by the
        // cmd/go/internal/imports package).
        // If nil, treated as equivalent to imports.Tags().
        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
 
+       // TidyCompatibleVersion is the oldest Go version that must be able to
+       // reproducibly reload the requested packages.
+       //
+       // If empty, the compatible version is the Go version immediately prior to the
+       // 'go' version listed in the go.mod file.
+       TidyCompatibleVersion string
+
        // VendorModulesInGOROOTSrc indicates that if we are within a module in
        // GOROOT/src, packages in the module's vendor directory should be resolved as
        // actual module dependencies (instead of standard-library packages).
@@ -155,6 +172,11 @@ type PackageOpts struct {
        // if the flag is set to "readonly" (the default) or "vendor".
        ResolveMissingImports bool
 
+       // AssumeRootsImported indicates that the transitive dependencies of the root
+       // packages should be treated as if those roots will be imported by the main
+       // module.
+       AssumeRootsImported bool
+
        // AllowPackage, if non-nil, is called after identifying the module providing
        // each package. If AllowPackage returns a non-nil error, that error is set
        // for the package, and the imports and test of that package will not be
@@ -209,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
@@ -234,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.
@@ -253,7 +286,9 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
 
                                                // If we're outside of a module, ensure that the failure mode
                                                // indicates that.
-                                               ModRoot()
+                                               if !HasModRoot() {
+                                                       die()
+                                               }
 
                                                if ld != nil {
                                                        m.AddError(err)
@@ -285,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, []module.Version{Target})
+                                       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".
@@ -303,14 +342,16 @@ 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,
                requirements: initialRS,
 
-               allClosesOverTests: index.allPatternClosesOverTests() && !opts.UseVendorAll,
-               allPatternIsRoot:   allPatternIsRoot,
+               allPatternIsRoot: allPatternIsRoot,
 
                listRoots: func(rs *Requirements) (roots []string) {
                        updateMatches(rs, nil)
@@ -329,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)
@@ -342,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.
@@ -363,12 +403,53 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
                        }
                }
 
-               modfetch.TrimGoSum(keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly))
+               keep := keepSums(ctx, ld, ld.requirements, loadedZipSumsOnly)
+               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 !ExplicitWriteGoMod {
+                       modfetch.TrimGoSum(keep)
+
+                       // commitRequirements below will also call WriteGoSum, but the "keep" map
+                       // we have here could be strictly larger: commitRequirements only commits
+                       // 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(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.requirements)
+       requirements = loaded.requirements
 
        for _, pkg := range ld.pkgs {
                if !pkg.isTest() {
@@ -376,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()))
        }
@@ -395,16 +483,29 @@ func matchLocalDirs(ctx context.Context, m *search.Match, rs *Requirements) {
                dir := filepath.Dir(filepath.Clean(m.Pattern()[:i+3]))
                absDir := dir
                if !filepath.IsAbs(dir) {
-                       absDir = filepath.Join(base.Cwd, dir)
+                       absDir = filepath.Join(base.Cwd(), dir)
+               }
+
+               modRoot := findModuleRoot(absDir)
+               found := false
+               for _, mainModuleRoot := range modRoots {
+                       if mainModuleRoot == modRoot {
+                               found = true
+                               break
+                       }
                }
-               if search.InDir(absDir, cfg.GOROOTsrc) == "" && search.InDir(absDir, ModRoot()) == "" && pathInModuleCache(ctx, absDir, rs) == "" {
+               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
                }
        }
 
-       m.MatchDirs()
+       m.MatchDirs(modRoots)
 }
 
 // resolveLocalPackage resolves a filesystem path to a package path.
@@ -413,7 +514,7 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
        if filepath.IsAbs(dir) {
                absDir = filepath.Clean(dir)
        } else {
-               absDir = filepath.Join(base.Cwd, dir)
+               absDir = filepath.Join(base.Cwd(), dir)
        }
 
        bp, err := cfg.BuildContext.ImportDir(absDir, 0)
@@ -446,49 +547,68 @@ func resolveLocalPackage(ctx context.Context, dir string, rs *Requirements) (str
                }
        }
 
-       if modRoot != "" && absDir == modRoot {
-               if absDir == cfg.GOROOTsrc {
-                       return "", errPkgIsGorootSrc
+       for _, mod := range MainModules.Versions() {
+               modRoot := MainModules.ModRoot(mod)
+               if modRoot != "" && absDir == modRoot {
+                       if absDir == cfg.GOROOTsrc {
+                               return "", errPkgIsGorootSrc
+                       }
+                       return MainModules.PathPrefix(mod), nil
                }
-               return targetPrefix, nil
        }
 
        // Note: The checks for @ here are just to avoid misinterpreting
        // the module cache directories (formerly GOPATH/src/mod/foo@v1.5.2/bar).
        // It's not strictly necessary but helpful to keep the checks.
-       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 cfg.BuildMod != "vendor" {
-                               return "", fmt.Errorf("without -mod=vendor, directory %s has no package path", absDir)
+       var pkgNotFoundErr error
+       pkgNotFoundLongestPrefix := ""
+       for _, mainModule := range MainModules.Versions() {
+               modRoot := MainModules.ModRoot(mainModule)
+               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(VendorDir())
+                               if _, ok := vendorPkgModule[pkg]; !ok {
+                                       return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+                               }
+                               return pkg, nil
                        }
 
-                       readVendorList()
-                       pkg := strings.TrimPrefix(suffix, "/vendor/")
-                       if _, ok := vendorPkgModule[pkg]; !ok {
-                               return "", fmt.Errorf("directory %s is not a package listed in vendor/modules.txt", absDir)
+                       mainModulePrefix := MainModules.PathPrefix(mainModule)
+                       if mainModulePrefix == "" {
+                               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 "."
+                                       // within module "std" either.
+                                       return "", errPkgIsBuiltin
+                               }
+                               return pkg, nil
                        }
-                       return pkg, nil
-               }
 
-               if targetPrefix == "" {
-                       pkg := strings.TrimPrefix(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 "."
-                               // within module "std" either.
-                               return "", errPkgIsBuiltin
+                       pkg := pathpkg.Join(mainModulePrefix, suffix)
+                       if _, ok, err := dirInModule(pkg, mainModulePrefix, modRoot, true); err != nil {
+                               return "", err
+                       } else if !ok {
+                               // This main module could contain the directory but doesn't. Other main
+                               // modules might contain the directory, so wait till we finish the loop
+                               // to see if another main module contains directory. But if not,
+                               // return an error.
+                               if len(mainModulePrefix) > len(pkgNotFoundLongestPrefix) {
+                                       pkgNotFoundLongestPrefix = mainModulePrefix
+                                       pkgNotFoundErr = &PackageNotInModuleError{MainModules: []module.Version{mainModule}, Pattern: pkg}
+                               }
+                               continue
                        }
                        return pkg, nil
                }
-
-               pkg := targetPrefix + suffix
-               if _, ok, err := dirInModule(pkg, targetPrefix, modRoot, true); err != nil {
-                       return "", err
-               } else if !ok {
-                       return "", &PackageNotInModuleError{Mod: Target, Pattern: pkg}
-               }
-               return pkg, nil
+       }
+       if pkgNotFoundErr != nil {
+               return "", pkgNotFoundErr
        }
 
        if sub := search.InDir(absDir, cfg.GOROOTsrc); sub != "" && sub != "." && !strings.Contains(sub, "@") {
@@ -501,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
 }
@@ -516,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
@@ -544,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.
@@ -557,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.
@@ -590,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{
@@ -599,112 +732,74 @@ func ImportFromFiles(ctx context.Context, gofiles []string) {
                        ResolveMissingImports: true,
                        SilencePackageErrors:  true,
                },
-               requirements:       rs,
-               allClosesOverTests: index.allPatternClosesOverTests(),
+               requirements: rs,
                listRoots: func(*Requirements) (roots []string) {
                        roots = append(roots, imports...)
                        roots = append(roots, testImports...)
                        return roots
                },
        })
-       commitRequirements(ctx, 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,
-// provided it is within the main module, or else returns ".".
-func DirImportPath(ctx context.Context, dir string) string {
+// provided it is within a main module, or else returns ".".
+func (mms *MainModuleSet) DirImportPath(ctx context.Context, dir string) (path string, m module.Version) {
        if !HasModRoot() {
-               return "."
+               return ".", module.Version{}
        }
        LoadModFile(ctx) // Sets targetPrefix.
 
        if !filepath.IsAbs(dir) {
-               dir = filepath.Join(base.Cwd, dir)
+               dir = filepath.Join(base.Cwd(), dir)
        } else {
                dir = filepath.Clean(dir)
        }
 
-       if dir == modRoot {
-               return targetPrefix
-       }
-       if strings.HasPrefix(dir, modRoot+string(filepath.Separator)) {
-               suffix := filepath.ToSlash(dir[len(modRoot):])
-               if strings.HasPrefix(suffix, "/vendor/") {
-                       return strings.TrimPrefix(suffix, "/vendor/")
+       var longestPrefix string
+       var longestPrefixPath string
+       var longestPrefixVersion module.Version
+       for _, v := range mms.Versions() {
+               modRoot := mms.ModRoot(v)
+               if dir == modRoot {
+                       return mms.PathPrefix(v), v
+               }
+               if str.HasFilePathPrefix(dir, modRoot) {
+                       pathPrefix := MainModules.PathPrefix(v)
+                       if pathPrefix > longestPrefix {
+                               longestPrefix = pathPrefix
+                               longestPrefixVersion = v
+                               suffix := filepath.ToSlash(str.TrimFilePathPrefix(dir, modRoot))
+                               if strings.HasPrefix(suffix, "vendor/") {
+                                       longestPrefixPath = suffix[len("vendor/"):]
+                                       continue
+                               }
+                               longestPrefixPath = pathpkg.Join(mms.PathPrefix(v), suffix)
+                       }
                }
-               return targetPrefix + suffix
        }
-       return "."
-}
-
-// TargetPackages returns the list of packages in the target (top-level) module
-// matching pattern, which may be relative to the working directory, under all
-// build tag settings.
-func TargetPackages(ctx context.Context, pattern string) *search.Match {
-       // TargetPackages is relative to the main module, so ensure that the main
-       // module is a thing that can contain packages.
-       LoadModFile(ctx) // Sets Target.
-       ModRoot()        // Emits an error if Target cannot contain packages.
-
-       m := search.NewMatch(pattern)
-       matchPackages(ctx, m, imports.AnyTags(), omitStd, []module.Version{Target})
-       return m
-}
-
-// 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 ""
+       if len(longestPrefix) > 0 {
+               return longestPrefixPath, longestPrefixVersion
        }
-       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
+       return ".", module.Version{}
 }
 
 // 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{}
        }
        return pkg.mod
 }
 
-// PackageImports returns the imports for the package named by the import path.
-// Test imports will be returned as well if tests were loaded for the package
-// (i.e., if "all" was loaded or if LoadTests was set and the path was matched
-// by a command line argument). PackageImports will return nil for
-// unknown package paths.
-func PackageImports(path string) (imports, testImports []string) {
-       pkg, ok := loaded.pkgCache.Get(path).(*loadPkg)
-       if !ok {
-               return nil, nil
-       }
-       imports = make([]string, len(pkg.imports))
-       for i, p := range pkg.imports {
-               imports[i] = p.path
-       }
-       if pkg.test != nil {
-               testImports = make([]string, len(pkg.test.imports))
-               for i, p := range pkg.test.imports {
-                       testImports[i] = p.path
-               }
-       }
-       return imports, testImports
-}
-
 // Lookup returns the source directory, import path, and any loading error for
 // the package at path as imported from the package in parentDir.
 // Lookup requires that one of the Load functions in this package has already
@@ -717,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:
@@ -743,11 +838,21 @@ func Lookup(parentPath string, parentIsStd bool, path string) (dir, realPath str
 type loader struct {
        loaderParams
 
+       // allClosesOverTests indicates whether the "all" pattern includes
+       // dependencies of tests outside the main module (as in Go 1.11–1.15).
+       // (Otherwise — as in Go 1.16+ — the "all" pattern includes only the packages
+       // 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
 }
 
@@ -757,8 +862,7 @@ type loaderParams struct {
        PackageOpts
        requirements *Requirements
 
-       allClosesOverTests bool // Does the "all" pattern include the transitive closure of tests of packages in "all"?
-       allPatternIsRoot   bool // Is the "all" pattern an additional root?
+       allPatternIsRoot bool // Is the "all" pattern an additional root?
 
        listRoots func(rs *Requirements) []string
 }
@@ -771,20 +875,46 @@ 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.
 type loadPkg struct {
        // Populated at construction time:
@@ -801,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
@@ -837,6 +968,11 @@ const (
        // are also roots (and must be marked pkgIsRoot).
        pkgIsRoot
 
+       // pkgFromRoot indicates that the package is in the transitive closure of
+       // imports starting at the roots. (Note that every package marked as pkgIsRoot
+       // is also trivially marked pkgFromRoot.)
+       pkgFromRoot
+
        // pkgImportsLoaded indicates that the imports and testImports fields of a
        // loadPkg have been populated.
        pkgImportsLoaded
@@ -850,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).
@@ -859,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)
                }
        }
@@ -869,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.
@@ -883,10 +1019,7 @@ func (pkg *loadPkg) fromExternalModule() bool {
        if pkg.mod.Path == "" {
                return false // loaded from the standard library, not a module
        }
-       if pkg.mod.Path == Target.Path {
-               return false // loaded from the main module.
-       }
-       return true
+       return !MainModules.Contains(pkg.mod.Path)
 }
 
 var errMissing = errors.New("cannot find package")
@@ -903,16 +1036,48 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                work:         par.NewQueue(runtime.GOMAXPROCS(0)),
        }
 
-       if ld.requirements.depth == eager {
+       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.errorf("go: %v\n", err)
+                       ld.error(err)
                }
        }
+       ld.exitIfErrors(ctx)
+
+       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 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
+
+               // 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,
@@ -920,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
@@ -958,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
@@ -975,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.
@@ -983,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
@@ -998,18 +1167,18 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                // iteration so we don't need to also update it here. (That would waste time
                // computing a "direct" map that we'll have to recompute later anyway.)
                direct := ld.requirements.direct
-               rs, err := updateRoots(ctx, direct, ld.requirements, noPkgs, toAdd)
+               rs, err := updateRoots(ctx, direct, ld.requirements, noPkgs, toAdd, ld.AssumeRootsImported)
                if err != nil {
                        // If an error was found in a newly added module, report the package
                        // import stack instead of the module requirement stack. Packages
                        // 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) {
@@ -1021,26 +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)
-               }
+                       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))
+                               }
+                       }
 
-               // 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. If not, 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: internal error: a requirement on %v is needed but was not added during package loading\n", m)
-                               base.ExitIfErrors()
+                       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.
@@ -1058,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()
@@ -1077,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
@@ -1120,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.mod != Target {
+               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.
@@ -1156,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
@@ -1200,14 +1451,21 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
                addRoots = tidy.rootModules
        }
 
-       rs, err = updateRoots(ctx, direct, rs, ld.pkgs, addRoots)
+       rs, err = updateRoots(ctx, direct, rs, ld.pkgs, addRoots, ld.AssumeRootsImported)
        if err != nil {
                // We don't actually know what even the root requirements are supposed to be,
                // so we can't proceed with loading. Return the error to the caller
                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.
@@ -1236,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); err == nil {
+                               if _, _, _, _, err = importFromModules(ctx, pkg.path, rs, nil, ld.skipImportModFiles); err == nil {
                                        changed = true
                                        break
                                }
@@ -1254,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
@@ -1280,6 +1538,15 @@ func (ld *loader) resolveMissingImports(ctx context.Context) (modAddedBy map[mod
                        var err error
                        mod, err = queryImport(ctx, pkg.path, ld.requirements)
                        if err != nil {
+                               var ime *ImportMissingError
+                               if errors.As(err, &ime) {
+                                       for curstack := pkg.stack; curstack != nil; curstack = curstack.stack {
+                                               if MainModules.Contains(curstack.mod.Path) {
+                                                       ime.ImportingMainModule = curstack.mod
+                                                       break
+                                               }
+                                       }
+                               }
                                // pkg.err was already non-nil, so we can reasonably attribute the error
                                // for pkg to either the original error or the one returned by
                                // queryImport. The existing error indicates only that we couldn't find
@@ -1306,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 == "" {
@@ -1318,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
@@ -1333,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,
                }
@@ -1341,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
@@ -1359,6 +1644,9 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
                // This package matches a root pattern by virtue of being in "all".
                flags |= pkgIsRoot
        }
+       if flags.has(pkgIsRoot) {
+               flags |= pkgFromRoot
+       }
 
        old := pkg.flags.update(flags)
        new := old | flags
@@ -1375,7 +1663,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
                // so it's ok if we call it more than is strictly necessary.
                wantTest := false
                switch {
-               case ld.allPatternIsRoot && pkg.mod == Target:
+               case ld.allPatternIsRoot && MainModules.Contains(pkg.mod.Path):
                        // We are loading the "all" pattern, which includes packages imported by
                        // tests in the main module. This package is in the main module, so we
                        // need to identify the imports of its test even if LoadTests is not set.
@@ -1396,7 +1684,7 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
 
                if wantTest {
                        var testFlags loadPkgFlags
-                       if pkg.mod == Target || (ld.allClosesOverTests && new.has(pkgInAll)) {
+                       if MainModules.Contains(pkg.mod.Path) || (ld.allClosesOverTests && new.has(pkgInAll)) {
                                // Tests of packages in the main module are in "all", in the sense that
                                // they cause the packages they import to also be in "all". So are tests
                                // of packages in "all" if "all" closes over test dependencies.
@@ -1413,6 +1701,12 @@ func (ld *loader) applyPkgFlags(ctx context.Context, pkg *loadPkg, flags loadPkg
                        ld.applyPkgFlags(ctx, dep, pkgInAll)
                }
        }
+
+       if new.has(pkgFromRoot) && !old.has(pkgFromRoot|pkgImportsLoaded) {
+               for _, dep := range pkg.imports {
+                       ld.applyPkgFlags(ctx, dep, pkgFromRoot)
+               }
+       }
 }
 
 // preloadRootModules loads the module requirements needed to identify the
@@ -1429,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)
+                       m, _, _, _, err := importFromModules(ctx, path, ld.requirements, nil, ld.skipImportModFiles)
                        if err != nil {
                                var missing *ImportMissingError
                                if errors.As(err, &missing) && ld.ResolveMissingImports {
@@ -1455,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
@@ -1473,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)
+       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) {
@@ -1498,29 +1793,29 @@ 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),
+       var mg *ModuleGraph
+       if ld.requirements.pruning == unpruned {
+               var err error
+               mg, err = ld.requirements.Graph(ctx)
+               if err != nil {
+                       // We already checked the error from Graph in loadFromRoots and/or
+                       // updateRequirements, so we ignored the error on purpose and we should
+                       // keep trying to push past it.
+                       //
+                       // However, because mg may be incomplete (and thus may select inaccurate
+                       // versions), we shouldn't use it to load packages. Instead, we pass a nil
+                       // *ModuleGraph, which will cause mg to first try loading from only the
+                       // main module and root dependencies.
+                       mg = nil
                }
-               return
        }
 
-       pkg.mod, pkg.dir, pkg.err = importFromModules(ctx, pkg.path, ld.requirements)
+       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
        }
-       if pkg.mod == Target {
+       if MainModules.Contains(pkg.mod.Path) {
                // Go ahead and mark pkg as in "all". This provides the invariant that a
                // package that is *only* imported by other packages in "all" is always
                // marked as such before loading its imports.
@@ -1546,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
@@ -1625,13 +1920,14 @@ func (ld *loader) stdVendor(parentPath, path string) string {
        }
 
        if str.HasPathPrefix(parentPath, "cmd") {
-               if !ld.VendorModulesInGOROOTSrc || Target.Path != "cmd" {
+               if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("cmd") {
                        vendorPath := pathpkg.Join("cmd", "vendor", path)
+
                        if _, err := os.Stat(filepath.Join(cfg.GOROOTsrc, filepath.FromSlash(vendorPath))); err == nil {
                                return vendorPath
                        }
                }
-       } else if !ld.VendorModulesInGOROOTSrc || Target.Path != "std" || str.HasPathPrefix(parentPath, "vendor") {
+       } else if !ld.VendorModulesInGOROOTSrc || !MainModules.Contains("std") || str.HasPathPrefix(parentPath, "vendor") {
                // If we are outside of the 'std' module, resolve imports from within 'std'
                // to the vendor directory.
                //
@@ -1673,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()
                }
        }
@@ -1684,11 +1980,229 @@ 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, compatVersion string) {
+       goVersion := rs.GoVersion()
+       suggestUpgrade := false
+       suggestEFlag := false
+       suggestFixes := func() {
+               if ld.AllowErrors {
+                       // The user is explicitly ignoring these errors, so don't bother them with
+                       // other options.
+                       return
+               }
+
+               // We print directly to os.Stderr because this information is advice about
+               // how to fix errors, not actually an error itself.
+               // (The actual errors should have been logged already.)
+
+               fmt.Fprintln(os.Stderr)
+
+               goFlag := ""
+               if goVersion != MainModules.GoVersion() {
+                       goFlag = " -go=" + goVersion
+               }
+
+               compatFlag := ""
+               if compatVersion != gover.Prev(goVersion) {
+                       compatFlag = " -compat=" + compatVersion
+               }
+               if suggestUpgrade {
+                       eDesc := ""
+                       eFlag := ""
+                       if suggestEFlag {
+                               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", 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", compatVersion, goFlag, compatFlag)
+               }
+
+               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")
+       }
+
+       mg, err := rs.Graph(ctx)
+       if err != nil {
+               ld.error(fmt.Errorf("error loading go %s module graph: %w", compatVersion, err))
+               ld.switchIfErrors(ctx)
+               suggestFixes()
+               ld.exitIfErrors(ctx)
+               return
+       }
+
+       // Re-resolve packages in parallel.
+       //
+       // We re-resolve each package — rather than just checking versions — to ensure
+       // that we have fetched module source code (and, importantly, checksums for
+       // that source code) for all modules that are necessary to ensure that imports
+       // are unambiguous. That also produces clearer diagnostics, since we can say
+       // exactly what happened to the package if it became ambiguous or disappeared
+       // entirely.
+       //
+       // We re-resolve the packages in parallel because this process involves disk
+       // I/O to check for package sources, and because the process of checking for
+       // ambiguous imports may require us to download additional modules that are
+       // otherwise pruned out in Go 1.17 — we don't want to block progress on other
+       // packages while we wait for a single new download.
+       type mismatch struct {
+               mod module.Version
+               err error
+       }
+       mismatchMu := make(chan map[*loadPkg]mismatch, 1)
+       mismatchMu <- map[*loadPkg]mismatch{}
+       for _, pkg := range ld.pkgs {
+               if pkg.mod.Path == "" && pkg.err == nil {
+                       // This package is from the standard library (which does not vary based on
+                       // the module graph).
+                       continue
+               }
+
+               pkg := pkg
+               ld.work.Add(func() {
+                       mod, _, _, _, err := importFromModules(ctx, pkg.path, rs, mg, ld.skipImportModFiles)
+                       if mod != pkg.mod {
+                               mismatches := <-mismatchMu
+                               mismatches[pkg] = mismatch{mod: mod, err: err}
+                               mismatchMu <- mismatches
+                       }
+               })
+       }
+       <-ld.work.Idle()
+
+       mismatches := <-mismatchMu
+       if len(mismatches) == 0 {
+               // Since we're running as part of 'go mod tidy', the roots of the module
+               // graph should contain only modules that are relevant to some package in
+               // the package graph. We checked every package in the package graph and
+               // didn't find any mismatches, so that must mean that all of the roots of
+               // the module graph are also consistent.
+               //
+               // If we're wrong, Go 1.16 in -mod=readonly mode will error out with
+               // "updates to go.mod needed", which would be very confusing. So instead,
+               // we'll double-check that our reasoning above actually holds — if it
+               // doesn't, we'll emit an internal error and hopefully the user will report
+               // it as a bug.
+               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, goVersion, m.Version, compatVersion, v)
+                       }
+               }
+               return
+       }
+
+       // Iterate over the packages (instead of the mismatches map) to emit errors in
+       // deterministic order.
+       for _, pkg := range ld.pkgs {
+               mismatch, ok := mismatches[pkg]
+               if !ok {
+                       continue
+               }
+
+               if pkg.isTest() {
+                       // We already did (or will) report an error for the package itself,
+                       // 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)
+                       }
+                       continue
+               }
+
+               switch {
+               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 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
+                       // longer does, or to a version for which the module source code (but
+                       // not the go.mod file in isolation) has a checksum error.
+                       if missing := (*ImportMissingError)(nil); errors.As(mismatch.err, &missing) {
+                               selected := module.Version{
+                                       Path:    pkg.mod.Path,
+                                       Version: mg.Selected(pkg.mod.Path),
+                               }
+                               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.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
+
+                       // Even if we press ahead with the '-e' flag, the older version will
+                       // error out in readonly mode if it thinks the go.mod file contains
+                       // any *explicit* dependency that is not at its selected version,
+                       // even if that dependency is not relevant to any package being loaded.
+                       //
+                       // We check for that condition here. If all of the roots are consistent
+                       // the '-e' flag suffices, but otherwise we need to suggest an upgrade.
+                       if !suggestUpgrade {
+                               for _, m := range ld.requirements.rootModules {
+                                       if v := mg.Selected(m.Path); v != m.Version {
+                                               suggestUpgrade = true
+                                               break
+                                       }
+                               }
+                       }
+
+               case pkg.err != nil:
+                       // 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 the pruned graph
+                       // (because the "latest" version of each candidate module either is
+                       // 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 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.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
+                       // different module in each. This could lead to subtle (and perhaps even
+                       // unnoticed!) variations in behavior between builds with different
+                       // toolchains.
+                       suggestUpgrade = true
+                       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()
+       ld.exitIfErrors(ctx)
+}
+
 // scanDir is like imports.ScanDir but elides known magic imports from the list,
 // so that we do not go looking for packages that don't really exist.
 //
@@ -1701,8 +2215,16 @@ func (ld *loader) checkMultiplePaths() {
 // 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
@@ -1760,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)
@@ -1811,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 ""
        }
@@ -1823,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++
        }