Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/tools/gopls: "renaming [method] is not possible because [package] has errors" #41870

Open
bcmills opened this issue Oct 8, 2020 · 4 comments
Labels
FeatureRequest gopls Issues related to the Go language server, gopls. help wanted Refactoring Issues related to refactoring tools Tools This label describes issues relating to any tools in the x/tools repository.

Comments

@bcmills
Copy link
Contributor

bcmills commented Oct 8, 2020

What version are you using?

$ go version -m $(which gopls)
/usr/local/google/home/bcmills/bin/gopls: devel +186f0220d0 Mon Oct 5 11:12:24 2020 -0400
	path	golang.org/x/tools/gopls
	mod	golang.org/x/tools/gopls	v0.5.1	h1:AF3Uh7HF08SZpKFfgJO6zfF3bbxyDXWqdkK4kMXiQ1o=
	dep	github.com/BurntSushi/toml	v0.3.1	h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
	dep	github.com/google/go-cmp	v0.5.1	h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
	dep	github.com/sergi/go-diff	v1.1.0	h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
	dep	golang.org/x/mod	v0.3.0	h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
	dep	golang.org/x/sync	v0.0.0-20200625203802-6e8e738ad208	h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
	dep	golang.org/x/tools	v0.0.0-20200930165741-f1523d29dbb9	h1:1R38tQp22dcHpTKJPjgVa16FhlDy/kHEaCM/ndi/FIc=
	dep	golang.org/x/xerrors	v0.0.0-20200804184101-5ec99f83aff1	h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
	dep	honnef.co/go/tools	v0.0.1-2020.1.5	h1:nI5egYTGJakVyOryqLs1cQO5dO0ksin5XXs2pspk75k=
	dep	mvdan.cc/gofumpt	v0.0.0-20200802201014-ab5a8192947d	h1:t8TAw9WgTLghti7RYkpPmqk4JtQ3+wcP5GgZqgWeWLQ=
	dep	mvdan.cc/xurls/v2	v2.2.0	h1:NSZPykBXJFCetGZykLAxaL6SIpvbVy/UFEniIfHAa8A=

with GNU Emacs 27.1 and eglot 20200830.1254.

Does this issue reproduce with the latest release?

Yes

What did you do?

Edit $GOROOT/src/cmd/go/modget/get.go to the following, which I believe is parseable but certainly includes many errors (including type errors).

// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package modget implements the module-aware ``go get'' command.
package modget

// The arguments to 'go get' are patterns with optional version queries, with
// the version queries defaulting to "upgrade".
//
// The patterns are normally interpreted as package patterns. However, if a
// pattern does not match any package, it is instead interpreted as a *module*
// pattern. For version queries such as "upgrade" and "patch" that depend on the
// selected version of a module (or of the module containing a package), the
// meaning of a pattern may shift between package and module as updates are
// applied (see the example in mod_get_patchmod.txt).
//
// There are a few other ambiguous cases to resolve, too. A package can exist in
// two different modules at the same version: for example, the package
// example.com/foo might be found in module example.com and also in module
// example.com/foo, and those modules may have independent v0.1.0 tags — so the
// input 'example.com/foo@v0.1.0' could syntactically refer to the variant of
// the package loaded from either module! (See mod_get_ambiguous_pkg.txt.)
// If the argument is ambiguous, the user can often disambiguate by specifying
// explicit versions for *all* of the potential module paths involved.

import (
	"context"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"sync"

	"cmd/go/internal/base"
	"cmd/go/internal/cfg"
	"cmd/go/internal/imports"
	"cmd/go/internal/load"
	"cmd/go/internal/modload"
	"cmd/go/internal/mvs"
	"cmd/go/internal/search"
	"cmd/go/internal/work"

	"golang.org/x/mod/module"
	"golang.org/x/mod/semver"
)

