go/analysis/passes/modernize: respect bootstrap toolchain version
fileMayUse (formerly fileUses) now checks not only the FileVersion
but the bootstrap toolchain version too, so that, for example,
we don't prematurely modernize packages in the compiler to use
features of go1.26.
Unfortunately this change cannot be tested via the analysistest
framework because it would require that we overwrite the sources
of actual std packages.
lostcancel now uses (a copy of) fileMayUse too. I will merge
and move both copies to analyzerutil once CL 718081 is merged.
For golang/go#71859
diff --git a/go/analysis/passes/loopclosure/loopclosure.go b/go/analysis/passes/loopclosure/loopclosure.go
index 8682263..677748c 100644
--- a/go/analysis/passes/loopclosure/loopclosure.go
+++ b/go/analysis/passes/loopclosure/loopclosure.go
@@ -14,6 +14,8 @@
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
+ "golang.org/x/tools/internal/packagepath"
+ "golang.org/x/tools/internal/stdlib"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/versions"
)
@@ -55,8 +57,8 @@
switch n := n.(type) {
case *ast.File:
// Only traverse the file if its goversion is strictly before go1.22.
- goversion := versions.FileVersion(pass.TypesInfo, n)
- return versions.Before(goversion, versions.Go1_22)
+ return !fileMayUse(pass, n, versions.Go1_22)
+
case *ast.RangeStmt:
body = n.Body
addVar(n.Key)
@@ -356,6 +358,7 @@
}
// Check that we are calling a method <method>
+ // TODO(adonovan): use [typesinternal.IsMethodNamed].
f := typeutil.StaticCallee(info, call)
if f == nil || f.Name() != method {
return false
@@ -370,3 +373,30 @@
_, named := typesinternal.ReceiverNamed(recv)
return typesinternal.IsTypeNamed(named, pkgPath, typeName)
}
+
+// fileMayUse reports whether the specified file may use features of the
+// specified version of Go (e.g. "go1.24").
+//
+// Tip: we recommend using this check "late", just before calling
+// pass.Report, rather than "early" (when entering each ast.File, or
+// each candidate node of interest, during the traversal), because the
+// operation is not free, yet is not a highly selective filter: the
+// fraction of files that pass most version checks is high and
+// increases over time.
+//
+// TODO(adonovan): move to analyzer library.
+func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
+ // Standard packages that are part of toolchain bootstrapping
+ // are not considered to use a version of Go later than the
+ // current bootstrap toolchain version.
+ pkgpath := pass.Pkg.Path()
+ if packagepath.IsStdPackage(pkgpath) &&
+ stdlib.IsBootstrapPackage(pkgpath) &&
+ versions.Before(version, stdlib.BootstrapVersion.String()) {
+ return false // package must bootstrap
+ }
+ if versions.Before(pass.TypesInfo.FileVersions[file], version) {
+ return false // file version is too old
+ }
+ return true // ok
+}
diff --git a/go/analysis/passes/modernize/any.go b/go/analysis/passes/modernize/any.go
index 05999f8..3685f57 100644
--- a/go/analysis/passes/modernize/any.go
+++ b/go/analysis/passes/modernize/any.go
@@ -9,9 +9,9 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
+ "golang.org/x/tools/internal/versions"
)
var AnyAnalyzer = &analysis.Analyzer{
@@ -29,9 +29,7 @@
func runAny(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
-
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.18") {
+ for curFile := range filesUsing(pass, versions.Go1_18) {
for curIface := range curFile.Preorder((*ast.InterfaceType)(nil)) {
iface := curIface.Node().(*ast.InterfaceType)
diff --git a/go/analysis/passes/modernize/bloop.go b/go/analysis/passes/modernize/bloop.go
index 90b23ad..70cfdfe 100644
--- a/go/analysis/passes/modernize/bloop.go
+++ b/go/analysis/passes/modernize/bloop.go
@@ -22,6 +22,7 @@
"golang.org/x/tools/internal/moreiters"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var BLoopAnalyzer = &analysis.Analyzer{
@@ -52,9 +53,8 @@
}
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
)
// edits computes the text edits for a matched for/range loop
@@ -102,7 +102,7 @@
(*ast.ForStmt)(nil),
(*ast.RangeStmt)(nil),
}
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsing(pass, versions.Go1_24) {
for curLoop := range curFile.Preorder(loops...) {
switch n := curLoop.Node().(type) {
case *ast.ForStmt:
diff --git a/go/analysis/passes/modernize/errorsastype.go b/go/analysis/passes/modernize/errorsastype.go
index b6387ad..5ee7e20 100644
--- a/go/analysis/passes/modernize/errorsastype.go
+++ b/go/analysis/passes/modernize/errorsastype.go
@@ -22,6 +22,7 @@
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var errorsastypeAnalyzer = &analysis.Analyzer{
@@ -97,7 +98,7 @@
}
file := astutil.EnclosingFile(curDeclStmt)
- if !fileUses(info, file, "go1.26") {
+ if !fileMayUse(pass, file, versions.Go1_26) {
continue // errors.AsType is too new
}
diff --git a/go/analysis/passes/modernize/fmtappendf.go b/go/analysis/passes/modernize/fmtappendf.go
index f2e5360..3bba708 100644
--- a/go/analysis/passes/modernize/fmtappendf.go
+++ b/go/analysis/passes/modernize/fmtappendf.go
@@ -18,6 +18,7 @@
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var FmtAppendfAnalyzer = &analysis.Analyzer{
@@ -50,7 +51,7 @@
conv := curCall.Parent().Node().(*ast.CallExpr)
tv := pass.TypesInfo.Types[conv.Fun]
if tv.IsType() && types.Identical(tv.Type, byteSliceType) &&
- fileUses(pass.TypesInfo, astutil.EnclosingFile(curCall), "go1.19") {
+ fileMayUse(pass, astutil.EnclosingFile(curCall), versions.Go1_19) {
// Have: []byte(fmt.SprintX(...))
// Find "Sprint" identifier.
diff --git a/go/analysis/passes/modernize/forvar.go b/go/analysis/passes/modernize/forvar.go
index 76e3a8a..20ac3e8 100644
--- a/go/analysis/passes/modernize/forvar.go
+++ b/go/analysis/passes/modernize/forvar.go
@@ -10,11 +10,11 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
+ "golang.org/x/tools/internal/versions"
)
var ForVarAnalyzer = &analysis.Analyzer{
@@ -45,8 +45,7 @@
func forvar(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.22") {
+ for curFile := range filesUsing(pass, versions.Go1_22) {
for curLoop := range curFile.Preorder((*ast.RangeStmt)(nil)) {
loop := curLoop.Node().(*ast.RangeStmt)
if loop.Tok != token.DEFINE {
diff --git a/go/analysis/passes/modernize/maps.go b/go/analysis/passes/modernize/maps.go
index 3072cf6..7ef2a21 100644
--- a/go/analysis/passes/modernize/maps.go
+++ b/go/analysis/passes/modernize/maps.go
@@ -21,6 +21,7 @@
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
var MapsLoopAnalyzer = &analysis.Analyzer{
@@ -223,8 +224,7 @@
}
// Find all range loops around m[k] = v.
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.23") {
+ for curFile := range filesUsing(pass, versions.Go1_23) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
diff --git a/go/analysis/passes/modernize/minmax.go b/go/analysis/passes/modernize/minmax.go
index 7ebf837..2877e93 100644
--- a/go/analysis/passes/modernize/minmax.go
+++ b/go/analysis/passes/modernize/minmax.go
@@ -21,6 +21,7 @@
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var MinMaxAnalyzer = &analysis.Analyzer{
@@ -201,8 +202,7 @@
// Find all "if a < b { lhs = rhs }" statements.
info := pass.TypesInfo
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsing(pass, versions.Go1_21) {
astFile := curFile.Node().(*ast.File)
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
diff --git a/go/analysis/passes/modernize/modernize.go b/go/analysis/passes/modernize/modernize.go
index 28bd101..bc8a265 100644
--- a/go/analysis/passes/modernize/modernize.go
+++ b/go/analysis/passes/modernize/modernize.go
@@ -16,6 +16,7 @@
"strings"
"golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
"golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal/generated"
@@ -95,21 +96,25 @@
// filesUsing returns a cursor for each *ast.File in the inspector
// that uses at least the specified version of Go (e.g. "go1.24").
//
+// The pass's analyzer must require [inspect.Analyzer].
+//
// TODO(adonovan): opt: eliminate this function, instead following the
-// approach of [fmtappendf], which uses typeindex and [fileUses].
-// See "Tip" at [fileUses] for motivation.
-func filesUsing(inspect *inspector.Inspector, info *types.Info, version string) iter.Seq[inspector.Cursor] {
+// approach of [fmtappendf], which uses typeindex and [fileMayUse].
+// See "Tip" at [fileMayUse] for motivation.
+func filesUsing(pass *analysis.Pass, version string) iter.Seq[inspector.Cursor] {
+ inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+
return func(yield func(inspector.Cursor) bool) {
for curFile := range inspect.Root().Children() {
file := curFile.Node().(*ast.File)
- if !versions.Before(info.FileVersions[file], version) && !yield(curFile) {
+ if fileMayUse(pass, file, version) && !yield(curFile) {
break
}
}
}
}
-// fileUses reports whether the specified file uses at least the
+// fileMayUse reports whether the specified file may use features of the
// specified version of Go (e.g. "go1.24").
//
// Tip: we recommend using this check "late", just before calling
@@ -118,8 +123,22 @@
// operation is not free, yet is not a highly selective filter: the
// fraction of files that pass most version checks is high and
// increases over time.
-func fileUses(info *types.Info, file *ast.File, version string) bool {
- return !versions.Before(info.FileVersions[file], version)
+//
+// TODO(adonovan): move to analyzer library.
+func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
+ // Standard packages that are part of toolchain bootstrapping
+ // are not considered to use a version of Go later than the
+ // current bootstrap toolchain version.
+ pkgpath := pass.Pkg.Path()
+ if packagepath.IsStdPackage(pkgpath) &&
+ stdlib.IsBootstrapPackage(pkgpath) &&
+ versions.Before(version, stdlib.BootstrapVersion.String()) {
+ return false // package must bootstrap
+ }
+ if versions.Before(pass.TypesInfo.FileVersions[file], version) {
+ return false // file version is too old
+ }
+ return true // ok
}
// within reports whether the current pass is analyzing one of the
diff --git a/go/analysis/passes/modernize/newexpr.go b/go/analysis/passes/modernize/newexpr.go
index 2d82e6f..a808c7b 100644
--- a/go/analysis/passes/modernize/newexpr.go
+++ b/go/analysis/passes/modernize/newexpr.go
@@ -19,6 +19,7 @@
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/versions"
)
var NewExprAnalyzer = &analysis.Analyzer{
@@ -60,7 +61,7 @@
// Check file version.
file := astutil.EnclosingFile(curFuncDecl)
- if !fileUses(info, file, "go1.26") {
+ if !fileMayUse(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
@@ -133,7 +134,7 @@
// Check file version.
file := astutil.EnclosingFile(curCall)
- if !fileUses(info, file, "go1.26") {
+ if !fileMayUse(pass, file, versions.Go1_26) {
continue // new(expr) not available in this file
}
diff --git a/go/analysis/passes/modernize/omitzero.go b/go/analysis/passes/modernize/omitzero.go
index 1405d13..34e2ed7 100644
--- a/go/analysis/passes/modernize/omitzero.go
+++ b/go/analysis/passes/modernize/omitzero.go
@@ -12,10 +12,10 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/versions"
)
var OmitZeroAnalyzer = &analysis.Analyzer{
@@ -101,12 +101,10 @@
func omitzero(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- info := pass.TypesInfo
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsing(pass, versions.Go1_24) {
for curStruct := range curFile.Preorder((*ast.StructType)(nil)) {
for _, curField := range curStruct.Node().(*ast.StructType).Fields.List {
- checkOmitEmptyField(pass, info, curField)
+ checkOmitEmptyField(pass, pass.TypesInfo, curField)
}
}
}
diff --git a/go/analysis/passes/modernize/plusbuild.go b/go/analysis/passes/modernize/plusbuild.go
index e8af807..1b695d2 100644
--- a/go/analysis/passes/modernize/plusbuild.go
+++ b/go/analysis/passes/modernize/plusbuild.go
@@ -12,6 +12,7 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/goplsexport"
+ "golang.org/x/tools/internal/versions"
)
var plusBuildAnalyzer = &analysis.Analyzer{
@@ -28,7 +29,7 @@
func plusbuild(pass *analysis.Pass) (any, error) {
check := func(f *ast.File) {
- if !fileUses(pass.TypesInfo, f, "go1.18") {
+ if !fileMayUse(pass, f, versions.Go1_18) {
return
}
diff --git a/go/analysis/passes/modernize/rangeint.go b/go/analysis/passes/modernize/rangeint.go
index eebe740..d887a89 100644
--- a/go/analysis/passes/modernize/rangeint.go
+++ b/go/analysis/passes/modernize/rangeint.go
@@ -21,6 +21,7 @@
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var RangeIntAnalyzer = &analysis.Analyzer{
@@ -68,12 +69,12 @@
func rangeint(pass *analysis.Pass) (any, error) {
skipGenerated(pass)
- info := pass.TypesInfo
+ var (
+ info = pass.TypesInfo
+ typeindex = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ )
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- typeindex := pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
-
- for curFile := range filesUsing(inspect, info, "go1.22") {
+ for curFile := range filesUsing(pass, versions.Go1_22) {
nextLoop:
for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
loop := curLoop.Node().(*ast.ForStmt)
diff --git a/go/analysis/passes/modernize/reflect.go b/go/analysis/passes/modernize/reflect.go
index c9b0fa4..6d5e867 100644
--- a/go/analysis/passes/modernize/reflect.go
+++ b/go/analysis/passes/modernize/reflect.go
@@ -89,7 +89,7 @@
}
file := astutil.EnclosingFile(curCall)
- if versions.Before(info.FileVersions[file], "go1.22") {
+ if !fileMayUse(pass, file, versions.Go1_22) {
continue // TypeFor requires go1.22
}
tokFile := pass.Fset.File(file.Pos())
diff --git a/go/analysis/passes/modernize/slices.go b/go/analysis/passes/modernize/slices.go
index 23b3952..f527d5d 100644
--- a/go/analysis/passes/modernize/slices.go
+++ b/go/analysis/passes/modernize/slices.go
@@ -13,13 +13,13 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default.
@@ -205,8 +205,7 @@
skip := make(map[*ast.CallExpr]bool)
// Visit calls of form append(x, y...).
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsing(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
diff --git a/go/analysis/passes/modernize/slicescontains.go b/go/analysis/passes/modernize/slicescontains.go
index b3c2e56..6dfaf5d 100644
--- a/go/analysis/passes/modernize/slicescontains.go
+++ b/go/analysis/passes/modernize/slicescontains.go
@@ -21,6 +21,7 @@
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typeparams"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var SlicesContainsAnalyzer = &analysis.Analyzer{
@@ -75,9 +76,8 @@
}
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
)
// check is called for each RangeStmt of this form:
@@ -388,7 +388,7 @@
}
}
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsing(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
diff --git a/go/analysis/passes/modernize/slicesdelete.go b/go/analysis/passes/modernize/slicesdelete.go
index b3e063d..ff38e2f 100644
--- a/go/analysis/passes/modernize/slicesdelete.go
+++ b/go/analysis/passes/modernize/slicesdelete.go
@@ -12,12 +12,12 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
+ "golang.org/x/tools/internal/versions"
)
// Warning: this analyzer is not safe to enable by default (not nil-preserving).
@@ -45,7 +45,6 @@
return nil, nil
}
- inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
info := pass.TypesInfo
report := func(file *ast.File, call *ast.CallExpr, slice1, slice2 *ast.SliceExpr) {
insert := func(pos token.Pos, text string) analysis.TextEdit {
@@ -55,7 +54,7 @@
return types.Identical(types.Default(info.TypeOf(e)), builtinInt.Type())
}
isIntShadowed := func() bool {
- scope := pass.TypesInfo.Scopes[file].Innermost(call.Lparen)
+ scope := info.Scopes[file].Innermost(call.Lparen)
if _, obj := scope.LookupParent("int", call.Lparen); obj != builtinInt {
return true // int type is shadowed
}
@@ -130,7 +129,7 @@
}},
})
}
- for curFile := range filesUsing(inspect, info, "go1.21") {
+ for curFile := range filesUsing(pass, versions.Go1_21) {
file := curFile.Node().(*ast.File)
for curCall := range curFile.Preorder((*ast.CallExpr)(nil)) {
call := curCall.Node().(*ast.CallExpr)
diff --git a/go/analysis/passes/modernize/sortslice.go b/go/analysis/passes/modernize/sortslice.go
index 66af16d..f8d18ed 100644
--- a/go/analysis/passes/modernize/sortslice.go
+++ b/go/analysis/passes/modernize/sortslice.go
@@ -17,6 +17,7 @@
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
// (Not to be confused with go/analysis/passes/sortslice.)
@@ -87,7 +88,7 @@
}
file := astutil.EnclosingFile(curCall)
if isIndex(compare.X, i) && isIndex(compare.Y, j) &&
- fileUses(info, file, "go1.21") {
+ fileMayUse(pass, file, versions.Go1_21) {
// Have: sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
prefix, importEdits := refactor.AddImport(
diff --git a/go/analysis/passes/modernize/stditerators.go b/go/analysis/passes/modernize/stditerators.go
index 0621448..03df5ba 100644
--- a/go/analysis/passes/modernize/stditerators.go
+++ b/go/analysis/passes/modernize/stditerators.go
@@ -313,7 +313,7 @@
// may be somewhat expensive.)
if v, ok := methodGoVersion(row.pkgpath, row.typename, row.itermethod); !ok {
panic("no version found")
- } else if file := astutil.EnclosingFile(curLenCall); !fileUses(info, file, v.String()) {
+ } else if file := astutil.EnclosingFile(curLenCall); !fileMayUse(pass, file, v.String()) {
continue nextCall
}
diff --git a/go/analysis/passes/modernize/stringscut.go b/go/analysis/passes/modernize/stringscut.go
index 1342965..719e134 100644
--- a/go/analysis/passes/modernize/stringscut.go
+++ b/go/analysis/passes/modernize/stringscut.go
@@ -26,6 +26,7 @@
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var stringscutAnalyzer = &analysis.Analyzer{
@@ -114,7 +115,7 @@
for curCall := range index.Calls(obj) {
// Check file version.
file := astutil.EnclosingFile(curCall)
- if !fileUses(info, file, "go1.18") {
+ if !fileMayUse(pass, file, versions.Go1_18) {
continue // strings.Index not available in this file
}
indexCall := curCall.Node().(*ast.CallExpr) // the call to strings.Index, etc.
diff --git a/go/analysis/passes/modernize/stringscutprefix.go b/go/analysis/passes/modernize/stringscutprefix.go
index 9e76f95..113b76c 100644
--- a/go/analysis/passes/modernize/stringscutprefix.go
+++ b/go/analysis/passes/modernize/stringscutprefix.go
@@ -12,7 +12,6 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
@@ -21,6 +20,7 @@
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var StringsCutPrefixAnalyzer = &analysis.Analyzer{
@@ -59,9 +59,8 @@
skipGenerated(pass)
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
stringsTrimPrefix = index.Object("strings", "TrimPrefix")
bytesTrimPrefix = index.Object("bytes", "TrimPrefix")
@@ -72,7 +71,7 @@
return nil, nil
}
- for curFile := range filesUsing(inspect, pass.TypesInfo, "go1.20") {
+ for curFile := range filesUsing(pass, versions.Go1_20) {
for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
ifStmt := curIfStmt.Node().(*ast.IfStmt)
diff --git a/go/analysis/passes/modernize/stringsseq.go b/go/analysis/passes/modernize/stringsseq.go
index ef2b546..9bacddb 100644
--- a/go/analysis/passes/modernize/stringsseq.go
+++ b/go/analysis/passes/modernize/stringsseq.go
@@ -13,12 +13,12 @@
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
"golang.org/x/tools/go/ast/edge"
- "golang.org/x/tools/go/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysisinternal"
"golang.org/x/tools/internal/analysisinternal/generated"
typeindexanalyzer "golang.org/x/tools/internal/analysisinternal/typeindex"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var StringsSeqAnalyzer = &analysis.Analyzer{
@@ -51,9 +51,8 @@
skipGenerated(pass)
var (
- inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
- index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
- info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ info = pass.TypesInfo
stringsSplit = index.Object("strings", "Split")
stringsFields = index.Object("strings", "Fields")
@@ -64,7 +63,7 @@
return nil, nil
}
- for curFile := range filesUsing(inspect, info, "go1.24") {
+ for curFile := range filesUsing(pass, versions.Go1_24) {
for curRange := range curFile.Preorder((*ast.RangeStmt)(nil)) {
rng := curRange.Node().(*ast.RangeStmt)
diff --git a/go/analysis/passes/modernize/testingcontext.go b/go/analysis/passes/modernize/testingcontext.go
index 558cf14..3de844d 100644
--- a/go/analysis/passes/modernize/testingcontext.go
+++ b/go/analysis/passes/modernize/testingcontext.go
@@ -23,6 +23,7 @@
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/typesinternal"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var TestingContextAnalyzer = &analysis.Analyzer{
@@ -137,7 +138,7 @@
testObj = isTestFn(info, n)
}
}
- if testObj != nil && fileUses(info, astutil.EnclosingFile(cur), "go1.24") {
+ if testObj != nil && fileMayUse(pass, astutil.EnclosingFile(cur), versions.Go1_24) {
// Have a test function. Check that we can resolve the relevant
// testing.{T,B,F} at the current position.
if _, obj := lhs[0].Parent().LookupParent(testObj.Name(), lhs[0].Pos()); obj == testObj {
diff --git a/go/analysis/passes/modernize/waitgroup.go b/go/analysis/passes/modernize/waitgroup.go
index b890f33..d31dd73 100644
--- a/go/analysis/passes/modernize/waitgroup.go
+++ b/go/analysis/passes/modernize/waitgroup.go
@@ -20,6 +20,7 @@
"golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
)
var WaitGroupAnalyzer = &analysis.Analyzer{
@@ -128,7 +129,7 @@
}
file := astutil.EnclosingFile(curAddCall)
- if !fileUses(info, file, "go1.25") {
+ if !fileMayUse(pass, file, versions.Go1_25) {
continue
}
tokFile := pass.Fset.File(file.Pos())
diff --git a/gopls/internal/analysis/maprange/maprange.go b/gopls/internal/analysis/maprange/maprange.go
index 1e44a4b..ef20d6a 100644
--- a/gopls/internal/analysis/maprange/maprange.go
+++ b/gopls/internal/analysis/maprange/maprange.go
@@ -152,6 +152,7 @@
// fileUses reports whether the file containing the specified cursor
// uses at least the specified version of Go (e.g. "go1.24").
+// FIXME
func fileUses(info *types.Info, c inspector.Cursor, version string) bool {
file, _ := cursorutil.FirstEnclosing[*ast.File](c)
return !versions.Before(info.FileVersions[file], version)
diff --git a/gopls/internal/cache/check.go b/gopls/internal/cache/check.go
index fae6d57..dfefd59 100644
--- a/gopls/internal/cache/check.go
+++ b/gopls/internal/cache/check.go
@@ -17,7 +17,6 @@
"go/types"
"regexp"
"runtime"
- "slices"
"sort"
"strings"
"sync"
@@ -1789,12 +1788,6 @@
return false // 'go list' is too new for go/types
}
- // TODO(rfindley): remove once we no longer support building gopls with Go
- // 1.20 or earlier.
- if !slices.Contains(build.Default.ReleaseTags, "go1.21") && strings.Count(goVersion, ".") >= 2 {
- return false // unsupported patch version
- }
-
return true
}
diff --git a/internal/versions/features.go b/internal/versions/features.go
index b53f178..a5f4e32 100644
--- a/internal/versions/features.go
+++ b/internal/versions/features.go
@@ -7,13 +7,17 @@
// 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.
+// named constants, to avoid misspelling
const (
Go1_18 = "go1.18"
Go1_19 = "go1.19"
Go1_20 = "go1.20"
Go1_21 = "go1.21"
Go1_22 = "go1.22"
+ Go1_23 = "go1.23"
+ Go1_24 = "go1.24"
+ Go1_25 = "go1.25"
+ Go1_26 = "go1.26"
)
// Future is an invalid unknown Go version sometime in the future.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
// fileMayUse reports whether the specified file may use features of theNit: may use what? How about `fileMayUseVersion`?
// operation is not free, yet is not a highly selective filter: theRemind me again why this is expensive?
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Is there not a common location for this type of helper function? Could aid in discoverability.
Perhaps `analyzerutil`?
for curFile := range filesUsing(pass, versions.Go1_18) {Aha, so I had a different phrasing in my head, and I think this is where it came from. In keeping with this naming, why not call the new helper 'fileUsesVersion'. I.e. a file "uses" a version if that version is present in its build.
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)FWIW, if this were moved to a shared location, I'd inject the inspect.Analyzer to make the dependency apparent.
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Aha: factor out.
canRangeOverInt := fileMayUse(pass, astutil.EnclosingFile(curCall), versions.Go1_22)Nit: inline?
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {!factor!
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Commit-Queue | +1 |
// fileMayUse reports whether the specified file may use features of theNit: may use what? How about `fileMayUseVersion`?
Done (...GoVersion).
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Is there not a common location for this type of helper function? Could aid in discoverability.
Perhaps `analyzerutil`?
See commit message: there will be in a few minutes when my renaming CL lands!
for curFile := range filesUsing(pass, versions.Go1_18) {Aha, so I had a different phrasing in my head, and I think this is where it came from. In keeping with this naming, why not call the new helper 'fileUsesVersion'. I.e. a file "uses" a version if that version is present in its build.
That's actually what I first used, but then I convinced myself that that file might not actually be using go1.x, but only has the right to. ;-) Done.
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Alan DonovanAha: factor out.
The place where I want to factor these to does not exist yet (since I am about to rename it into existence). Will send a cleanup CL later today.
canRangeOverInt := fileMayUse(pass, astutil.EnclosingFile(curCall), versions.Go1_22)Alan DonovanNit: inline?
Done
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Alan Donovan!factor!
(see above)
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
| Code-Review | +2 |
func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {Alan DonovanIs there not a common location for this type of helper function? Could aid in discoverability.
Perhaps `analyzerutil`?
See commit message: there will be in a few minutes when my renaming CL lands!
Sorry, this is one of my failure modes (not reading the commit message carefully enough). SGTM.
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
go/analysis/passes/modernize: respect bootstrap toolchain version
fileUsesGoVersion (formerly fileUses) now checks not only the FileVersion
but the bootstrap toolchain version too, so that, for example,
we don't prematurely modernize packages in the compiler to use
features of go1.26.
Unfortunately this change cannot be tested via the analysistest
framework because it would require that we overwrite the sources
of actual std packages.
lostcancel and maprange now both use copies of fileUsesGoVersion too.
I will merge them into analyzerutil once CL 718081 is merged.
For golang/go#71859
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |
stdlib.IsBootstrapPackage(pkgpath) &&@adon...@google.com Would it make sense to add a special case here to not apply the lower BootstrapVersion to package tests?
The packages used during bootstrap do need to build and work with the bootstrap toolchain version. However, for better or worse, their tests are never run with the bootstrap toolchain, only with the "current" toolchain.
Right now TestVetStdlib is failing on debug/elf/file_test.go with "loop variable test captured by func literal", but debug/elf tests at tip are only run with tip (i.e., Go 1.26-ish). It shouldn't be needed to modify debug/elf tests to be compatible with older versions.
Hmm, that said, even the bootstrap version of 1.24.6 is higher than 1.22, so maybe something else is off?
| Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. |