[tools] go/analysis/passes/modernize: slicesbackward - skip is s[i] is mutated

5 views
Skip to first unread message

Madeline Kalil (Gerrit)

unread,
Jul 17, 2026, 3:01:24 PMJul 17
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Madeline Kalil has uploaded the change for review

Commit message

go/analysis/passes/modernize: slicesbackward - skip is s[i] is mutated

In the slicesbackward modernizer, we replace backward loops over
slices with a call to slices.Backward. However, if the index
expression s[i] is address-taken or mutated within the loop,
we should not offer this fix. For example:

for i := len(s) - 1; i > 0; i-- {
s[i].n = 5
}

for _, v := range slices.Backward(s) {
v.n = 5
}

v is a local copy, so the original s will not get mutated.
To avoid changing the program behavior, we should
not suggest a modernization.

Also, add a case to isScalarLValue for completeness: an ident
appearing in the value of a range statement is the equivalent
of an assignment (we already covered the key of a range statement).
Add tests in rangeint modernizer for this case.

Fixes golang/go#80410
Change-Id: I873afe8965dee39994547e16f284ca80541334d6

Change diff

diff --git a/go/analysis/passes/modernize/modernize.go b/go/analysis/passes/modernize/modernize.go
index 755c0d6..1e2fb6a 100644
--- a/go/analysis/passes/modernize/modernize.go
+++ b/go/analysis/passes/modernize/modernize.go
@@ -17,8 +17,10 @@

"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/ast/inspector"
"golang.org/x/tools/internal/analysis/analyzerutil"
+ "golang.org/x/tools/internal/astutil"
"golang.org/x/tools/internal/refactor"
"golang.org/x/tools/internal/typesinternal/typeindex"

@@ -187,3 +189,40 @@
}
return depth >= 4
}
+
+// isScalarLvalue reports whether the specified identifier is
+// address-taken or appears on the left side of an assignment.
+//
+// This function is valid only for scalars (x = ...),
+// not for aggregates (x.a[i] = ...)
+func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool {
+ // Unfortunately we can't simply use info.Types[e].Assignable()
+ // as it is always true for a variable even when that variable is
+ // used only as an r-value. So we must inspect enclosing syntax.
+
+ cur := astutil.UnparenEnclosingCursor(curId)
+
+ switch cur.ParentEdgeKind() {
+ case edge.AssignStmt_Lhs:
+ assign := cur.Parent().Node().(*ast.AssignStmt)
+ if assign.Tok != token.DEFINE {
+ return true // i = j or i += j
+ }
+ id := curId.Node().(*ast.Ident)
+ if v, ok := info.Defs[id]; ok && v.Pos() != id.Pos() {
+ return true // reassignment of i (i, j := 1, 2)
+ }
+ case edge.RangeStmt_Key, edge.RangeStmt_Value:
+ 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:
+ if cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
+ return true // &i
+ }
+ }
+ return false
+}
diff --git a/go/analysis/passes/modernize/rangeint.go b/go/analysis/passes/modernize/rangeint.go
index f7cb965..d882374 100644
--- a/go/analysis/passes/modernize/rangeint.go
+++ b/go/analysis/passes/modernize/rangeint.go
@@ -13,8 +13,6 @@

"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/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
@@ -345,40 +343,3 @@
}
return nil, nil
}
-
-// isScalarLvalue reports whether the specified identifier is
-// address-taken or appears on the left side of an assignment.
-//
-// This function is valid only for scalars (x = ...),
-// not for aggregates (x.a[i] = ...)
-func isScalarLvalue(info *types.Info, curId inspector.Cursor) bool {
- // Unfortunately we can't simply use info.Types[e].Assignable()
- // as it is always true for a variable even when that variable is
- // used only as an r-value. So we must inspect enclosing syntax.
-
- cur := astutil.UnparenEnclosingCursor(curId)
-
- switch cur.ParentEdgeKind() {
- case edge.AssignStmt_Lhs:
- assign := cur.Parent().Node().(*ast.AssignStmt)
- if assign.Tok != token.DEFINE {
- return true // i = j or i += j
- }
- id := curId.Node().(*ast.Ident)
- 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:
- if cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND {
- return true // &i
- }
- }
- return false
-}
diff --git a/go/analysis/passes/modernize/slicesbackward.go b/go/analysis/passes/modernize/slicesbackward.go
index 02cd30a..ee5be5c 100644
--- a/go/analysis/passes/modernize/slicesbackward.go
+++ b/go/analysis/passes/modernize/slicesbackward.go
@@ -14,6 +14,7 @@
"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/ast/inspector"
"golang.org/x/tools/go/types/typeutil"
"golang.org/x/tools/internal/analysis/analyzerutil"
typeindexanalyzer "golang.org/x/tools/internal/analysis/typeindex"
@@ -161,10 +162,10 @@
// mutating the slice or taking an element's address, a fix will not
// be offered.
if curUse.ParentEdgeKind() == edge.IndexExpr_Index {
- if isScalarLvalue(pass.TypesInfo, curUse.Parent()) {
+ idxCur := curUse.Parent()
+ if isScalarLvalue(info, idxCur) || isMutating(info, idxCur) {
continue nextLoop
}
- idxCur := curUse.Parent()
idxExpr := idxCur.Node().(*ast.IndexExpr)
if astutil.EqualSyntax(idxExpr.X, sliceExpr) {
sliceIdxs++
@@ -268,3 +269,56 @@
}
return "v"
}
+
+// isMutating reports whether the index expression idxCur (s[i]) is mutated or
+// address-taken within its enclosing statement.
+// Modernization to "for _, v := range slices.Backward(s)" is unsafe if s[i] or
+// its fields are mutated or address-taken (since v would be a local copy of the
+// element so s[i] wouldn't get mutated).
+// We don't need to worry about indirect selections (e.g. s[i].n++ where s is
+// []*item) or indirect references like indexing a slice of slices.
+func isMutating(info *types.Info, idxCur inspector.Cursor) bool {
+ for cur := range idxCur.Enclosing() {
+ switch cur.ParentEdgeKind() {
+ case edge.SelectorExpr_X:
+ selExpr := cur.Parent().Node().(*ast.SelectorExpr)
+ if sel, ok := info.Selections[selExpr]; ok {
+ if sel.Indirect() {
+ return false
+ }
+ if sel.Kind() == types.MethodVal {
+ // s[i].Method()
+ if sig, ok := sel.Obj().Type().(*types.Signature); ok && sig.Recv() != nil {
+ if _, isPtr := sig.Recv().Type().Underlying().(*types.Pointer); isPtr {
+ // Calling a method on a pointer receiver may result in mutation.
+ return true
+ }
+ }
+ }
+ // Else, look at the next enclosing node in case we have a chain of selections.
+ }
+ case edge.IndexExpr_X, edge.SliceExpr_X:
+ // s[i][0] or s[i][0:2]
+ if t := info.TypeOf(cur.Node().(ast.Expr)); t != nil {
+ switch t.Underlying().(type) {
+ // Indexing/slicing a slice of pointers, slice of slices, or slices of
+ // maps is an indirect reference (slice of arrays, slice of structs,
+ // etc. is a direct reference).
+ case *types.Pointer, *types.Slice, *types.Map:
+ return false
+ }
+ }
+ // s[i] = 2; s[i]++; for s[i], _ = range s; for _, s[i] = range s
+ case edge.AssignStmt_Lhs, edge.IncDecStmt_X, edge.RangeStmt_Key, edge.RangeStmt_Value:
+ return true
+ // &s[i]
+ case edge.UnaryExpr_X:
+ return cur.Parent().Node().(*ast.UnaryExpr).Op == token.AND
+ case edge.ParenExpr_X:
+ // Skip parentheses and evaluate next enclosing node
+ default:
+ return false
+ }
+ }
+ return false
+}
diff --git a/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go b/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
index d3ea1e9..4f41813 100644
--- a/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
+++ b/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
@@ -317,6 +317,14 @@
}
}