var CmdGet = &base.Command{
	// Note: -d -u are listed explicitly because they are the most common get flags.
	// Do not send CLs removing them because they're covered by [get flags].
	UsageLine: "go get [-d] [-t] [-u] [-v] [-insecure] [build flags] [packages]",
	Short:     "add dependencies to current module and install them",
	Long: `
Get resolves and adds dependencies to the current development module
and then builds and installs them.

The first step is to resolve which dependencies to add.

For each named package or package pattern, get must decide which version of
the corresponding module to use. By default, get looks up the latest tagged
release version, such as v0.4.5 or v1.2.3. If there are no tagged release
versions, get looks up the latest tagged pre-release version, such as
v0.0.1-pre1. If there are no tagged versions at all, get looks up the latest
known commit. If the module is not already required at a later version
(for example, a pre-release newer than the latest release), get will use
the version it looked up. Otherwise, get will use the currently
required version.

This default version selection can be overridden by adding an @version
suffix to the package argument, as in 'go get golang.org/x/text@v0.3.0'.
The version may be a prefix: @v1 denotes the latest available version starting
with v1. See 'go help modules' under the heading 'Module queries' for the
full query syntax.

For modules stored in source control repositories, the version suffix can
also be a commit hash, branch identifier, or other syntax known to the
source control system, as in 'go get golang.org/x/text@master'. Note that
branches with names that overlap with other module query syntax cannot be
selected explicitly. For example, the suffix @v2 means the latest version
starting with v2, not the branch named v2.

If a module under consideration is already a dependency of the current
development module, then get will update the required version.
Specifying a version earlier than the current required version is valid and
downgrades the dependency. The version suffix @none indicates that the
dependency should be removed entirely, downgrading or removing modules
depending on it as needed.

The version suffix @latest explicitly requests the latest minor release of the
module named by the given path. The suffix @upgrade is like @latest but
will not downgrade a module if it is already required at a revision or
pre-release version newer than the latest released version. The suffix
@patch requests the latest patch release: the latest released version
with the same major and minor version numbers as the currently required
version. Like @upgrade, @patch will not downgrade a module already required
at a newer version. If the path is not already required, @upgrade and @patch
are equivalent to @latest.

Although get defaults to using the latest version of the module containing
a named package, it does not use the latest version of that module's
dependencies. Instead it prefers to use the specific dependency versions
requested by that module. For example, if the latest A requires module
B v1.2.3, while B v1.2.4 and v1.3.1 are also available, then 'go get A'
will use the latest A but then use B v1.2.3, as requested by A. (If there
are competing requirements for a particular module, then 'go get' resolves
those requirements by taking the maximum requested version.)

The -t flag instructs get to consider modules needed to build tests of
packages specified on the command line.

The -u flag instructs get to update modules providing dependencies
of packages named on the command line to use newer minor or patch
releases when available. Continuing the previous example, 'go get -u A'
will use the latest A with B v1.3.1 (not B v1.2.3). If B requires module C,
but C does not provide any packages needed to build packages in A
(not including tests), then C will not be updated.

The -u=patch flag (not -u patch) also instructs get to update dependencies,
but changes the default to select patch releases.
Continuing the previous example,
'go get -u=patch A@latest' will use the latest A with B v1.2.4 (not B v1.2.3),
while 'go get -u=patch A' will use a patch release of A instead.

When the -t and -u flags are used together, get will update
test dependencies as well.

In general, adding a new dependency may require upgrading
existing dependencies to keep a working build, and 'go get' does
this automatically. Similarly, downgrading one dependency may
require downgrading other dependencies, and 'go get' does
this automatically as well.

The -insecure flag permits fetching from repositories and resolving
custom domains using insecure schemes such as HTTP. Use with caution.
This flag is deprecated and will be removed in a future version of go.
The GOINSECURE environment variable is usually a better alternative, since
it provides control over which modules may be retrieved using an insecure
scheme. It should be noted that the -insecure flag also turns the module
checksum validation off. GOINSECURE does not do that, use GONOSUMDB.
See 'go help environment' for details.

The second step is to download (if needed), build, and install
the named packages.

If an argument names a module but not a package (because there is no
Go source code in the module's root directory), then the install step
is skipped for that argument, instead of causing a build failure.
For example 'go get golang.org/x/perf' succeeds even though there
is no code corresponding to that import path.

Note that package patterns are allowed and are expanded after resolving
the module versions. For example, 'go get golang.org/x/perf/cmd/...'
adds the latest golang.org/x/perf and then installs the commands in that
latest version.

The -d flag instructs get to download the source code needed to build
the named packages, including downloading necessary dependencies,
but not to build and install them.

With no package arguments, 'go get' applies to Go package in the
current directory, if any. In particular, 'go get -u' and
'go get -u=patch' update all the dependencies of that package.
With no package arguments and also without -u, 'go get' is not much more
than 'go install', and 'go get -d' not much more than 'go list'.

For more about modules, see 'go help modules'.

For more about specifying packages, see 'go help packages'.

This text describes the behavior of get using modules to manage source
code and dependencies. If instead the go command is running in GOPATH
mode, the details of get's flags and effects change, as does 'go help get'.
See 'go help modules' and 'go help gopath-get'.

See also: go build, go install, go clean, go mod.
	`,
}

// Note that this help text is a stopgap to make the module-aware get help text
// available even in non-module settings. It should be deleted when the old get
// is deleted. It should NOT be considered to set a precedent of having hierarchical
// help names with dashes.
var HelpModuleGet = &base.Command{
	UsageLine: "module-get",
	Short:     "module-aware go get",
	Long: `
The 'go get' command changes behavior depending on whether the
go command is running in module-aware mode or legacy GOPATH mode.
This help text, accessible as 'go help module-get' even in legacy GOPATH mode,
describes 'go get' as it operates in module-aware mode.

Usage: ` + CmdGet.UsageLine + `
` + CmdGet.Long,
}

var (
	getD   = CmdGet.Flag.Bool("d", false, "")
	getF   = CmdGet.Flag.Bool("f", false, "")
	getFix = CmdGet.Flag.Bool("fix", false, "")
	getM   = CmdGet.Flag.Bool("m", false, "")
	getT   = CmdGet.Flag.Bool("t", false, "")
	getU   upgradeFlag
	// -insecure is cfg.Insecure
	// -v is cfg.BuildV
)

// upgradeFlag is a custom flag.Value for -u.
type upgradeFlag string

func (*upgradeFlag) IsBoolFlag() bool { return true } // allow -u

func (v *upgradeFlag) Set(s string) error {
	if s == "false" {
		s = ""
	}
	if s == "true" {
		s = "upgrade"
	}
	*v = upgradeFlag(s)
	return nil
}

func (v *upgradeFlag) String() string { return "" }

func init() {
	work.AddBuildFlags(CmdGet, work.OmitModFlag)
	CmdGet.Run = runGet // break init loop
	CmdGet.Flag.BoolVar(&cfg.Insecure, "insecure", cfg.Insecure, "")
	CmdGet.Flag.Var(&getU, "u", "")
}

// A getArg holds a parsed positional argument for go get (path@vers).
type getArg struct {
	// raw is the original argument, to be printed in error messages.
	raw string

	// pattern is the part of the argument before "@" (or the whole argument
	// if there is no "@"), which may match either packages (preferred) or
	// modules (if no matching packages).
	pattern string

	// vers is the part of the argument after "@", or an implied
	// "upgrade" or "patch" if there is no "@". vers specifies the
	// module version to get.
	vers string
}

func (arg getArg) String() string { return arg.raw }

