Skip to content

Commit de6db98

Browse files
committed
internal/check: filter out too-new Go versions for type checking
The type checker produces an error if the Go version is too new. When compiled with Go 1.21, this error is silently dropped on the floor and the type checked package is empty, due to golang/go##66525. Guard against this very problematic failure mode by filtering out Go versions that are too new. We should also produce a diagnostic, but that is more complicated and covered by golang/go#61673. Also: fix a bug where sandbox cleanup would fail due to being run with a non-local toolchain. Fixes golang/go#66677 Change-Id: Ia66f17c195382c9c55cf0ef883e898553ce950e3 Reviewed-on: https://go-review.googlesource.com/c/tools/+/576678 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 5c3c2ff commit de6db98

File tree

5 files changed

+95
-2
lines changed

5 files changed

+95
-2
lines changed

gopls/internal/cache/check.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1654,6 +1654,10 @@ func validGoVersion(goVersion string) bool {
16541654
return false // malformed version string
16551655
}
16561656

1657+
if relVer := releaseVersion(); relVer != "" && versions.Before(relVer, goVersion) {
1658+
return false // 'go list' is too new for go/types
1659+
}
1660+
16571661
// TODO(rfindley): remove once we no longer support building gopls with Go
16581662
// 1.20 or earlier.
16591663
if !slices.Contains(build.Default.ReleaseTags, "go1.21") && strings.Count(goVersion, ".") >= 2 {
@@ -1663,6 +1667,19 @@ func validGoVersion(goVersion string) bool {
16631667
return true
16641668
}
16651669

1670+
// releaseVersion reports the Go language version used to compile gopls, or ""
1671+
// if it cannot be determined.
1672+
func releaseVersion() string {
1673+
if len(build.Default.ReleaseTags) > 0 {
1674+
v := build.Default.ReleaseTags[len(build.Default.ReleaseTags)-1]
1675+
var dummy int
1676+
if _, err := fmt.Sscanf(v, "go1.%d", &dummy); err == nil {
1677+
return v
1678+
}
1679+
}
1680+
return ""
1681+
}
1682+
16661683
// depsErrors creates diagnostics for each metadata error (e.g. import cycle).
16671684
// These may be attached to import declarations in the transitive source files
16681685
// of pkg, or to 'requires' declarations in the package's go.mod file.

gopls/internal/test/integration/fake/sandbox.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,9 @@ func (sb *Sandbox) GoVersion(ctx context.Context) (int, error) {
289289
func (sb *Sandbox) Close() error {
290290
var goCleanErr error
291291
if sb.gopath != "" {
292-
goCleanErr = sb.RunGoCommand(context.Background(), "", "clean", []string{"-modcache"}, nil, false)
292+
// Important: run this command in RootDir so that it doesn't interact with
293+
// any toolchain downloads that may occur
294+
goCleanErr = sb.RunGoCommand(context.Background(), sb.RootDir(), "clean", []string{"-modcache"}, nil, false)
293295
}
294296
err := robustio.RemoveAll(sb.rootdir)
295297
if err != nil || goCleanErr != nil {

gopls/internal/test/integration/workspace/goversion_test.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ package workspace
77
import (
88
"flag"
99
"os"
10+
"os/exec"
1011
"runtime"
12+
"strings"
1113
"testing"
1214

1315
. "golang.org/x/tools/gopls/internal/test/integration"
16+
"golang.org/x/tools/internal/testenv"
1417
)
1518

1619
var go121bin = flag.String("go121bin", "", "bin directory containing go 1.21 or later")
@@ -60,3 +63,64 @@ type I interface { string }
6063
)
6164
})
6265
}
66+
67+
func TestTypeCheckingFutureVersions(t *testing.T) {
68+
// This test checks the regression in golang/go#66677, where go/types fails
69+
// silently when the language version is 1.22.
70+
//
71+
// It does this by recreating the scenario of a toolchain upgrade to 1.22, as
72+
// reported in the issue. For this to work, the test must be able to download
73+
// toolchains from proxy.golang.org.
74+
//
75+
// This is really only a problem for Go 1.21, because with Go 1.23, the bug
76+
// is fixed, and starting with 1.23 we're going to *require* 1.23 to build
77+
// gopls.
78+
//
79+
// TODO(golang/go#65917): delete this test after Go 1.23 is released and
80+
// gopls requires the latest Go to build.
81+
testenv.SkipAfterGo1Point(t, 21)
82+
83+
if testing.Short() {
84+
t.Skip("skipping with -short, as this test uses the network")
85+
}
86+
87+
// If go 1.22.2 is already available in the module cache, reuse it rather
88+
// than downloading it anew.
89+
out, err := exec.Command("go", "env", "GOPATH").Output()
90+
if err != nil {
91+
t.Fatal(err)
92+
}
93+
gopath := strings.TrimSpace(string(out)) // use the ambient 1.22.2 toolchain if available
94+
95+
const files = `
96+
-- go.mod --
97+
module example.com/foo
98+
99+
go 1.22.2
100+
101+
-- main.go --
102+
package main
103+
104+
func main() {
105+
x := 1
106+
}
107+
`
108+
109+
WithOptions(
110+
Modes(Default), // slow test, only run in one mode
111+
EnvVars{
112+
"GOPATH": gopath,
113+
"GOTOOLCHAIN": "", // not local
114+
"GOPROXY": "https://proxy.golang.org",
115+
"GOSUMDB": "sum.golang.org",
116+
},
117+
).Run(t, files, func(t *testing.T, env *Env) {
118+
env.OpenFile("main.go")
119+
env.AfterChange(
120+
Diagnostics(
121+
env.AtRegexp("main.go", "x"),
122+
WithMessage("not used"),
123+
),
124+
)
125+
})
126+
}