+func issue77034_value(slice []int) {
+ for i := 0; i < 5; i++ { // nope: inner loop modifies i
+ for _, i = range slice {
+ }
+ }
+}
+
+
func issue77034_define_inner() {
for i := 0; i < 5; i++ { // want "for loop can be modernized using range over int"
for i := range 10 { // inner "i" doesn't modify outer "i"
diff --git a/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden b/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
index f85e986..7cc05a9 100644
--- a/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
@@ -316,6 +316,14 @@
}
}

+func issue77034_value(slice []int) {
+ for i := 0; i < 5; i++ { // nope: inner loop modifies i
+ for _, i = range slice {
+ }
+ }
+}
+
+
func issue77034_define_inner() {
for range 5 { // want "for loop can be modernized using range over int"
for i := range 10 { // inner "i" doesn't modify outer "i"
diff --git a/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go b/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go
index fe3fc25..a56dcc7 100644
--- a/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go
+++ b/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go
@@ -4,7 +4,10 @@

package slicesbackward

-import "slices"
+import (
+ "slices"
+ "sync"
+)

var _ = slices.Backward[[]int] // force import of "slices" to avoid duplicate import edits

@@ -157,3 +160,108 @@
_ = &s[i]
}
}
+
+type item struct {
+ n int
+}
+
+// Should NOT fire: field mutation
+func indexExprMutated(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].n++
+ }
+}
+
+type outer struct {
+ inner item
+}
+
+// Should NOT fire: nested field mutation
+func indexExprNestedField(s []outer) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].inner.n++
+ }
+}
+
+// Should NOT fire: field assignment
+func indexExprFieldAssign(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].n = 5
+ }
+}
+
+// Should NOT fire: multi-field assignment
+func indexExprMultiAssign(s []int) {
+ for i := len(s) - 2; i >= 0; i-- {
+ s[i], s[i+1] = 1, 2
+ }
+}
+
+// Should NOT fire: parenthesized assignment
+func indexExprParenthesizedAssign(s []int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ (s[i]) = 5
+ }
+}
+
+// Should NOT fire: address-taken
+func indexExprFieldAddr(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ _ = &s[i].n
+ }
+}
+
+// Should NOT fire: method call with pointer receiver may mutate element
+func indexExprMethodCall(s []sync.Mutex) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].Lock()
+ s[i].Unlock()
+ }
+}
+
+type stringer struct{}
+
+func (stringer) String() string { return "" }
+
+// SHOULD fire: method call with value receiver does not mutate element
+func indexExprValueReceiver(s []stringer) {
+ for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward"
+ _ = s[i].String()
+ }
+}
+
+// Should NOT fire: range assignment mutating slice elements
+func indexExprRangeAssign(s []int, x []int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ for s[i] = range x {
+ }
+ }
+}
+
+// SHOULD fire: slice of slices is indirect reference
+func indexExprSliceOfSlices(s [][]int) {
+ for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward"
+ s[i][0]++
+ }
+}
+
+// SHOULD fire: slice of pointers is indirect reference
+func indexExprPointerSlice(s []*item) {
+ for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward"
+ s[i].n++
+ }
+}
+
+// Should NOT fire: slice of arrays, mutating array element is direct mutation
+func indexExprSliceOfArrays(s [][3]int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i][0]++
+ }
+}
+
+// SHOULD fire: index expression used as index of another slice which is mutated
+func indexExprUsedAsIndex(s []int) {
+ for i := len(s) - 1; i >= 0; i-- { // want "backward loop over slice can be modernized using slices.Backward"
+ s[s[i]] = 5
+ }
+}
diff --git a/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden b/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden
index e3fb892..dffe408 100644
--- a/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden
@@ -4,7 +4,10 @@