// isDynamic reports whether the meaning of arg can change depending on the
// selected versions of modules in the build list.
func (arg getArg) isDynamic() bool {
	if arg.vers == "upgrade" || arg.vers == "patch" {
		// The version from which we are upgrading may depend on the requirements
		// imposed by other requested versions and upgrades, and those imposed
		// by module dependencies added in order to resolve transitive imports
		// of the requested packages.
		return true
	}

	if arg.pattern == "all" {
		// We won't know which packages are in "all" until we actually load them.
		return true
	}

	if strings.Contains(arg.pattern, "...") {
		// The pattern could match packages from multiple modules, and the set of
		// matching packages can change as the selected versions change.
		//
		// If possible, we only want to upgrade matching packages that are already
		// in scope: we should only try to resolve a new module dependency if
		// there aren't any existing packages matching the pattern.
		return true
	}

	if arg.vers == "none" {
		// The pattern may refer to the module with the given path, or the module
		// containing the package with the given path, or both.
		return true
	}

	return false
}

type query struct {
	arg           getArg
	matchWildcard func(path string) bool

	candidates []modload.QueryResult
	mod        module.Version // the module dependency imposed by the query, if known
}

// matchPattern reports whether the given package or module path matches q.pattern.
func (q *query) matchPattern(path string, inAll bool) bool {
	if q.pattern == "all" {
		return inAll
	}
	if q.matchWildcard == nil {
		return path == q.pattern
	}
	return q.matchWildcard(path)
}

func (q *query) String() string {
	switch q.mod.Version {
	case "", "none", q.vers:
		return q.getArg.String()
	}
	if q.mod.Path == q.pattern {
		return fmt.Sprintf("%v (resolved to %s)", q.getArg, q.mod.Version)
	}
	return fmt.Sprintf("%v (resolved to %v)", q.getArg, q.mod)
}

