[tools] go/analysis/passes/modernize: respect bootstrap toolchain version

3 views
Skip to first unread message

Alan Donovan (Gerrit)

unread,
Nov 7, 2025, 3:48:43 PM (5 days ago) Nov 7
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Alan Donovan has uploaded the change for review

Commit message

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
Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7

Change diff

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.

Change information

Files:
  • M go/analysis/passes/loopclosure/loopclosure.go
  • M go/analysis/passes/modernize/any.go
  • M go/analysis/passes/modernize/bloop.go
  • M go/analysis/passes/modernize/errorsastype.go
  • M go/analysis/passes/modernize/fmtappendf.go
  • M go/analysis/passes/modernize/forvar.go
  • M go/analysis/passes/modernize/maps.go
  • M go/analysis/passes/modernize/minmax.go
  • M go/analysis/passes/modernize/modernize.go
  • M go/analysis/passes/modernize/newexpr.go
  • M go/analysis/passes/modernize/omitzero.go
  • M go/analysis/passes/modernize/plusbuild.go
  • M go/analysis/passes/modernize/rangeint.go
  • M go/analysis/passes/modernize/reflect.go
  • M go/analysis/passes/modernize/slices.go
  • M go/analysis/passes/modernize/slicescontains.go
  • M go/analysis/passes/modernize/slicesdelete.go
  • M go/analysis/passes/modernize/sortslice.go
  • M go/analysis/passes/modernize/stditerators.go
  • M go/analysis/passes/modernize/stringscut.go
  • M go/analysis/passes/modernize/stringscutprefix.go
  • M go/analysis/passes/modernize/stringsseq.go
  • M go/analysis/passes/modernize/testingcontext.go
  • M go/analysis/passes/modernize/waitgroup.go
  • M gopls/internal/analysis/maprange/maprange.go
  • M gopls/internal/cache/check.go
  • M internal/versions/features.go
Change size: M
Delta: 27 files changed, 121 insertions(+), 74 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
Gerrit-Change-Number: 718503
Gerrit-PatchSet: 1
Gerrit-Owner: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
Nov 7, 2025, 3:55:03 PM (5 days ago) Nov 7
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Robert Findley

Alan Donovan uploaded new patchset

Alan Donovan uploaded patch set #2 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Robert Findley
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
Gerrit-Change-Number: 718503
Gerrit-PatchSet: 2
Gerrit-Owner: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Robert Findley <rfin...@google.com>
Gerrit-Attention: Alan Donovan <adon...@google.com>
Gerrit-Attention: Robert Findley <rfin...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
Nov 7, 2025, 3:56:44 PM (5 days ago) Nov 7
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Alan Donovan and Robert Findley

Alan Donovan uploaded new patchset

Alan Donovan uploaded patch set #3 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
  • Robert Findley
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
Gerrit-Change-Number: 718503
Gerrit-PatchSet: 3
Gerrit-Owner: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Robert Findley <rfin...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Robert Findley (Gerrit)

unread,
Nov 10, 2025, 11:12:07 AM (2 days ago) Nov 10
to Alan Donovan, goph...@pubsubhelper.golang.org, Go LUCI, golang-co...@googlegroups.com
Attention needed from Alan Donovan

Robert Findley added 8 comments

File go/analysis/passes/loopclosure/loopclosure.go
Line 377, Patchset 3 (Latest):// fileMayUse reports whether the specified file may use features of the
Robert Findley . unresolved

Nit: may use what? How about `fileMayUseVersion`?

Line 383, Patchset 3 (Latest):// operation is not free, yet is not a highly selective filter: the
Robert Findley . resolved

Remind me again why this is expensive?

Line 388, Patchset 3 (Latest):func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
Robert Findley . unresolved

Is there not a common location for this type of helper function? Could aid in discoverability.

Perhaps `analyzerutil`?

