]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/go/internal/modload: use "pruned" instead of "lazy" to describe pruned module...
authorBryan C. Mills <bcmills@google.com>
Fri, 27 Aug 2021 19:07:15 +0000 (15:07 -0400)
committerBryan C. Mills <bcmills@google.com>
Mon, 30 Aug 2021 20:08:45 +0000 (20:08 +0000)
The level of support for pruning — not the lazy/eager loading behavior
— is the more fundamental property, and what matters in terms of what
invariants we need to maintain.

If the main module supports pruned module graphs we load its
dependencies lazily, and if it does not support pruned module graphs
we load its dependencies eagerly. However, in principle we could also
load the module graph lazily even in modules that do not support graph
pruning — we would just be more likely to overlook inconsistent
requirements introduced by hand-edits or bad VCS merges to the go.mod
file.

(After this change, a “lazy” module is just one in which we happen not
to have loaded the module graph, and an “eager” one is one in which we
happen to load the module graph more aggressively.)

Updates #36460
For #47397

Change-Id: I0d2ffd21acc913f72ff56b59a6bdc539ebc3d377
Reviewed-on: https://go-review.googlesource.com/c/go/+/345393
Trust: Bryan C. Mills <bcmills@google.com>
Run-TryBot: Bryan C. Mills <bcmills@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Jay Conrod <jayconrod@google.com>
src/cmd/go/internal/modload/build.go
src/cmd/go/internal/modload/buildlist.go
src/cmd/go/internal/modload/edit.go
src/cmd/go/internal/modload/import_test.go
src/cmd/go/internal/modload/init.go
src/cmd/go/internal/modload/list.go
src/cmd/go/internal/modload/load.go
src/cmd/go/internal/modload/modfile.go

index 0efd84123af6d2e54a8bd20af8d6f4c2b193512d..8a9792089b94a54c858ed121fca1aa6d1edab1bb 100644 (file)
@@ -80,7 +80,7 @@ func ModuleInfo(ctx context.Context, path string) *modinfo.ModulePublic {
                v  string
                ok bool
        )