func runGet(ctx context.Context, cmd *base.Command, args []string) {
	switch getU {
	case "", "upgrade", "patch":
		// ok
	default:
		base.Fatalf("go get: unknown upgrade flag -u=%s", getU)
	}
	if *getF {
		fmt.Fprintf(os.Stderr, "go get: -f flag is a no-op when using modules\n")
	}
	if *getFix {
		fmt.Fprintf(os.Stderr, "go get: -fix flag is a no-op when using modules\n")
	}
	if *getM {
		base.Fatalf("go get: -m flag is no longer supported; consider -d to skip building packages")
	}
	if cfg.Insecure {
		fmt.Fprintf(os.Stderr, "go get: -insecure flag is deprecated; see 'go help get' for details\n")
	}
	load.ModResolveTests = *getT

	// Do not allow any updating of go.mod until we've applied
	// all the requested changes and checked that the result matches
	// what was requested.
	modload.DisallowWriteGoMod()

	// Allow looking up modules for import paths when outside of a module.
	// 'go get' is expected to do this, unlike other commands.
	modload.AllowMissingModuleImports()

	getArgs := parseArgs(args)
	validateArgs(ctx, getArgs)

	for _, arg := range getArgs {

	}

	// Query modules referenced by command line arguments at requested versions.
	// We need to do this before loading packages since patterns that refer to
	// packages in unknown modules can't be expanded. This also avoids looking
	// up new modules while loading packages, only to downgrade later.
	queryCache := make(map[querySpec]*query)
	byPath := runQueries(ctx, queryCache, queries, nil)

	// Add missing modules to the build list.
	// We call SetBuildList here and elsewhere, since newUpgrader,
	// LoadPackages, and other functions read the global build list.
	for _, q := range queries {
		if _, ok := selectedVersion[q.m.Path]; !ok && q.m.Version != "none" {
			buildList = append(buildList, q.m)
		}
	}
	selectedVersion = nil // out of date now; rebuilt later when needed
	modload.SetBuildList(buildList)

	// Upgrade modules specifically named on the command line. This is our only
	// chance to upgrade modules without root packages (modOnly below).
	// This also skips loading packages at an old version, only to upgrade
	// and reload at a new version.
	upgrade := make(map[string]*query)
	for path, q := range byPath {
		if q.path == q.m.Path && q.m.Version != "none" {
			upgrade[path] = q
		}
	}
	buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(upgrade, nil))
	if err != nil {
		base.Fatalf("go get: %v", err)
	}
	modload.SetBuildList(buildList)
	base.ExitIfErrors()
	prevBuildList := buildList

	// Build a set of module paths that we don't plan to load packages from.
	// This includes explicitly requested modules that don't have a root package
	// and modules with a target version of "none".
	var wg sync.WaitGroup
	var modOnlyMu sync.Mutex
	modOnly := make(map[string]*query)
	for _, q := range queries {
		if q.m.Version == "none" {
			modOnlyMu.Lock()
			modOnly[q.m.Path] = q
			modOnlyMu.Unlock()
			continue
		}
		if q.path == q.m.Path {
			wg.Add(1)
			go func(q *query) {
				if hasPkg, err := modload.ModuleHasRootPackage(ctx, q.m); err != nil {
					base.Errorf("go get: %v", err)
				} else if !hasPkg {
					modOnlyMu.Lock()
					modOnly[q.m.Path] = q
					modOnlyMu.Unlock()
				}
				wg.Done()
			}(q)
		}
	}
	wg.Wait()
	base.ExitIfErrors()

	// Build a list of arguments that may refer to packages.
	var pkgPatterns []string
	var pkgGets []getArg
	for _, arg := range getArgs {
		if modOnly[arg.pattern] == nil && arg.vers != "none" {
			pkgPatterns = append(pkgPatterns, arg.pattern)
			pkgGets = append(pkgGets, arg)
		}
	}

	// Load packages and upgrade the modules that provide them. We do this until
	// we reach a fixed point, since modules providing packages may change as we
	// change versions. This must terminate because the module graph is finite,
	// and the load and upgrade operations may only add and upgrade modules
	// in the build list.
	var matches []*search.Match
	for {
		var seenPkgs map[string]bool
		seenQuery := make(map[querySpec]bool)
		var queries []*query
		addQuery := func(q *query) {
			if !seenQuery[q.querySpec] {
				seenQuery[q.querySpec] = true
				queries = append(queries, q)
			}
		}

		if len(pkgPatterns) > 0 {
			// Don't load packages if pkgPatterns is empty. Both
			// modload.LoadPackages and ModulePackages convert an empty list
			// of patterns to []string{"."}, which is not what we want.
			loadOpts := modload.PackageOpts{
				Tags:                     imports.AnyTags(),
				ResolveMissingImports:    true, // dubious; see https://golang.org/issue/32567
				LoadTests:                *getT,
				SilenceErrors:            true, // Errors may be fixed by subsequent upgrades or downgrades.
				SilenceUnmatchedWarnings: true, // We will warn after iterating below.
			}
			matches, _ = modload.LoadPackages(ctx, loadOpts, pkgPatterns...)

			seenPkgs = make(map[string]bool)
			for i, match := range matches {
				arg := pkgGets[i]

				if len(match.Pkgs) == 0 {
					// If the pattern did not match any packages, look up a new module.
					// If the pattern doesn't match anything on the last iteration,
					// we'll print a warning after the outer loop.
					if !match.IsLocal() && !match.IsLiteral() && arg.pattern != "all" {
						addQuery(&query{querySpec: querySpec{path: arg.pattern, vers: arg.vers}, arg: arg.raw})
					} else {
						for _, err := range match.Errs {
							base.Errorf("go get: %v", err)
						}
					}
					continue
				}

				allStd := true
				for _, pkg := range match.Pkgs {
					if !seenPkgs[pkg] {
						seenPkgs[pkg] = true
						if _, _, err := modload.Lookup("", false, pkg); err != nil {
							allStd = false
							base.Errorf("go get %s: %v", arg.raw, err)
							continue
						}
					}
					m := modload.PackageModule(pkg)
					if m.Path == "" {
						// pkg is in the standard library.
						continue
					}
					allStd = false
					if m.Path == modload.Target.Path {
						// pkg is in the main module.
						continue
					}
					addQuery(&query{querySpec: querySpec{path: m.Path, vers: arg.vers, forceModulePath: true, prevM: m}, arg: arg.raw})
				}
				if allStd && arg.pattern != arg.raw {
					base.Errorf("go get %s: cannot use pattern %q with explicit version", arg.raw, arg.raw)
				}
			}
		}
		base.ExitIfErrors()

		// Query target versions for modules providing packages matched by
		// command line arguments.
		byPath = runQueries(ctx, queryCache, queries, modOnly)

		// Handle upgrades. This is needed for arguments that didn't match
		// modules or matched different modules from a previous iteration. It
		// also upgrades modules providing package dependencies if -u is set.
		buildList, err := mvs.UpgradeAll(modload.Target, newUpgrader(byPath, seenPkgs))
		if err != nil {
			base.Fatalf("go get: %v", err)
		}
		modload.SetBuildList(buildList)
		base.ExitIfErrors()

		// Stop if no changes have been made to the build list.
		buildList = modload.LoadedModules()
		eq := len(buildList) == len(prevBuildList)
		for i := 0; eq && i < len(buildList); i++ {
			eq = buildList[i] == prevBuildList[i]
		}
		if eq {
			break
		}
		prevBuildList = buildList
	}

	// Handle downgrades.
	var down []module.Version
	for _, m := range modload.LoadedModules() {
		q := byPath[m.Path]
		if q != nil && semver.Compare(m.Version, q.m.Version) > 0 {
			down = append(down, module.Version{Path: m.Path, Version: q.m.Version})
		}
	}
	if len(down) > 0 {
		buildList, err := mvs.Downgrade(modload.Target, modload.Reqs(), down...)
		if err != nil {
			base.Fatalf("go: %v", err)
		}

		// TODO(bcmills) What should happen here under lazy loading?
		// Downgrading may intentionally violate the lazy-loading invariants.

		modload.SetBuildList(buildList)
		modload.ReloadBuildList() // note: does not update go.mod
		base.ExitIfErrors()
	}

	// Scan for any upgrades lost by the downgrades.
	var lostUpgrades []*query
	if len(down) > 0 {
		selectedVersion = make(map[string]string)
		for _, m := range modload.LoadedModules() {
			selectedVersion[m.Path] = m.Version
		}
		for _, q := range byPath {
			if v, ok := selectedVersion[q.m.Path]; q.m.Version != "none" && (!ok || semver.Compare(v, q.m.Version) != 0) {
				lostUpgrades = append(lostUpgrades, q)
			}
		}
		sort.Slice(lostUpgrades, func(i, j int) bool {
			return lostUpgrades[i].m.Path < lostUpgrades[j].m.Path
		})
	}
	if len(lostUpgrades) > 0 {
		desc := func(m module.Version) string {
			s := m.Path + "@" + m.Version
			t := byPath[m.Path]
			if t != nil && t.arg != s {
				s += " from " + t.arg
			}
			return s
		}
		downByPath := make(map[string]module.Version)
		for _, d := range down {
			downByPath[d.Path] = d
		}

		var buf strings.Builder
		fmt.Fprintf(&buf, "go get: inconsistent versions:")
		reqs := modload.Reqs()
		for _, q := range lostUpgrades {
			// We lost q because its build list requires a newer version of something in down.
			// Figure out exactly what.
			// Repeatedly constructing the build list is inefficient
			// if there are MANY command-line arguments,
			// but at least all the necessary requirement lists are cached at this point.
			list, err := buildListForLostUpgrade(q.m, reqs)
			if err != nil {
				base.Fatalf("go: %v", err)
			}

			fmt.Fprintf(&buf, "\n\t%s", desc(q.m))
			sep := " requires"
			for _, m := range list {
				if down, ok := downByPath[m.Path]; ok && semver.Compare(down.Version, m.Version) < 0 {
					fmt.Fprintf(&buf, "%s %s@%s (not %s)", sep, m.Path, m.Version, desc(down))
					sep = ","
				}
			}
			if sep != "," {
				// We have no idea why this happened.
				// At least report the problem.
				if v := selectedVersion[q.m.Path]; v == "" {
					fmt.Fprintf(&buf, " removed unexpectedly")
				} else {
					fmt.Fprintf(&buf, " ended up at %s unexpectedly", v)
				}
				fmt.Fprintf(&buf, " (please report at golang.org/issue/new)")
			}
		}
		base.Fatalf("%v", buf.String())
	}

	if len(pkgPatterns) > 0 || len(args) == 0 {
		// Before we write the updated go.mod file, reload the requested packages to
		// check for errors.
		loadOpts := modload.PackageOpts{
			Tags:      imports.AnyTags(),
			LoadTests: *getT,

			// Only print warnings after the last iteration, and only if we aren't going
			// to build (to avoid doubled warnings).
			//
			// Only local patterns in the main module, such as './...', can be unmatched.
			// (See the mod_get_nopkgs test for more detail.)
			SilenceUnmatchedWarnings: !*getD,
		}
		modload.LoadPackages(ctx, loadOpts, pkgPatterns...)
	}

	// If -d was specified, we're done after the module work.
	// We've already downloaded modules by loading packages above.
	// Otherwise, we need to build and install the packages matched by
	// command line arguments. This may be a different set of packages,
	// since we only build packages for the target platform.
	// Note that 'go get -u' without arguments is equivalent to
	// 'go get -u .', so we'll typically build the package in the current
	// directory.
	if !*getD && len(pkgPatterns) > 0 {
		work.BuildInit()
		pkgs := load.PackagesForBuild(ctx, pkgPatterns)
		work.InstallPackages(ctx, pkgPatterns, pkgs)
	}

	// Everything succeeded. Update go.mod.
	modload.AllowWriteGoMod()
	modload.WriteGoMod()
	modload.DisallowWriteGoMod()

	// Report warnings if any retracted versions are in the build list.
	// This must be done after writing go.mod to avoid spurious '// indirect'
	// comments. These functions read and write global state.
	// TODO(golang.org/issue/40775): ListModules resets modload.loader, which
	// contains information about direct dependencies that WriteGoMod uses.
	// Refactor to avoid these kinds of global side effects.
	reportRetractions(ctx)
}

