diff options
| author | Mark Freeman <mark@golang.org> | 2026-04-06 15:36:17 -0400 |
|---|---|---|
| committer | Mark Freeman <mark@golang.org> | 2026-04-07 09:07:22 -0700 |
| commit | 9a1e20beb0240df1f9dcc8e7d4d66de573828309 (patch) | |
| tree | 254fca4143449b4119bd31b601b47a91582daa81 | |
| parent | 33e66cfb0406b27ce11229a4ebd944c4cb20006f (diff) | |
| download | go-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>
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 |
