cmd/go/internal/modfetch: add detail to "missing go.mod" errors

Explain what's wrong better than "missing or invalid go.mod".
While we're here, improve the signal-to-noise ratio too.

Before this CL, using the scenario from golang/go#24522:

$ vgo get github.com/rsc/blackfriday/v2
go: github.com/rsc/blackfriday/v2 v2.0.1: missing or invalid go.mod
go get github.com/rsc/blackfriday/v2: missing or invalid go.mod

$ vgo get github.com/rsc/blackfriday/v2@v2.0.1-bad
go: github.com/rsc/blackfriday/v2 v2.0.1-bad: missing or invalid go.mod
go get github.com/rsc/blackfriday/v2@v2.0.1-bad: missing or invalid go.mod

After this CL:

$ vgo get github.com/rsc/blackfriday/v2
go: github.com/rsc/blackfriday/v2@v2.0.0: missing github.com/rsc/blackfriday/go.mod and .../v2/go.mod at revision v2.0.0
go: error loading module requirements

$ vgo get github.com/rsc/blackfriday/v2@v2.0.1-bad
go: github.com/rsc/blackfriday/v2@v2.0.1-bad: go.mod has non-.../v2 module path "github.com/rsc/blackfriday" (and .../v2/go.mod does not exist) at revision v2.0.1-bad
go: error loading module requirements

Fixes golang/go#24522.

Change-Id: I1471729cdde8345130d771f26b1501638b7452d2
Reviewed-on: https://go-review.googlesource.com/122399
Reviewed-by: Bryan C. Mills <bcmills@google.com>
diff --git a/vendor/cmd/go/internal/modfetch/coderepo.go b/vendor/cmd/go/internal/modfetch/coderepo.go
index daaea9f..33be117 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo.go
@@ -12,7 +12,6 @@
 	"io/ioutil"
 	"os"
 	"path"
-	"path/filepath"
 	"regexp"
 	"strings"
 	"time"
@@ -224,60 +223,100 @@
 	if err != nil {
 		return "", "", nil, err
 	}
-	if r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".") {
-		if r.codeDir == "" {
-			return rev, "", nil, nil
-		}
-		file1 := path.Join(r.codeDir, "go.mod")
-		gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
-		if err1 != nil {
-			if os.IsNotExist(err1) {
-				return "", "", nil, errors.New("missing go.mod")
-			}
-			return "", "", nil, fmt.Errorf("reading go.mod: %v", err1)
-		}
-		return rev, r.codeDir, gomod1, nil
-	}
 
-	// Suppose pathMajor is "/v2".
-	// Either go.mod should claim v2 and v2/go.mod should not exist,
-	// or v2/go.mod should exist and claim v2. Not both.
-	// Note that we don't check the full path, just the major suffix,
-	// because of replacement modules. This might be a fork of
-	// the real module, found at a different path, usable only in
-	// a replace directive.
+	// Load info about go.mod but delay consideration
+	// (except I/O error) until we rule out v2/go.mod.
 	file1 := path.Join(r.codeDir, "go.mod")
-	file2 := path.Join(r.codeDir, r.pathMajor[1:], "go.mod")
 	gomod1, err1 := r.code.ReadFile(rev, file1, codehost.MaxGoMod)
-	gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
-
 	if err1 != nil && !os.IsNotExist(err1) {
-		return "", "", nil, fmt.Errorf("reading %s: %v", file1, err1)
+		return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file1, rev, err1)
 	}
