Skip to content

Commit 1ca2185

Browse files
committed
go/analysis: pass package's Go version to type checker
Type checking of a package depends on the Go language version in effect for that package. We have been not setting it and assuming "latest" is good enough, but that is likely to become untrue in the future, and it violates Go 1.21's emphasis on forward compatibility, namely tools recognizing when they shouldn't be processing newer code. Pass the Go version along from go/analysis to go/types, to allow go/types to apply the version when type-checking. In the one analysis pass that does its own type-checking (cgocall), pass the Go version along explicitly there too. Fixes golang/go#61174. Fixes golang/go#61176. Change-Id: I5a357f7c357c41b77d0eb83c905490cc1f866956 Reviewed-on: https://go-review.googlesource.com/c/tools/+/507880 gopls-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> Run-TryBot: Russ Cox <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent a721062 commit 1ca2185

File tree

7 files changed

+151
-8
lines changed

7 files changed

+151
-8
lines changed

go/analysis/analysistest/analysistest.go

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -242,12 +242,16 @@ func RunWithSuggestedFixes(t Testing, dir string, a *analysis.Analyzer, patterns
242242

243243
// Run applies an analysis to the packages denoted by the "go list" patterns.
244244
//
245-
// It loads the packages from the specified GOPATH-style project
245+
// It loads the packages from the specified
246246
// directory using golang.org/x/tools/go/packages, runs the analysis on
247247
// them, and checks that each analysis emits the expected diagnostics
248248
// and facts specified by the contents of '// want ...' comments in the
249249
// package's source files. It treats a comment of the form
250-
// "//...// want..." or "/*...// want... */" as if it starts at 'want'
250+
// "//...// want..." or "/*...// want... */" as if it starts at 'want'.
251+
//
252+
// If the directory contains a go.mod file, Run treats it as the root of the
253+
// Go module in which to work. Otherwise, Run treats it as the root of a
254+
// GOPATH-style tree, with package contained in the src subdirectory.
251255
//
252256
// An expectation of a Diagnostic is specified by a string literal
253257
// containing a regular expression that must match the diagnostic
@@ -309,10 +313,17 @@ func Run(t Testing, dir string, a *analysis.Analyzer, patterns ...string) []*Res
309313
type Result = checker.TestAnalyzerResult
310314

311315
// loadPackages uses go/packages to load a specified packages (from source, with
312-
// dependencies) from dir, which is the root of a GOPATH-style project
313-
// tree. It returns an error if any package had an error, or the pattern
316+
// dependencies) from dir, which is the root of a GOPATH-style project tree.
317+
// loadPackages returns an error if any package had an error, or the pattern
314318
// matched no packages.
315319
func loadPackages(a *analysis.Analyzer, dir string, patterns ...string) ([]*packages.Package, error) {
320+
env := []string{"GOPATH=" + dir, "GO111MODULE=off"} // GOPATH mode
321+
322+
// Undocumented module mode. Will be replaced by something better.
323+
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
324+
env = []string{"GO111MODULE=on", "GOPROXY=off"} // module mode
325+
}
326+
316327
// packages.Load loads the real standard library, not a minimal
317328
// fake version, which would be more efficient, especially if we
318329
// have many small tests that import, say, net/http.
@@ -322,12 +333,12 @@ func loadPackages(a *analysis.Analyzer, dir string, patterns ...string) ([]*pack
322333

323334
mode := packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports |
324335
packages.NeedTypes | packages.NeedTypesSizes | packages.NeedSyntax | packages.NeedTypesInfo |
325-
packages.NeedDeps
336+
packages.NeedDeps | packages.NeedModule
326337
cfg := &packages.Config{
327338
Mode: mode,
328339
Dir: dir,
329340
Tests: true,
330-
Env: append(os.Environ(), "GOPATH="+dir, "GO111MODULE=off", "GOPROXY=off"),
341+
Env: append(os.Environ(), env...),
331342
}
332343
pkgs, err := packages.Load(cfg, patterns...)
333344
if err != nil {

go/analysis/internal/checker/checker.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,7 @@ func load(patterns []string, allSyntax bool) ([]*packages.Package, error) {
172172
if allSyntax {
173173
mode = packages.LoadAllSyntax
174174
}
175+
mode |= packages.NeedModule
175176
conf := packages.Config{
176177
Mode: mode,
177178
Tests: IncludeTests,
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
7+
// Check that GoVersion propagates through to checkers.
8+
// Depends on Go 1.21 go/types.
9+
10+
package versiontest
11+
12+
import (
13+
"os"
14+
"os/exec"
15+
"path/filepath"
16+
"strings"
17+
"testing"
18+
19+
"golang.org/x/tools/go/analysis"
20+
"golang.org/x/tools/go/analysis/analysistest"
21+
"golang.org/x/tools/go/analysis/multichecker"
22+
"golang.org/x/tools/go/analysis/singlechecker"
23+
)
24+
25+
var analyzer = &analysis.Analyzer{
26+
Name: "versiontest",
27+
Doc: "off",
28+
Run: func(pass *analysis.Pass) (interface{}, error) {
29+
pass.Reportf(pass.Files[0].Package, "goversion=%s", pass.Pkg.GoVersion())
30+
return nil, nil
31+
},
32+
}
33+
34+
func init() {
35+
if os.Getenv("VERSIONTEST_MULTICHECKER") == "1" {
36+
multichecker.Main(analyzer)
37+
os.Exit(0)
38+
}
39+
if os.Getenv("VERSIONTEST_SINGLECHECKER") == "1" {
40+
singlechecker.Main(analyzer)
41+
os.Exit(0)
42+
}
43+
}
44+
45+
func testDir(t *testing.T) (dir string) {
46+
dir = t.TempDir()
47+
if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("go 1.20\nmodule m\n"), 0666); err != nil {
48+
t.Fatal(err)
49+
}
50+
if err := os.WriteFile(filepath.Join(dir, "x.go"), []byte("package main // want \"goversion=go1.20\"\n"), 0666); err != nil {
51+
t.Fatal(err)
52+
}
53+
return dir
54+
}
55+
56+
// There are many ways to run analyzers. Test all the ones here in x/tools.
57+
58+
func TestAnalysistest(t *testing.T) {
59+
analysistest.Run(t, testDir(t), analyzer)
60+
}
61+
62+
func TestMultichecker(t *testing.T) {
63+
exe, err := os.Executable()
64+
if err != nil {
65+
t.Fatal(err)
66+
}
67+
cmd := exec.Command(exe, ".")
68+
cmd.Dir = testDir(t)
69+
cmd.Env = append(os.Environ(), "VERSIONTEST_MULTICHECKER=1")
70+
out, err := cmd.CombinedOutput()
71+
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20\n") {
72+
t.Fatalf("multichecker: %v\n%s", err, out)
73+
}
74+
}
75+
76+
func TestSinglechecker(t *testing.T) {
77+
exe, err := os.Executable()
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
cmd := exec.Command(exe, ".")
82+
cmd.Dir = testDir(t)
83+
cmd.Env = append(os.Environ(), "VERSIONTEST_SINGLECHECKER=1")
84+
out, err := cmd.CombinedOutput()
85+
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20\n") {
86+
t.Fatalf("multichecker: %v\n%s", err, out)
87+
}
88+
}
89+
90+
func TestVettool(t *testing.T) {
91+
exe, err := os.Executable()
92+
if err != nil {
93+
t.Fatal(err)
94+
}
95+
cmd := exec.Command("go", "vet", "-vettool="+exe, ".")
96+
cmd.Dir = testDir(t)
97+
cmd.Env = append(os.Environ(), "VERSIONTEST_MULTICHECKER=1")
98+
out, err := cmd.CombinedOutput()
99+
if err == nil || !strings.Contains(string(out), "x.go:1:1: goversion=go1.20\n") {
100+
t.Fatalf("vettool: %v\n%s", err, out)
101+
}
102+
}

go/analysis/passes/cgocall/cgocall.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ func typeCheckCgoSourceFiles(fset *token.FileSet, pkg *types.Package, files []*a
271271
Sizes: sizes,
272272
Error: func(error) {}, // ignore errors (e.g. unused import)
273273
}
274+
setGoVersion(tc, pkg)
274275

275276
// It's tempting to record the new types in the
276277
// existing pass.TypesInfo, but we don't own it.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build !go1.21
6+
7+
package cgocall
8+
9+
import "go/types"
10+
11+
func setGoVersion(tc *types.Config, pkg *types.Package) {
12+
// no types.Package.GoVersion until Go 1.21
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
// Copyright 2023 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
//go:build go1.21
6+
7+
package cgocall
8+
9+
import "go/types"
10+
11+
func setGoVersion(tc *types.Config, pkg *types.Package) {
12+
tc.GoVersion = pkg.GoVersion()
13+
}

go/analysis/unitchecker/unitchecker.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ type Config struct {
6262
Compiler string
6363
Dir string
6464
ImportPath string
65+
GoVersion string // minimum required Go version, such as "go1.21.0"
6566
GoFiles []string
6667
NonGoFiles []string
6768
IgnoredFiles []string
@@ -217,8 +218,9 @@ func run(fset *token.FileSet, cfg *Config, analyzers []*analysis.Analyzer) ([]re
217218
return compilerImporter.Import(path)
218219
})
219220
tc := &types.Config{
220-
Importer: importer,
221-
Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
221+
Importer: importer,
222+
Sizes: types.SizesFor("gc", build.Default.GOARCH), // assume gccgo ≡ gc?
223+
GoVersion: cfg.GoVersion,
222224
}
223225
info := &types.Info{
224226
Types: make(map[ast.Expr]types.TypeAndValue),

0 commit comments

Comments
 (0)