-       if rs.depth == lazy {
+       if rs.pruning == pruned {
                v, ok = rs.rootSelected(path)
        }
        if !ok {
index 94414278abb4013f4d54ff0c325d4b9be632ca75..777e29af10debebc6e9f247513336b0bd41b5480 100644 (file)
@@ -30,14 +30,15 @@ func capVersionSlice(s []module.Version) []module.Version {
 
 // A Requirements represents a logically-immutable set of root module requirements.
 type Requirements struct {
-       // depth is the depth at which the requirement graph is computed.
+       // pruning is the pruning at which the requirement graph is computed.
        //
-       // If eager, the graph includes all transitive requirements regardless of depth.
+       // If unpruned, the graph includes all transitive requirements regardless
+       // of whether the requiring module supports pruning.
        //
-       // If lazy, the graph includes only the root modules, the explicit
+       // If pruned, the graph includes only the root modules, the explicit
        // requirements of those root modules, and the transitive requirements of only
-       // the *non-lazy* root modules.
-       depth modDepth
+       // the root modules that do not support pruning.
+       pruning modPruning
 
        // rootModules is the set of module versions explicitly required by the main
        // modules, sorted and capped to length. It may contain duplicates, and may
@@ -97,7 +98,7 @@ var requirements *Requirements
 //
 // If vendoring is in effect, the caller must invoke initVendor on the returned
 // *Requirements before any other method.
-func newRequirements(depth modDepth, rootModules []module.Version, direct map[string]bool) *Requirements {
+func newRequirements(pruning modPruning, rootModules []module.Version, direct map[string]bool) *Requirements {
        for i, m := range rootModules {
                if m.Version == "" && MainModules.Contains(m.Path) {
                        panic(fmt.Sprintf("newRequirements called with untrimmed build list: rootModules[%v] is a main module", i))
@@ -114,7 +115,7 @@ func newRequirements(depth modDepth, rootModules []module.Version, direct map[st
        }
 
        rs := &Requirements{
-               depth:          depth,
+               pruning:        pruning,
                rootModules:    capVersionSlice(rootModules),
                maxRootVersion: make(map[string]string, len(rootModules)),
                direct:         direct,
@@ -143,10 +144,10 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
                }
                mainModule := MainModules.Versions()[0]
 
-               if rs.depth == lazy {
-                       // The roots of a lazy module should already include every module in the
-                       // vendor list, because the vendored modules are the same as those
-                       // maintained as roots by the lazy loading “import invariant”.
+               if rs.pruning == pruned {
+                       // The roots of a pruned module should already include every module in the
+                       // vendor list, because the vendored modules are the same as those needed
+                       // for graph pruning.
                        //
                        // Just to be sure, we'll double-check that here.
                        inconsistent := false
@@ -161,8 +162,8 @@ func (rs *Requirements) initVendor(vendorList []module.Version) {
                        }
 
                        // Now we can treat the rest of the module graph as effectively “pruned
-                       // out”, like a more aggressive version of lazy loading: in vendor mode,
-                       // the root requirements *are* the complete module graph.
+                       // out”, as though we are viewing the main module from outside: in vendor
+                       // mode, the root requirements *are* the complete module graph.
                        mg.g.Require(mainModule, rs.rootModules)
                } else {
                        // The transitive requirements of the main module are not in general available
@@ -219,7 +220,7 @@ func (rs *Requirements) hasRedundantRoot() bool {
 // returns a non-nil error of type *mvs.BuildListError.
 func (rs *Requirements) Graph(ctx context.Context) (*ModuleGraph, error) {
        rs.graphOnce.Do(func() {
-               mg, mgErr := readModGraph(ctx, rs.depth, rs.rootModules)
+               mg, mgErr := readModGraph(ctx, rs.pruning, rs.rootModules)
                rs.graph.Store(cachedGraph{mg, mgErr})
        })
        cached := rs.graph.Load().(cachedGraph)
@@ -259,8 +260,16 @@ var readModGraphDebugOnce sync.Once
 //
 // Unlike LoadModGraph, readModGraph does not attempt to diagnose or update
 // inconsistent roots.
-func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (*ModuleGraph, error) {
-       if depth == lazy {
+func readModGraph(ctx context.Context, pruning modPruning, roots []module.Version) (*ModuleGraph, error) {
+       if pruning == pruned {
+               // Enable diagnostics for lazy module loading
+               // (https://golang.org/ref/mod#lazy-loading) only if the module graph is
+               // pruned.
+               //
+               // In unpruned modules,we load the module graph much more aggressively (in
+               // order to detect inconsistencies that wouldn't be feasible to spot-check),
+               // so it wouldn't be useful to log when that occurs (because it happens in
+               // normal operation all the time).
                readModGraphDebugOnce.Do(func() {
                        for _, f := range strings.Split(os.Getenv("GODEBUG"), ",") {
                                switch f {
@@ -292,13 +301,13 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
        }
 
        var (
-               loadQueue    = par.NewQueue(runtime.GOMAXPROCS(0))
-               loadingEager sync.Map // module.Version → nil; the set of modules that have been or are being loaded via eager roots
+               loadQueue       = par.NewQueue(runtime.GOMAXPROCS(0))
+               loadingUnpruned sync.Map // module.Version → nil; the set of modules that have been or are being loaded via roots that do not support pruning
        )
 
        // loadOne synchronously loads the explicit requirements for module m.
        // It does not load the transitive requirements of m even if the go version in
-       // m's go.mod file indicates eager loading.
+       // m's go.mod file indicates that it supports graph pruning.
        loadOne := func(m module.Version) (*modFileSummary, error) {
                cached := mg.loadCache.Do(m, func() interface{} {
                        summary, err := goModSummary(m)
@@ -317,15 +326,15 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
                return cached.summary, cached.err
        }
 
-       var enqueue func(m module.Version, depth modDepth)
-       enqueue = func(m module.Version, depth modDepth) {
+       var enqueue func(m module.Version, pruning modPruning)
+       enqueue = func(m module.Version, pruning modPruning) {
                if m.Version == "none" {
                        return
                }
 
-               if depth == eager {
-                       if _, dup := loadingEager.LoadOrStore(m, nil); dup {
-                               // m has already been enqueued for loading. Since eager loading may
+               if pruning == unpruned {
+                       if _, dup := loadingUnpruned.LoadOrStore(m, nil); dup {
+                               // m has already been enqueued for loading. Since unpruned loading may
                                // follow cycles in the the requirement graph, we need to return early
                                // to avoid making the load queue infinitely long.
                                return
@@ -338,21 +347,21 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
                                return // findError will report the error later.
                        }
 
-                       // If the version in m's go.mod file implies eager loading, then we cannot
-                       // assume that the explicit requirements of m (added by loadOne) are
-                       // sufficient to build the packages it contains. We must load its full
+                       // If the version in m's go.mod file does not support pruning, then we
+                       // cannot assume that the explicit requirements of m (added by loadOne)
+                       // are sufficient to build the packages it contains. We must load its full
                        // transitive dependency graph to be sure that we see all relevant
                        // dependencies.
-                       if depth == eager || summary.depth == eager {
+                       if pruning == unpruned || summary.pruning == unpruned {
                                for _, r := range summary.require {
-                                       enqueue(r, eager)
+                                       enqueue(r, unpruned)
                                }
                        }
                })
        }
 
        for _, m := range roots {
-               enqueue(m, depth)
+               enqueue(m, pruning)
        }
        <-loadQueue.Idle()
 
@@ -363,8 +372,7 @@ func readModGraph(ctx context.Context, depth modDepth, roots []module.Version) (
 }
 
 // RequiredBy returns the dependencies required by module m in the graph,
-// or ok=false if module m's dependencies are not relevant (such as if they
-// are pruned out by lazy loading).
+// or ok=false if module m's dependencies are pruned out.
 //
 // The caller must not modify the returned slice, but may safely append to it
 // and may rely on it not to be modified.
@@ -441,12 +449,12 @@ func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
        rs := LoadModFile(ctx)
 
        if goVersion != "" {
-               depth := modDepthFromGoVersion(goVersion)
-               if depth == eager && rs.depth != eager {
+               pruning := pruningForGoVersion(goVersion)
+               if pruning == unpruned && rs.pruning != unpruned {
                        // Use newRequirements instead of convertDepth because convertDepth
                        // also updates roots; here, we want to report the unmodified roots
                        // even though they may seem inconsistent.
-                       rs = newRequirements(eager, rs.rootModules, rs.direct)
+                       rs = newRequirements(unpruned, rs.rootModules, rs.direct)
                }
 
                mg, err := rs.Graph(ctx)
@@ -469,9 +477,8 @@ func LoadModGraph(ctx context.Context, goVersion string) *ModuleGraph {
 //
 // If the complete graph reveals that some root of rs is not actually the
 // selected version of its path, expandGraph computes a new set of roots that
-// are consistent. (When lazy loading is implemented, this may result in
-// upgrades to other modules due to requirements that were previously pruned
-// out.)
+// are consistent. (With a pruned module graph, this may result in upgrades to
+// other modules due to requirements that were previously pruned out.)
 //
 // expandGraph returns the updated roots, along with the module graph loaded
 // from those roots and any error encountered while loading that graph.
@@ -487,9 +494,9 @@ func expandGraph(ctx context.Context, rs *Requirements) (*Requirements, *ModuleG
 
        if !mg.allRootsSelected() {
                // The roots of rs are not consistent with the rest of the graph. Update
-               // them. In an eager module this is a no-op for the build list as a whole —
+               // them. In an unpruned module this is a no-op for the build list as a whole —
                // it just promotes what were previously transitive requirements to be
-               // roots — but in a lazy module it may pull in previously-irrelevant
+               // roots — but in a pruned module it may pull in previously-irrelevant
                // transitive dependencies.
 
                newRS, rsErr := updateRoots(ctx, rs.direct, rs, nil, nil, false)
@@ -558,24 +565,25 @@ type Conflict struct {
 
 // tidyRoots trims the root dependencies to the minimal requirements needed to
 // both retain the same versions of all packages in pkgs and satisfy the
-// lazy loading invariants (if applicable).
+// graph-pruning invariants (if applicable).
 func tidyRoots(ctx context.Context, rs *Requirements, pkgs []*loadPkg) (*Requirements, error) {
        mainModule := MainModules.mustGetSingleMainModule()
-       if rs.depth == eager {
-               return tidyEagerRoots(ctx, mainModule, rs.direct, pkgs)
+       if rs.pruning == unpruned {
+               return tidyUnprunedRoots(ctx, mainModule, rs.direct, pkgs)
        }
-       return tidyLazyRoots(ctx, mainModule, rs.direct, pkgs)
+       return tidyPrunedRoots(ctx, mainModule, rs.direct, pkgs)
 }
 
 func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
-       if rs.depth == eager {
-               return updateEagerRoots(ctx, direct, rs, add)
+       if rs.pruning == unpruned {
+               return updateUnprunedRoots(ctx, direct, rs, add)
        }
-       return updateLazyRoots(ctx, direct, rs, pkgs, add, rootsImported)
+       return updatePrunedRoots(ctx, direct, rs, pkgs, add, rootsImported)
 }
 
-// tidyLazyRoots returns a minimal set of root requirements that maintains the
-// "lazy loading" invariants of the go.mod file for the given packages:
+// tidyPrunedRoots returns a minimal set of root requirements that maintains the
+// invariants of the go.mod file needed to support graph pruning for the given
+// packages:
 //
 //     1. For each package marked with pkgInAll, the module path that provided that
 //        package is included as a root.
@@ -589,7 +597,7 @@ func updateRoots(ctx context.Context, direct map[string]bool, rs *Requirements,
 // To ensure that the loading process eventually converges, the caller should
 // add any needed roots from the tidy root set (without removing existing untidy
 // roots) until the set of roots has converged.
-func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyPrunedRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
        var (
                roots        []module.Version
                pathIncluded = map[string]bool{mainModule.Path: true}
@@ -620,7 +628,7 @@ func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[st
                queued[pkg] = true
        }
        module.Sort(roots)
-       tidy := newRequirements(lazy, roots, direct)
+       tidy := newRequirements(pruned, roots, direct)
 
        for len(queue) > 0 {
                roots = tidy.rootModules
@@ -656,7 +664,7 @@ func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[st
 
                if len(roots) > len(tidy.rootModules) {
                        module.Sort(roots)
-                       tidy = newRequirements(lazy, roots, tidy.direct)
+                       tidy = newRequirements(pruned, roots, tidy.direct)
                }
        }
 
@@ -667,8 +675,8 @@ func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[st
        return tidy, nil
 }
 
-// updateLazyRoots returns a set of root requirements that maintains the “lazy
-// loading” invariants of the go.mod file:
+// updatePrunedRoots returns a set of root requirements that maintains the
+// invariants of the go.mod file needed to support graph pruning:
 //
 //     1. The selected version of the module providing each package marked with
 //        either pkgInAll or pkgIsRoot is included as a root.
@@ -685,7 +693,7 @@ func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[st
 // The packages in pkgs are assumed to have been loaded from either the roots of
 // rs or the modules selected in the graph of rs.
 //
-// The above invariants together imply the “lazy loading” invariants for the
+// The above invariants together imply the graph-pruning invariants for the
 // go.mod file:
 //
 //     1. (The import invariant.) Every module that provides a package transitively
@@ -705,13 +713,13 @@ func tidyLazyRoots(ctx context.Context, mainModule module.Version, direct map[st
 //        it requires explicitly. This invariant is left up to the caller, who must
 //        not load packages from outside the module graph but may add roots to the
 //        graph, but is facilited by (3). If the caller adds roots to the graph in
-//        order to resolve missing packages, then updateLazyRoots will retain them,
+//        order to resolve missing packages, then updatePrunedRoots will retain them,
 //        the selected versions of those roots cannot regress, and they will
 //        eventually be written back to the main module's go.mod file.
 //
 // (See https://golang.org/design/36460-lazy-module-loading#invariants for more
 // detail.)
-func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
+func updatePrunedRoots(ctx context.Context, direct map[string]bool, rs *Requirements, pkgs []*loadPkg, add []module.Version, rootsImported bool) (*Requirements, error) {
        roots := rs.rootModules
        rootsUpgraded := false
 
@@ -732,11 +740,11 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
                        // pkg is transitively imported by a package or test in the main module.
                        // We need to promote the module that maintains it to a root: if some
                        // other module depends on the main module, and that other module also
-                       // uses lazy loading, it will expect to find all of our transitive
-                       // dependencies by reading just our go.mod file, not the go.mod files of
-                       // everything we depend on.
+                       // uses a pruned module graph, it will expect to find all of our
+                       // transitive dependencies by reading just our go.mod file, not the go.mod
+                       // files of everything we depend on.
                        //
-                       // (This is the “import invariant” that makes lazy loading possible.)
+                       // (This is the “import invariant” that makes graph pruning possible.)
 
                case rootsImported && pkg.flags.has(pkgFromRoot):
                        // pkg is a transitive dependency of some root, and we are treating the
@@ -747,17 +755,18 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
                        // it matches a command-line argument.) We want future invocations of the
                        // 'go' command — such as 'go test' on the same package — to continue to
                        // use the same versions of its dependencies that we are using right now.
-                       // So we need to bring this package's dependencies inside the lazy-loading
-                       // horizon.
+                       // So we need to bring this package's dependencies inside the pruned
+                       // module graph.
                        //
                        // Making the module containing this package a root of the module graph
-                       // does exactly that: if the module containing the package is lazy it
-                       // should satisfy the import invariant itself, so all of its dependencies
-                       // should be in its go.mod file, and if the module containing the package
-                       // is eager then if we make it a root we will load all of its transitive
-                       // dependencies into the module graph.
+                       // does exactly that: if the module containing the package supports graph
+                       // pruning then it should satisfy the import invariant itself, so all of
+                       // its dependencies should be in its go.mod file, and if the module
+                       // containing the package does not support pruning then if we make it a
+                       // root we will load all of its (unpruned) transitive dependencies into
+                       // the module graph.
                        //
-                       // (This is the “argument invariant” of lazy loading, and is important for
+                       // (This is the “argument invariant”, and is important for
                        // reproducibility.)
 
                default:
@@ -824,14 +833,13 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
                        // requirements.
                        if mustHaveCompleteRequirements() {
                                // Our changes to the roots may have moved dependencies into or out of
-                               // the lazy-loading horizon, which could in turn change the selected
-                               // versions of other modules. (Unlike for eager modules, for lazy
-                               // modules adding or removing an explicit root is a semantic change, not
-                               // just a cosmetic one.)
+                               // the graph-pruning horizon, which could in turn change the selected
+                               // versions of other modules. (For pruned modules adding or removing an
+                               // explicit root is a semantic change, not just a cosmetic one.)
                                return rs, errGoModDirty
                        }
 
-                       rs = newRequirements(lazy, roots, direct)
+                       rs = newRequirements(pruned, roots, direct)
                        var err error
                        mg, err = rs.Graph(ctx)
                        if err != nil {
@@ -846,7 +854,7 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
                        if rs.graph.Load() != nil {
                                // We've already loaded the full module graph, which includes the
                                // requirements of all of the root modules — even the transitive
-                               // requirements, if they are eager!
+                               // requirements, if they are unpruned!
                                mg, _ = rs.Graph(ctx)
                        } else if cfg.BuildMod == "vendor" {
                                // We can't spot-check the requirements of other modules because we
@@ -925,12 +933,12 @@ func updateLazyRoots(ctx context.Context, direct map[string]bool, rs *Requiremen
                }
        }
 
-       if rs.depth == lazy && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
-               // The root set is unchanged and rs was already lazy, so keep rs to
+       if rs.pruning == pruned && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
+               // The root set is unchanged and rs was already pruned, so keep rs to
                // preserve its cached ModuleGraph (if any).
                return rs, nil
        }
-       return newRequirements(lazy, roots, direct), nil
+       return newRequirements(pruned, roots, direct), nil
 }
 
 // spotCheckRoots reports whether the versions of the roots in rs satisfy the
@@ -972,10 +980,10 @@ func spotCheckRoots(ctx context.Context, rs *Requirements, mods map[module.Versi
        return true
 }
 
-// tidyEagerRoots returns a minimal set of root requirements that maintains the
+// tidyUnprunedRoots returns a minimal set of root requirements that maintains the
 // selected version of every module that provided a package in pkgs, and
 // includes the selected version of every such module in direct as a root.
-func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
+func tidyUnprunedRoots(ctx context.Context, mainModule module.Version, direct map[string]bool, pkgs []*loadPkg) (*Requirements, error) {
        var (
                keep     []module.Version
                keptPath = map[string]bool{}
@@ -1002,10 +1010,10 @@ func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[s
        if err != nil {
                return nil, err
        }
-       return newRequirements(eager, min, direct), nil
+       return newRequirements(unpruned, min, direct), nil
 }
 
-// updateEagerRoots returns a set of root requirements that includes the selected
+// updateUnprunedRoots returns a set of root requirements that includes the selected
 // version of every module path in direct as a root, and maintains the selected
 // version of every module selected in the graph of rs.
 //
@@ -1019,7 +1027,7 @@ func tidyEagerRoots(ctx context.Context, mainModule module.Version, direct map[s
 //        by a dependency in add.
 //     4. Every version in add is selected at its given version unless upgraded by
 //        (the dependencies of) an existing root or another module in add.
-func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) {
+func updateUnprunedRoots(ctx context.Context, direct map[string]bool, rs *Requirements, add []module.Version) (*Requirements, error) {
        mg, err := rs.Graph(ctx)
        if err != nil {
                // We can't ignore errors in the module graph even if the user passed the -e
@@ -1084,7 +1092,7 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
 
        // “The selected version of every module path in direct is included as a root.”
        //
-       // This is only for convenience and clarity for end users: in an eager module,
+       // This is only for convenience and clarity for end users: in an unpruned module,
        // the choice of explicit vs. implicit dependency has no impact on MVS
        // selection (for itself or any other module).
        keep := append(mg.BuildList()[MainModules.Len():], add...)
@@ -1107,41 +1115,40 @@ func updateEagerRoots(ctx context.Context, direct map[string]bool, rs *Requireme
        if MainModules.Len() > 1 {
                module.Sort(roots)
        }
-       if rs.depth == eager && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
-               // The root set is unchanged and rs was already eager, so keep rs to
+       if rs.pruning == unpruned && reflect.DeepEqual(roots, rs.rootModules) && reflect.DeepEqual(direct, rs.direct) {
+               // The root set is unchanged and rs was already unpruned, so keep rs to
                // preserve its cached ModuleGraph (if any).
                return rs, nil
        }
 
-       return newRequirements(eager, roots, direct), nil
+       return newRequirements(unpruned, roots, direct), nil
 }
 
-// convertDepth returns a version of rs with the given depth.
-// If rs already has the given depth, convertDepth returns rs unmodified.
-func convertDepth(ctx context.Context, rs *Requirements, depth modDepth) (*Requirements, error) {
-       if rs.depth == depth {
+// convertPruning returns a version of rs with the given pruning behavior.
+// If rs already has the given pruning, convertPruning returns rs unmodified.
+func convertPruning(ctx context.Context, rs *Requirements, pruning modPruning) (*Requirements, error) {
+       if rs.pruning == pruning {
                return rs, nil
        }
 
-       if depth == eager {
-               // We are converting a lazy module to an eager one. The roots of an eager
-               // module graph are a superset of the roots of a lazy graph, so we don't
-               // need to add any new roots — we just need to prune away the ones that are
-               // redundant given eager loading, which is exactly what updateEagerRoots
-               // does.
-               return updateEagerRoots(ctx, rs.direct, rs, nil)
+       if pruning == unpruned {
+               // We are converting a pruned module to an unpruned one. The roots of a
+               // ppruned module graph are a superset of the roots of an unpruned one, so
+               // we don't need to add any new roots — we just need to drop the ones that
+               // are redundant, which is exactly what updateUnprunedRoots does.
+               return updateUnprunedRoots(ctx, rs.direct, rs, nil)
        }
 
-       // We are converting an eager module to a lazy one. The module graph of an
-       // eager module includes the transitive dependencies of every module in the
-       // build list.
+       // We are converting an unpruned module to a pruned one.
        //
-       // Hey, we can express that as a lazy root set! “Include the transitive
-       // dependencies of every module in the build list” is exactly what happens in
-       // a lazy module if we promote every module in the build list to a root!
+       // An unpruned module graph includes the transitive dependencies of every
+       // module in the build list. As it turns out, we can express that as a pruned
+       // root set! “Include the transitive dependencies of every module in the build
+       // list” is exactly what happens in a pruned module if we promote every module
+       // in the build list to a root.
        mg, err := rs.Graph(ctx)
        if err != nil {
                return rs, err
        }
-       return newRequirements(lazy, mg.BuildList()[MainModules.Len():], rs.direct), nil
+       return newRequirements(pruned, mg.BuildList()[MainModules.Len():], rs.direct), nil
 }
index 12a23468afcf69c5a84756e363952ee7e8eeacb8..b99ac1242b4cc169a9dd933d012c8ecbeed83c70 100644 (file)
@@ -21,7 +21,7 @@ import (
 //     2. Each module version in tryUpgrade is upgraded toward the indicated
 //        version as far as can be done without violating (1).
 //
-//     3. Each module version in rs.rootModules (or rs.graph, if rs.depth is eager)
+//     3. Each module version in rs.rootModules (or rs.graph, if rs is unpruned)
 //        is downgraded from its original version only to the extent needed to
 //        satisfy (1), or upgraded only to the extent needed to satisfy (1) and
 //        (2).
@@ -69,10 +69,11 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
        }
 
        var roots []module.Version
-       if rs.depth == eager {
-               // In an eager module, modules that provide packages imported by the main
-               // module may either be explicit roots or implicit transitive dependencies.
-               // We promote the modules in mustSelect to be explicit requirements.
+       if rs.pruning == unpruned {
+               // In a module without graph pruning, modules that provide packages imported
+               // by the main module may either be explicit roots or implicit transitive
+               // dependencies. We promote the modules in mustSelect to be explicit
+               // requirements.
                var rootPaths []string
                for _, m := range mustSelect {
                        if !MainModules.Contains(m.Path) && m.Version != "none" {
@@ -102,8 +103,8 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
                        return nil, false, err
                }
        } else {
-               // In a lazy module, every module that provides a package imported by the
-               // main module must be retained as a root.
+               // In a module with a pruned graph, every module that provides a package
+               // imported by the main module must be retained as a root.
                roots = mods
                if !changed {
                        // Because the roots we just computed are unchanged, the entire graph must
@@ -126,7 +127,7 @@ func editRequirements(ctx context.Context, rs *Requirements, tryUpgrade, mustSel
                        direct[m.Path] = true
                }
        }
-       return newRequirements(rs.depth, roots, direct), changed, nil
+       return newRequirements(rs.pruning, roots, direct), changed, nil
 }
 
 // limiterForEdit returns a versionLimiter with its max versions set such that
@@ -149,11 +150,12 @@ func limiterForEdit(ctx context.Context, rs *Requirements, tryUpgrade, mustSelec
                }
        }
 
-       if rs.depth == eager {
-               // Eager go.mod files don't indicate which transitive dependencies are
-               // actually relevant to the main module, so we have to assume that any module
-               // that could have provided any package — that is, any module whose selected
-               // version was not "none" — may be relevant.
+       if rs.pruning == unpruned {
+               // go.mod files that do not support graph pruning don't indicate which
+               // transitive dependencies are actually relevant to the main module, so we
+               // have to assume that any module that could have provided any package —
+               // that is, any module whose selected version was not "none" — may be
+               // relevant.
                for _, m := range mg.BuildList() {
                        restrictTo(m)
                }
@@ -175,7 +177,7 @@ func limiterForEdit(ctx context.Context, rs *Requirements, tryUpgrade, mustSelec
                }
        }
 
-       if err := raiseLimitsForUpgrades(ctx, maxVersion, rs.depth, tryUpgrade, mustSelect); err != nil {
+       if err := raiseLimitsForUpgrades(ctx, maxVersion, rs.pruning, tryUpgrade, mustSelect); err != nil {
                return nil, err
        }
 
@@ -185,7 +187,7 @@ func limiterForEdit(ctx context.Context, rs *Requirements, tryUpgrade, mustSelec
                restrictTo(m)
        }
 
-       return newVersionLimiter(rs.depth, maxVersion), nil
+       return newVersionLimiter(rs.pruning, maxVersion), nil
 }
 
 // raiseLimitsForUpgrades increases the module versions in maxVersions to the
@@ -195,12 +197,12 @@ func limiterForEdit(ctx context.Context, rs *Requirements, tryUpgrade, mustSelec
 //
 // Versions not present in maxVersion are unrestricted, and it is assumed that
 // they will not be promoted to root requirements (and thus will not contribute
-// their own dependencies if the main module is lazy).
+// their own dependencies if the main module supports graph pruning).
 //
 // These limits provide an upper bound on how far a module may be upgraded as
 // part of an incidental downgrade, if downgrades are needed in order to select
 // the versions in mustSelect.
-func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, depth modDepth, tryUpgrade []module.Version, mustSelect []module.Version) error {
+func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, pruning modPruning, tryUpgrade []module.Version, mustSelect []module.Version) error {
        // allow raises the limit for m.Path to at least m.Version.
        // If m.Path was already unrestricted, it remains unrestricted.
        allow := func(m module.Version) {
@@ -213,9 +215,9 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
                }
        }
 
-       var eagerUpgrades []module.Version
-       if depth == eager {
-               eagerUpgrades = tryUpgrade
+       var unprunedUpgrades []module.Version
+       if pruning == unpruned {
+               unprunedUpgrades = tryUpgrade
        } else {
                for _, m := range tryUpgrade {
                        if MainModules.Contains(m.Path) {
@@ -229,11 +231,11 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
                        if err != nil {
                                return err
                        }
-                       if summary.depth == eager {
-                               // For efficiency, we'll load all of the eager upgrades as one big
+                       if summary.pruning == unpruned {
+                               // For efficiency, we'll load all of the unpruned upgrades as one big
                                // graph, rather than loading the (potentially-overlapping) subgraph for
                                // each upgrade individually.
-                               eagerUpgrades = append(eagerUpgrades, m)
+                               unprunedUpgrades = append(unprunedUpgrades, m)
                                continue
                        }
 
@@ -244,14 +246,14 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
                }
        }
 
-       if len(eagerUpgrades) > 0 {
-               // Compute the max versions for eager upgrades all together.
-               // Since these modules are eager, we'll end up scanning all of their
+       if len(unprunedUpgrades) > 0 {
+               // Compute the max versions for unpruned upgrades all together.
+               // Since these modules are unpruned, we'll end up scanning all of their
                // transitive dependencies no matter which versions end up selected,
                // and since we have a large dependency graph to scan we might get
                // a significant benefit from not revisiting dependencies that are at
                // common versions among multiple upgrades.
-               upgradeGraph, err := readModGraph(ctx, eager, eagerUpgrades)
+               upgradeGraph, err := readModGraph(ctx, unpruned, unprunedUpgrades)
                if err != nil {
                        // Compute the requirement path from a module path in tryUpgrade to the
                        // error, and the requirement path (if any) from rs.rootModules to the
@@ -268,7 +270,7 @@ func raiseLimitsForUpgrades(ctx context.Context, maxVersion map[string]string, d
        }
 
        if len(mustSelect) > 0 {
-               mustGraph, err := readModGraph(ctx, depth, mustSelect)
+               mustGraph, err := readModGraph(ctx, pruning, mustSelect)
                if err != nil {
                        return err
                }
@@ -300,7 +302,7 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
        }
 
        var initial []module.Version
-       if rs.depth == eager {
+       if rs.pruning == unpruned {
                mg, err := rs.Graph(ctx)
                if err != nil {
                        return nil, false, err
@@ -327,7 +329,7 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
        // downgraded module may require a higher (but still allowed) version of
        // another. The lower version may require extraneous dependencies that aren't
        // actually relevant, so we need to compute the actual selected versions.
-       mg, err := readModGraph(ctx, rs.depth, mods)
+       mg, err := readModGraph(ctx, rs.pruning, mods)
        if err != nil {
                return nil, false, err
        }
@@ -349,16 +351,16 @@ func selectPotentiallyImportedModules(ctx context.Context, limiter *versionLimit
 // A versionLimiter tracks the versions that may be selected for each module
 // subject to constraints on the maximum versions of transitive dependencies.
 type versionLimiter struct {
-       // depth is the depth at which the dependencies of the modules passed to
+       // pruning is the pruning at which the dependencies of the modules passed to
        // Select and UpgradeToward are loaded.
-       depth modDepth
+       pruning modPruning
 
        // max maps each module path to the maximum version that may be selected for
        // that path.
        //
        // Paths with no entry are unrestricted, and we assume that they will not be
        // promoted to root dependencies (so will not contribute dependencies if the
-       // main module is lazy).
+       // main module supports graph pruning).
        max map[string]string
 
        // selected maps each module path to a version of that path (if known) whose
@@ -410,16 +412,16 @@ func (dq dqState) isDisqualified() bool {
 // in the map are unrestricted. The limiter assumes that unrestricted paths will
 // not be promoted to root dependencies.
 //
-// If depth is lazy, then if a module passed to UpgradeToward or Select is
-// itself lazy, its unrestricted dependencies are skipped when scanning
-// requirements.
-func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter {
+// If module graph pruning is in effect, then if a module passed to
+// UpgradeToward or Select supports pruning, its unrestricted dependencies are
+// skipped when scanning requirements.
+func newVersionLimiter(pruning modPruning, max map[string]string) *versionLimiter {
        selected := make(map[string]string)
        for _, m := range MainModules.Versions() {
                selected[m.Path] = m.Version
        }
        return &versionLimiter{
-               depth:     depth,
+               pruning:   pruning,
                max:       max,
                selected:  selected,
                dqReason:  map[module.Version]dqState{},
@@ -430,8 +432,8 @@ func newVersionLimiter(depth modDepth, max map[string]string) *versionLimiter {
 // UpgradeToward attempts to upgrade the selected version of m.Path as close as
 // possible to m.Version without violating l's maximum version limits.
 //
-// If depth is lazy and m itself is lazy, the the dependencies of unrestricted
-// dependencies of m will not be followed.
+// If module graph pruning is in effect and m itself supports pruning, the
+// dependencies of unrestricted dependencies of m will not be followed.
 func (l *versionLimiter) UpgradeToward(ctx context.Context, m module.Version) error {
        selected, ok := l.selected[m.Path]
        if ok {
@@ -443,7 +445,7 @@ func (l *versionLimiter) UpgradeToward(ctx context.Context, m module.Version) er
                selected = "none"
        }
 
-       if l.check(m, l.depth).isDisqualified() {
+       if l.check(m, l.pruning).isDisqualified() {
                candidates, err := versions(ctx, m.Path, CheckAllowed)
                if err != nil {
                        // This is likely a transient error reaching the repository,
@@ -460,7 +462,7 @@ func (l *versionLimiter) UpgradeToward(ctx context.Context, m module.Version) er
                })
                candidates = candidates[:i]
 
-               for l.check(m, l.depth).isDisqualified() {
+               for l.check(m, l.pruning).isDisqualified() {
                        n := len(candidates)
                        if n == 0 || cmpVersion(selected, candidates[n-1]) >= 0 {
                                // We couldn't find a suitable candidate above the already-selected version.
@@ -477,7 +479,7 @@ func (l *versionLimiter) UpgradeToward(ctx context.Context, m module.Version) er
 
 // Select attempts to set the selected version of m.Path to exactly m.Version.
 func (l *versionLimiter) Select(m module.Version) (conflict module.Version, err error) {
-       dq := l.check(m, l.depth)
+       dq := l.check(m, l.pruning)
        if !dq.isDisqualified() {
                l.selected[m.Path] = m.Version
        }
@@ -487,14 +489,14 @@ func (l *versionLimiter) Select(m module.Version) (conflict module.Version, err
 // check determines whether m (or its transitive dependencies) would violate l's
 // maximum version limits if added to the module requirement graph.
 //
-// If depth is lazy and m itself is lazy, then the dependencies of unrestricted
-// dependencies of m will not be followed. If the lazy loading invariants hold
-// for the main module up to this point, the packages in those modules are at
-// best only imported by tests of dependencies that are themselves loaded from
-// outside modules. Although we would like to keep 'go test all' as reproducible
-// as is feasible, we don't want to retain test dependencies that are only
-// marginally relevant at best.
-func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
+// If pruning is in effect and m itself supports graph pruning, the dependencies
+// of unrestricted dependencies of m will not be followed. If the graph-pruning
+// invariants hold for the main module up to this point, the packages in those
+// modules are at best only imported by tests of dependencies that are
+// themselves loaded from outside modules. Although we would like to keep
+// 'go test all' as reproducible as is feasible, we don't want to retain test
+// dependencies that are only marginally relevant at best.
+func (l *versionLimiter) check(m module.Version, pruning modPruning) dqState {
        if m.Version == "none" || m == MainModules.mustGetSingleMainModule() {
                // version "none" has no requirements, and the dependencies of Target are
                // tautological.
@@ -525,20 +527,20 @@ func (l *versionLimiter) check(m module.Version, depth modDepth) dqState {
                return l.disqualify(m, dqState{err: err})
        }
 
-       if summary.depth == eager {
-               depth = eager
+       if summary.pruning == unpruned {
+               pruning = unpruned
        }
        for _, r := range summary.require {
-               if depth == lazy {
+               if pruning == pruned {
                        if _, restricted := l.max[r.Path]; !restricted {
                                // r.Path is unrestricted, so we don't care at what version it is
                                // selected. We assume that r.Path will not become a root dependency, so
-                               // since m is lazy, r's dependencies won't be followed.
+                               // since m supports pruning, r's dependencies won't be followed.
                                continue
                        }
                }
 
-               if dq := l.check(r, depth); dq.isDisqualified() {
+               if dq := l.check(r, pruning); dq.isDisqualified() {
                        return l.disqualify(m, dq)
                }
 
index 98145887e9dcc5c6b18aa071f08c27ce7c4681ca..11310489addbecccf79c848ee4933635957b58cb 100644 (file)
@@ -69,7 +69,7 @@ func TestQueryImport(t *testing.T) {
        RootMode = NoRoot
 
        ctx := context.Background()
-       rs := newRequirements(eager, nil, nil)
+       rs := newRequirements(unpruned, nil, nil)
 
        for _, tt := range importTests {
                t.Run(strings.ReplaceAll(tt.path, "/", "_"), func(t *testing.T) {
index 0843e1ad4d9349deebb00f43d0f78586d74fa147..bc155c7310f2b33d5427659fdfa9f47041845233 100644 (file)
@@ -625,7 +625,7 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
                MainModules = makeMainModules([]module.Version{mainModule}, []string{""}, []*modfile.File{nil}, []*modFileIndex{nil}, "")
                goVersion := LatestGoVersion()
                rawGoVersion.Store(mainModule, goVersion)
-               requirements = newRequirements(modDepthFromGoVersion(goVersion), nil, nil)
+               requirements = newRequirements(pruningForGoVersion(goVersion), nil, nil)
                return requirements, false
        }
 
@@ -712,11 +712,11 @@ func loadModFile(ctx context.Context) (rs *Requirements, needCommit bool) {
 
                        // We need to add a 'go' version to the go.mod file, but we must assume
                        // that its existing contents match something between Go 1.11 and 1.16.
-                       // Go 1.11 through 1.16 have eager requirements, but the latest Go
-                       // version uses lazy requirements instead — so we need to convert the
-                       // requirements to be lazy.
+                       // Go 1.11 through 1.16 do not support graph pruning, but the latest Go
+                       // version uses a pruned module graph — so we need to convert the
+                       // requirements to support pruning.
                        var err error
-                       rs, err = convertDepth(ctx, rs, lazy)
+                       rs, err = convertPruning(ctx, rs, pruned)
                        if err != nil {
                                base.Fatalf("go: %v", err)
                        }
@@ -978,7 +978,7 @@ func requirementsFromModFiles(ctx context.Context, modFiles []*modfile.File) *Re
                }
        }
        module.Sort(roots)
-       rs := newRequirements(modDepthFromGoVersion(MainModules.GoVersion()), roots, direct)
+       rs := newRequirements(pruningForGoVersion(MainModules.GoVersion()), roots, direct)
        return rs
 }
 
@@ -1485,12 +1485,13 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
                                continue
                        }
 
-                       if rs.depth == lazy && pkg.mod.Path != "" {
+                       if rs.pruning == pruned && pkg.mod.Path != "" {
                                if v, ok := rs.rootSelected(pkg.mod.Path); ok && v == pkg.mod.Version {
-                                       // pkg was loaded from a root module, and because the main module is
-                                       // lazy we do not check non-root modules for conflicts for packages
-                                       // that can be found in roots. So we only need the checksums for the
-                                       // root modules that may contain pkg, not all possible modules.
+                                       // pkg was loaded from a root module, and because the main module has
+                                       // a pruned module graph we do not check non-root modules for
+                                       // conflicts for packages that can be found in roots. So we only need
+                                       // the checksums for the root modules that may contain pkg, not all
+                                       // possible modules.
                                        for prefix := pkg.path; prefix != "."; prefix = path.Dir(prefix) {
                                                if v, ok := rs.rootSelected(prefix); ok && v != "none" {
                                                        m := module.Version{Path: prefix, Version: v}
@@ -1514,8 +1515,7 @@ func keepSums(ctx context.Context, ld *loader, rs *Requirements, which whichSums
        }
 
        if rs.graph.Load() == nil {
-               // The module graph was not loaded, possibly because the main module is lazy
-               // or possibly because we haven't needed to load the graph yet.
+               // We haven't needed to load the module graph so far.
                // Save sums for the root modules (or their replacements), but don't
                // incur the cost of loading the graph just to find and retain the sums.
                for _, m := range rs.rootModules {
index 9c5018f340bbf4067c2fe2056dc86eee91a401ba..ac10a42c5a23c64c9f382ce9201027ef5c9ac520 100644 (file)
@@ -105,7 +105,7 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
                        path := arg[:i]
                        vers := arg[i+1:]
                        if vers == "upgrade" || vers == "patch" {
-                               if _, ok := rs.rootSelected(path); !ok || rs.depth == eager {
+                               if _, ok := rs.rootSelected(path); !ok || rs.pruning == unpruned {
                                        needFullGraph = true
                                        if !HasModRoot() {
                                                base.Fatalf("go: cannot match %q: %v", arg, ErrNoModRoot)
@@ -114,7 +114,7 @@ func listModules(ctx context.Context, rs *Requirements, args []string, mode List
                        }
                        continue
                }
-               if _, ok := rs.rootSelected(arg); !ok || rs.depth == eager {
+               if _, ok := rs.rootSelected(arg); !ok || rs.pruning == unpruned {
                        needFullGraph = true
                        if mode&ListVersions == 0 && !HasModRoot() {
                                base.Fatalf("go: cannot match %q without -versions or an explicit version: %v", arg, ErrNoModRoot)
index efe6ad1319cb84f6211104fe52737f0ba47056ce..20c007a03a11b716327f6394f20d2240c51a3b2d 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.)
 //
@@ -367,7 +368,7 @@ func LoadPackages(ctx context.Context, opts PackageOpts, patterns ...string) (ma
 
                        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,7 +387,7 @@ 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 {
+               if compatDepth := pruningForGoVersion(ld.TidyCompatibleVersion); compatDepth != ld.requirements.pruning {
                        compatRS := newRequirements(compatDepth, ld.requirements.rootModules, ld.requirements.direct)
                        ld.checkTidyCompatibility(ctx, compatRS)
 
@@ -622,7 +623,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.
@@ -635,9 +636,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.
@@ -986,18 +987,26 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
        }
 
        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.
+               // The module's go version explicitly predates the change in "all" for graph
+               // pruning, so continue to use the older interpretation.
                ld.allClosesOverTests = true
        }
 
        var err error
-       ld.requirements, err = convertDepth(ctx, ld.requirements, modDepthFromGoVersion(ld.GoVersion))
+       ld.requirements, err = convertPruning(ctx, ld.requirements, pruningForGoVersion(ld.GoVersion))
        if err != nil {
                ld.errorf("go: %v\n", err)
        }
 
-       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 {
@@ -1014,7 +1023,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
@@ -1125,11 +1134,11 @@ func loadFromRoots(ctx context.Context, params loaderParams) *loader {
                        ld.errorf("go: %v\n", err)
                }
 
-               if ld.requirements.depth == lazy {
+               if ld.requirements.pruning == pruned {
                        // 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.
+                       // 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 v, ok := ld.requirements.rootSelected(m.Path); !ok || v != m.Version {
@@ -1257,14 +1266,14 @@ func (ld *loader) updateRequirements(ctx context.Context) (changed bool, err err
 
        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
@@ -1571,7 +1580,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
@@ -1633,7 +1643,7 @@ func (ld *loader) load(ctx context.Context, pkg *loadPkg) {
        }
 
        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 {
@@ -1961,9 +1971,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
@@ -2001,18 +2012,18 @@ 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.
index d2b13fb89f2a9d493a4cb6ac7ca796cd380c07c8..79ac1227caf4856fc29ffd9f07b8b3879ffa5796 100644 (file)
@@ -33,10 +33,13 @@ const (
        // tests outside of the main module.
        narrowAllVersionV = "v1.16"
 
-       // lazyLoadingVersionV is the Go version (plus leading "v") at which a
+       // explicitIndirectVersionV is the Go version (plus leading "v") at which a
        // module's go.mod file is expected to list explicit requirements on every
        // module that provides any package transitively imported by that module.
-       lazyLoadingVersionV = "v1.17"
+       //
+       // Other indirect dependencies of such a module can be safely pruned out of
+       // the module graph; see https://golang.org/ref/mod#graph-pruning.
+       explicitIndirectVersionV = "v1.17"
 
        // separateIndirectVersionV is the Go version (plus leading "v") at which
        // "// indirect" dependencies are added in a block separate from the direct
@@ -57,9 +60,9 @@ func modFileGoVersion(modFile *modfile.File) string {
                // has been erroneously hand-edited.
                //
                // The semantics of the go.mod file are more-or-less the same from Go 1.11
-               // through Go 1.16, changing at 1.17 for lazy loading. So even though a
-               // go.mod file without a 'go' directive is theoretically a Go 1.11 file,
-               // scripts may assume that it ends up as a Go 1.16 module.
+               // through Go 1.16, changing at 1.17 to support module graph pruning.
+               // So even though a go.mod file without a 'go' directive is theoretically a
+               // Go 1.11 file, scripts may assume that it ends up as a Go 1.16 module.
                return "1.16"
        }
        return modFile.Go.Version
@@ -82,19 +85,23 @@ type requireMeta struct {
        indirect bool
 }
 
-// A modDepth indicates which dependencies should be loaded for a go.mod file.
-type modDepth uint8
+// A modPruning indicates whether transitive dependencies of Go 1.17 dependencies
+// are pruned out of the module subgraph rooted at a given module.
+// (See https://golang.org/ref/mod#graph-pruning.)
+type modPruning uint8
 
 const (
-       lazy  modDepth = iota // load dependencies only as needed
-       eager                 // load all transitive dependencies eagerly
+       pruned   modPruning = iota // transitive dependencies of modules at go 1.17 and higher are pruned out
+       unpruned                   // no transitive dependencies are pruned out
 )
 
-func modDepthFromGoVersion(goVersion string) modDepth {
-       if semver.Compare("v"+goVersion, lazyLoadingVersionV) < 0 {
-               return eager
+func pruningForGoVersion(goVersion string) modPruning {
+       if semver.Compare("v"+goVersion, explicitIndirectVersionV) < 0 {
+               // The go.mod file does not duplicate relevant information about transitive
+               // dependencies, so they cannot be pruned out.
+               return unpruned
        }
-       return lazy
+       return pruned
 }
 
 // CheckAllowed returns an error equivalent to ErrDisallowed if m is excluded by
@@ -468,7 +475,7 @@ var rawGoVersion sync.Map // map[module.Version]string
 type modFileSummary struct {
        module     module.Version
        goVersion  string
-       depth      modDepth
+       pruning    modPruning
        require    []module.Version
        retract    []retraction
        deprecated string
@@ -620,9 +627,9 @@ func rawGoModSummary(m module.Version, replacedFrom string) (*modFileSummary, er
                if f.Go != nil && f.Go.Version != "" {
                        rawGoVersion.LoadOrStore(m, f.Go.Version)
                        summary.goVersion = f.Go.Version
-                       summary.depth = modDepthFromGoVersion(f.Go.Version)
+                       summary.pruning = pruningForGoVersion(f.Go.Version)
                } else {
-                       summary.depth = eager
+                       summary.pruning = unpruned
                }
                if len(f.Require) > 0 {
                        summary.require = make([]module.Version, 0, len(f.Require))