-	if err2 != nil && !os.IsNotExist(err2) {
-		return "", "", nil, fmt.Errorf("reading %s: %v", file2, err2)
+	mpath1 := modfile.ModulePath(gomod1)
+	found1 := err1 == nil && isMajor(mpath1, r.pathMajor)
+
+	var file2 string
+	if r.pathMajor != "" && !strings.HasPrefix(r.pathMajor, ".") {
+		// Suppose pathMajor is "/v2".
+		// Either go.mod should claim v2 and v2/go.mod should not exist,
+		// or v2/go.mod should exist and claim v2. Not both.
+		// Note that we don't check the full path, just the major suffix,
+		// because of replacement modules. This might be a fork of
+		// the real module, found at a different path, usable only in
+		// a replace directive.
+		dir2 := path.Join(r.codeDir, r.pathMajor[1:])
+		file2 = path.Join(dir2, "go.mod")
+		gomod2, err2 := r.code.ReadFile(rev, file2, codehost.MaxGoMod)
+		if err2 != nil && !os.IsNotExist(err2) {
+			return "", "", nil, fmt.Errorf("reading %s/%s at revision %s: %v", r.pathPrefix, file2, rev, err2)
+		}
+		mpath2 := modfile.ModulePath(gomod2)
+		found2 := err2 == nil && isMajor(mpath2, r.pathMajor)
+
+		if found1 && found2 {
+			return "", "", nil, fmt.Errorf("%s/%s and ...%s/go.mod both have ...%s module paths at revision %s", r.pathPrefix, file1, r.pathMajor, r.pathMajor, rev)
+		}
+		if found2 {
+			return rev, dir2, gomod2, nil
+		}
+		if err2 == nil {
+			if mpath2 == "" {
+				return "", "", nil, fmt.Errorf("%s/%s is missing module path at revision %s", r.pathPrefix, file2, rev)
+			}
+			return "", "", nil, fmt.Errorf("%s/%s has non-...%s module path %q at revision %s", r.pathPrefix, file2, r.pathMajor, mpath2, rev)
+		}
 	}
 
-	found1 := err1 == nil && isMajor(gomod1, r.pathMajor)
-	found2 := err2 == nil && isMajor(gomod2, r.pathMajor)
-
-	if err2 == nil && !found2 {
-		return "", "", nil, fmt.Errorf("%s has non-...%s module path", file2, r.pathMajor)
-	}
-	if found1 && found2 {
-		return "", "", nil, fmt.Errorf("both %s and %s claim ...%s module", file1, file2, r.pathMajor)
-	}
-	if found2 {
-		return rev, filepath.Join(r.codeDir, r.pathMajor), gomod2, nil
-	}
+	// Not v2/go.mod, so it's either go.mod or nothing. Which is it?
 	if found1 {
+		// Explicit go.mod with matching module path OK.
 		return rev, r.codeDir, gomod1, nil
 	}
-	return "", "", nil, fmt.Errorf("missing or invalid go.mod")
+	if err1 == nil {
+		// Explicit go.mod with non-matching module path disallowed.
+		suffix := ""
+		if file2 != "" {
+			suffix = fmt.Sprintf(" (and ...%s/go.mod does not exist)", r.pathMajor)
+		}
+		if mpath1 == "" {
+			return "", "", nil, fmt.Errorf("%s is missing module path%s at revision %s", file1, suffix, rev)
+		}
+		if r.pathMajor != "" { // ".v1", ".v2" for gopkg.in
+			return "", "", nil, fmt.Errorf("%s has non-...%s module path %q%s at revision %s", file1, r.pathMajor, mpath1, suffix, rev)
+		}
+		return "", "", nil, fmt.Errorf("%s has post-%s module path %q%s at revision %s", file1, semver.Major(version), mpath1, suffix, rev)
+	}
+
+	if r.codeDir == "" && (r.pathMajor == "" || strings.HasPrefix(r.pathMajor, ".")) {
+		// Implicit go.mod at root of repo OK for v0/v1 and for gopkg.in.
+		return rev, "", nil, nil
+	}
+
+	// Implicit go.mod below root of repo or at v2+ disallowed.
+	// Be clear about possibility of using either location for v2+.
+	if file2 != "" {
+		return "", "", nil, fmt.Errorf("missing %s/go.mod and ...%s/go.mod at revision %s", r.pathPrefix, r.pathMajor, rev)
+	}
+	return "", "", nil, fmt.Errorf("missing %s/go.mod at revision %s", r.pathPrefix, rev)
 }
 
