[release-branch.go1.16] cmd/go: error out of 'go mod tidy' if the go version is newer than supported

This backports the test from CL 319669, but — because of extensive
changes to the module loader during the Go 1.17 cycle — the
implementation is entirely different. (This implementation is based on
the addGoStmt function present in init.go in the 1.16 branch.)

Fixes #46144
Updates #46142

Change-Id: Ib7a0a159e53cbe476be6aa9a050add10cc750dec
Reviewed-on: https://go-review.googlesource.com/c/go/+/319671
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>
diff --git a/src/cmd/go/internal/modcmd/tidy.go b/src/cmd/go/internal/modcmd/tidy.go
index 8bc9ed5..67f90b1 100644
--- a/src/cmd/go/internal/modcmd/tidy.go
+++ b/src/cmd/go/internal/modcmd/tidy.go
@@ -61,6 +61,8 @@
 	modload.ForceUseModules = true
 	modload.RootMode = modload.NeedRoot
 
+	modload.CheckTidyVersion(ctx, tidyE)
+
 	modload.LoadPackages(ctx, modload.PackageOpts{
 		Tags:                     imports.AnyTags(),
 		ResolveMissingImports:    true,
diff --git a/src/cmd/go/internal/modload/buildlist.go b/src/cmd/go/internal/modload/buildlist.go
index 45f220a..0ed9853 100644
--- a/src/cmd/go/internal/modload/buildlist.go
+++ b/src/cmd/go/internal/modload/buildlist.go
@@ -11,10 +11,13 @@
 	"cmd/go/internal/mvs"
 	"context"
 	"fmt"
+	"go/build"
 	"os"
 	"strings"
 
+	"golang.org/x/mod/modfile"
 	"golang.org/x/mod/module"
+	"golang.org/x/mod/semver"
 )
 
 // buildList is the list of modules to use for building packages.
@@ -226,6 +229,33 @@
 	return capVersionSlice(buildList)
 }
 
+// CheckTidyVersion reports an error to stderr if the Go version indicated by
+// the go.mod file is not supported by this version of the 'go' command.
+//
+// If allowError is false, such an error terminates the program.
+func CheckTidyVersion(ctx context.Context, allowError bool) {
+	LoadModFile(ctx)
+	if index.goVersionV == "" {
+		return
+	}
+
+	tags := build.Default.ReleaseTags
+	maxGo := tags[len(tags)-1]
+	if !strings.HasPrefix(maxGo, "go") || !modfile.GoVersionRE.MatchString(maxGo[2:]) {
+		base.Fatalf("go: unrecognized go version %q", maxGo)
+	}
+	max := maxGo[2:]
+
+	if semver.Compare(index.goVersionV, "v"+max) > 0 {
+		have := index.goVersionV[1:]
+		if allowError {
+			fmt.Fprintf(os.Stderr, "go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", have, max)
+		} else {
+			base.Fatalf("go mod tidy: go.mod file indicates go %s, but maximum supported version is %s\n", have, max)
+		}
+	}
+}
+
 // TidyBuildList trims the build list to the minimal requirements needed to
 // retain the same versions of all packages from the preceding call to
 // LoadPackages.
diff --git a/src/cmd/go/testdata/script/mod_tidy_too_new.txt b/src/cmd/go/testdata/script/mod_tidy_too_new.txt
new file mode 100644
index 0000000..ca3163e
--- /dev/null
+++ b/src/cmd/go/testdata/script/mod_tidy_too_new.txt
@@ -0,0 +1,48 @@
+# https://golang.org/issue/46142: 'go mod tidy' should error out if the version
+# in the go.mod file is newer than the most recent supported version.
+
+cp go.mod go.mod.orig
+
+
+# If the go.mod file specifies an unsupported Go version, 'go mod tidy' should
+# refuse to edit it: we don't know what a tidy go.mod file for that version
+# would look like.
+
+! go mod tidy
+stderr 'go mod tidy: go.mod file indicates go 2000.0, but maximum supported version is '$goversion'$'
+cmp go.mod go.mod.orig
+
+
+# The -e flag should push past the error and edit the file anyway,
+# but preserve the too-high version.
+
+cp go.mod.orig go.mod
+go mod tidy -e
+stderr 'go mod tidy: go.mod file indicates go 2000.0, but maximum supported version is '$goversion'$'
+cmp go.mod go.mod.tidy
+
+
+-- go.mod --
+module example.net/from/the/future
+
+go 2000.0
+
+replace example.net/m v0.0.0 => ./m
+-- go.mod.tidy --
+module example.net/from/the/future
+
+go 2000.0
+
+replace example.net/m v0.0.0 => ./m
+
+require example.net/m v0.0.0
+-- x.go --
+package x
+
+import "example.net/m"
+-- m/go.mod --
+module example.net/m
+
+go 1.17
+-- m/m.go --
+package m