package slicesbackward

-import "slices"
+import (
+ "slices"
+ "sync"
+)

var _ = slices.Backward[[]int] // force import of "slices" to avoid duplicate import edits

@@ -157,3 +160,109 @@
_ = &s[i]
}
}
+
+type item struct {
+ n int
+}
+
+// Should NOT fire: field mutation
+func indexExprMutated(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].n++
+ }
+}
+
+type outer struct {
+ inner item
+}
+
+// Should NOT fire: nested field mutation
+func indexExprNestedField(s []outer) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].inner.n++
+ }
+}
+
+// Should NOT fire: field assignment
+func indexExprFieldAssign(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].n = 5
+ }
+}
+
+// Should NOT fire: multi-field assignment
+func indexExprMultiAssign(s []int) {
+ for i := len(s) - 2; i >= 0; i-- {
+ s[i], s[i+1] = 1, 2
+ }
+}
+
+// Should NOT fire: parenthesized assignment
+func indexExprParenthesizedAssign(s []int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ (s[i]) = 5
+ }
+}
+
+// Should NOT fire: address-taken
+func indexExprFieldAddr(s []item) {
+ for i := len(s) - 1; i >= 0; i-- {
+ _ = &s[i].n
+ }
+}
+
+// Should NOT fire: method call with pointer receiver may mutate element
+func indexExprMethodCall(s []sync.Mutex) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i].Lock()
+ s[i].Unlock()
+ }
+}
+
+type stringer struct{}
+
+func (stringer) String() string { return "" }
+
+// SHOULD fire: method call with value receiver does not mutate element
+func indexExprValueReceiver(s []stringer) {
+ for _, v := range slices.Backward(s) { // want "backward loop over slice can be modernized using slices.Backward"
+ _ = v.String()
+ }
+}
+
+// Should NOT fire: range assignment mutating slice elements
+func indexExprRangeAssign(s []int, x []int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ for s[i] = range x {
+ }
+ }
+}
+
+// SHOULD fire: slice of slices is indirect reference
+func indexExprSliceOfSlices(s [][]int) {
+ for _, v := range slices.Backward(s) { // want "backward loop over slice can be modernized using slices.Backward"
+ v[0]++
+ }
+}
+
+// SHOULD fire: slice of pointers is indirect reference
+func indexExprPointerSlice(s []*item) {
+ for _, v := range slices.Backward(s) { // want "backward loop over slice can be modernized using slices.Backward"
+ v.n++
+ }
+}
+
+// Should NOT fire: slice of arrays, mutating array element is direct mutation
+func indexExprSliceOfArrays(s [][3]int) {
+ for i := len(s) - 1; i >= 0; i-- {
+ s[i][0]++
+ }
+}
+
+// SHOULD fire: index expression used as index of another slice which is mutated
+func indexExprUsedAsIndex(s []int) {
+ for _, v := range slices.Backward(s) { // want "backward loop over slice can be modernized using slices.Backward"
+ s[v] = 5
+ }
+}
+

Change information

Files:
  • M go/analysis/passes/modernize/modernize.go
  • M go/analysis/passes/modernize/rangeint.go
  • M go/analysis/passes/modernize/slicesbackward.go
  • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
  • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
  • M go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go
  • M go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden
Change size: L
Delta: 7 files changed, 330 insertions(+), 43 deletions(-)
Open in Gerrit

Related details

Attention set is empty
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newchange
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
Gerrit-Change-Number: 802260
Gerrit-PatchSet: 1
Gerrit-Owner: Madeline Kalil <mka...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
Jul 17, 2026, 3:03:39 PMJul 17
to goph...@pubsubhelper.golang.org, Alan Donovan, golang-co...@googlegroups.com
Attention needed from Alan Donovan

Madeline Kalil voted Commit-Queue+1

Commit-Queue+1
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
Gerrit-Change-Number: 802260
Gerrit-PatchSet: 1
Gerrit-Owner: Madeline Kalil <mka...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
Gerrit-Attention: Alan Donovan <adon...@google.com>
Gerrit-Comment-Date: Fri, 17 Jul 2026 19:03:35 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
Jul 17, 2026, 3:04:00 PMJul 17
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
Attention needed from Alan Donovan

Madeline Kalil uploaded new patchset

Madeline Kalil uploaded patch set #2 to this change.
Open in Gerrit

Related details

Attention is currently required from:
  • Alan Donovan
Submit Requirements:
  • requirement is not satisfiedCode-Review
  • requirement satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
  • requirement is not satisfiedTryBots-Pass
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: newpatchset
Gerrit-Project: tools
Gerrit-Branch: master
Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
Gerrit-Change-Number: 802260
Gerrit-PatchSet: 2
Gerrit-Owner: Madeline Kalil <mka...@google.com>
Gerrit-Reviewer: Alan Donovan <adon...@google.com>
Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
Jul 17, 2026, 5:53:07 PMJul 17
to Madeline Kalil, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
Attention needed from Madeline Kalil

Alan Donovan added 6 comments

File go/analysis/passes/modernize/modernize.go
Line 215, Patchset 2 (Latest): case edge.RangeStmt_Key, edge.RangeStmt_Value:
Alan Donovan . resolved

For viewers at home, the only delta here is this addition.

File go/analysis/passes/modernize/rangeint.go
Line 352, Patchset 2 (Parent):// This function is valid only for scalars (x = ...),

// not for aggregates (x.a[i] = ...)
Alan Donovan . unresolved