-func isMajor(gomod []byte, pathMajor string) bool {
-	return strings.HasSuffix(modfile.ModulePath(gomod), pathMajor)
+func isMajor(mpath, pathMajor string) bool {
+	if mpath == "" {
+		return false
+	}
+	if pathMajor == "" {
+		// mpath must NOT have version suffix.
+		i := len(mpath)
+		for i > 0 && '0' <= mpath[i-1] && mpath[i-1] <= '9' {
+			i--
+		}
+		if i < len(mpath) && i >= 2 && mpath[i-1] == 'v' && mpath[i-2] == '/' {
+			// Found valid suffix.
+			return false
+		}
+		return true
+	}
+	// Otherwise pathMajor is ".v1", ".v2" (gopkg.in), or "/v2", "/v3" etc.
+	return strings.HasSuffix(mpath, pathMajor)
 }
 
 func (r *codeRepo) GoMod(version string) (data []byte, err error) {
diff --git a/vendor/cmd/go/internal/modfetch/coderepo_test.go b/vendor/cmd/go/internal/modfetch/coderepo_test.go
index fcf849b..ac88471 100644
--- a/vendor/cmd/go/internal/modfetch/coderepo_test.go
+++ b/vendor/cmd/go/internal/modfetch/coderepo_test.go
@@ -91,7 +91,7 @@
 		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:   "80d85c5d4d17",
 		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
-		ziperr:  "missing or invalid go.mod",
+		ziperr:  "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
 	},
 	{
 		path:    "github.com/rsc/vgotest1",
@@ -126,8 +126,8 @@
 		name:     "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:    "80d85c5d4d17",
 		time:     time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
-		gomoderr: "missing or invalid go.mod",
-		ziperr:   "missing or invalid go.mod",
+		gomoderr: "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
+		ziperr:   "missing github.com/rsc/vgotest1/go.mod and .../v2/go.mod at revision v2.0.0",
 	},
 	{
 		path:    "github.com/rsc/vgotest1/v54321",
@@ -136,7 +136,7 @@
 		name:    "80d85c5d4d17598a0e9055e7c175a32b415d6128",
 		short:   "80d85c5d4d17",
 		time:    time.Date(2018, 2, 19, 23, 10, 6, 0, time.UTC),
-		ziperr:  "missing or invalid go.mod",
+		ziperr:  "missing github.com/rsc/vgotest1/go.mod and .../v54321/go.mod at revision 80d85c5d4d17",
 	},
 	{
 		path: "github.com/rsc/vgotest1/submod",
@@ -193,7 +193,7 @@
 		name:     "f18795870fb14388a21ef3ebc1d75911c8694f31",
 		short:    "f18795870fb1",
 		time:     time.Date(2018, 2, 19, 23, 16, 4, 0, time.UTC),
-		gomoderr: "v2/go.mod has non-.../v2 module path",
+		gomoderr: "github.com/rsc/vgotest1/v2/go.mod has non-.../v2 module path \"github.com/rsc/vgotest\" at revision v2.0.3",
 	},
 	{
 		path:     "github.com/rsc/vgotest1/v2",
@@ -202,7 +202,7 @@
 		name:     "1f863feb76bc7029b78b21c5375644838962f88d",
 		short:    "1f863feb76bc",
 		time:     time.Date(2018, 2, 20, 0, 3, 38, 0, time.UTC),
-		gomoderr: "both go.mod and v2/go.mod claim .../v2 module",
+		gomoderr: "github.com/rsc/vgotest1/go.mod and .../v2/go.mod both have .../v2 module paths at revision v2.0.4",
 	},
 	{
 		path:    "github.com/rsc/vgotest1/v2",
@@ -262,7 +262,7 @@
 		// Because it's a package, Stat should fail entirely.
 		path: "github.com/rsc/quote/buggy",
 		rev:  "c4d4236f",
-		err:  "missing go.mod",
+		err:  "missing github.com/rsc/quote/buggy/go.mod at revision c4d4236f9242",
 	},
 	{
 		path:    "gopkg.in/yaml.v2",
@@ -441,6 +441,8 @@
 				tt.name = remap(tt.name, m)
 				tt.short = remap(tt.short, m)
 				tt.rev = remap(tt.rev, m)
+				tt.gomoderr = remap(tt.gomoderr, m)
+				tt.ziperr = remap(tt.ziperr, m)
 				t.Run(strings.Replace(tt.path, "/", "_", -1)+"/"+tt.rev, f)
 				tt = old
 			}
@@ -449,6 +451,7 @@
 }
 
 var hgmap = map[string]string{
+	"github.com/rsc/vgotest1/":                 "vcs-test.golang.org/hg/vgotest1.hg/",
 	"f18795870fb14388a21ef3ebc1d75911c8694f31": "a9ad6d1d14eb544f459f446210c7eb3b009807c6",
 	"ea65f87c8f52c15ea68f3bdd9925ef17e20d91e9": "f1fc0f22021b638d073d31c752847e7bf385def7",
 	"b769f2de407a4db81af9c5de0a06016d60d2ea09": "92c7eb888b4fac17f1c6bd2e1060a1b881a3b832",
@@ -469,8 +472,11 @@
 			}
 		}
 	}
-	if i := strings.LastIndex(name, "-"); i >= 0 { // remap tail of pseudo-version
-		return name[:i+1] + remap(name[i+1:], m)
+	for k, v := range m {
+		name = strings.Replace(name, k, v, -1)
+		if codehost.AllHex(k) {
+			name = strings.Replace(name, k[:12], v[:12], -1)
+		}
 	}
 	return name
 }