gopls/internal/test/marker/doc.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ treatment by the test runner:
4343
4444
- "flags": this file is treated as a whitespace-separated list of flags
4545
that configure the MarkerTest instance. Supported flags:
46-
-min_go=go1.20 sets the minimum Go version for the test;
46+
-{min,max}_go=go1.20 sets the {min,max}imum Go version for the test
47+
(inclusive)
4748
-cgo requires that CGO_ENABLED is set and the cgo tool is available
4849
-write_sumfile=a,b,c instructs the test runner to generate go.sum files
4950
in these directories before running the test.

gopls/internal/test/marker/marker_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ func Test(t *testing.T) {
131131
}
132132
testenv.NeedsGo1Point(t, go1point)
133133
}
134+
if test.maxGoVersion != "" {
135+
var go1point int
136+
if _, err := fmt.Sscanf(test.maxGoVersion, "go1.%d", &go1point); err != nil {
137+
t.Fatalf("parsing -max_go version: %v", err)
138+
}
139+
testenv.SkipAfterGo1Point(t, go1point)
140+
}
134141
if test.cgo {
135142
testenv.NeedsTool(t, "cgo")
136143
}
@@ -498,6 +505,7 @@ type markerTest struct {
498505

499506
// Parsed flags values.
500507
minGoVersion string
508+
maxGoVersion string
501509
cgo bool
502510
writeGoSum []string // comma separated dirs to write go sum for
503511
skipGOOS []string // comma separated GOOS values to skip
@@ -512,6 +520,7 @@ type markerTest struct {
512520
func (t *markerTest) flagSet() *flag.FlagSet {
513521
flags := flag.NewFlagSet(t.name, flag.ContinueOnError)
514522
flags.StringVar(&t.minGoVersion, "min_go", "", "if set, the minimum go1.X version required for this test")
523+
flags.StringVar(&t.maxGoVersion, "max_go", "", "if set, the maximum go1.X version required for this test")
515524
flags.BoolVar(&t.cgo, "cgo", false, "if set, requires cgo (both the cgo tool and CGO_ENABLED=1)")
516525
flags.Var((*stringListValue)(&t.writeGoSum), "write_sumfile", "if set, write the sumfile for these directories")
517526
flags.Var((*stringListValue)(&t.skipGOOS), "skip_goos", "if set, skip this test on these GOOS values")

0 commit comments

Comments
 (0)