aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMark Freeman <mark@golang.org>2026-04-06 15:36:17 -0400
committerMark Freeman <mark@golang.org>2026-04-07 09:07:22 -0700
commit9a1e20beb0240df1f9dcc8e7d4d66de573828309 (patch)
tree254fca4143449b4119bd31b601b47a91582daa81
parent33e66cfb0406b27ce11229a4ebd944c4cb20006f (diff)
downloadgo-9a1e20beb0240df1f9dcc8e7d4d66de573828309.tar.xz
all: update to x/tools@b36d1d12a1a724eb9be6609c9789aec3d99e6030
Change-Id: If1c8d07aa53c5444b815b922ff4e0c18649ab83c Reviewed-on: https://go-review.googlesource.com/c/go/+/763181 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
-rw-r--r--src/cmd/go.mod2
-rw-r--r--src/cmd/go.sum4
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go27
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go14
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go22
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go10
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go60
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go1
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go9
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go215
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go10
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go28
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go2
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go2
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go4
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go13
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go12
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go48
-rw-r--r--src/cmd/vendor/golang.org/x/tools/internal/versions/features.go1
-rw-r--r--src/cmd/vendor/modules.txt2
20 files changed, 419 insertions, 67 deletions
diff --git a/src/cmd/go.mod b/src/cmd/go.mod
index d15db1306b..95ad3a1b96 100644
--- a/src/cmd/go.mod
+++ b/src/cmd/go.mod
@@ -11,7 +11,7 @@ require (
golang.org/x/sys v0.42.1-0.20260320201212-a76ec62d6c53
golang.org/x/telemetry v0.0.0-20260311193753-579e4da9a98c
golang.org/x/term v0.39.0
- golang.org/x/tools v0.43.1-0.20260319213245-5d7afbc08aec
+ golang.org/x/tools v0.43.1-0.20260406190732-b36d1d12a1a7
)
require (
diff --git a/src/cmd/go.sum b/src/cmd/go.sum
index 23627773f0..b24a77c2ee 100644
--- a/src/cmd/go.sum
+++ b/src/cmd/go.sum
@@ -22,7 +22,7 @@ golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.33.1-0.20260122225119-3264de9174be h1:EwuAS7HtEmZVDSL0zq464yhyVIjdDETleE+K94kfwxg=
golang.org/x/text v0.33.1-0.20260122225119-3264de9174be/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
-golang.org/x/tools v0.43.1-0.20260319213245-5d7afbc08aec h1:kTU64nIpH5vbfY0lQLyoZB98LkAmp2WzOvYoylbOIhg=
-golang.org/x/tools v0.43.1-0.20260319213245-5d7afbc08aec/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
+golang.org/x/tools v0.43.1-0.20260406190732-b36d1d12a1a7 h1:R5vAEs3WMOT7VWE+EU8EC8MDRvhwi1nkl/arkEXn26Q=
+golang.org/x/tools v0.43.1-0.20260406190732-b36d1d12a1a7/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef h1:mqLYrXCXYEZOop9/Dbo6RPX11539nwiCNBb1icVPmw8=
rsc.io/markdown v0.0.0-20240306144322-0bf8f97ee8ef/go.mod h1:8xcPgWmwlZONN1D9bjxtHEjrUtSEa3fakVF8iaewYKQ=
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
index 415058db80..efa2dcaf89 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/inline/inline.go
@@ -332,10 +332,33 @@ func (a *analyzer) inlineAlias(tn *types.TypeName, curId inspector.Cursor) {
// Given C[int], TypeOf(C) is generic but TypeOf(C[int]) is instantiated.
switch curId.ParentEdgeKind() {
case edge.IndexExpr_X:
- expr = curId.Parent().Node().(*ast.IndexExpr)
+ curId = curId.Parent()
+ expr = curId.Node().(*ast.IndexExpr)
case edge.IndexListExpr_X:
- expr = curId.Parent().Node().(*ast.IndexListExpr)
+ curId = curId.Parent()
+ expr = curId.Node().(*ast.IndexListExpr)
+ }
+
+ fieldType := curId
+ if fieldType.ParentEdgeKind() == edge.StarExpr_X {
+ fieldType = fieldType.Parent()
+ }
+ if fieldType.ParentEdgeKind() == edge.Field_Type {
+ field := fieldType.Parent().Node().(*ast.Field)
+ if len(field.Names) == 0 {
+ identicalName := false
+ if rhs, ok := alias.Rhs().(*types.Named); ok {
+ identicalName = alias.Obj().Name() == rhs.Obj().Name()
+ }
+ if !identicalName {
+ // Type is embedded, inlining the alias will cause
+ // the field name to be changed, which might break
+ // programs in terms of backwards compatibility.
+ return
+ }
+ }
}
+
t := a.pass.TypesInfo.TypeOf(expr).(*types.Alias) // type of entire identifier
if targs := t.TypeArgs(); targs.Len() > 0 {
// Instantiate the alias with the type args from this use.
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go
index 2f314b2972..ec0044b307 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/atomictypes.go
@@ -114,7 +114,6 @@ func runAtomic(pass *analysis.Pass) (any, error) {
// appear in calls of the form atomic.AddInt32(&v, ...).
nextvar:
for v, funcName := range vars {
- var edits []analysis.TextEdit
fixFiles := make(map[*ast.File]bool) // unique files involved in the current fix
// Check the form of the declaration: var v int or struct { v int }
@@ -145,12 +144,15 @@ nextvar:
oldType := info.TypeOf(typ) // e.g. "int32"
newType := strings.Title(oldType.Underlying().String()) // e.g. "Int32"
- // Get package prefix to avoid shadowing.
file := astutil.EnclosingFile(def)
- pkgPrefix, impEdits := refactor.AddImport(pass.TypesInfo, file, "atomic", "sync/atomic", "", def.Node().Pos())
- if len(impEdits) > 0 {
- panic("unexpected import edits") // atomic PkgName should be in scope already
- }
+ // Get package prefix to avoid shadowing, and import edits, which may be
+ // necessary if the fix spans files.
+ // For example: file "a" declares an int32, which doesn't require the
+ // sync/atomic import, while file "b" calls atomic.LoadInt32(v). After
+ // the fix, the need for the import will shift from the use in file "b"
+ // (which becomes a method call v.Load) to the declaration in file "a"
+ // (which becomes atomic.Int32).
+ pkgPrefix, edits := refactor.AddImport(pass.TypesInfo, file, "atomic", "sync/atomic", "", def.Node().Pos())
// Edit the type.
//
// var v int32
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
index 7aa7046b39..e9a3a0d985 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
@@ -303,6 +303,28 @@ No fix is offered in cases when the runtime type is dynamic, such as:
or when the operand has potential side effects.
+# Analyzer slicesbackward
+
+slicesbackward: replace backward loops over slices with slices.Backward
+
+The slicesbackward analyzer suggests replacing manually-written backward
+loops of the form
+
+ for i := len(s) - 1; i >= 0; i-- {
+ use(s[i])
+ }
+
+with the more readable Go 1.23 style using slices.Backward:
+
+ for _, v := range slices.Backward(s) {
+ use(v)
+ }
+
+If the loop index is needed beyond just indexing into the slice, both
+the index and value variables are kept:
+
+ for i, v := range slices.Backward(s) { ... }
+
# Analyzer slicescontains
slicescontains: replace loops with slices.Contains or slices.ContainsFunc
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
index 795f5b6c6b..7f3fd4e6f6 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/maps.go
@@ -92,13 +92,14 @@ func mapsloop(pass *analysis.Pass) (any, error) {
// and can we replace its RHS with slices.{Clone,Collect}?
//
// Beware: if x may be nil, we cannot use Clone as it preserves nilness.
- var mrhs ast.Expr // make(M) or M{}, or nil
+ var mrhs ast.Expr // make(M) or M{}, or nil
+ var mAssign token.Token // token used to assign m
if curPrev, ok := curRange.PrevSibling(); ok {
if assign, ok := curPrev.Node().(*ast.AssignStmt); ok &&
len(assign.Lhs) == 1 &&
len(assign.Rhs) == 1 &&
astutil.EqualSyntax(assign.Lhs[0], m) {
-
+ mAssign = assign.Tok
// Have: m = rhs; for k, v := range x { m[k] = v }
var newMap bool
rhs := assign.Rhs[0]
@@ -175,12 +176,13 @@ func mapsloop(pass *analysis.Pass) (any, error) {
// ->
//
// /* comments */
- // m = maps.Copy(x)
+ // m = maps.Collect(x)
curPrev, _ := curRange.PrevSibling()
start, end = curPrev.Node().Pos(), rng.End()
- newText = fmt.Appendf(nil, "%s%s = %s%s(%s)",
+ newText = fmt.Appendf(nil, "%s%s %s %s%s(%s)",
allComments(file, start, end),
astutil.Format(pass.Fset, m),
+ mAssign.String(),
prefix,
funcName,
astutil.Format(pass.Fset, x))
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
index b4b8dba3d1..93aadf04a1 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
@@ -55,6 +55,10 @@ var MinMaxAnalyzer = &analysis.Analyzer{
// - "x := a" or "x = a" or "var x = a" in pattern 2
// - "x < b" or "a < b" in pattern 2
func minmax(pass *analysis.Pass) (any, error) {
+ var (
+ inspect = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
+ info = pass.TypesInfo
+ )
// Check for user-defined min/max functions that can be removed
checkUserDefinedMinMax(pass)
@@ -104,6 +108,9 @@ func minmax(pass *analysis.Pass) (any, error) {
if !is[*types.Builtin](lookup(pass.TypesInfo, curIfStmt, sym)) {
return // min/max function is shadowed
}
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
+ return // min/max is too new
+ }
// pattern 1
//
@@ -177,6 +184,10 @@ func minmax(pass *analysis.Pass) (any, error) {
b = rhs0
}
+ if !analyzerutil.FileUsesGoVersion(pass, file, versions.Go1_21) {
+ return // min/max is too new
+ }
+
// pattern 2
pass.Report(analysis.Diagnostic{
// Highlight the condition a < b.
@@ -204,31 +215,26 @@ func minmax(pass *analysis.Pass) (any, error) {
}
// Find all "if a < b { lhs = rhs }" statements.
- info := pass.TypesInfo
- for curFile := range filesUsingGoVersion(pass, versions.Go1_21) {
- astFile := curFile.Node().(*ast.File)
- for curIfStmt := range curFile.Preorder((*ast.IfStmt)(nil)) {
- ifStmt := curIfStmt.Node().(*ast.IfStmt)
-
- // Don't bother handling "if a < b { lhs = rhs }" when it appears
- // as the "else" branch of another if-statement.
- // if cond { ... } else if a < b { lhs = rhs }
- // (This case would require introducing another block
- // if cond { ... } else { if a < b { lhs = rhs } }
- // and checking that there is no following "else".)
- if curIfStmt.ParentEdgeKind() == edge.IfStmt_Else {
- continue
- }
+ for curIfStmt := range inspect.Root().Preorder((*ast.IfStmt)(nil)) {
+ ifStmt := curIfStmt.Node().(*ast.IfStmt)
+ // Don't bother handling "if a < b { lhs = rhs }" when it appears
+ // as the "else" branch of another if-statement.
+ // if cond { ... } else if a < b { lhs = rhs }
+ // (This case would require introducing another block
+ // if cond { ... } else { if a < b { lhs = rhs } }
+ // and checking that there is no following "else".)
+ if curIfStmt.ParentEdgeKind() == edge.IfStmt_Else {
+ continue
+ }
- if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
- ifStmt.Init == nil &&
- isInequality(compare.Op) != 0 &&
- isAssignBlock(ifStmt.Body) {
- // a blank var has no type.
- if tLHS := info.TypeOf(ifStmt.Body.List[0].(*ast.AssignStmt).Lhs[0]); tLHS != nil && !maybeNaN(tLHS) {
- // Have: if a < b { lhs = rhs }
- check(astFile, curIfStmt, compare)
- }
+ if compare, ok := ifStmt.Cond.(*ast.BinaryExpr); ok &&
+ ifStmt.Init == nil &&
+ isInequality(compare.Op) != 0 &&
+ isAssignBlock(ifStmt.Body) {
+ // a blank var has no type.
+ if tLHS := info.TypeOf(ifStmt.Body.List[0].(*ast.AssignStmt).Lhs[0]); tLHS != nil && !maybeNaN(tLHS) {
+ // Have: if a < b { lhs = rhs }
+ check(astutil.EnclosingFile(curIfStmt), curIfStmt, compare)
}
}
}
@@ -300,8 +306,10 @@ func checkUserDefinedMinMax(pass *analysis.Pass) {
// Use typeindex to get the FuncDecl directly
if def, ok := index.Def(fn); ok {
decl := def.Parent().Node().(*ast.FuncDecl)
- // Check if this function matches the built-in min/max signature and behavior
- if canUseBuiltinMinMax(fn, decl.Body) {
+ // Check if this function matches the built-in min/max signature
+ // and behavior, and verify that we have go1.21.
+ if canUseBuiltinMinMax(fn, decl.Body) &&
+ analyzerutil.FileUsesGoVersion(pass, astutil.EnclosingFile(def), versions.Go1_21) {
// Expand to include leading doc comment
pos := decl.Pos()
if docs := astutil.DocComment(decl); docs != nil {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
index 42519eab53..aa59e7e4ee 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
@@ -47,6 +47,7 @@ var Suite = []*analysis.Analyzer{
plusBuildAnalyzer,
RangeIntAnalyzer,
ReflectTypeForAnalyzer,
+ slicesbackwardAnalyzer,
SlicesContainsAnalyzer,
// SlicesDeleteAnalyzer, // not nil-preserving!
SlicesSortAnalyzer,
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
index cd924ec85e..c99d9c24de 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/newexpr.go
@@ -6,12 +6,11 @@ package modernize
import (
_ "embed"
+ "fmt"
"go/ast"
"go/token"
"go/types"
- "strings"
-
- "fmt"
+ "slices"
"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/analysis/passes/inspect"
@@ -94,7 +93,9 @@ func run(pass *analysis.Pass) (any, error) {
// older Go file; see https://go.dev/issue/75726.
//
// TODO(adonovan): use ast.ParseDirective when go1.26 is assured.
- if !strings.Contains(decl.Doc.Text(), "go:fix inline") {
+ if !slices.ContainsFunc(astutil.Directives(decl.Doc), func(d *astutil.Directive) bool {
+ return d.Tool == "go" && d.Name == "fix" && d.Args == "inline"
+ }) {
edits = append(edits, analysis.TextEdit{
Pos: decl.Pos(),
End: decl.Pos(),
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
new file mode 100644
index 0000000000..293a7c0c3f
--- /dev/null
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slicesbackward.go
@@ -0,0 +1,215 @@
+// Copyright 2025 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 modernize
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "go/types"
+
+ "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/types/typeutil"
+ "golang.org/x/tools/internal/analysis/analyzerutil"
+ typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
+ "golang.org/x/tools/internal/astutil"
+ "golang.org/x/tools/internal/goplsexport"
+ "golang.org/x/tools/internal/refactor"
+ "golang.org/x/tools/internal/typesinternal/typeindex"
+ "golang.org/x/tools/internal/versions"
+)
+
+var slicesbackwardAnalyzer = &analysis.Analyzer{
+ Name: "slicesbackward",
+ Doc: analyzerutil.MustExtractDoc(doc, "slicesbackward"),
+ Requires: []*analysis.Analyzer{
+ inspect.Analyzer,
+ typeindexanalyzer.Analyzer,
+ },
+ Run: slicesbackward,
+ URL: "https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize#slicesbackward",
+}
+
+func init() {
+ // Export to gopls until this is a published modernizer.
+ goplsexport.SlicesBackwardModernizer = slicesbackwardAnalyzer
+}
+
+// slicesbackward offers a fix to replace a manually-written backward loop:
+//
+// for i := len(s) - 1; i >= 0; i-- {
+// use(s[i])
+// }
+//
+// with a range loop using slices.Backward (added in Go 1.23):
+//
+// for _, v := range slices.Backward(s) {
+// use(v)
+// }
+//
+// If the loop index is needed beyond just indexing into the slice, both
+// the index and value variables are kept:
+//
+// for i, v := range slices.Backward(s) { ... }
+func slicesbackward(pass *analysis.Pass) (any, error) {
+ // Skip packages that are in the slices stdlib dependency tree to
+ // avoid import cycles.
+ if within(pass, "slices") {
+ return nil, nil
+ }
+
+ var (
+ info = pass.TypesInfo
+ index = pass.ResultOf[typeindexanalyzer.Analyzer].(*typeindex.Index)
+ )
+
+ for curFile := range filesUsingGoVersion(pass, versions.Go1_23) {
+ file := curFile.Node().(*ast.File)
+
+ nextLoop:
+ for curLoop := range curFile.Preorder((*ast.ForStmt)(nil)) {
+ loop := curLoop.Node().(*ast.ForStmt)
+
+ // Match init: i := len(s) - 1 or i = len(s) - 1
+ init, ok := loop.Init.(*ast.AssignStmt)
+ if !ok || !isSimpleAssign(init) {
+ continue
+ }
+ indexIdent, ok := init.Lhs[0].(*ast.Ident)
+ if !ok {
+ continue
+ }
+ indexObj := info.ObjectOf(indexIdent).(*types.Var)
+
+ // RHS must be len(s) - 1.
+ binRhs, ok := init.Rhs[0].(*ast.BinaryExpr)
+ if !ok || binRhs.Op != token.SUB {
+ continue
+ }
+ if !isIntLiteral(info, binRhs.Y, 1) {
+ continue
+ }
+ lenCall, ok := binRhs.X.(*ast.CallExpr)
+ if !ok || typeutil.Callee(info, lenCall) != builtinLen {
+ continue
+ }
+ if len(lenCall.Args) != 1 {
+ continue
+ }
+ sliceExpr := lenCall.Args[0]
+ if _, ok := info.TypeOf(sliceExpr).Underlying().(*types.Slice); !ok {
+ continue
+ }
+
+ // Match cond: i >= 0
+ cond, ok := loop.Cond.(*ast.BinaryExpr)
+ if !ok || cond.Op != token.GEQ {
+ continue
+ }
+ if !astutil.EqualSyntax(cond.X, indexIdent) {
+ continue
+ }
+ if !isZeroIntConst(info, cond.Y) {
+ continue
+ }
+
+ // Match post: i--
+ dec, ok := loop.Post.(*ast.IncDecStmt)
+ if !ok || dec.Tok != token.DEC {
+ continue
+ }
+ if !astutil.EqualSyntax(dec.X, indexIdent) {
+ continue
+ }
+
+ // Check that i is not used as an lvalue in the loop body.
+ // If init is = (not :=), i is a pre-existing variable; also
+ // check that it is not used as an lvalue outside the loop
+ // (e.g. &i before the loop).
+ bodyCur := curLoop.Child(loop.Body)
+ for curUse := range index.Uses(indexObj) {
+ if !isScalarLvalue(info, curUse) {
+ continue
+ }
+ if bodyCur.Contains(curUse) {
+ continue nextLoop // i is mutated in loop body
+ }
+ if init.Tok == token.ASSIGN && !curLoop.Contains(curUse) {
+ continue nextLoop // pre-existing i is an lvalue outside the loop
+ }
+ }
+
+ // Find all uses of i in the loop body. Classify as:
+ // s[i] — pure element accesses that can be replaced by the value var
+ // other — index used for non-indexing purposes
+ var (
+ sliceIndexes []*ast.IndexExpr
+ otherUses int
+ )
+ for curUse := range index.Uses(indexObj) {
+ if !bodyCur.Contains(curUse) {
+ continue
+ }
+ // Is i in the Index position of an s[i] expression?
+ if curUse.ParentEdgeKind() == edge.IndexExpr_Index {
+ idxExpr := curUse.Parent().Node().(*ast.IndexExpr)
+ if astutil.EqualSyntax(idxExpr.X, sliceExpr) {
+ sliceIndexes = append(sliceIndexes, idxExpr)
+ continue
+ }
+ }
+ otherUses++
+ }
+
+ // Build the suggested fix.
+ //
+ // for i := len(s) - 1; i >= 0; i-- { ... s[i] ... }
+ // ---------------------------- ----
+ // _, v := range slices.Backward(s) v
+ sliceStr := astutil.Format(pass.Fset, sliceExpr)
+ prefix, edits := refactor.AddImport(info, file, "slices", "slices", "Backward", loop.Pos())
+ elemName := freshName(info, index, info.Scopes[loop], loop.Pos(), bodyCur, bodyCur, token.NoPos, "v")
+
+ // Replace each s[i] with elemName.
+ for _, sx := range sliceIndexes {
+ edits = append(edits, analysis.TextEdit{
+ Pos: sx.Pos(),
+ End: sx.End(),
+ NewText: []byte(elemName),
+ })
+ }
+
+ // Replace the loop header with a range over slices.Backward.
+ var header string
+ if otherUses == 0 && len(sliceIndexes) > 0 {
+ // All uses of i are s[i]; drop the index variable.
+ header = fmt.Sprintf("_, %s := range %sBackward(%s)",
+ elemName, prefix, sliceStr)
+ } else {
+ // i is used for other purposes; keep both index and value.
+ header = fmt.Sprintf("%s, %s := range %sBackward(%s)",
+ indexIdent.Name, elemName, prefix, sliceStr)
+ }
+ edits = append(edits, analysis.TextEdit{
+ Pos: loop.Init.Pos(),
+ End: loop.Post.End(),
+ NewText: []byte(header),
+ })
+
+ pass.Report(analysis.Diagnostic{
+ Pos: loop.Init.Pos(),
+ End: loop.Post.End(),
+ Message: "backward loop over slice can be modernized using slices.Backward",
+ SuggestedFixes: []analysis.SuggestedFix{{
+ Message: fmt.Sprintf("Replace with range slices.Backward(%s)", sliceStr),
+ TextEdits: edits,
+ }},
+ })
+ }
+ }
+ return nil, nil
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
index a09bfd1c6c..1d5366483a 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/doc.go
@@ -57,6 +57,16 @@
//
// fmt.Printf("%s", message)
//
+// The %w verb, as used in fmt.Errorf, should have an operand whose type
+// implements error, not a pointer to a type that implements error. Using a
+// pointer can result in surprising behavior when passing the resulting error to
+// errors.Is and errors.As. In the example below, MyError implements error:
+//
+// // %w wants operand of error type MyError, not pointer type *MyError (defeats errors.Is)
+// err := fmt.Errorf("%w", &MyError{Msg: "my error"})
+//
+// This feature applies only to files using at least Go 1.27.
+//
// # Inferred printf wrappers
//
// Functions that delegate their arguments to fmt.Printf are
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
index 1e130f4290..1306531273 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/printf/printf.go
@@ -770,10 +770,6 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
anyIndex = true
}
rng := opRange(formatArg, op)
- if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
- // One error per format is enough.
- return
- }
if op.Verb.Verb == 'w' {
switch kind {
case KindNone, KindPrint, KindPrintf:
@@ -781,6 +777,10 @@ func checkPrintf(pass *analysis.Pass, fileVersion string, kind Kind, call *ast.C
return
}
}
+ if !okPrintfArg(pass, fileVersion, call, rng, &maxArgIndex, firstArg, name, op) {
+ // One error per format is enough.
+ return
+ }
}
// Dotdotdot is hard.
if call.Ellipsis.IsValid() && maxArgIndex >= len(call.Args)-2 {
@@ -966,6 +966,26 @@ func okPrintfArg(pass *analysis.Pass, fileVersion string, call *ast.CallExpr, rn
return false
}
arg := call.Args[verbArgIndex]
+ if verb == 'w' {
+ // Check if arg is of type *E where E implements error.
+ // This diagnostic will help prevent potential misuses of errors.As,
+ // errors.Is, etc. If the %w argument in fmt.Errorf is a pointer to an error
+ // type, where the pointer type also happens to implement error, then calls
+ // to errors.Is using the result of fmt.Errorf may behave unexpectedly.
+ // See golang/go#61342.
+ typ := pass.TypesInfo.Types[arg].Type
+ if t, ok := typ.Underlying().(*types.Pointer); ok && types.Implements(t.Elem(), errorType) && versions.AtLeast(fileVersion, versions.Go1_27) {
+ // qual is a qualifier that returns the package name if different from the current package.
+ qual := func(p *types.Package) string {
+ if p == pass.Pkg {
+ return ""
+ }
+ return p.Name()
+ }
+ pass.ReportRangef(rng, "%%w wants operand of error type %s, not pointer type %s (defeats errors.Is)", types.TypeString(t.Elem(), qual), types.TypeString(typ, qual))
+ return false
+ }
+ }
if isFunctionValue(pass, arg) && verb != 'p' && verb != 'T' {
pass.ReportRangef(rng, "%s format %s arg %s is a func value, not called", name, operation.Text, astutil.Format(pass.Fset, arg))
return false
diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
index 826add2c44..7e97a4a133 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/structtag/structtag.go
@@ -222,7 +222,7 @@ var (
// in the canonical format, which is a space-separated list of key:"value"
// settings. The value may contain spaces.
func validateStructTag(tag string) error {
- // This code is based on the StructTag.Get code in package reflect.
+ // This code is based on the StructTag.Lookup code in package reflect.
n := 0
for ; tag != ""; n++ {
diff --git a/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go b/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
index 56723d1f82..77aad553d5 100644
--- a/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
+++ b/src/cmd/vendor/golang.org/x/tools/go/types/objectpath/objectpath.go
@@ -524,7 +524,7 @@ func (f *finder) find(T types.Type, path []byte) []byte {
for i := 0; i < T.NumMethods(); i++ {
m := T.Method(i)
if f.seenMethods[m] {
- return nil
+ continue // break cycles (see TestIssue70418)
}
path2 := appendOpArg(path, opMethod, i)
if m == f.obj {
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
index f211d2cc3d..b855a56008 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/astutil/util.go
@@ -147,6 +147,10 @@ func (r Range) IsValid() bool { return r.Start.IsValid() && r.Start <= r.EndPos
// extraneous whitespace and comments. Use it in new code instead of
// PathEnclosingInterval. When the exact extent of a node is known,
// use [Cursor.FindByPos] instead.
+//
+// TODO(hxjiang): Consider refactoring the function signature. It is currently
+// confusing that an error is returned even when a valid enclosing node is
+// successfully found. Consider grouping all cursors into one struct.
func Select(curFile inspector.Cursor, start, end token.Pos) (_enclosing, _start, _end inspector.Cursor, _ error) {
curEnclosing, ok := curFile.FindByPos(start, end)
if !ok {
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go b/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
index e960b36db4..57c15c414e 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/goplsexport/export.go
@@ -9,10 +9,11 @@ package goplsexport
import "golang.org/x/tools/go/analysis"
var (
- ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
- StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
- PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
- StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
- UnsafeFuncsModernizer *analysis.Analyzer // = modernize.unsafeFuncsAnalyzer
- AtomicTypesModernizer *analysis.Analyzer // = modernize.atomicTypesAnalyzer
+ ErrorsAsTypeModernizer *analysis.Analyzer // = modernize.errorsastypeAnalyzer
+ SlicesBackwardModernizer *analysis.Analyzer // = modernize.slicesbackwardAnalyzer
+ StdIteratorsModernizer *analysis.Analyzer // = modernize.stditeratorsAnalyzer
+ PlusBuildModernizer *analysis.Analyzer // = modernize.plusbuildAnalyzer
+ StringsCutModernizer *analysis.Analyzer // = modernize.stringscutAnalyzer
+ UnsafeFuncsModernizer *analysis.Analyzer // = modernize.unsafeFuncsAnalyzer
+ AtomicTypesModernizer *analysis.Analyzer // = modernize.atomicTypesAnalyzer
)
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
index e968908fd6..6a31d3bdd7 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/refactor/inline/inline.go
@@ -649,6 +649,9 @@ func (st *state) inlineCall() (*inlineCallResult, error) {
return
}
}
+ if len(path) > 0 {
+ repl = internalastutil.MaybeParenthesize(last(path), id, repl)
+ }
replaceNode(calleeDecl, id, repl)
}
@@ -1334,15 +1337,6 @@ func (st *state) typeArguments(call *ast.CallExpr) []*argument {
var args []*argument
for _, e := range exprs {
arg := &argument{expr: e, freevars: freeVars(st.caller.Info, e)}
- // Wrap the instantiating type in parens when it's not an
- // ident or qualified ident to prevent "if x == struct{}"
- // parsing ambiguity, or "T(x)" where T = "*int" or "func()"
- // from misparsing.
- // TODO(adonovan): this fails in cases where parens are disallowed, such as
- // in the composite literal expression T{k: v}.
- if _, ok := arg.expr.(*ast.Ident); !ok {
- arg.expr = &ast.ParenExpr{X: arg.expr}
- }
args = append(args, arg)
}
return args
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go
index 7112318fc2..6582cc81f5 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/typesinternal/types.go
@@ -194,3 +194,51 @@ func Imports(pkg *types.Package, path string) bool {
}
return false
}
+
+// ObjectKind returns a description of the object's kind.
+//
+// from objectKind in go/types
+func ObjectKind(obj types.Object) string {
+ switch obj := obj.(type) {
+ case *types.PkgName:
+ return "package name"
+ case *types.Const:
+ return "constant"
+ case *types.TypeName:
+ if obj.IsAlias() {
+ return "type alias"
+ } else if _, ok := obj.Type().(*types.TypeParam); ok {
+ return "type parameter"
+ } else {
+ return "defined type"
+ }
+ case *types.Var:
+ switch obj.Kind() {
+ case PackageVar:
+ return "package-level variable"
+ case LocalVar:
+ return "local variable"
+ case RecvVar:
+ return "receiver"
+ case ParamVar:
+ return "parameter"
+ case ResultVar:
+ return "result variable"
+ case FieldVar:
+ return "struct field"
+ }
+ case *types.Func:
+ if obj.Signature().Recv() != nil {
+ return "method"
+ } else {
+ return "function"
+ }
+ case *types.Label:
+ return "label"
+ case *types.Builtin:
+ return "built-in function"
+ case *types.Nil:
+ return "untyped nil"
+ }
+ return "unknown symbol"
+}
diff --git a/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go b/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
index cdd36c388a..360a5b5529 100644
--- a/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
+++ b/src/cmd/vendor/golang.org/x/tools/internal/versions/features.go
@@ -19,6 +19,7 @@ const (
Go1_24 = "go1.24"
Go1_25 = "go1.25"
Go1_26 = "go1.26"
+ Go1_27 = "go1.27"
)
// Future is an invalid unknown Go version sometime in the future.
diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt
index 7b5b9aa37f..9d799530e1 100644
--- a/src/cmd/vendor/modules.txt
+++ b/src/cmd/vendor/modules.txt
@@ -73,7 +73,7 @@ golang.org/x/text/internal/tag
golang.org/x/text/language
golang.org/x/text/transform
golang.org/x/text/unicode/norm
-# golang.org/x/tools v0.43.1-0.20260319213245-5d7afbc08aec
+# golang.org/x/tools v0.43.1-0.20260406190732-b36d1d12a1a7
## explicit; go 1.25.0
golang.org/x/tools/cmd/bisect
golang.org/x/tools/cover