This comment describes the fundamental problem: rangeint cares only about mutations of integer variables, but slicesbackward wants to observe more general mutations of aggregates (structs and arrays), as your isMutation function does. In effect, this comment is a TODO that needs to be done before the function is fit to be renamed just `isLvalue` and used more widely, such as by slicesbackward, which of course is using it already. :)

So I think you can just fold the ideas of isMutation into this function. In place of L359, which ascends upwards from x to (x), you can broaden it to also ascend to x.f or x[i] or x[i:j] if the operations don't traverse a pointer. Then the base case is exactly the logic that's already below.

For example:

```
// isLvalue reports whether the specified expression appears on the
// left side of an assignment or in a context where its address is taken.
func isLvalue(info *types.Info, cur inspector.Cursor) bool {

// Unfortunately we can't simply use info.Types[e].Assignable()
	// as it is always true for a variable even when that variable is
	// used only as an r-value. So we must inspect enclosing syntax.
	for cur := range cur.Enclosing() {
// Ascend to outermost aggregate of which
// original cur is a part:
// x -> (x) | x.f | x[i] | x[i:j]
		switch cur.ParentEdgeKind() {
case edge.ParenExpr_X:
// If x is an lvalue, then (x) is an lvalue.
		case edge.SelectorExpr_X:
// If x is an lvalue, then x.f is an lvalue iff
// the selection does not traverse a pointer.
sel := cur.Parent().Node().(*ast.SelectorExpr)
seln := info.Selections[sel]
if seln.Indirect() {
return false
}
if seln.Kind() == types.MethodVal {
sig := seln.Obj().Type().(*types.Signature)
if !is[*types.Pointer](TypeOf(cur.Node().(ast.Expr))) &&
is[*types.Pointer](sig.Recv().Type().Underlying()) {
// Calling (*T).method on an lvalue of type T takes its address.
return true
}
}
		case edge.IndexExpr_X, edge.SliceExpr_X:
// If x[i] or x[i:j] is an lvalue,
// then x is an lvalue iff x is an array.
if !is[*types.Array](info.TypeOf(cur.Node().(ast.Expr)).Underlying()) {
return false
}
		default:
// base case
// ... all the old logic of isScalarValue ...
}
}
}
```
File go/analysis/passes/modernize/slicesbackward.go
Line 165, Patchset 2 (Latest): idxCur := curUse.Parent()
Alan Donovan . unresolved

curIdx ("cur" first seems to be our convention)

