aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCherry Mui <cherryyz@google.com>2026-03-02 17:05:16 -0500
committerCherry Mui <cherryyz@google.com>2026-03-02 18:06:21 -0800
commit4d1051fdc9425deed04d63d78e073223ea9fa2cd (patch)
tree9c7c62fe5a1f7a4e784715f0a57bb2c397d9c43a
parent8cce3ab20c49a5c3c9fa8e97ad47335c3ccd2620 (diff)
downloadgo-4d1051fdc9425deed04d63d78e073223ea9fa2cd.tar.xz
[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 <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Alan Donovan <adonovan@google.com>
-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/modernize/doc.go9
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go7
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go2
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go33
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go8
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go3
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go2
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go64
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go158
-rw-r--r--src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go3
-rw-r--r--src/cmd/vendor/modules.txt2
13 files changed, 237 insertions, 60 deletions
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