@@ -598,7 +604,7 @@
 	},
 	{
 		path: "github.com/rsc/vgotest1/subdir",
-		err:  "missing go.mod",
+		err:  "missing github.com/rsc/vgotest1/subdir/go.mod at revision a08abb797a67",
 	},
 	{
 		path:    "swtch.com/testmod",
diff --git a/vendor/cmd/go/internal/modload/load.go b/vendor/cmd/go/internal/modload/load.go
index 8c1fac2..fe3f64d 100644
--- a/vendor/cmd/go/internal/modload/load.go
+++ b/vendor/cmd/go/internal/modload/load.go
@@ -98,7 +98,9 @@
 				paths = append(paths, "all") // will expand after load completes
 
 			case search.IsMetaPackage(pkg): // std, cmd
-				fmt.Fprintf(os.Stderr, "go: warning: %q matches no packages when using modules\n", pkg)
+				list := search.AllPackages(pkg)
+				roots = append(roots, list...)
+				paths = append(paths, list...)
 
 			case strings.Contains(pkg, "..."):
 				// TODO: Don't we need to reevaluate this one last time once the build list stops changing?
@@ -830,11 +832,13 @@
 			gomod := filepath.Join(dir, "go.mod")
 			data, err := ioutil.ReadFile(gomod)
 			if err != nil {
-				return nil, err
+				base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
+				return nil, ErrRequire
 			}
 			f, err := modfile.Parse(gomod, data, nil)
 			if err != nil {
-				return nil, err
+				base.Errorf("go: parsing %s: %v", base.ShortPath(gomod), err)
+				return nil, ErrRequire
 			}
 			var list []module.Version
 			for _, r := range f.Require {
@@ -851,40 +855,41 @@
 
 	if !semver.IsValid(mod.Version) {
 		// Disallow the broader queries supported by fetch.Lookup.
-		panic(fmt.Errorf("invalid semantic version %q for %s", mod.Version, mod.Path))
-		// TODO: return nil, fmt.Errorf("invalid semantic version %q", mod.Version)
+		base.Fatalf("go: internal error: %s@%s: unexpected invalid semantic version", mod.Path, mod.Version)
 	}
 
 	data, err := modfetch.GoMod(mod.Path, mod.Version)
 	if err != nil {
-		base.Errorf("go: %s %s: %v\n", mod.Path, mod.Version, err)
-		return nil, err
+		base.Errorf("go: %s@%s: %v\n", mod.Path, mod.Version, err)
+		return nil, ErrRequire
 	}
 	f, err := modfile.Parse("go.mod", data, nil)
 	if err != nil {
-		return nil, fmt.Errorf("parsing downloaded go.mod: %v", err)
+		base.Errorf("go: %s@%s: parsing go.mod: %v", mod.Path, mod.Version, err)
+		return nil, ErrRequire
 	}
 
 	if f.Module == nil {
-		return nil, fmt.Errorf("%v@%v go.mod: missing module line", mod.Path, mod.Version)
+		base.Errorf("go: %s@%s: parsing go.mod: missing module line", mod.Path, mod.Version)
+		return nil, ErrRequire
 	}
 	if mpath := f.Module.Mod.Path; mpath != origPath && mpath != mod.Path {
-		return nil, fmt.Errorf("downloaded %q and got module %q", mod.Path, mpath)
+		base.Errorf("go: %s@%s: parsing go.mod: unexpected module path %q", mod.Path, mod.Version, mpath)
+		return nil, ErrRequire
 	}
 
 	var list []module.Version
 	for _, req := range f.Require {
 		list = append(list, req.Mod)
 	}
-	if false {
-		fmt.Fprintf(os.Stderr, "REQLIST %v:\n", mod)
-		for _, req := range list {
-			fmt.Fprintf(os.Stderr, "\t%v\n", req)
-		}
-	}
 	return list, nil
 }
 
+// ErrRequire is the sentinel error returned when Require encounters problems.
+// It prints the problems directly to standard error, so that multiple errors
+// can be displayed easily.
+var ErrRequire = errors.New("error loading module requirements")
+
 func (*mvsReqs) Max(v1, v2 string) string {
 	if v1 != "" && semver.Compare(v1, v2) == -1 {
 		return v2
diff --git a/vendor/cmd/go/mod_test.go b/vendor/cmd/go/mod_test.go
index bd3d8db..bb303c0 100644
--- a/vendor/cmd/go/mod_test.go
+++ b/vendor/cmd/go/mod_test.go
@@ -842,7 +842,7 @@
 		require rsc.io/quote v1.2.0
 	`), 0666))
 	tg.cd(tg.path("x"))
-	
+
 	tg.run("list", "-m", "-f={{.Main}}: {{.Dir}}")
 	tg.grepStdout(`^true: `, "expected main module to have Main=true")
 	tg.grepStdout(regexp.QuoteMeta(tg.path("x")), "expected Dir of main module to be present")
@@ -856,7 +856,10 @@
 
 	tg.run("list", "-m", "-f={{.Dir}}", "rsc.io/quote") // now module list should find it too
 	tg.grepStdout(`mod[\\/]rsc.io[\\/]quote@v1.2.0`, "expected cached copy of code")
-}	
+
+	tg.run("list", "std")
+	tg.grepStdout("^math/big", "expected standard library")
+}
 
 func TestModInitLegacy(t *testing.T) {
 	testenv.MustHaveExternalNetwork(t)