]> Cypherpunks repositories - gostls13.git/commitdiff
[release-branch.go1.26] cmd: re-vendor x/tools for Go 1.26.1
authorCherry Mui <cherryyz@google.com>
Mon, 2 Mar 2026 22:05:16 +0000 (17:05 -0500)
committerCherry Mui <cherryyz@google.com>
Tue, 3 Mar 2026 02:06:21 +0000 (18:06 -0800)
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>
13 files changed:
src/cmd/go.mod
src/cmd/go.sum
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/doc.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/minmax.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/modernize.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/rangeint.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/reflect.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/slices.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stditerators.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringsbuilder.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/stringscut.go
src/cmd/vendor/golang.org/x/tools/go/analysis/passes/modernize/waitgroup.go
src/cmd/vendor/modules.txt

index a85acc825a1c465880e8833b527ee10d9a4b765b..023a63059c1fdbbbaa286295a4fc8512cf6f53e4 100644 (file)
@@ -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 (
index d13c8dcc240553e1003cdf0e2c0b89f28df94fc0..269fbb17b04e976f05e569325c5ed317f7f4bef0 100644 (file)
@@ -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=
index f1202c7a117739494bb45d7ce6298b9dabb7499d..45480e936c226a95742bfe8078998a4b908031eb 100644 (file)
@@ -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
 
index 23a0977f21239be6054b96d3e2b88e621de362bf..f95a9f3f0ebcb2310bc4a7688d5e19109af4e129 100644 (file)
@@ -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
index f09a2d26ca23507880e7110443e3c0731922c3ef..0838c342feb686daa8c568fc6df5849f4195ed9a 100644 (file)
@@ -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 }
index c42ec58ec3a7a7c19bfc3a758125709de7d48c78..17bbd1b8ca29c269c60877c95c20a4e1599b1de8 100644 (file)
@@ -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
+}
index 0fc781813f6959b1978ae2e2e263e6fb923590d3..ed056beb259805329546b2540ca201630cfcfb32 100644 (file)
@@ -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
                }
index 960a46644bf1adb6b97dd8dc897a69404642c8fd..6c8ea22b3c1d29eac01df581cca89752da453c83 100644 (file)
@@ -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(
index f7318b123daba1c16c10e32897995c50eb8a7613..95f2127fbf33de13e9216f360f675070674fcfaa 100644 (file)
@@ -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 (
index 98c8287a13dde25be9070c8d9ffd0dda45bcbab3..8420c95642ad3aa1d321ca30b1544b6853a602f7 100644 (file)
@@ -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
index 62088f0e9164dd35ade35ac9048e474a1a1480d9..8db0b3b9b5eb668ddfd06885642f31f744569690 100644 (file)
@@ -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 {
index abf5885cee28cc00eaf60dc5ce53bc98d6e79b6c..5e425871056a6361586a8c0935220f87e984ef0d 100644 (file)
@@ -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
index 7bd6bf86ce00575a3fc03bf46faa7da46b85da32..cb5b826075bd175c80cd5568682ed522b45f6171 100644 (file)
@@ -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