From: Cherry Mui Date: Mon, 2 Mar 2026 22:05:16 +0000 (-0500) Subject: [release-branch.go1.26] cmd: re-vendor x/tools for Go 1.26.1 X-Git-Tag: go1.26.1~6 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=4d1051fdc9425deed04d63d78e073223ea9fa2cd;p=gostls13.git [release-branch.go1.26] cmd: re-vendor x/tools for Go 1.26.1 Update x/tools vendor to the tip of internal-branch.go1.26-vendor branch (642dd50), to pull in recent fixes of the modernizer. Done by cd GOROOT/cmd go get golang.org/x/tools@internal-branch.go1.26-vendor go mod tidy go mod vendor Fixes #77766 Fixes #77803 Fixes #77804 Fixes #77805 Fixes #77807 Fixes #77849 Fixes #77899 Fixes #77904 Change-Id: Id7aa8c2247949bdc104898270a4ceb3eee68a818 Reviewed-on: https://go-review.googlesource.com/c/go/+/750761 LUCI-TryBot-Result: Go LUCI Reviewed-by: Alan Donovan --- diff --git a/src/cmd/go.mod b/src/cmd/go.mod index a85acc825a..023a63059c 100644 --- a/src/cmd/go.mod +++ b/src/cmd/go.mod @@ -11,7 +11,7 @@ require ( golang.org/x/sys v0.39.0 golang.org/x/telemetry v0.0.0-20251128220624-abf20d0e57ec golang.org/x/term v0.38.0 - golang.org/x/tools v0.39.1-0.20260217205208-88886dd9587b + golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc ) require ( diff --git a/src/cmd/go.sum b/src/cmd/go.sum index d13c8dcc24..269fbb17b0 100644 --- a/src/cmd/go.sum +++ b/src/cmd/go.sum @@ -22,7 +22,7 @@ golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= -golang.org/x/tools v0.39.1-0.20260217205208-88886dd9587b h1:eAvGtZBetn9/vYF11p6xXS7Wug5moPBH4j+kgEIZWig= -golang.org/x/tools v0.39.1-0.20260217205208-88886dd9587b/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc h1:RzEk8N4Q57niCI1HA49wovYfk90ufvZo8j3JA87GZH8= +golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= 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/modernize/doc.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go index f1202c7a11..45480e936c 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 @@ -460,14 +460,15 @@ is replaced by: This avoids quadratic memory allocation and improves performance. -The analyzer requires that all references to s except the final one +The analyzer requires that all references to s before the final uses are += operations. To avoid warning about trivial cases, at least one must appear within a loop. The variable s must be a local variable, not a global or parameter. -The sole use of the finished string must be the last reference to the -variable s. (It may appear within an intervening loop or function literal, -since even s.String() is called repeatedly, it does not allocate memory.) +All uses of the finished string must come after the last += operation. +Each such use will be replaced by a call to strings.Builder's String method. +(These may appear within an intervening loop or function literal, since even +if s.String() is called repeatedly, it does not allocate memory.) # Analyzer testingcontext 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 23a0977f21..f95a9f3f0e 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 @@ -147,6 +147,13 @@ func minmax(pass *analysis.Pass) (any, error) { lhs0 := fassign.Lhs[0] rhs0 := fassign.Rhs[0] + // If the assignment occurs within a select + // comms clause (like "case lhs0 := <-rhs0:"), + // there's no way of rewriting it into a min/max call. + if ek, _ := prev.ParentEdge(); ek == edge.CommClause_Comm { + return + } + if astutil.EqualSyntax(lhs, lhs0) { if astutil.EqualSyntax(rhs, a) && (astutil.EqualSyntax(rhs0, b) || astutil.EqualSyntax(lhs0, b)) { sign = +sign 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 f09a2d26ca..0838c342fe 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 @@ -141,3 +141,5 @@ func lookup(info *types.Info, cur inspector.Cursor, name string) types.Object { _, obj := scope.LookupParent(name, cur.Node().Pos()) return obj } + +func first[T any](x T, _ any) T { return x } diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go index c42ec58ec3..17bbd1b8ca 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go @@ -209,7 +209,7 @@ func rangeint(pass *analysis.Pass) (any, error) { // such as "const limit = 1e3", its effective type may // differ between the two forms. // In a for loop, it must be comparable with int i, - // for i := 0; i < limit; i++ + // for i := 0; i < limit; i++ {} // but in a range loop it would become a float, // for i := range limit {} // which is a type error. We need to convert it to int @@ -228,9 +228,24 @@ func rangeint(pass *analysis.Pass) (any, error) { beforeLimit, afterLimit = fmt.Sprintf("%s(", types.TypeString(tVar, qual)), ")" info2 := &types.Info{Types: make(map[ast.Expr]types.TypeAndValue)} if types.CheckExpr(pass.Fset, pass.Pkg, limit.Pos(), limit, info2) == nil { - tLimit := types.Default(info2.TypeOf(limit)) - if types.AssignableTo(tLimit, tVar) { - beforeLimit, afterLimit = "", "" + tLimit := info2.TypeOf(limit) + // Eliminate conversion when safe. + // + // Redundant conversions are not only unsightly but may in some cases cause + // architecture-specific types (e.g. syscall.Timespec.Nsec) to be inserted + // into otherwise portable files. + // + // The operand must have an integer type (not, say, '1e6') + // even when assigning to an existing integer variable. + if isInteger(tLimit) { + // When declaring a new var from an untyped limit, + // the limit's default type is what matters. + if init.Tok != token.ASSIGN { + tLimit = types.Default(tLimit) + } + if types.AssignableTo(tLimit, tVar) { + beforeLimit, afterLimit = "", "" + } } } } @@ -311,6 +326,11 @@ func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool { if v, ok := info.Defs[id]; ok && v.Pos() != id.Pos() { return true // reassignment of i (i, j := 1, 2) } + case edge.RangeStmt_Key: + rng := cur.Parent().Node().(*ast.RangeStmt) + if rng.Tok == token.ASSIGN { + return true // "for k, v = range x" is like an AssignStmt to k, v + } case edge.IncDecStmt_X: return true // i++, i-- case edge.UnaryExpr_X: @@ -320,3 +340,8 @@ func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool { } return false } + +func isInteger(t types.Type) bool { + basic, ok := t.Underlying().(*types.Basic) + return ok && basic.Info()&types.IsInteger != 0 +} diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go index 0fc781813f..ed056beb25 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go @@ -47,6 +47,14 @@ func reflecttypefor(pass *analysis.Pass) (any, error) { // Have: reflect.TypeOf(expr) expr := call.Args[0] + + // reflect.TypeFor cannot be instantiated with an untyped nil. + // We use type information rather than checking the identifier name + // to correctly handle edge cases where "nil" is shadowed (e.g. nil := "nil"). + if info.Types[expr].IsNil() { + continue + } + if !typesinternal.NoEffects(info, expr) { continue // don't eliminate operand: may have effects } diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go index 960a46644b..6c8ea22b3c 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go @@ -144,8 +144,7 @@ func appendclipped(pass *analysis.Pass) (any, error) { // https://go.dev/issue/70815#issuecomment-2671572984 fileImports := func(path string) bool { return slices.ContainsFunc(file.Imports, func(spec *ast.ImportSpec) bool { - value, _ := strconv.Unquote(spec.Path.Value) - return value == path + return first(strconv.Unquote(spec.Path.Value)) == path }) } clonepkg := cond( diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go index f7318b123d..95f2127fbf 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go @@ -220,7 +220,7 @@ func stditerators(pass *analysis.Pass) (any, error) { ) // Analyze enclosing loop. - switch ek, _ := curLenCall.ParentEdge(); ek { + switch first(curLenCall.ParentEdge()) { case edge.BinaryExpr_Y: // pattern 1: for i := 0; i < x.Len(); i++ { ... } var ( diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go index 98c8287a13..8420c95642 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go @@ -78,14 +78,16 @@ nextcand: for _, v := range slices.SortedFunc(maps.Keys(candidates), lexicalOrder) { var edits []analysis.TextEdit - // Check declaration of s: + // Check declaration of s has one of these forms: // // s := expr // var s [string] [= expr] + // var ( ...; s [string] [= expr] ) (s is last) // - // and transform to: + // and transform to one of: // - // var s strings.Builder; s.WriteString(expr) + // var s strings.Builder ; s.WriteString(expr) + // var ( s strings.Builder); s.WriteString(expr) // def, ok := index.Def(v) if !ok { @@ -153,9 +155,20 @@ nextcand: } } else if ek == edge.ValueSpec_Names && - len(def.Parent().Node().(*ast.ValueSpec).Names) == 1 { - // Have: var s [string] [= expr] + len(def.Parent().Node().(*ast.ValueSpec).Names) == 1 && + first(def.Parent().Parent().LastChild()) == def.Parent() { + // Have: var s [string] [= expr] + // or: var ( s [string] [= expr] ) // => var s strings.Builder; s.WriteString(expr) + // + // The LastChild check rejects this case: + // var ( s [string] [= expr]; others... ) + // => + // var ( s strings.Builder; others... ); s.WriteString(expr) + // since it moves 'expr' across 'others', requiring + // reformatting of syntax, which in general is lossy + // of comments and vertical space. + // We expect this to be rare. // Add strings import. prefix, importEdits := refactor.AddImport( @@ -170,6 +183,8 @@ nextcand: init = spec.Type.End() } + // Replace (possibly absent) type: + // // var s [string] // ---------------- // var s strings.Builder @@ -180,6 +195,25 @@ nextcand: }) if len(spec.Values) > 0 && !isEmptyString(pass.TypesInfo, spec.Values[0]) { + if decl.Rparen.IsValid() { + // var decl with explicit parens: + // + // var ( ... = expr ) + // - - + // var ( ... ); s.WriteString(expr) + edits = append(edits, []analysis.TextEdit{ + { + Pos: init, + End: init, + NewText: []byte(")"), + }, + { + Pos: spec.Values[0].End(), + End: decl.End(), + }, + }...) + } + // = expr // ---------------- - // ; s.WriteString(expr) @@ -190,8 +224,8 @@ nextcand: NewText: fmt.Appendf(nil, "; %s.WriteString(", v.Name()), }, { - Pos: decl.End(), - End: decl.End(), + Pos: spec.Values[0].End(), + End: spec.Values[0].End(), NewText: []byte(")"), }, }...) @@ -220,8 +254,8 @@ nextcand: // var s string // for ... { s += expr } // - // - The final use of s must be as an rvalue (e.g. use(s), not &s). - // This will become s.String(). + // - All uses of s after the last += must be rvalue uses (e.g. use(s), not &s). + // Each of these will become s.String(). // // Perhaps surprisingly, it is fine for there to be an // intervening loop or lambda w.r.t. the declaration of s: @@ -236,7 +270,7 @@ nextcand: var ( numLoopAssigns int // number of += assignments within a loop loopAssign *ast.AssignStmt // first += assignment within a loop - seenRvalueUse bool // => we've seen the sole final use of s as an rvalue + seenRvalueUse bool // => we've seen at least one rvalue use of s ) for curUse := range index.Uses(v) { // Strip enclosing parens around Ident. @@ -246,11 +280,6 @@ nextcand: ek, _ = curUse.ParentEdge() } - // The rvalueUse must be the lexically last use. - if seenRvalueUse { - continue nextcand - } - // intervening reports whether cur has an ancestor of // one of the given types that is within the scope of v. intervening := func(types ...ast.Node) bool { @@ -263,6 +292,11 @@ nextcand: } if ek == edge.AssignStmt_Lhs { + // After an rvalue use, no more assignments are allowed. + if seenRvalueUse { + continue nextcand + } + assign := curUse.Parent().Node().(*ast.AssignStmt) if assign.Tok != token.ADD_ASSIGN { continue nextcand diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go index 62088f0e91..8db0b3b9b5 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go @@ -183,7 +183,7 @@ func stringscut(pass *analysis.Pass) (any, error) { // len(substr)]), then we can replace the call to Index() // with a call to Cut() and use the returned ok, before, // and after variables accordingly. - negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr) + negative, nonnegative, beforeSlice, afterSlice := checkIdxUses(pass.TypesInfo, index.Uses(iObj), s, substr, iObj) // Either there are no uses of before, after, or ok, or some use // of i does not match our criteria - don't suggest a fix. @@ -374,14 +374,31 @@ func indexArgValid(info *types.Info, index *typeindex.Index, expr ast.Expr, afte // 2. nonnegative - a condition equivalent to i >= 0 // 3. beforeSlice - a slice of `s` that matches either s[:i], s[0:i] // 4. afterSlice - a slice of `s` that matches one of: s[i+len(substr):], s[len(substr) + i:], s[i + const], s[k + i] (where k = len(substr)) -func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) { +// +// Additionally, all beforeSlice and afterSlice uses must be dominated by a +// nonnegative guard on i (i.e., inside the body of an if whose condition +// checks i >= 0, or in the else of a negative check, or after an +// early-return negative check). This ensures that the rewrite from +// s[i+len(sep):] to "after" preserves semantics, since when i == -1, +// s[i+len(sep):] may yield a valid substring (e.g. s[0:] for single-byte +// separators), but "after" would be "". +// +// When len(substr)==1, it's safe to use s[i+1:] even when i < 0. +// Otherwise, each replacement of s[i+1:] must be guarded by a check +// that i is nonnegative. +func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr ast.Expr, iObj types.Object) (negative, nonnegative, beforeSlice, afterSlice []ast.Expr) { + requireGuard := true + if l := constSubstrLen(info, substr); l != -1 && l != 1 { + requireGuard = false + } + use := func(cur inspector.Cursor) bool { ek, _ := cur.ParentEdge() n := cur.Parent().Node() switch ek { case edge.BinaryExpr_X, edge.BinaryExpr_Y: check := n.(*ast.BinaryExpr) - switch checkIdxComparison(info, check) { + switch checkIdxComparison(info, check, iObj) { case -1: negative = append(negative, check) return true @@ -397,10 +414,10 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a if slice, ok := cur.Parent().Parent().Node().(*ast.SliceExpr); ok && sameObject(info, s, slice.X) && slice.Max == nil { - if isBeforeSlice(info, ek, slice) { + if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) { beforeSlice = append(beforeSlice, slice) return true - } else if isAfterSlice(info, ek, slice, substr) { + } else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) { afterSlice = append(afterSlice, slice) return true } @@ -410,10 +427,10 @@ func checkIdxUses(info *types.Info, uses iter.Seq[inspector.Cursor], s, substr a // Check that the thing being sliced is s and that the slice doesn't // have a max index. if sameObject(info, s, slice.X) && slice.Max == nil { - if isBeforeSlice(info, ek, slice) { + if isBeforeSlice(info, ek, slice) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) { beforeSlice = append(beforeSlice, slice) return true - } else if isAfterSlice(info, ek, slice, substr) { + } else if isAfterSlice(info, ek, slice, substr) && (!requireGuard || isSliceIndexGuarded(info, cur, iObj)) { afterSlice = append(afterSlice, slice) return true } @@ -465,8 +482,15 @@ func hasModifyingUses(info *types.Info, uses iter.Seq[inspector.Cursor], afterPo // Since strings.Index returns exactly -1 if the substring is not found, we // don't need to handle expressions like i <= -3. // We return 0 if the expression does not match any of these options. -// We assume that a check passed to checkIdxComparison has i as one of its operands. -func checkIdxComparison(info *types.Info, check *ast.BinaryExpr) int { +func checkIdxComparison(info *types.Info, check *ast.BinaryExpr, iObj types.Object) int { + isI := func(e ast.Expr) bool { + id, ok := e.(*ast.Ident) + return ok && info.Uses[id] == iObj + } + if !isI(check.X) && !isI(check.Y) { + return 0 + } + // Ensure that the constant (if any) is on the right. x, op, y := check.X, check.Op, check.Y if info.Types[x].Value != nil { @@ -515,44 +539,49 @@ func isBeforeSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr) bool { return ek == edge.SliceExpr_High && (slice.Low == nil || isZeroIntConst(info, slice.Low)) } -// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):], -// or s[i + k:] where k is a const is equal to len(substr). -func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool { - lowExpr, ok := slice.Low.(*ast.BinaryExpr) - if !ok || slice.High != nil { - return false - } - // Returns true if the expression is a call to len(substr). - isLenCall := func(expr ast.Expr) bool { - call, ok := expr.(*ast.CallExpr) - if !ok || len(call.Args) != 1 { - return false - } - return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen - } - +// constSubstrLen returns the constant length of substr, or -1 if unknown. +func constSubstrLen(info *types.Info, substr ast.Expr) int { // Handle len([]byte(substr)) - if is[*ast.CallExpr](substr) { - call := substr.(*ast.CallExpr) + if call, ok := substr.(*ast.CallExpr); ok { tv := info.Types[call.Fun] if tv.IsType() && types.Identical(tv.Type, byteSliceType) { // Only one arg in []byte conversion. substr = call.Args[0] } } - substrLen := -1 substrVal := info.Types[substr].Value if substrVal != nil { switch substrVal.Kind() { case constant.String: - substrLen = len(constant.StringVal(substrVal)) + return len(constant.StringVal(substrVal)) case constant.Int: // constant.Value is a byte literal, e.g. bytes.IndexByte(_, 'a') // or a numeric byte literal, e.g. bytes.IndexByte(_, 65) - substrLen = 1 + // ([]byte(rune) is not legal.) + return 1 + } + } + return -1 +} + +// isAfterSlice reports whether the SliceExpr is of the form s[i+len(substr):], +// or s[i + k:] where k is a const is equal to len(substr). +func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr ast.Expr) bool { + lowExpr, ok := slice.Low.(*ast.BinaryExpr) + if !ok || slice.High != nil { + return false + } + // Returns true if the expression is a call to len(substr). + isLenCall := func(expr ast.Expr) bool { + call, ok := expr.(*ast.CallExpr) + if !ok || len(call.Args) != 1 { + return false } + return sameObject(info, substr, call.Args[0]) && typeutil.Callee(info, call) == builtinLen } + substrLen := constSubstrLen(info, substr) + switch ek { case edge.BinaryExpr_X: kVal := info.Types[lowExpr.Y].Value @@ -578,6 +607,75 @@ func isAfterSlice(info *types.Info, ek edge.Kind, slice *ast.SliceExpr, substr a return false } +// isSliceIndexGuarded reports whether a use of the index variable i (at the given cursor) +// inside a slice expression is dominated by a nonnegative guard. +// A use is considered guarded if any of the following are true: +// - It is inside the Body of an IfStmt whose condition is a nonnegative check on i. +// - It is inside the Else of an IfStmt whose condition is a negative check on i. +// - It is preceded (in the same block) by an IfStmt whose condition is a +// negative check on i with a terminating body (e.g., early return). +// +// Conversely, a use is immediately rejected if: +// - It is inside the Body of an IfStmt whose condition is a negative check on i. +// - It is inside the Else of an IfStmt whose condition is a nonnegative check on i. +// +// We have already checked (see [hasModifyingUses]) that there are no +// intervening uses (incl. via aliases) of i that might alter its value. +func isSliceIndexGuarded(info *types.Info, cur inspector.Cursor, iObj types.Object) bool { + for anc := range cur.Enclosing() { + switch ek, _ := anc.ParentEdge(); ek { + case edge.IfStmt_Body, edge.IfStmt_Else: + ifStmt := anc.Parent().Node().(*ast.IfStmt) + check := condChecksIdx(info, ifStmt.Cond, iObj) + if ek == edge.IfStmt_Else { + check = -check + } + if check > 0 { + return true // inside nonnegative-guarded block (i >= 0 here) + } + if check < 0 { + return false // inside negative-guarded block (i < 0 here) + } + case edge.BlockStmt_List: + // Check preceding siblings for early-return negative checks. + for sib, ok := anc.PrevSibling(); ok; sib, ok = sib.PrevSibling() { + ifStmt, ok := sib.Node().(*ast.IfStmt) + if ok && condChecksIdx(info, ifStmt.Cond, iObj) < 0 && bodyTerminates(ifStmt.Body) { + return true // preceded by early-return negative check + } + } + case edge.FuncDecl_Body, edge.FuncLit_Body: + return false // stop at function boundary + } + } + return false +} + +// condChecksIdx reports whether cond is a BinaryExpr that checks +// the index variable iObj for negativity or non-negativity. +// Returns -1 for negative (e.g. i < 0), +1 for nonnegative (e.g. i >= 0), 0 otherwise. +func condChecksIdx(info *types.Info, cond ast.Expr, iObj types.Object) int { + binExpr, ok := cond.(*ast.BinaryExpr) + if !ok { + return 0 + } + return checkIdxComparison(info, binExpr, iObj) +} + +// bodyTerminates reports whether the given block statement unconditionally +// terminates execution (via return, break, continue, or goto). +func bodyTerminates(block *ast.BlockStmt) bool { + if len(block.List) == 0 { + return false + } + last := block.List[len(block.List)-1] + switch last.(type) { + case *ast.ReturnStmt, *ast.BranchStmt: + return true // return, break, continue, goto + } + return false +} + // sameObject reports whether we know that the expressions resolve to the same object. func sameObject(info *types.Info, expr1, expr2 ast.Expr) bool { if ident1, ok := expr1.(*ast.Ident); ok { diff --git a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go index abf5885cee..5e42587105 100644 --- a/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go +++ b/src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go @@ -97,6 +97,9 @@ func waitgroup(pass *analysis.Pass) (any, error) { if !ok || len(goStmt.Call.Args) != 0 { continue // go argument is not func(){...}() } + if lit.Type.Results != nil && len(lit.Type.Results.List) > 0 { + continue // function literal has return values; wg.Go requires func() + } list := lit.Body.List if len(list) == 0 { continue diff --git a/src/cmd/vendor/modules.txt b/src/cmd/vendor/modules.txt index 7bd6bf86ce..cb5b826075 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.39.1-0.20260217205208-88886dd9587b +# golang.org/x/tools v0.39.1-0.20260302211140-642dd50cb7cc ## explicit; go 1.24.0 golang.org/x/tools/cmd/bisect golang.org/x/tools/cover