Line 281, Patchset 2 (Latest): for cur := range idxCur.Enclosing() {
Alan Donovan . unresolved

The way to think about this loop is not in terms of s[i], but as asking how far can we walk up the expression tree while still referring to (part of) the same variable? Three cases (Paren, Selector, Index) are inductive cases that walk up the tree. The remaining cases (Assign, etc) are the base case: they are outside the loop, and they correspond exactly to what isScalarLvalue does.

Line 293, Patchset 2 (Latest): // Calling a method on a pointer receiver may result in mutation.
Alan Donovan . unresolved

It's ok to call a method `(*T).method` if you already have a value of type `*T`, as that just makes a copy of the pointer; the interesting case is when you call it on a variable of type T, because that implicitly takes the address. So I think you need to check that TypeOf(x) is not also a pointer.

Line 313, Patchset 2 (Latest): return true
Alan Donovan . unresolved

(Don't forget all the other checks from isScalarLvalue.)

Open in Gerrit

Related details

Attention is currently required from:
  • Madeline Kalil
Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: tools
    Gerrit-Branch: master
    Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
    Gerrit-Change-Number: 802260
    Gerrit-PatchSet: 2
    Gerrit-Owner: Madeline Kalil <mka...@google.com>
    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
    Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
    Gerrit-Attention: Madeline Kalil <mka...@google.com>
    Gerrit-Comment-Date: Fri, 17 Jul 2026 21:53:04 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Alan Donovan (Gerrit)

    unread,
    Jul 17, 2026, 5:59:35 PMJul 17
    to Madeline Kalil, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
    Attention needed from Madeline Kalil

    Alan Donovan added 1 comment

    File go/analysis/passes/modernize/slicesbackward.go
    Line 286, Patchset 2 (Latest): if sel.Indirect() {
    Alan Donovan . unresolved

    Beware that this has an annoyingly subtle bug that we have decided not to fix (#8353).
    See indirectSelection in internal/refactor/inline/util.go for a workaround.

    It would be nice to elevate the final isLvalue function to the typesinternal package and use it in gopls/internal/golang/inline.go instead of isLvalueUse. It's a sufficiently subtle function that a test suite for it there would be nice too.

    Open in Gerrit

    Related details

    Attention is currently required from:
    • Madeline Kalil
    Submit Requirements:
    • requirement is not satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement is not satisfiedReview-Enforcement
    • requirement satisfiedTryBots-Pass
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: tools
    Gerrit-Branch: master
    Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
    Gerrit-Change-Number: 802260
    Gerrit-PatchSet: 2
    Gerrit-Owner: Madeline Kalil <mka...@google.com>
    Gerrit-Reviewer: Alan Donovan <adon...@google.com>
    Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
    Gerrit-Attention: Madeline Kalil <mka...@google.com>
    Gerrit-Comment-Date: Fri, 17 Jul 2026 21:59:31 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Madeline Kalil (Gerrit)

    unread,
    Jul 20, 2026, 5:49:20 PMJul 20
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Madeline Kalil

    Madeline Kalil uploaded new patchset

    Madeline Kalil uploaded patch set #3 to this change.
    Following approvals got outdated and were removed:
    Open in Gerrit

    Related details

    Attention is currently required from:
    • Madeline Kalil
    Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: newpatchset
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
      Gerrit-Change-Number: 802260
      Gerrit-PatchSet: 3
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      Jul 20, 2026, 6:00:51 PMJul 20
      to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from Madeline Kalil

      Madeline Kalil uploaded new patchset

      Madeline Kalil uploaded patch set #4 to this change.
      Open in Gerrit

      Related details

      Attention is currently required from:
      • Madeline Kalil
      Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: newpatchset
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
      Gerrit-Change-Number: 802260
      Gerrit-PatchSet: 4
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      Jul 20, 2026, 6:01:33 PMJul 20
      to goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Alan Donovan, golang-co...@googlegroups.com
      Attention needed from Alan Donovan

      Madeline Kalil voted and added 6 comments

      Votes added by Madeline Kalil

      Commit-Queue+1

      6 comments

      File go/analysis/passes/modernize/rangeint.go
      Line 352, Patchset 2 (Parent):// This function is valid only for scalars (x = ...),
      // not for aggregates (x.a[i] = ...)
      Alan Donovan . resolved
      Madeline Kalil

      Thanks, done. I moved the IsLValue function to typesinternal and added test cases for it that use a comment scheme like in zerovalue_test.go

      File go/analysis/passes/modernize/slicesbackward.go
      Line 165, Patchset 2: idxCur := curUse.Parent()
      Alan Donovan . resolved

      curIdx ("cur" first seems to be our convention)

      Madeline Kalil

      Done

      Line 281, Patchset 2: for cur := range idxCur.Enclosing() {
      Alan Donovan . resolved

      The way to think about this loop is not in terms of s[i], but as asking how far can we walk up the expression tree while still referring to (part of) the same variable? Three cases (Paren, Selector, Index) are inductive cases that walk up the tree. The remaining cases (Assign, etc) are the base case: they are outside the loop, and they correspond exactly to what isScalarLvalue does.

      Madeline Kalil

      Done

      Line 286, Patchset 2: if sel.Indirect() {
      Alan Donovan . unresolved

      Beware that this has an annoyingly subtle bug that we have decided not to fix (#8353).
      See indirectSelection in internal/refactor/inline/util.go for a workaround.

      It would be nice to elevate the final isLvalue function to the typesinternal package and use it in gopls/internal/golang/inline.go instead of isLvalueUse. It's a sufficiently subtle function that a test suite for it there would be nice too.

      Madeline Kalil

      Thanks, I saw that comment but should have been more careful. For this function I think it's okay, and we don't need the workaround? If both the receiver argument and parameter are pointers, the selection is not an l-value use, and we return false when sel.Indirect() is true (it's spuriously true for this case).

      Line 293, Patchset 2: // Calling a method on a pointer receiver may result in mutation.
      Alan Donovan . resolved

      It's ok to call a method `(*T).method` if you already have a value of type `*T`, as that just makes a copy of the pointer; the interesting case is when you call it on a variable of type T, because that implicitly takes the address. So I think you need to check that TypeOf(x) is not also a pointer.

      Madeline Kalil

      Done

      Line 313, Patchset 2: return true
      Alan Donovan . resolved

      (Don't forget all the other checks from isScalarLvalue.)

      Madeline Kalil

      Done

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Alan Donovan
      Submit Requirements:
      • requirement is not satisfiedCode-Review
      • requirement is not satisfiedNo-Unresolved-Comments
      • requirement is not satisfiedReview-Enforcement
      • requirement is not satisfiedTryBots-Pass
      Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
      Gerrit-MessageType: comment
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
      Gerrit-Change-Number: 802260
      Gerrit-PatchSet: 4
      Gerrit-Owner: Madeline Kalil <mka...@google.com>
      Gerrit-Reviewer: Alan Donovan <adon...@google.com>
      Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
      Gerrit-Attention: Alan Donovan <adon...@google.com>
      Gerrit-Comment-Date: Mon, 20 Jul 2026 22:01:28 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes
      Comment-In-Reply-To: Alan Donovan <adon...@google.com>
      unsatisfied_requirement
      open
      diffy

      Alan Donovan (Gerrit)

      unread,
      Jul 21, 2026, 1:51:00 PMJul 21
      to Madeline Kalil, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
      Attention needed from Madeline Kalil

      Alan Donovan added 11 comments

      File go/analysis/passes/modernize/slicesbackward.go
      Line 286, Patchset 2: if sel.Indirect() {
      Alan Donovan . unresolved

      Beware that this has an annoyingly subtle bug that we have decided not to fix (#8353).
      See indirectSelection in internal/refactor/inline/util.go for a workaround.

      It would be nice to elevate the final isLvalue function to the typesinternal package and use it in gopls/internal/golang/inline.go instead of isLvalueUse. It's a sufficiently subtle function that a test suite for it there would be nice too.

      Madeline Kalil

      Thanks, I saw that comment but should have been more careful. For this function I think it's okay, and we don't need the workaround? If both the receiver argument and parameter are pointers, the selection is not an l-value use, and we return false when sel.Indirect() is true (it's spuriously true for this case).

      Alan Donovan

      Yeah, I think you're exactly right. But it would be good to leave a note that the Indirect bug is benign here.

      File internal/typesinternal/islvalue.go
      Line 18, Patchset 4 (Latest):func IsLValue(info *types.Info, cur inspector.Cursor) bool {
      Alan Donovan . unresolved

      Let's rename it yet again (sorry) for maximum clarity. How about:

      ```
      // IsAssignedOrAddressTaken reports whether the expression cur
      // denotes a variable and appears in a context that assigns it
      // or that takes its address, potentially leading to indirect
      // assignment.
      //
      // These examples cause IsAssignedOrAddressTaken on the identifier
      // for x to return true:
      //
      // x = 1
      // x++
      // x[i] = 1 (assume x is an array)
      // x.a[i] = 1 (assume x.a is a non-pointer struct field)
      // use(&x)
      //
      // whereas these cause it to return false:
      //
      // y = x
      // f(x)
      // use(x.a[i])
      // use(*x)
      //
      // The expression may itself be a compound, for example:
      //
      // use(&(*ptr)) => IsAssignedOrAddressTaken("*ptr") = true
      // x.a[i] = 1 => IsAssignedOrAddressTaken("x.a") = true
      // _ = x.a[i] => IsAssignedOrAddressTaken("x.a") = false
      //
      // A variable's declaration is not considered to be an assignment:
      //
      // var x int => IsAssignedOrAddressTaken(x) = false
      // x := 1 => IsAssignedOrAddressTaken(x) = false
      //
      // TODO(adonovan): revisit the surprising behavior for declarations.
      func IsAssignedOrAddressTaken(...)
      ```


      (Now that I think about it, I wonder why isScalarValue went to the trouble of handling the declaration of a variable differently from an assignment to it. Possibly so that scanning for lvalue uses in `var x int; print(x)` would not say "yes, x is used as an lvalue"? But this is not a change, and it's fine for now.)

      Line 68, Patchset 4 (Latest): if assign.Tok != token.DEFINE {
      return true // x = j or x += j
      }
      id := cur.Node().(*ast.Ident)
      // Re-assigned identifiers are recorded in the Uses map.
      if _, ok := info.Uses[id]; ok {
      return true // reassignment of x (x, y := 1, 2)
      }
      case edge.RangeStmt_Key, edge.RangeStmt_Value:
      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
      }
      Alan Donovan . unresolved

      Now that I think about it, I wonder why isScalarValue went to the trouble of handling the declaration of a variable differently from an assignment to it. (Possibly so that scanning for lvalue uses in `var x int; print(x)` would not say "yes, x is used as an lvalue!"?)

      But this is not a change, and it's fine for now.

      File internal/typesinternal/islvalue_test.go
      Line 57, Patchset 4 (Latest): ptr(&(x) /*false*/)
      Alan Donovan . unresolved

      ```
      ptr(&(x /*true*/)) // x has is address taken
      ptr(&(x) /*false*/) // &x is copied during argument passing
      ```

      Line 59, Patchset 4 (Latest): y /*false*/ := 1
      Alan Donovan . unresolved

      // For our purposes, declarations do
      // not count as l-value references.

      Line 69, Patchset 4 (Latest): s /*false*/ [0] = 1
      s[0] /*true*/ = 1
      Alan Donovan . unresolved

      Since these are quite subtle, let's add comments:
      ```
      s /*false*/ [0] = 1 // a load of the pointer in s
      s[0] /*true*/ = 1 // a store to the array element s[0]
      ```

      Line 152, Patchset 4 (Latest): for _, c := range cg.List {
      Alan Donovan . unresolved
      There's no need for a slice. Just replace the append with the body of the L162 loop:
      ```
      for _, comment := range file.Comments {
      pos := comment.Pos()
      want := strings.TrimSpace(comment.Text()) == "true"
      ...FindByPos etc...
      }
      ```
      Line 153, Patchset 4 (Latest): text := strings.TrimSpace(strings.Trim(c.Text, "/*"))
      Alan Donovan . unresolved

      Let CommentGroup.Text() do all the heavy lifting (removing /* etc).

      Line 154, Patchset 4 (Latest): testCases = append(testCases, testCase{comment: c, want: cond(text == "true", true, false)})
      Alan Donovan . unresolved

      This is just `text == "true"`. ;-)

      Line 167, Patchset 4 (Latest): cur, ok := inspect.Root().FindByPos(pos-1, pos-1)
      Alan Donovan . unresolved

      This is a little fragile: it assumes there's exactly one space.
      Rather than make the code more complex, let's just say "no spaces allowed", replace ` /*` with `/*`, and remove -1.

      Line 186, Patchset 4 (Latest):func cond[T any](cond bool, t, f T) T {
      Alan Donovan . unresolved

      delete

      Open in Gerrit

      Related details

      Attention is currently required from:
      • Madeline Kalil
      Submit Requirements:
        • requirement is not satisfiedCode-Review
        • requirement is not satisfiedNo-Unresolved-Comments
        • requirement is not satisfiedReview-Enforcement
        • requirement satisfiedTryBots-Pass
        Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
        Gerrit-MessageType: comment
        Gerrit-Project: tools
        Gerrit-Branch: master
        Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
        Gerrit-Change-Number: 802260
        Gerrit-PatchSet: 4
        Gerrit-Owner: Madeline Kalil <mka...@google.com>
        Gerrit-Reviewer: Alan Donovan <adon...@google.com>
        Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
        Gerrit-Attention: Madeline Kalil <mka...@google.com>
        Gerrit-Comment-Date: Tue, 21 Jul 2026 17:50:56 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: No
        Comment-In-Reply-To: Madeline Kalil <mka...@google.com>
        Comment-In-Reply-To: Alan Donovan <adon...@google.com>
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Madeline Kalil (Gerrit)

        unread,
        Jul 21, 2026, 5:22:52 PMJul 21
        to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
        Attention needed from Madeline Kalil

        Madeline Kalil uploaded new patchset

        Madeline Kalil uploaded patch set #5 to this change.
        Following approvals got outdated and were removed:

        Related details

        Attention is currently required from:
        • Madeline Kalil
        Submit Requirements:
          • requirement is not satisfiedCode-Review
          • requirement is not satisfiedNo-Unresolved-Comments
          • requirement is not satisfiedReview-Enforcement
          • requirement is not satisfiedTryBots-Pass
          Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
          Gerrit-MessageType: newpatchset
          Gerrit-Project: tools
          Gerrit-Branch: master
          Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
          Gerrit-Change-Number: 802260
          Gerrit-PatchSet: 5
          unsatisfied_requirement
          open
          diffy

          Madeline Kalil (Gerrit)

          unread,
          Jul 21, 2026, 5:23:38 PMJul 21
          to goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, Alan Donovan, golang-co...@googlegroups.com
          Attention needed from Alan Donovan

          Madeline Kalil voted and added 13 comments

          Votes added by Madeline Kalil

          Commit-Queue+1

          13 comments

          File go/analysis/passes/modernize/slicesbackward.go
          Line 286, Patchset 2: if sel.Indirect() {
          Alan Donovan . resolved

          Beware that this has an annoyingly subtle bug that we have decided not to fix (#8353).
          See indirectSelection in internal/refactor/inline/util.go for a workaround.

          It would be nice to elevate the final isLvalue function to the typesinternal package and use it in gopls/internal/golang/inline.go instead of isLvalueUse. It's a sufficiently subtle function that a test suite for it there would be nice too.

          Madeline Kalil

          Thanks, I saw that comment but should have been more careful. For this function I think it's okay, and we don't need the workaround? If both the receiver argument and parameter are pointers, the selection is not an l-value use, and we return false when sel.Indirect() is true (it's spuriously true for this case).

          Alan Donovan

          Yeah, I think you're exactly right. But it would be good to leave a note that the Indirect bug is benign here.

          Madeline Kalil

          Agreed, done!

          File internal/typesinternal/islvalue.go
          Line 18, Patchset 4:func IsLValue(info *types.Info, cur inspector.Cursor) bool {
          Alan Donovan . resolved
          Madeline Kalil

          Thank you!! This is much clearer. I also added test cases to fully reflect all the cases mentioned in the doc comment.

          Looking at how isScalarLValue was used previously in rangeint, slicesbackward and stringscut, it makes sense that it would want to report false for the case you mentioned.
          Also, isScalarLValue was mishandling assignments:
          ```

          if v, ok := info.Defs[id]; ok && v.Pos() != id.Pos() {
          	return true // reassignment of i (i, j := 1, 2)
          }
          ``` 
          (i is reassigned and j is a new var)
          the Defs maps has the invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos(), so this case is impossible. Reassignments are stored in the Uses map.
          Line 68, Patchset 4: if assign.Tok != token.DEFINE {

          return true // x = j or x += j
          }
          id := cur.Node().(*ast.Ident)
          // Re-assigned identifiers are recorded in the Uses map.
          if _, ok := info.Uses[id]; ok {
          return true // reassignment of x (x, y := 1, 2)
          }
          case edge.RangeStmt_Key, edge.RangeStmt_Value:
          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
          }
          Alan Donovan . resolved

          Now that I think about it, I wonder why isScalarValue went to the trouble of handling the declaration of a variable differently from an assignment to it. (Possibly so that scanning for lvalue uses in `var x int; print(x)` would not say "yes, x is used as an lvalue!"?)

          But this is not a change, and it's fine for now.

          Madeline Kalil

          Acknowledged

          File internal/typesinternal/islvalue_test.go
          Line 57, Patchset 4: ptr(&(x) /*false*/)
          Alan Donovan . unresolved

          ```
          ptr(&(x /*true*/)) // x has is address taken
          ptr(&(x) /*false*/) // &x is copied during argument passing
          ```

          Madeline Kalil

          I think adding this comment might mess up my comment testing logic?

          Line 57, Patchset 4: ptr(&(x) /*false*/)
          Alan Donovan . resolved

          ```
          ptr(&(x /*true*/)) // x has is address taken
          ptr(&(x) /*false*/) // &x is copied during argument passing
          ```

          Madeline Kalil

          Used slightly different wording but done.

          Line 59, Patchset 4: y /*false*/ := 1
          Alan Donovan . resolved

          // For our purposes, declarations do
          // not count as l-value references.

          Madeline Kalil

          I don't think we want to use the term l-value anymore, but I added a different comment.

          Line 69, Patchset 4: s /*false*/ [0] = 1

          s[0] /*true*/ = 1
          Alan Donovan . resolved

          Since these are quite subtle, let's add comments:
          ```
          s /*false*/ [0] = 1 // a load of the pointer in s
          s[0] /*true*/ = 1 // a store to the array element s[0]
          ```

          Madeline Kalil

          Thanks! Also adding a comment to highlight that this shows the contrast between array and slice memory model.

          Line 69, Patchset 4: s /*false*/ [0] = 1

          s[0] /*true*/ = 1
          Alan Donovan . resolved

          Since these are quite subtle, let's add comments:
          ```
          s /*false*/ [0] = 1 // a load of the pointer in s
          s[0] /*true*/ = 1 // a store to the array element s[0]
          ```

          Madeline Kalil

          Done.

          Line 152, Patchset 4: for _, c := range cg.List {
          Alan Donovan . resolved
          There's no need for a slice. Just replace the append with the body of the L162 loop:
          ```
          for _, comment := range file.Comments {
          pos := comment.Pos()
          want := strings.TrimSpace(comment.Text()) == "true"
          ...FindByPos etc...
          }
          ```
          Madeline Kalil

          Done

          Line 153, Patchset 4: text := strings.TrimSpace(strings.Trim(c.Text, "/*"))
          Alan Donovan . resolved

          Let CommentGroup.Text() do all the heavy lifting (removing /* etc).

          Madeline Kalil

          Done

          Line 154, Patchset 4: testCases = append(testCases, testCase{comment: c, want: cond(text == "true", true, false)})
          Alan Donovan . resolved

          This is just `text == "true"`. ;-)

          Madeline Kalil

          Oooops.

          Line 167, Patchset 4: cur, ok := inspect.Root().FindByPos(pos-1, pos-1)
          Alan Donovan . resolved

          This is a little fragile: it assumes there's exactly one space.
          Rather than make the code more complex, let's just say "no spaces allowed", replace ` /*` with `/*`, and remove -1.

          Madeline Kalil

          Done

          Line 186, Patchset 4:func cond[T any](cond bool, t, f T) T {
          Alan Donovan . resolved

          delete

          Madeline Kalil

          Done

          Open in Gerrit

          Related details

          Attention is currently required from:
          • Alan Donovan
          Submit Requirements:
            • requirement is not satisfiedCode-Review
            • requirement satisfiedNo-Unresolved-Comments
            • requirement is not satisfiedReview-Enforcement
            • requirement is not satisfiedTryBots-Pass
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: comment
            Gerrit-Project: tools
            Gerrit-Branch: master
            Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
            Gerrit-Change-Number: 802260
            Gerrit-PatchSet: 5
            Gerrit-Owner: Madeline Kalil <mka...@google.com>
            Gerrit-Reviewer: Alan Donovan <adon...@google.com>
            Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
            Gerrit-Attention: Alan Donovan <adon...@google.com>
            Gerrit-Comment-Date: Tue, 21 Jul 2026 21:23:34 +0000
            Gerrit-HasComments: Yes
            Gerrit-Has-Labels: Yes
            unsatisfied_requirement
            satisfied_requirement
            open
            diffy

            Alan Donovan (Gerrit)

            unread,
            Jul 21, 2026, 9:38:50 PMJul 21
            to Madeline Kalil, goph...@pubsubhelper.golang.org, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com
            Attention needed from Madeline Kalil

            Alan Donovan voted and added 3 comments

            Votes added by Alan Donovan

            Code-Review+2

            3 comments

            Patchset-level comments
            File-level comment, Patchset 5 (Latest):
            Alan Donovan . resolved

            LGTM. Thanks for your patience.

            File internal/typesinternal/assignedaddress_test.go
            Line 169, Patchset 5 (Latest): if text != "true" && text != "false" {
            continue // skip other comments
            }
            Alan Donovan . resolved

            That's one solution. Another is to use the /*..*/ vs // distinction.

            File internal/typesinternal/islvalue.go
            Alan Donovan

            Good catch!

            Open in Gerrit

            Related details

            Attention is currently required from:
            • Madeline Kalil
            Submit Requirements:
            • requirement satisfiedCode-Review
            • requirement satisfiedNo-Unresolved-Comments
            • requirement satisfiedReview-Enforcement
            • requirement satisfiedTryBots-Pass
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: comment
            Gerrit-Project: tools
            Gerrit-Branch: master
            Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
            Gerrit-Change-Number: 802260
            Gerrit-PatchSet: 5
            Gerrit-Owner: Madeline Kalil <mka...@google.com>
            Gerrit-Reviewer: Alan Donovan <adon...@google.com>
            Gerrit-Reviewer: Madeline Kalil <mka...@google.com>
            Gerrit-Attention: Madeline Kalil <mka...@google.com>
            Gerrit-Comment-Date: Wed, 22 Jul 2026 01:38:46 +0000
            satisfied_requirement
            open
            diffy

            Madeline Kalil (Gerrit)

            unread,
            Jul 22, 2026, 9:41:59 AMJul 22
            to goph...@pubsubhelper.golang.org, golang-...@googlegroups.com, Alan Donovan, golang...@luci-project-accounts.iam.gserviceaccount.com, golang-co...@googlegroups.com

            Madeline Kalil submitted the change

            Change information

            Commit message:
            go/analysis/passes/modernize: slicesbackward - skip if s[i] is mutated


            In the slicesbackward modernizer, we replace backward loops over
            slices with a call to slices.Backward. However, if the index
            expression s[i] is address-taken or mutated within the loop,
            we should not offer this fix. For example:

            for i := len(s) - 1; i > 0; i-- {
            s[i].n = 5
            }

            for _, v := range slices.Backward(s) {
            v.n = 5
            }

            v is a local copy, so the original s will not get mutated.
            To avoid changing the program behavior, we should
            not suggest a modernization.

            Promote a new function IsAssignedOrAddressTaken that replaces the uses
            of isScalarLValue in modernizers and isLValueUse
            in the inliner and add tests for it.

            Fixes golang/go#80410
            Change-Id: I873afe8965dee39994547e16f284ca80541334d6
            Reviewed-by: Alan Donovan <adon...@google.com>
            Files:
            • M go/analysis/passes/modernize/minmax.go
            • M go/analysis/passes/modernize/modernize.go
            • M go/analysis/passes/modernize/rangeint.go
            • M go/analysis/passes/modernize/slicesbackward.go
            • M go/analysis/passes/modernize/stringscut.go
            • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go
            • M go/analysis/passes/modernize/testdata/src/rangeint/rangeint.go.golden
            • M go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go
            • M go/analysis/passes/modernize/testdata/src/slicesbackward/slicesbackward.go.golden
            • M gopls/internal/golang/inline.go
            • A internal/typesinternal/assignedaddress.go
            • A internal/typesinternal/assignedaddress_test.go
            Change size: L
            Delta: 12 files changed, 603 insertions(+), 107 deletions(-)
            Branch: refs/heads/master
            Submit Requirements:
            Open in Gerrit
            Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
            Gerrit-MessageType: merged
            Gerrit-Project: tools
            Gerrit-Branch: master
            Gerrit-Change-Id: I873afe8965dee39994547e16f284ca80541334d6
            Gerrit-Change-Number: 802260
            Gerrit-PatchSet: 6
            open
            diffy
            satisfied_requirement
            Reply all
            Reply to author
            Forward
            0 new messages