Skip to content

Commit 7f348c7

Browse files
committed
internal/versions: updates the meaning of FileVersions.
For Go >=1.22, FileVersions returns an unknown [invalid] Future version when the file versions would be invalid. This better matches go/types. For Go <=1.21, FileVersions returns either the runtime.Version() or the Future version. Adds AtLeast and Before for doing comparisons with Future. Updates golang/go#65612 Fixes golang/go#66007 Change-Id: I93ff1681b0f9117765614a20d82642749597307c Reviewed-on: https://go-review.googlesource.com/c/tools/+/567635 Reviewed-by: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Run-TryBot: Tim King <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent 38b0e9b commit 7f348c7

17 files changed

+274
-25
lines changed

go/analysis/passes/loopclosure/loopclosure.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
5555
switch n := n.(type) {
5656
case *ast.File:
5757
// Only traverse the file if its goversion is strictly before go1.22.
58-
goversion := versions.Lang(versions.FileVersions(pass.TypesInfo, n))
59-
// goversion is empty for older go versions (or the version is invalid).
60-
return goversion == "" || versions.Compare(goversion, "go1.22") < 0
58+
goversion := versions.FileVersion(pass.TypesInfo, n)
59+
return versions.Before(goversion, versions.Go1_22)
6160
case *ast.RangeStmt:
6261
body = n.Body
6362
addVar(n.Key)

go/analysis/passes/loopclosure/loopclosure_test.go

+3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import (
1717
)
1818

1919
func Test(t *testing.T) {
20+
// legacy loopclosure test expectations are incorrect > 1.21.
21+
testenv.SkipAfterGo1Point(t, 21)
22+
2023
testdata := analysistest.TestData()
2124
analysistest.Run(t, testdata, loopclosure.Analyzer,
2225
"a", "golang.org/...", "subtests", "typeparams")

go/analysis/passes/loopclosure/testdata/src/a/a.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// This file contains tests for the loopclosure checker.
5+
// This file contains legacy tests for the loopclosure checker.
6+
// Legacy expectations are incorrect after go1.22.
67

78
package testdata
89

go/analysis/passes/loopclosure/testdata/src/subtests/subtest.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// This file contains tests that the loopclosure analyzer detects leaked
5+
// This file contains legacy tests that the loopclosure analyzer detects leaked
66
// references via parallel subtests.
7+
// Legacy expectations are incorrect after go1.22.
78

89
package subtests
910

go/analysis/passes/loopclosure/testdata/src/typeparams/typeparams.go

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
// This file contains tests for the loopclosure checker.
5+
// This file contains legacy tests for the loopclosure checker for GoVersion <go1.22.
6+
// Expectations are incorrect after go1.22.
67

78
//go:build go1.18
89

@@ -45,16 +46,16 @@ type T[P any] struct {
4546
a P
4647
}
4748

48-
func (t T[P]) Go(func() error) { }
49+
func (t T[P]) Go(func() error) {}
4950

5051
func _(g T[errgroup.Group]) {
5152
var s []int
5253
for i, v := range s {
5354
// "T.a" is method "(*...errgroup.Group).Go".
5455
g.a.Go(func() error {
55-
print(i) // want "loop variable i captured by func literal"
56-
print(v) // want "loop variable v captured by func literal"
56+
print(i) // want "loop variable i captured by func literal"
57+
print(v) // want "loop variable v captured by func literal"
5758
return nil
5859
})
5960
}
60-
}
61+
}

go/ssa/builder.go

+2-3
Original file line numberDiff line numberDiff line change
@@ -1747,8 +1747,7 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
17471747
// Use forStmtGo122 instead if it applies.
17481748
if s.Init != nil {
17491749
if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
1750-
afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0
1751-
if afterGo122 {
1750+
if versions.AtLeast(fn.goversion, versions.Go1_22) {
17521751
b.forStmtGo122(fn, s, label)
17531752
return
17541753
}
@@ -2243,7 +2242,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
22432242
}
22442243
}
22452244

2246-
afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0
2245+
afterGo122 := versions.AtLeast(fn.goversion, versions.Go1_22)
22472246
if s.Tok == token.DEFINE && !afterGo122 {
22482247
// pre-go1.22: If iteration variables are defined (:=), this
22492248
// occurs once outside the loop.

go/ssa/create.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *
245245
if len(files) > 0 {
246246
// Go source package.
247247
for _, file := range files {
248-
goversion := versions.Lang(versions.FileVersions(p.info, file))
248+
goversion := versions.Lang(versions.FileVersion(p.info, file))
249249
for _, decl := range file.Decls {
250250
membersFromDecl(p, decl, goversion)
251251
}

internal/versions/features.go

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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+
package versions
6+
7+
// This file contains predicates for working with file versions to
8+
// decide when a tool should consider a language feature enabled.
9+
10+
// GoVersions that features in x/tools can be gated to.
11+
const (
12+
Go1_18 = "go1.18"
13+
Go1_19 = "go1.19"
14+
Go1_20 = "go1.20"
15+
Go1_21 = "go1.21"
16+
Go1_22 = "go1.22"
17+
)
18+
19+
// Future is an invalid unknown Go version sometime in the future.
20+
// Do not use directly with Compare.
21+
const Future = ""
22+
23+
// AtLeast reports whether the file version v comes after a Go release.
24+
//
25+
// Use this predicate to enable a behavior once a certain Go release
26+
// has happened (and stays enabled in the future).
27+
func AtLeast(v, release string) bool {
28+
if v == Future {
29+
return true // an unknown future version is always after y.
30+
}
31+
return Compare(Lang(v), Lang(release)) >= 0
32+
}
33+
34+
// Before reports whether the file version v is strictly before a Go release.
35+
//
36+
// Use this predicate to disable a behavior once a certain Go release
37+
// has happened (and stays enabled in the future).
38+
func Before(v, release string) bool {
39+
if v == Future {
40+
return false // an unknown future version happens after y.
41+
}
42+
return Compare(Lang(v), Lang(release)) < 0
43+
}

internal/versions/toolchain.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2024 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+
package versions
6+
7+
// toolchain is maximum version (<1.22) that the go toolchain used
8+
// to build the current tool is known to support.
9+
//
10+
// When a tool is built with >=1.22, the value of toolchain is unused.
11+
//
12+
// x/tools does not support building with go <1.18. So we take this
13+
// as the minimum possible maximum.
14+
var toolchain string = Go1_18

internal/versions/toolchain_go119.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2024 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.19
6+
// +build go1.19
7+
8+
package versions
9+
10+
func init() {
11+
if Compare(toolchain, Go1_19) < 0 {
12+
toolchain = Go1_19
13+
}
14+
}

internal/versions/toolchain_go120.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2024 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.20
6+
// +build go1.20
7+
8+
package versions
9+
10+
func init() {
11+
if Compare(toolchain, Go1_20) < 0 {
12+
toolchain = Go1_20
13+
}
14+
}

internal/versions/toolchain_go121.go

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Copyright 2024 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+
// +build go1.21
7+
8+
package versions
9+
10+
func init() {
11+
if Compare(toolchain, Go1_21) < 0 {
12+
toolchain = Go1_21
13+
}
14+
}

internal/versions/types_go121.go

+14-4
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,19 @@ import (
1212
"go/types"
1313
)
1414

15-
// FileVersions always reports the a file's Go version as the
16-
// zero version at this Go version.
17-
func FileVersions(info *types.Info, file *ast.File) string { return "" }
15+
// FileVersion returns a language version (<=1.21) derived from runtime.Version()
16+
// or an unknown future version.
17+
func FileVersion(info *types.Info, file *ast.File) string {
18+
// In x/tools built with Go <= 1.21, we do not have Info.FileVersions
19+
// available. We use a go version derived from the toolchain used to
20+
// compile the tool by default.
21+
// This will be <= go1.21. We take this as the maximum version that
22+
// this tool can support.
23+
//
24+
// There are no features currently in x/tools that need to tell fine grained
25+
// differences for versions <1.22.
26+
return toolchain
27+
}
1828

19-
// InitFileVersions is a noop at this Go version.
29+
// InitFileVersions is a noop when compiled with this Go version.
2030
func InitFileVersions(*types.Info) {}

internal/versions/types_go122.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,27 @@ import (
1212
"go/types"
1313
)
1414

15-
// FileVersions maps a file to the file's semantic Go version.
16-
// The reported version is the zero version if a version cannot be determined.
17-
func FileVersions(info *types.Info, file *ast.File) string {
18-
return info.FileVersions[file]
15+
// FileVersions returns a file's Go version.
16+
// The reported version is an unknown Future version if a
17+
// version cannot be determined.
18+
func FileVersion(info *types.Info, file *ast.File) string {
19+
// In tools built with Go >= 1.22, the Go version of a file
20+
// follow a cascades of sources:
21+
// 1) types.Info.FileVersion, which follows the cascade:
22+
// 1.a) file version (ast.File.GoVersion),
23+
// 1.b) the package version (types.Config.GoVersion), or
24+
// 2) is some unknown Future version.
25+
//
26+
// File versions require a valid package version to be provided to types
27+
// in Config.GoVersion. Config.GoVersion is either from the package's module
28+
// or the toolchain (go run). This value should be provided by go/packages
29+
// or unitchecker.Config.GoVersion.
30+
if v := info.FileVersions[file]; IsValid(v) {
31+
return v
32+
}
33+
// Note: we could instead return runtime.Version() [if valid].
34+
// This would act as a max version on what a tool can support.
35+
return Future
1936
}
2037

2138
// InitFileVersions initializes info to record Go versions for Go files.

internal/versions/types_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ func Test(t *testing.T) {
5252
if got, want := versions.GoVersion(pkg), item.pversion; versions.Compare(got, want) != 0 {
5353
t.Errorf("GoVersion()=%q. expected %q", got, want)
5454
}
55-
if got := versions.FileVersions(info, nil); got != "" {
55+
if got := versions.FileVersion(info, nil); got != "" {
5656
t.Errorf(`FileVersions(nil)=%q. expected ""`, got)
5757
}
5858
for i, test := range item.tests {
59-
if got, want := versions.FileVersions(info, files[i]), test.want; got != want {
59+
if got, want := versions.FileVersion(info, files[i]), test.want; got != want {
6060
t.Errorf("FileVersions(%s)=%q. expected %q", test.fname, got, want)
6161
}
6262
}

internal/versions/versions.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44

55
package versions
66

7-
import "strings"
7+
import (
8+
"strings"
9+
)
810

911
// Note: If we use build tags to use go/versions when go >=1.22,
1012
// we run into go.dev/issue/53737. Under some operations users would see an

0 commit comments

Comments
 (0)