// parseArgs parses command-line arguments and reports errors.
//
// The command-line arguments are of the form path@version or simply path, with
// implicit @upgrade. path@none is "downgrade away".
func parseArgs(rawArgs []string) []getArg {
	defer base.ExitIfErrors()

	var gets []getArg
	for _, raw := range search.CleanPatterns(rawArgs) {
		// Argument is path or path@vers.
		pattern := raw
		vers := ""
		if i := strings.Index(raw, "@"); i >= 0 {
			pattern, vers = raw[:i], raw[i+1:]
		}
		if strings.Contains(vers, "@") || raw != pattern && vers == "" {
			base.Errorf("go get %s: invalid module version syntax", raw)
			continue
		}

		// Guard against 'go get x.go', a common mistake.
		// Note that package and module paths may end with '.go', so only print an error
		// if the argument has no version and either has no slash or refers to an existing file.
		if strings.HasSuffix(raw, ".go") && vers == "" {
			if !strings.Contains(raw, "/") {
				base.Errorf("go get %s: arguments must be package or module paths", raw)
				continue
			}
			if fi, err := os.Stat(raw); err == nil && !fi.IsDir() {
				base.Errorf("go get: %s exists as a file, but 'go get' requires package arguments", raw)
				continue
			}
		}

		// If no version suffix is specified, assume @upgrade.
		// If -u=patch was specified, assume @patch instead.
		if vers == "" {
			if getU != "" {
				vers = string(getU)
			} else {
				vers = "upgrade"
			}
		}

		arg := getArg{
			raw:     raw,
			pattern: pattern,
			vers:    vers,
		}
		gets = append(gets, arg)
	}

	return gets
}

// validateArgs ensures that the arguments to 'get' are sensible and well-formed.
func validateArgs(ctx context.Context, args []getArg) {
	defer base.ExitIfErrors()

	for _, arg := range args {
		pattern := arg.pattern
		switch {
		case filepath.IsAbs(pattern) || search.IsRelativePath(pattern):
			// Absolute paths like C:\foo and relative paths like ../foo...
			// are restricted to matching packages in the main module. If the path
			// is explicit and contains no wildcards (...), check that it is a
			// package in the main module. If the path contains wildcards but
			// matches no packages, we'll warn after package loading.
			if !strings.Contains(pattern, "...") {
				m := search.NewMatch(pattern)
				if pkgPath := modload.DirImportPath(pattern); pkgPath != "." {
					m = modload.TargetPackages(ctx, pkgPath)
				}
				if len(m.Pkgs) == 0 {
					for _, err := range m.Errs {
						base.Errorf("go get %s: %v", arg, err)
					}

					abs, err := filepath.Abs(pattern)
					if err != nil {
						abs = pattern
					}
					base.Errorf("go get %s: path %s is not a package in module rooted at %s", arg, abs, modload.ModRoot())
					continue
				}
			}

			if arg.pattern != arg.raw {
				base.Errorf("go get %s: can't request explicit version of path in main module", arg)
			}

		case pattern == "all":
			// If there is no main module, "all" is not meaningful.
			if !modload.HasModRoot() {
				base.Errorf(`go get %s: cannot match "all": working directory is not part of a module`, arg)
			}

		case search.IsMetaPackage(pattern):
			if pattern != arg.raw {
				base.Errorf("go get %s: can't request explicit version of standard-library pattern %q", arg, pattern)
			}

		default:
			// The argument is a package or module path or pattern.
			if modload.HasModRoot() {
				if m := modload.TargetPackages(ctx, pattern); len(m.Pkgs) != 0 {
					if arg.vers != "upgrade" && arg.vers != "patch" {
						base.Errorf("go get %s: can't request explicit version of path in main module", arg)
					}
				}
			}
		}
	}

	return queries
}

