Skip to content

Commit

Permalink
internal/versions: updates the meaning of FileVersions.
Browse files Browse the repository at this point in the history
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 <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Tim King <taking@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
  • Loading branch information
timothy-king committed Feb 28, 2024
1 parent 38b0e9b commit 7f348c7
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 25 deletions.
5 changes: 2 additions & 3 deletions go/analysis/passes/loopclosure/loopclosure.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ func run(pass *analysis.Pass) (interface{}, error) {
switch n := n.(type) {
case *ast.File:
// Only traverse the file if its goversion is strictly before go1.22.
goversion := versions.Lang(versions.FileVersions(pass.TypesInfo, n))
// goversion is empty for older go versions (or the version is invalid).
return goversion == "" || versions.Compare(goversion, "go1.22") < 0
goversion := versions.FileVersion(pass.TypesInfo, n)
return versions.Before(goversion, versions.Go1_22)
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
Expand Down
3 changes: 3 additions & 0 deletions go/analysis/passes/loopclosure/loopclosure_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import (
)

func Test(t *testing.T) {
// legacy loopclosure test expectations are incorrect > 1.21.
testenv.SkipAfterGo1Point(t, 21)

testdata := analysistest.TestData()
analysistest.Run(t, testdata, loopclosure.Analyzer,
"a", "golang.org/...", "subtests", "typeparams")
Expand Down
3 changes: 2 additions & 1 deletion go/analysis/passes/loopclosure/testdata/src/a/a.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains tests for the loopclosure checker.
// This file contains legacy tests for the loopclosure checker.
// Legacy expectations are incorrect after go1.22.

package testdata

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

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

package subtests

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains tests for the loopclosure checker.
// This file contains legacy tests for the loopclosure checker for GoVersion <go1.22.
// Expectations are incorrect after go1.22.

//go:build go1.18

Expand Down Expand Up @@ -45,16 +46,16 @@ type T[P any] struct {
a P
}

func (t T[P]) Go(func() error) { }
func (t T[P]) Go(func() error) {}

func _(g T[errgroup.Group]) {
var s []int
for i, v := range s {
// "T.a" is method "(*...errgroup.Group).Go".
g.a.Go(func() error {
print(i) // want "loop variable i captured by func literal"
print(v) // want "loop variable v captured by func literal"
print(i) // want "loop variable i captured by func literal"
print(v) // want "loop variable v captured by func literal"
return nil
})
}
}
}
5 changes: 2 additions & 3 deletions go/ssa/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -1747,8 +1747,7 @@ func (b *builder) forStmt(fn *Function, s *ast.ForStmt, label *lblock) {
// Use forStmtGo122 instead if it applies.
if s.Init != nil {
if assign, ok := s.Init.(*ast.AssignStmt); ok && assign.Tok == token.DEFINE {
afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0
if afterGo122 {
if versions.AtLeast(fn.goversion, versions.Go1_22) {
b.forStmtGo122(fn, s, label)
return
}
Expand Down Expand Up @@ -2243,7 +2242,7 @@ func (b *builder) rangeStmt(fn *Function, s *ast.RangeStmt, label *lblock) {
}
}

afterGo122 := versions.Compare(fn.goversion, "go1.21") > 0
afterGo122 := versions.AtLeast(fn.goversion, versions.Go1_22)
if s.Tok == token.DEFINE && !afterGo122 {
// pre-go1.22: If iteration variables are defined (:=), this
// occurs once outside the loop.
Expand Down
2 changes: 1 addition & 1 deletion go/ssa/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ func (prog *Program) CreatePackage(pkg *types.Package, files []*ast.File, info *
if len(files) > 0 {
// Go source package.
for _, file := range files {
goversion := versions.Lang(versions.FileVersions(p.info, file))
goversion := versions.Lang(versions.FileVersion(p.info, file))
for _, decl := range file.Decls {
membersFromDecl(p, decl, goversion)
}
Expand Down
43 changes: 43 additions & 0 deletions internal/versions/features.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package versions

// This file contains predicates for working with file versions to
// decide when a tool should consider a language feature enabled.

// GoVersions that features in x/tools can be gated to.
const (
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
Go1_21 = "go1.21"
Go1_22 = "go1.22"
)

// Future is an invalid unknown Go version sometime in the future.
// Do not use directly with Compare.
const Future = ""

// AtLeast reports whether the file version v comes after a Go release.
//
// Use this predicate to enable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func AtLeast(v, release string) bool {
if v == Future {
return true // an unknown future version is always after y.
}
return Compare(Lang(v), Lang(release)) >= 0
}

// Before reports whether the file version v is strictly before a Go release.
//
// Use this predicate to disable a behavior once a certain Go release
// has happened (and stays enabled in the future).
func Before(v, release string) bool {
if v == Future {
return false // an unknown future version happens after y.
}
return Compare(Lang(v), Lang(release)) < 0
}
14 changes: 14 additions & 0 deletions internal/versions/toolchain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package versions

// toolchain is maximum version (<1.22) that the go toolchain used
// to build the current tool is known to support.
//
// When a tool is built with >=1.22, the value of toolchain is unused.
//
// x/tools does not support building with go <1.18. So we take this
// as the minimum possible maximum.
var toolchain string = Go1_18
14 changes: 14 additions & 0 deletions internal/versions/toolchain_go119.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.19
// +build go1.19

package versions

func init() {
if Compare(toolchain, Go1_19) < 0 {
toolchain = Go1_19
}
}
14 changes: 14 additions & 0 deletions internal/versions/toolchain_go120.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.20
// +build go1.20

package versions

func init() {
if Compare(toolchain, Go1_20) < 0 {
toolchain = Go1_20
}
}
14 changes: 14 additions & 0 deletions internal/versions/toolchain_go121.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.21
// +build go1.21

package versions

func init() {
if Compare(toolchain, Go1_21) < 0 {
toolchain = Go1_21
}
}
18 changes: 14 additions & 4 deletions internal/versions/types_go121.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,19 @@ import (
"go/types"
)

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

// InitFileVersions is a noop at this Go version.
// InitFileVersions is a noop when compiled with this Go version.
func InitFileVersions(*types.Info) {}
25 changes: 21 additions & 4 deletions internal/versions/types_go122.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,27 @@ import (
"go/types"
)

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

// InitFileVersions initializes info to record Go versions for Go files.
Expand Down
4 changes: 2 additions & 2 deletions internal/versions/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ func Test(t *testing.T) {
if got, want := versions.GoVersion(pkg), item.pversion; versions.Compare(got, want) != 0 {
t.Errorf("GoVersion()=%q. expected %q", got, want)
}
if got := versions.FileVersions(info, nil); got != "" {
if got := versions.FileVersion(info, nil); got != "" {
t.Errorf(`FileVersions(nil)=%q. expected ""`, got)
}
for i, test := range item.tests {
if got, want := versions.FileVersions(info, files[i]), test.want; got != want {
if got, want := versions.FileVersion(info, files[i]), test.want; got != want {
t.Errorf("FileVersions(%s)=%q. expected %q", test.fname, got, want)
}
}
Expand Down
4 changes: 3 additions & 1 deletion internal/versions/versions.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

package versions

import "strings"
import (
"strings"
)

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

0 comments on commit 7f348c7

Please sign in to comment.