File go/analysis/passes/modernize/any.go
Line 32, Patchset 3 (Latest): for curFile := range filesUsing(pass, versions.Go1_18) {
Robert Findley . unresolved

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.

File go/analysis/passes/modernize/modernize.go
Line 105, Patchset 3 (Latest): inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
Robert Findley . resolved

FWIW, if this were moved to a shared location, I'd inject the inspect.Analyzer to make the dependency apparent.

Line 128, Patchset 3 (Latest):func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
Robert Findley . unresolved

Aha: factor out.

File gopls/internal/analysis/maprange/maprange.go
Line 95, Patchset 3 (Latest): canRangeOverInt := fileMayUse(pass, astutil.EnclosingFile(curCall), versions.Go1_22)
Robert Findley . unresolved

Nit: inline?

Line 165, Patchset 3 (Latest):func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
Robert Findley . unresolved

!factor!

Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: tools
    Gerrit-Branch: master
    Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
    Gerrit-Change-Number: 718503
    Gerrit-PatchSet: 3
    Gerrit-Owner: Alan Donovan <adon...@google.com>
    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
    Gerrit-Reviewer: Robert Findley <rfin...@google.com>
    Gerrit-Attention: Alan Donovan <adon...@google.com>
    Gerrit-Comment-Date: Mon, 10 Nov 2025 16:12:02 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Alan Donovan (Gerrit)

    unread,
    Nov 10, 2025, 2:26:42 PM (2 days ago) Nov 10
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Alan Donovan

    Alan Donovan uploaded new patchset

    Alan Donovan uploaded patch set #5 to this change.
    Open in Gerrit

    Related details

    Attention is currently required from:
    • Alan Donovan
    Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: newpatchset
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
      Gerrit-Change-Number: 718503
      Gerrit-PatchSet: 5
      unsatisfied_requirement
      open
      diffy

      Alan Donovan (Gerrit)

      unread,
      Nov 10, 2025, 2:26:42 PM (2 days ago) Nov 10
      to goph...@pubsubhelper.golang.org, Go LUCI, Robert Findley, golang-co...@googlegroups.com
      Attention needed from Robert Findley

      Alan Donovan voted and added 6 comments

      Votes added by Alan Donovan

      Commit-Queue+1

      6 comments

      File go/analysis/passes/loopclosure/loopclosure.go
      Line 377, Patchset 3:// fileMayUse reports whether the specified file may use features of the
      Robert Findley . resolved

      Nit: may use what? How about `fileMayUseVersion`?

      Alan Donovan

      Done (...GoVersion).

      Line 388, Patchset 3:func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
      Robert Findley . resolved

      Is there not a common location for this type of helper function? Could aid in discoverability.

      Perhaps `analyzerutil`?

      Alan Donovan

      See commit message: there will be in a few minutes when my renaming CL lands!

      File go/analysis/passes/modernize/any.go
      Line 32, Patchset 3: for curFile := range filesUsing(pass, versions.Go1_18) {
      Robert Findley . resolved

      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.

      Alan Donovan

      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.

      File go/analysis/passes/modernize/modernize.go
      Line 128, Patchset 3:func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
      Robert Findley . resolved

      Aha: factor out.

      Alan Donovan

      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.

      File gopls/internal/analysis/maprange/maprange.go
      Line 95, Patchset 3: canRangeOverInt := fileMayUse(pass, astutil.EnclosingFile(curCall), versions.Go1_22)
      Robert Findley . resolved

      Nit: inline?

      Alan Donovan

      Done

      Line 165, Patchset 3:func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
      Robert Findley . resolved

      !factor!

      Alan Donovan

      (see above)

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Robert Findley
      Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
        Gerrit-Change-Number: 718503
        Gerrit-PatchSet: 4
        Gerrit-Owner: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Robert Findley <rfin...@google.com>
        Gerrit-Attention: Robert Findley <rfin...@google.com>
        Gerrit-Comment-Date: Mon, 10 Nov 2025 19:26:39 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: Yes
        Comment-In-Reply-To: Robert Findley <rfin...@google.com>
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Robert Findley (Gerrit)

        unread,
        Nov 10, 2025, 2:33:36 PM (2 days ago) Nov 10
        to Alan Donovan, goph...@pubsubhelper.golang.org, Go LUCI, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Robert Findley voted and added 1 comment

        Votes added by Robert Findley

        Code-Review+2

        1 comment

        File go/analysis/passes/loopclosure/loopclosure.go
        Line 388, Patchset 3:func fileMayUse(pass *analysis.Pass, file *ast.File, version string) bool {
        Robert Findley . resolved

        Is there not a common location for this type of helper function? Could aid in discoverability.

        Perhaps `analyzerutil`?

        Alan Donovan

        See commit message: there will be in a few minutes when my renaming CL lands!

        Robert Findley

        Sorry, this is one of my failure modes (not reading the commit message carefully enough). SGTM.

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement satisfiedCode-Review
        • requirement satisfiedNo-Unresolved-Comments
        • requirement satisfiedReview-Enforcement
        • requirement is not satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
        Gerrit-Change-Number: 718503
        Gerrit-PatchSet: 5
        Gerrit-Owner: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Robert Findley <rfin...@google.com>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Mon, 10 Nov 2025 19:33:32 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: Yes
        Comment-In-Reply-To: Alan Donovan <adon...@google.com>
        Comment-In-Reply-To: Robert Findley <rfin...@google.com>
        satisfied_requirement
        unsatisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        Nov 10, 2025, 2:52:46 PM (2 days ago) Nov 10
        to goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Go LUCI, Robert Findley, golang-co...@googlegroups.com

        Alan Donovan submitted the change

        Change information

        Commit message:
        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
        Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
        Reviewed-by: Robert Findley <rfin...@google.com>
        Delta: 27 files changed, 149 insertions(+), 83 deletions(-)
        Branch: refs/heads/master
        Submit Requirements:
        • requirement satisfiedCode-Review: +2 by Robert Findley
        • requirement satisfiedTryBots-Pass: LUCI-TryBot-Result+1 by Go LUCI
        Open in Gerrit
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: merged
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
        Gerrit-Change-Number: 718503
        Gerrit-PatchSet: 6
        open
        diffy
        satisfied_requirement

        Dmitri Shuralyov (Gerrit)

        unread,
        Nov 11, 2025, 6:36:11 PM (20 hours ago) Nov 11
        to Alan Donovan, goph...@pubsubhelper.golang.org, Dmitri Shuralyov, Go LUCI, Robert Findley, golang-co...@googlegroups.com
        Attention needed from Alan Donovan

        Dmitri Shuralyov added 1 comment

        File go/analysis/passes/loopclosure/loopclosure.go
        Line 394, Patchset 6 (Latest): stdlib.IsBootstrapPackage(pkgpath) &&
        Dmitri Shuralyov . resolved

        @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?

        Open in Gerrit

        Related details

        Attention is currently required from:
        • Alan Donovan
        Submit Requirements:
        • requirement satisfiedCode-Review
        • requirement satisfiedNo-Unresolved-Comments
        • requirement satisfiedReview-Enforcement
        • requirement satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I4b8ede616a46c03bc58f017aa7fc522d1df535f7
        Gerrit-Change-Number: 718503
        Gerrit-PatchSet: 6
        Gerrit-Owner: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Robert Findley <rfin...@google.com>
        Gerrit-CC: Dmitri Shuralyov <dmit...@golang.org>
        Gerrit-Attention: Alan Donovan <adon...@google.com>
        Gerrit-Comment-Date: Tue, 11 Nov 2025 23:36:06 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        satisfied_requirement
        open
        diffy
        Reply all
        Reply to author
        Forward
        0 new messages