func upgrade(ctx context.Context, upgradedTo map[string]string, args []getArg) (changed bool) {
	buildList := modload.LoadAllModules(ctx)
	selectedVersion := make(map[string]string)
	for _, m := range buildList {
		selectedVersion[m.Path] = m.Version
	}

	// First, upgrade any explicit arguments.
	for _, arg := range getArgs {
		path := arg.pattern
		switch {
		case filepath.IsAbs(path) || search.IsRelativePath(path):
			// All matching packages are in the main module, so the module containing
			// them (that is, the main module) cannot be changed. All we can hope to
			// do is fix up transitive dependencies, which we must only do after we
			// have processed any other requested upgrades.

		case path == "all":
			// The user has requested that we upgrade all imported packages.
			// The proper way to do this is to start with the direct imports of the main module, then progressively scan and upgrade the imported packages
			// We need to identify those packages. We don't currently have a way to do that without resolving transitive dependencies, so for now we'll just do that and hope it's decent enough.
		}
	}

	// Next, upgrade the transitive dependencies of those arguments if requested.
	if getU != "" {
		TODO
	}
}

func runQueries(ctx context.Context, args []getArg) TODO {
	for arg := range args {
		first := path
		if i := strings.IndexByte(first, '/'); i >= 0 {
			first = pattern
		}
		if !strings.Contains(first, ".") {
			// The path doesn't have a dot in the first component and cannot be
			// queried as a module. It may be a package in the standard library,
			// which is fine, so don't report an error unless we encounter
			// a problem loading packages.
			continue
		}

		// If we're querying "upgrade" or "patch", we need to know the current
		// version of the module. For "upgrade", we want to avoid accidentally
		// downgrading from a newer prerelease. For "patch", we need to query
		// the correct minor version.
		// Here, we check if "path" is the name of a module in the build list
		// (other than the main module) and set prevM if so. If "path" isn't
		// a module in the build list, the current version doesn't matter
		// since it's either an unknown module or a package within a module
		// that we'll discover later.
		q := &query{querySpec: querySpec{path: arg.pattern, vers: arg.vers}, arg: arg.raw}
		if v, ok := selectedVersion[pattern]; ok {
			if pattern == modload.Target.Path {
				// TODO(bcmills): This is held over from a previous version of the get
				// implementation. Why was it a special case?
			} else {
				q.prevM = module.Version{Path: path, Version: v}
				q.forceModulePath = true
			}
		}
		queries = append(queries, q)
	}
}

// runQueries looks up modules at target versions in parallel. Results will be
// cached. If the same module is referenced by multiple queries at different
// versions (including earlier queries in the modOnly map), an error will be
// reported. A map from module paths to queries is returned, which includes
// queries and modOnly.
func runQueries(ctx context.Context, cache map[querySpec]*query, queries []*query, modOnly map[string]*query) map[string]*query {

	runQuery := func(q *query) {
		if q.vers == "none" {
			// Wait for downgrade step.
			q.m = module.Version{Path: q.path, Version: "none"}
			return
		}
		m, err := getQuery(ctx, q.path, q.vers, q.prevM, q.forceModulePath)
		if err != nil {
			base.Errorf("go get %s: %v", q.arg, err)
		}
		q.m = m
	}

	type token struct{}
	sem := make(chan token, runtime.GOMAXPROCS(0))
	for _, q := range queries {
		if cached := cache[q.querySpec]; cached != nil {
			*q = *cached
		} else {
			sem <- token{}
			go func(q *query) {
				runQuery(q)
				<-sem
			}(q)
		}
	}

	// Fill semaphore channel to wait for goroutines to finish.
	for n := cap(sem); n > 0; n-- {
		sem <- token{}
	}

	// Add to cache after concurrent section to avoid races...
	for _, q := range queries {
		cache[q.querySpec] = q
	}

	base.ExitIfErrors()

	byPath := make(map[string]*query)
	check := func(q *query) {
		if prev, ok := byPath[q.m.Path]; prev != nil && prev.m != q.m {
			base.Errorf("go get: conflicting versions for module %s: %s and %s", q.m.Path, prev.m.Version, q.m.Version)
			byPath[q.m.Path] = nil // sentinel to stop errors
			return
		} else if !ok {
			byPath[q.m.Path] = q
		}
	}
	for _, q := range queries {
		check(q)
	}
	for _, q := range modOnly {
		check(q)
	}
	base.ExitIfErrors()

	return byPath
}

// getQuery evaluates the given (package or module) path and version
// to determine the underlying module version being requested.
// If forceModulePath is set, getQuery must interpret path
// as a module path.
func getQuery(ctx context.Context, path, vers string, prevM module.Version, forceModulePath bool) (module.Version, error) {
	if (prevM.Version != "") != forceModulePath {
		// We resolve package patterns by calling QueryPattern, which does not
		// accept a previous version and therefore cannot take it into account for
		// the "latest" or "patch" queries.
		// If we are resolving a package path or pattern, the caller has already
		// resolved any existing packages to their containing module(s), and
		// will set both prevM.Version and forceModulePath for those modules.
		// The only remaining package patterns are those that are not already
		// provided by the build list, which are indicated by
		// an empty prevM.Version.
		base.Fatalf("go get: internal error: prevM may be set if and only if forceModulePath is set")
	}

	// If vers is a query like "latest", we should ignore retracted and excluded
	// versions. If vers refers to a specific version or commit like "v1.0.0"
	// or "master", we should only ignore excluded versions.
	allowed := modload.CheckAllowed
	if modload.IsRevisionQuery(vers) {
		allowed = modload.CheckExclusions
	} else if vers == "upgrade" || vers == "patch" {
		allowed = checkAllowedOrCurrent(prevM.Version)
	}

	// If the query must be a module path, try only that module path.
	if forceModulePath {
		if pattern == modload.Target.Path {
			if vers != "latest" {
				return module.Version{}, fmt.Errorf("can't get a specific version of the main module")
			}
		}

		info, err := modload.Query(ctx, path, vers, prevM.Version, allowed)
		if err == nil {
			if info.Version != vers && info.Version != prevM.Version {
				logOncef("go: %s %s => %s", pattern, vers, info.Version)
			}
			return module.Version{Path: pattern, Version: info.Version}, nil
		}

		// If the query was "upgrade" or "patch" and the current version has been
		// replaced, check to see whether the error was for that same version:
		// if so, the version was probably replaced because it is invalid,
		// and we should keep that replacement without complaining.
		if vers == "upgrade" || vers == "patch" {
			var vErr *module.InvalidVersionError
			if errors.As(err, &vErr) && vErr.Version == prevM.Version && modload.Replacement(prevM).Path != "" {
				return prevM, nil
			}
		}

		return module.Version{}, err
	}

	// If the query may be either a package or a module, try it as a package path.
	// If it turns out to only exist as a module, we can detect the resulting
	// PackageNotInModuleError and avoid a second round-trip through (potentially)
	// all of the configured proxies.
	results, err := modload.QueryPattern(ctx, path, vers, allowed)
	if err != nil {
		// If the path doesn't contain a wildcard, check whether it was actually a
		// module path instead. If so, return that.
		if !strings.Contains(pattern, "...") {
			var modErr *modload.PackageNotInModuleError
			if errors.As(err, &modErr) && modErr.Mod.Path == pattern {
				if modErr.Mod.Version != vers {
					logOncef("go: %s %s => %s", pattern, vers, modErr.Mod.Version)
				}
				return modErr.Mod, nil
			}
		}

		return module.Version{}, err
	}

	m := results[0].Mod
	if m.Path != pattern {
		logOncef("go: found %s in %s %s", pattern, m.Path, m.Version)
	} else if m.Version != vers {
		logOncef("go: %s %s => %s", pattern, vers, m.Version)
	}
	return m, nil
}

// reportRetractions prints warnings if any modules in the build list are
// retracted.
func reportRetractions(ctx context.Context) {
	// Query for retractions of modules in the build list.
	// Use modload.ListModules, since that provides information in the same format
	// as 'go list -m'. Don't query for "all", since that's not allowed outside a
	// module.
	buildList := modload.LoadedModules()
	args := make([]string, 0, len(buildList))
	for _, m := range buildList {
		if m.Version == "" {
			// main module or dummy target module
			continue
		}
		args = append(args, m.Path+"@"+m.Version)
	}
	listU := false
	listVersions := false
	listRetractions := true
	mods := modload.ListModules(ctx, args, listU, listVersions, listRetractions)
	retractPath := ""
	for _, mod := range mods {
		if len(mod.Retracted) > 0 {
			if retractPath == "" {
				retractPath = mod.Path
			} else {
				retractPath = "<module>"
			}
			rationale := modload.ShortRetractionRationale(mod.Retracted[0])
			logOncef("go: warning: %s@%s is retracted: %s", mod.Path, mod.Version, rationale)
		}
	}
	if modload.HasModRoot() && retractPath != "" {
		logOncef("go: run 'go get %s@latest' to switch to the latest unretracted version", retractPath)
	}
}

var loggedLines sync.Map

func logOncef(format string, args ...interface{}) {
	msg := fmt.Sprintf(format, args...)
	if _, dup := loggedLines.LoadOrStore(msg, true); !dup {
		fmt.Fprintln(os.Stderr, msg)
	}
}

// checkAllowedOrCurrent is like modload.CheckAllowed, but always allows the
// current version (even if it is retracted or otherwise excluded).
func checkAllowedOrCurrent(current string) modload.AllowedFunc {
	if current == "" {
		return modload.CheckAllowed
	}

	return func(ctx context.Context, m module.Version) error {
		if m.Version == current {
			return nil
		}
		return modload.CheckAllowed(ctx, m)
	}
}

type modReason struct {
	version string
	first   *query
}

type adjuster struct {
	unresolved       []*query
	resolved         []*query
	resolvedVersions map[string]modReason
}

func (a *adjuster) fixVersion(q *query, m module.Version) {
	q.mod = m
	mv, ok := a.resolvedVersions[m.Path]
	if ok {
		if mv.version != m.Version {
			base.Errorf("go get: argument %v conflicts with %v", q, mv.first)
		}
	} else {
		a.resolvedVersions[m.Path] = modReason{
			version: m.Version,
			first:   q,
		}
	}
}

func (a *adjuster) resolveStatic(ctx context.Context, args []getArg) {
	defer base.ExitIfErrors()

	var dynamic, unresolved []*query
	for _, arg := range args {
		q := &query{getArg: arg}

		if arg.isDynamic() {
			if strings.Contains(arg.pattern, "...") {
				q.matchWildcard = search.MatchPattern(arg.pattern)
			} else if arg.vers == "none" {
				// In addition to dynamically downgrading whatever module contains the
				// named package, this query should statically prune away the named
				// module.
				a.fixVersion(q, module.Version{Path: arg.pattern, Version: "none"})
			}
			dynamic = append(dynamic, q)
			continue
		}

		var err error
		q.candidates, err = modload.QueryPattern(ctx, q.pattern, q.vers, modload.CheckAllowed)
		if err == nil {
			unresolved = append(unresolved, q)
			continue
		}

		var notInModErr *modload.PackageNotInModuleError
		if errors.As(err, &notInModErr) && notInModErr.Mod.Path == arg.pattern {
			q.mod = notInModErr.Mod
			a.fixVersion(q)
			continue
		}

		var mErr *module.ModuleError
		if errors.As(err, &mErr) && mErr.Path == q.pattern && mErr.Version == q.vers {
			base.Errorf("go get: %v", err)
		} else {
			base.Errorf("go get: %v: %v", q, err)
		}
	}

	for len(unresolved) > 0 {
		prevUnresolved := unresolved
		unresolved = nil

		for _, q := range prevUnresolved {
			var filtered []modload.QueryResult
			for _, c := range q.candidates {
				vr, ok := a.resolvedVersions[c.Mod.Path]

				if !ok {
					// The candidate module is not yet constrained by any other argument.
					// This module could be used to satisfy the query.
					filtered = append(filtered, c)
					continue
				}

				if vr.version == c.Mod.Version {
					// This version is already forced by some other argument.
					// It is the only viable choice to provide this package.
					//
					// For example, consider the command
					//
					// 	go get example.com/foo@latest example.com/foo/bar/baz@latest
					//
					// If modules example.com/foo and example.com/foo/bar both provide
					// package example.com/foo/bar/baz, then we *must* resolve the package
					// from example.com/foo: if we instead resolved it from
					// example.com/foo/bar, we would introduce an ambiguous import error.
					filtered = []modload.QueryResult{c}
					break
				}

				// Some other query forces the candidate module to some version other
				// than the version of it returned by QueryPatterns.
				//
				// Following the example above, the command could be something like
				//
				// 	go get example.com/foo/bar@none example.com/foo/bar/baz@latest
				//
				// in which case we *cannot* resolve the package from
				// example.com/foo/bar (because it is constrained to version "none") and
				// must fall through to module example.com/foo@latest.
			}

			switch len(filtered) {
			case 0:
				// All candidate modules are excluded by conflicts with other arguments.
				// Arbitrarily force the first candidate, which will report the error.
				a.fixVersion(q, q.candidates[0].Mod)

			case 1:
				// Only one candidate is viable (by far the common case), so the result
				// of the query is unambiguous.
				a.fixVersion(q, filtered[0].Mod)

			default:
				// Multiple modules contain the requested package at the requested
				// version of that module: the query is ambiguous. Leave this query
				// unresolved and see if the remaining arguments clear things up.
				//
				// This makes 'go get' technically O(N²) in the worst case for N
				// arguments, but N tends to be extremely small, and ambiguous
				// pkg@version combinations should be very rare to begin with.
				q.candidates = filtered
				unresolved = append(unresolved, q)
			}
		}

		if len(unresolved) == len(prevUnresolved) {
			// We made no progress toward resolving the queries:
			// every remaining package argument has at least two remaining candidates.
			// Arbitrarily accept
			// so the user needs to supply additional arguments to disambiguate.
		}
	}
}

func (a *adjuster) adjustModules(buildList []module.Version) []module.Version {
	return TODO
}

func (a *adjuster) adjustPackage(path string, m module.Version, inAll bool) (adjusted module.Version, mayUpgrade bool) {
	ctx := context.TODO()

	for _, arg := range a.args {
		if search.MatchPattern(arg.pattern)(path) {
			res, err := modload.QueryPattern(ctx, pattern, arg.vers, checkAllowedOrCurrent(m.Version))
			if err != nil {
				pattern
				TODO
			}
			for _, r := range res {
				adjusted = r.Mod
				if arg.vers == "upgrade" || arg.vers == "patch" {
					mayUpgrade = true
				}
				if adjusted.Path == m.Path {
					// If one of the candidates retains the same module path as m,
					// prefer that candidate.
					return
				}
			}
		}
	}

	return m, true
}

Move the editor point to the method fixVersion at line 1078.

(Note that references to that method elsewhere in the file are highlighted, so cross-references seem to be working more-or-less correctly.)

M-x eglot-rename, and give a new name for the method.

What did you expect to see?

Since references to the named method resolve successfully, those references should be renamed despite any other errors in the file.

What did you see instead?

[client-request] (id:190) Thu Oct  8 13:54:59 2020:
(:jsonrpc "2.0" :id 190 :method "textDocument/rename" :params
					(:textDocument
					 (:uri "file:///usr/local/google/home/bcmills/go/src/cmd/go/internal/modget/get.go")
					 :position
					 (:line 1077 :character 22)
					 :newName "resolveVersion"))
[server-reply] (id:190) ERROR Thu Oct  8 13:54:59 2020:
(:jsonrpc "2.0" :error
					(:code 0 :message "renaming \"fixVersion\" to \"resolveVersion\" not possible because \"cmd/go/internal/modget\" has errors")
					:id 190)
@gopherbot gopherbot added Tools This label describes issues relating to any tools in the x/tools repository. gopls Issues related to the Go language server, gopls. labels Oct 8, 2020
@gopherbot gopherbot added this to the Unreleased milestone Oct 8, 2020
@bcmills bcmills changed the title x/tools/gopls: "renaming [method] is not possible because [package] has errors” x/tools/gopls: "renaming [method] is not possible because [package] has errors" Oct 8, 2020
@stamblerre stamblerre removed this from the Unreleased milestone Oct 9, 2020
@stamblerre stamblerre added this to the gopls/unplanned milestone Oct 21, 2020
@davidv1992
Copy link

This would ease the burden currently caused by having no great solution to #34438, because it would allow the use of rename to dig yourself out of the hole created by an initial interface rename.

@rentziass
Copy link
Contributor

I'd like to give this one a try if that's ok.

@rentziass
Copy link
Contributor

After a first look, it seems this applies only when trying to rename a method, and the reason that is is because when renaming a method gopls tries to

		// Find the set of concrete or abstract methods directly
		// coupled to abstract method 'from' by some
		// satisfy.Constraint, and rename them too.

to keep the code compilable after the rename. If the package has errors we might miss some necessary renames and so just perform an incomplete rename. If anyone wants to take a look this is where a method is checked for a rename and this is where the constraints mentioned in documentation are put together.

@stamblerre or anyone else, do you have an idea is there is a safe way around this?

@stamblerre
Copy link
Contributor

/cc @findleyr @suzmue

@adonovan adonovan added the Refactoring Issues related to refactoring tools label Apr 24, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
FeatureRequest gopls Issues related to the Go language server, gopls. help wanted Refactoring Issues related to refactoring tools Tools This label describes issues relating to any tools in the x/tools repository.
Projects
None yet
Development

No branches or pull requests

6 participants