[tools] go/analysis/passes/modernize: additional pattern for embedlit

6 views
Skip to first unread message

Madeline Kalil (Gerrit)

unread,
May 1, 2026, 3:33:05 PM (10 days ago) May 1
to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com

Madeline Kalil has uploaded the change for review

Commit message

go/analysis/passes/modernize: additional pattern for embedlit

Modifies the embedlit modernizer to recognize an additional pattern:
t := T{...}
t.x = x

becomes

t := T{... x:x,}

Updates golang/go#77965
Change-Id: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418

Change diff

diff --git a/go/analysis/passes/modernize/embedlit.go b/go/analysis/passes/modernize/embedlit.go
index 090ee4c..c0e410f 100644
--- a/go/analysis/passes/modernize/embedlit.go
+++ b/go/analysis/passes/modernize/embedlit.go
@@ -7,6 +7,7 @@
import (
"fmt"
"go/ast"
+ "go/printer"
"go/types"
"strings"

@@ -52,10 +53,13 @@
names []string // names of the embedded field types that can be removed
compLit = curLit.Node().(*ast.CompositeLit)
compLitType = info.TypeOf(compLit)
- check func(*ast.CompositeLit)
+ checkA func(*ast.CompositeLit)
+ checkB func(inspector.Cursor)
)
- check = func(lit *ast.CompositeLit) {
- for _, elt := range lit.Elts {
+ // Pattern A:
+ // T{U: U{f: v, ...}} => T{f: v, ...}
+ checkA = func(lit *ast.CompositeLit) {
+ for idx, elt := range lit.Elts {
// Can't promote an unkeyed field; would result in a syntax error.
if kv, ok := elt.(*ast.KeyValueExpr); ok {
if innerLit := isEmbeddedFieldLit(info, compLitType, kv); innerLit != nil {
@@ -76,6 +80,12 @@
if !moreiters.Empty(astutil.Comments(file, closingPos, innerLit.Rbrace+1)) {
continue
}
+ end := innerLit.Rbrace + 1
+ // If the current innerLit is empty, and it is not the last element
+ // in the outer lit, we must expand the range to delete the trailing comma.
+ if len(innerLit.Elts) == 0 && idx < len(lit.Elts)-1 {
+ end += 1
+ }
edits = append(edits, []analysis.TextEdit{
// T{U: U{f: v, ...}}
// ----- -
@@ -89,19 +99,146 @@
// white space or commas. Failing to delete trailing commas may
// result in invalid code.
Pos: closingPos,
- End: innerLit.Rbrace + 1,
+ End: end,
},
}...)
names = append(names, kv.Key.(*ast.Ident).Name)
- check(innerLit)
+ checkA(innerLit)
}
}
}
}

+ // Pattern B:
+ // t := T{...}; t.x = x => t := T{..., x: x}
+ // (or var t = ...)
+ // cur is the cursor for a comp lit.
+ checkB = func(cur inspector.Cursor) {
+ var (
+ // Ident for "t" in the assignment.
+ lhs *ast.Ident
+ // The cursor representing the statement that initializes the comp lit "t".
+ // We use its siblings to search for field assignments and verify that
+ // no intervening statements observe "t".
+ curEnclosing inspector.Cursor
+ )
+ compLit := cur.Node().(*ast.CompositeLit)
+ if hasUnkeyed(compLit) {
+ // Promoting additional embedded fields would result in mixing keyed and
+ // unkeyed fields, which isn't allowed.
+ return
+ }
+
+ if cur.ParentEdgeKind() == edge.AssignStmt_Rhs {
+ assign := cur.Parent().Node().(*ast.AssignStmt)
+ lhs = assign.Lhs[cur.ParentEdgeIndex()].(*ast.Ident)
+ if enc, ok := moreiters.First(cur.Enclosing((*ast.AssignStmt)(nil), (*ast.File)(nil))); ok {
+ curEnclosing = enc
+ }
+ } else if cur.ParentEdgeKind() == edge.ValueSpec_Values {
+ spec := cur.Parent().Node().(*ast.ValueSpec)
+ lhs = spec.Names[cur.ParentEdgeIndex()]
+ if enc, ok := moreiters.First(cur.Enclosing((*ast.DeclStmt)(nil), (*ast.File)(nil))); ok {
+ curEnclosing = enc
+ }
+ }
+ if lhs == nil || !curEnclosing.Valid() {
+ return
+ }
+ tObj := info.ObjectOf(lhs)
+ for sib, ok := curEnclosing.NextSibling(); ok; sib, ok = sib.NextSibling() {
+ var assign *ast.AssignStmt
+ // Any assignments of embedded field values must immediately proceed
+ // the struct initialization.
+ // TODO(mkalil): Check if intervening statements actually observe t.
+ if assign, ok = sib.Node().(*ast.AssignStmt); !ok {
+ return
+ }
+ for idx, expr := range assign.Lhs {
+ sel, ok := expr.(*ast.SelectorExpr)
+ if !ok {
+ return
+ }
+ // Verify that sel.X refers to the same object as "t"
+ selXId, ok := sel.X.(*ast.Ident)
+ if !ok {
+ // TODO: handle deeply nested expressions like t.B.x
+ return
+ }
+ obj := info.ObjectOf(selXId)
+ if obj != tObj {
+ return
+ }
+ // If the rhs uses a value of t (e.g. t.x = t.y), don't suggest a fix because
+ // we can't evaluate t.y when constructing the new literal.
+ rhs := assign.Rhs[idx]
+ rhsCur, _ := sib.FindNode(rhs)
+ for c := range rhsCur.Preorder((*ast.SelectorExpr)(nil), (*ast.CallExpr)(nil)) {
+ if sel, ok := c.Node().(*ast.SelectorExpr); ok {
+ if xid, ok := sel.X.(*ast.Ident); ok {
+ if info.ObjectOf(xid) == tObj {
+ return
+ }
+ } else {
+ // TODO(mkalil): handle deeply nested expressions like t.B.x
+ return
+ }
+ } else if _, ok := c.Node().(*ast.CallExpr); ok {
+ // Skip suggesting fixes when the rhs has a call expression, which
+ // could panic. Moving a call that panics into the literal will
+ // change the program's behavior, because thte program will
+ // terminate before the composite literal is constructed.
+ // (I don't think we need typesinternal.NoEffects here, because
+ // the order of effects will be preserved when we append the key value
+ // pairs in the comp lit. We just need to worry about a function call
+ // that panics)
+ return
+ }
+ }
+
+ // TODO(mkalil): When we allow intervening statements that do not
+ // observe t, we will also have to ensure that these assignments do
+ // not use variables declared after the struct lit initialization, as
+ // moving these values into the literal would reference a variable
+ // that is out of scope. For example:
+ // t := T{...}
+ // v := 2
+ // t.x = v // can't move t.x initialization into struct lit because v is declared after
+
+ // Emit edits to move the field assignment into the struct lit and
+ // then remove it from its current place.
+ var rhsBuf strings.Builder
+ if err := printer.Fprint(&rhsBuf, pass.Fset, rhs); err != nil {
+ // bug.Reportf?
+ return
+ }
+ // TODO(mkalil): Are comments preserved?
+ edits = append(edits, []analysis.TextEdit{
+ {
+ // Remove the assignment t.x = x.
+ Pos: assign.Pos(),
+ End: assign.End(),
+ },
+ {
+ // Append the key-value pair to the CompositeLit.
+ Pos: compLit.Rbrace,
+ NewText: fmt.Appendf(nil, ", %s: %s", sel.Sel.Name, rhsBuf.String()),
+ },
+ }...)
+ }
+ }
+ }
+
if curLit.ParentEdgeKind() != edge.KeyValueExpr_Value {
+ // non-nested comp lit
compLit := curLit.Node().(*ast.CompositeLit)
- check(compLit) // non-nested comp lit
+ checkA(compLit)
+ // TODO(mkalil): Figure out how to handle addition/removal of commas
+ // in the comp lit when we observe code where both patterns apply.
+ // For now, only apply edits from one pattern at a time.
+ if len(edits) == 0 {
+ checkB(curLit)
+ }
}
if len(edits) > 0 {
pass.Report(analysis.Diagnostic{
@@ -152,6 +289,7 @@
// type B struct { x int }
// _ = T{A: A{x: 1}}
// cannot be simplified to T{x: 1} because T has two different embedded fields called "x".
+ // We also reject composite literals with slice elements, as parentObj will be nil.
parentObj, _, _ := types.LookupFieldOrMethod(topLevelType, true, obj.Pkg(), k.Name)
if parentObj != obj {
return nil
@@ -173,3 +311,12 @@
}
return obj
}
+
+func hasUnkeyed(lit *ast.CompositeLit) bool {
+ for _, elt := range lit.Elts {
+ if _, ok := elt.(*ast.KeyValueExpr); !ok {
+ return true
+ }
+ }
+ return false
+}
diff --git a/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go b/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
index f79de53..f559e97 100644
--- a/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
+++ b/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
@@ -2,6 +2,8 @@

package embedlit

+import "log"
+
type A struct {
a int
B
@@ -55,6 +57,16 @@
type K struct{ L }
type L []int

+type T struct {
+ a int
+ b int
+ U
+}
+
+type U struct {
+ x int
+}
+
var (
_ = A{B: B{b: 1}} // want "embedded field type can be removed from struct literal"
_ = A{a: 1, B: B{b: 1, C: C{c: 1}}} // want "embedded field type can be removed from struct literal"
@@ -72,6 +84,8 @@
}
// empty composite lit
_ = A{a: 1, B: B{}} // want "embedded field type can be removed from struct literal"
+ _ = T{U: U{}, b: 1} // want "embedded field type can be removed from struct literal"
+
// don't suggest a fix if it's too tricky to preserve comments
_ = A{ // nope: comments within range to delete
B: B{ // one
@@ -92,4 +106,39 @@
}
_ = A{B: B{b: 1} /* comment */, a: 2} // want "embedded field type can be removed from struct literal"
_ = K{L: L{zero: 0}} // nope: cannot promote slice elements
+ _ = K{L: L{0: 100}} // nope: cannot promote slice elements
)
+
+func _() {
+ t1 := A{a: 1} // want "embedded field type can be removed from struct literal"
+ t1.b = 2
+
+ var t2 A
+ t2 = A{a: 1} // want "embedded field type can be removed from struct literal"
+ t2.b = 2
+
+ var t3 = A{a: 1} // want "embedded field type can be removed from struct literal"
+ t3.b = 2
+
+ t4 := T{1, 2, U{x: 3}} // nope: can't mix keyed and unkeyed elements
+ t4.x = 4
+
+ t5 := A{a: 1}
+ log.Print(t5) // nope: intervening statement observes t5
+ t5.b = 2
+
+ t6 := A{a: 1} // nope: value assigned depends on t6 itself
+ t6.b = t6.a + 1
+
+ t7 := A{a: 1} // nope: assigning a function call, don't know if it will panic
+ t7.b = foo()
+
+ t8 := A{ // want "embedded field type can be removed from struct literal"
+ B: B{b: 1},
+ }
+ t8.a = 2
+}
+
+func foo() int {
+ return 0
+}
diff --git a/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden b/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden
index d20da95..353022b 100644
--- a/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden
+++ b/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden
@@ -2,6 +2,8 @@

package embedlit

+import "log"
+
type A struct {
a int
B
@@ -55,6 +57,16 @@
type K struct{ L }
type L []int

+type T struct {
+ a int
+ b int
+ U
+}
+
+type U struct {
+ x int
+}
+
var (
_ = A{b: 1} // want "embedded field type can be removed from struct literal"
_ = A{a: 1, b: 1, c: 1} // want "embedded field type can be removed from struct literal"
@@ -71,6 +83,8 @@
}
// empty composite lit
_ = A{a: 1} // want "embedded field type can be removed from struct literal"
+ _ = T{b: 1} // want "embedded field type can be removed from struct literal"
+
// don't suggest a fix if it's too tricky to preserve comments
_ = A{ // nope: comments within range to delete
B: B{ // one
@@ -90,5 +104,37 @@
a: 2,
}
_ = A{b: 1 /* comment */, a: 2} // want "embedded field type can be removed from struct literal"
- _ = K{L: L{zero: 0}} // nope: cannot promote slice elements
+ _ = K{L: L{zero: 0}} // nope: cannot promote slice elements
+ _ = K{L: L{0: 100}} // nope: cannot promote slice elements
)
+
+func _() {
+ t1 := A{a: 1, b: 2} // want "embedded field type can be removed from struct literal"
+
+ var t2 A
+ t2 = A{a: 1, b: 2} // want "embedded field type can be removed from struct literal"
+
+ var t3 = A{a: 1, b: 2} // want "embedded field type can be removed from struct literal"
+
+ t4 := T{1, 2, U{x: 3}} // nope: can't mix keyed and unkeyed elements
+ t4.x = 4
+
+ t5 := A{a: 1}
+ log.Print(t5) // nope: intervening statement observes t5
+ t5.b = 2
+
+ t6 := A{a: 1} // nope: value assigned depends on t6 itself
+ t6.b = t6.a + 1
+
+ t7 := A{a: 1} // nope: assigning a function call, don't know if it will panic
+ t7.b = foo()
+
+ t8 := A{ // want "embedded field type can be removed from struct literal"
+ b: 1,
+ }
+ t8.a = 2
+}
+
+func foo() int {
+ return 0
+}

Change information

Files:
  • M go/analysis/passes/modernize/embedlit.go
  • M go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
  • M go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden
Change size: L
Delta: 3 files changed, 249 insertions(+), 7 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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
Gerrit-Change-Number: 773220
Gerrit-PatchSet: 1
Gerrit-Owner: Madeline Kalil <mka...@google.com>
unsatisfied_requirement
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
May 1, 2026, 3:33:55 PM (10 days ago) May 1
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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
Gerrit-Change-Number: 773220
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, 01 May 2026 19:33:52 +0000
Gerrit-HasComments: No
Gerrit-Has-Labels: Yes
unsatisfied_requirement
satisfied_requirement
open
diffy

Madeline Kalil (Gerrit)

unread,
May 1, 2026, 3:35:10 PM (10 days ago) May 1
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 added 1 comment

Patchset-level comments
File-level comment, Patchset 1 (Latest):
Madeline Kalil . resolved

I left several TODOs so this still a WIP, but it would be great if you could review what I have so far if you have time

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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
Gerrit-Change-Number: 773220
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, 01 May 2026 19:35:06 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
unsatisfied_requirement
satisfied_requirement
open
diffy

Alan Donovan (Gerrit)

unread,
May 1, 2026, 4:20:55 PM (10 days ago) May 1
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 24 comments

File go/analysis/passes/modernize/embedlit.go
Line 56, Patchset 1 (Latest): checkA func(*ast.CompositeLit)
checkB func(inspector.Cursor)
Alan Donovan . unresolved

Let's declare these as late as possible, otherwise it looks like they might be mutually recursive, but in fact it's just two recursive functions.

Line 62, Patchset 1 (Latest): for idx, elt := range lit.Elts {
Alan Donovan . unresolved

i

Line 85, Patchset 1 (Latest): // in the outer lit, we must expand the range to delete the trailing comma.
Alan Donovan . unresolved

Does the logic below assume that the comma immediately follows the brace?

Line 85, Patchset 1 (Latest): // in the outer lit, we must expand the range to delete the trailing comma.
Alan Donovan . unresolved

A little picture would help here.

Line 87, Patchset 1 (Latest): end += 1
Alan Donovan . unresolved

++

Line 120, Patchset 1 (Latest): // The cursor representing the statement that initializes the comp lit "t".
Alan Donovan . unresolved

File or Stmt

but see below. I think File is actually unreachable, so this should be curStmt.


// Ident for "t" in the assignment.
lhs *ast.Ident

// The cursor representing the statement that initializes the comp lit "t".
// We use its siblings to search for field assignments and verify that
// no intervening statements observe "t".
curEnclosing inspector.Cursor
)
Alan Donovan . unresolved

Move down to L132.

Line 126, Patchset 1 (Latest): if hasUnkeyed(compLit) {
Alan Donovan . unresolved

if !moreiters.Every(lit.Elts, is[*ast.KeyValueExpr]) {

Line 134, Patchset 1 (Latest): lhs = assign.Lhs[cur.ParentEdgeIndex()].(*ast.Ident)
Alan Donovan . unresolved

How do we know it's an ident? It could be x.y[i] = ...

Line 135, Patchset 1 (Latest): if enc, ok := moreiters.First(cur.Enclosing((*ast.AssignStmt)(nil), (*ast.File)(nil))); ok {
Alan Donovan . unresolved

Isn't the first enclosing AssignStmt or File always going to be the Parent?

Line 141, Patchset 1 (Latest): if enc, ok := moreiters.First(cur.Enclosing((*ast.DeclStmt)(nil), (*ast.File)(nil))); ok {
Alan Donovan . unresolved

How can it match the File?

Line 144, Patchset 1 (Latest): }
Alan Donovan . unresolved

else return

Then you can assume lhs != nil && curEnclosing.Valid.

Line 149, Patchset 1 (Latest): for sib, ok := curEnclosing.NextSibling(); ok; sib, ok = sib.NextSibling() {
Alan Donovan . unresolved

Since we don't need curStmt (nee curEnclosing), we might as well update it:

```
for {
var ok bool
curStmt, ok = curStmt.NextSibling()
if ok { return } // end of (e.g.) block
```
Line 150, Patchset 1 (Latest): var assign *ast.AssignStmt
Alan Donovan . unresolved

assign, ok := sib.Node().(*ast.AssignStmt)
!ok { return }

Line 157, Patchset 1 (Latest): for idx, expr := range assign.Lhs {
Alan Donovan . unresolved

I don't see any testcases for an assignment with multiple Lhs variables. Does it actually work? (It looks like it might.) I wouldn't worry about that case if it's tricky (just stop if len(Lhs) != 1), but either way we should test it.

Line 165, Patchset 1 (Latest): // TODO: handle deeply nested expressions like t.B.x
Alan Donovan . unresolved

TODO(mkalil):

Line 176, Patchset 1 (Latest): for c := range rhsCur.Preorder((*ast.SelectorExpr)(nil), (*ast.CallExpr)(nil)) {
Alan Donovan . unresolved

Ident

Any reference to t on the RHS is a problem.

Line 187, Patchset 1 (Latest): // Skip suggesting fixes when the rhs has a call expression, which

// could panic. Moving a call that panics into the literal will
// change the program's behavior, because thte program will
// terminate before the composite literal is constructed.
Alan Donovan . unresolved

This is true, but I don't think panic is the interesting thing here. If `t = T{...}` expression is populating a local variable then it will disappear during the panic; and if it's a global variable, the panic means you have bigger problems. In general, we can assume that programs shouldn't panic.

There's a concern that any expression with side effects might mutate the values of the expressions that we move up to the literal; but shouldn't the transformed code keep all the `k: v` pairs in the same order, so there's nothing to worry about?

A concrete example of the problem we're trying to solve here would help.

Line 199, Patchset 1 (Latest): // TODO(mkalil): When we allow intervening statements that do not

// observe t, we will also have to ensure that these assignments do
Alan Donovan . unresolved

I suggest we don't attempt to do that. Basically, permit t := T{...} immediately followed by one or more t.x = ... assignments, and that's all.

Line 211, Patchset 1 (Latest): if err := printer.Fprint(&rhsBuf, pass.Fset, rhs); err != nil {
Alan Donovan . unresolved

Remember that reformatting an expression is liable to lose its comments and vertical space. Is it necessary to reformat? All the expressions are in the correct order, so I think we should be able to replace the glue in between them. Something like this:

```
t := T{...}; t.x = v
----- --- -
t := T{..., x: v}
```

(Obviously spaces and semicolons are a little more fiddly. We should keep the newline if that's how the logical semicolon was written in the source.)

Line 237, Patchset 1 (Latest): // in the comp lit when we observe code where both patterns apply.
Alan Donovan . unresolved

Yeah, that's gonna be tricky. I think what you have here is the best we're going to be able to do without heroic effort.

File go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
Line 4, Patchset 1 (Latest):
import "log"
Alan Donovan . unresolved

Tip: analysis tests run faster if they don't use imports: `go list -deps log` pulls in 61 packages. I suggest you declare a use(any) function instead of log.Print.

Line 140, Patchset 1 (Latest):}
Alan Donovan . unresolved

Can we add a test that uses two following statements?

x = T{...}
x.a.b = 123
x.a.c = 456

I suspect only the first will get fixed, and the user will have to run the tool again to get the second one.

File go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go.golden
Line 112, Patchset 1 (Latest): t1 := A{a: 1, b: 2} // want "embedded field type can be removed from struct literal"
Alan Donovan . resolved

nice

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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
    Gerrit-Change-Number: 773220
    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: Madeline Kalil <mka...@google.com>
    Gerrit-Comment-Date: Fri, 01 May 2026 20:20:51 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: No
    unsatisfied_requirement
    satisfied_requirement
    open
    diffy

    Madeline Kalil (Gerrit)

    unread,
    May 4, 2026, 4:28:31 PM (7 days ago) May 4
    to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
    Attention needed from Madeline Kalil

    Madeline Kalil uploaded new patchset

    Madeline Kalil uploaded patch set #2 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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
      Gerrit-Change-Number: 773220
      Gerrit-PatchSet: 2
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      May 4, 2026, 4:28:42 PM (7 days ago) May 4
      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 23 comments

      Votes added by Madeline Kalil

      Commit-Queue+1

      23 comments

      File go/analysis/passes/modernize/embedlit.go
      Line 56, Patchset 1: checkA func(*ast.CompositeLit)
      checkB func(inspector.Cursor)
      Alan Donovan . resolved

      Let's declare these as late as possible, otherwise it looks like they might be mutually recursive, but in fact it's just two recursive functions.

      Madeline Kalil

      Done

      Line 62, Patchset 1: for idx, elt := range lit.Elts {
      Alan Donovan . resolved

      i

      Madeline Kalil

      Done

      Line 85, Patchset 1: // in the outer lit, we must expand the range to delete the trailing comma.
      Alan Donovan . resolved

      A little picture would help here.

      Madeline Kalil

      Done

      Line 85, Patchset 1: // in the outer lit, we must expand the range to delete the trailing comma.
      Alan Donovan . unresolved

      Does the logic below assume that the comma immediately follows the brace?

      Madeline Kalil

      Yes, I thought this was the case in valid code, is that incorrect?

      Line 87, Patchset 1: end += 1
      Alan Donovan . resolved

      ++

      Madeline Kalil

      Done

      Line 120, Patchset 1: // The cursor representing the statement that initializes the comp lit "t".
      Alan Donovan . resolved

      File or Stmt

      but see below. I think File is actually unreachable, so this should be curStmt.

      Madeline Kalil

      Changed to curStmt, but see my other comment about why File is reachable


      // Ident for "t" in the assignment.
      lhs *ast.Ident
      // The cursor representing the statement that initializes the comp lit "t".
      // We use its siblings to search for field assignments and verify that
      // no intervening statements observe "t".
      curEnclosing inspector.Cursor
      )
      Alan Donovan . resolved

      Move down to L132.

      Madeline Kalil

      Done

      Line 126, Patchset 1: if hasUnkeyed(compLit) {
      Alan Donovan . resolved

      if !moreiters.Every(lit.Elts, is[*ast.KeyValueExpr]) {

      Madeline Kalil
      Had to slightly modify to satisfy the params:
      ```
      if !moreiters.Every(slices.Values(compLit.Elts), func(e ast.Expr) bool {
      _, ok := e.(*ast.KeyValueExpr)
      return ok
      })
      ```
      Line 134, Patchset 1: lhs = assign.Lhs[cur.ParentEdgeIndex()].(*ast.Ident)
      Alan Donovan . resolved

      How do we know it's an ident? It could be x.y[i] = ...

      Madeline Kalil

      Good point, although x.y[i] = T{..} is a bizarre case, will leave a TODO

      Line 135, Patchset 1: if enc, ok := moreiters.First(cur.Enclosing((*ast.AssignStmt)(nil), (*ast.File)(nil))); ok {
      Alan Donovan . resolved

      Isn't the first enclosing AssignStmt or File always going to be the Parent?

      Madeline Kalil

      Oh yes, thanks. In the value spec case we need to search for the enclosing DeclStmt since it won't just be the parent, but we don't need to use this pattern for the normal assignment case.

      Line 141, Patchset 1: if enc, ok := moreiters.First(cur.Enclosing((*ast.DeclStmt)(nil), (*ast.File)(nil))); ok {
      Alan Donovan . resolved

      How can it match the File?

      Madeline Kalil

      A declaration using the blank identifier doesn't have a DeclStmt, the enclosing path is [*ast.CompositeLit *ast.ValueSpec *ast.GenDecl *ast.File]. But I think the *ast.File and the check of lhs == nil and curStmt.IsValid() is redundant, so I will delete the *ast.File and leave the nil checks. (And decls using the blank identifier aren't relevant to pattern B anyways).

      Line 144, Patchset 1: }
      Alan Donovan . resolved

      else return

      Then you can assume lhs != nil && curEnclosing.Valid.

      Madeline Kalil

      See my other comment: we still need a nil check here

      Line 149, Patchset 1: for sib, ok := curEnclosing.NextSibling(); ok; sib, ok = sib.NextSibling() {
      Alan Donovan . resolved

      Since we don't need curStmt (nee curEnclosing), we might as well update it:

      ```
      for {
      var ok bool
      curStmt, ok = curStmt.NextSibling()
      if ok { return } // end of (e.g.) block
      ```
      Madeline Kalil

      Done

      Line 150, Patchset 1: var assign *ast.AssignStmt
      Alan Donovan . resolved

      assign, ok := sib.Node().(*ast.AssignStmt)
      !ok { return }

      Madeline Kalil

      Done

      Line 157, Patchset 1: for idx, expr := range assign.Lhs {
      Alan Donovan . resolved

      I don't see any testcases for an assignment with multiple Lhs variables. Does it actually work? (It looks like it might.) I wouldn't worry about that case if it's tricky (just stop if len(Lhs) != 1), but either way we should test it.

      Madeline Kalil

      Oops! Yeah, the edits get trickier when we handle multiple lhs vars. I'll leave this as a TODO for now and will prioritize it if it ends up being a common pattern in std, etc.

      Line 165, Patchset 1: // TODO: handle deeply nested expressions like t.B.x
      Alan Donovan . resolved

      TODO(mkalil):

      Madeline Kalil

      Done

      Line 176, Patchset 1: for c := range rhsCur.Preorder((*ast.SelectorExpr)(nil), (*ast.CallExpr)(nil)) {
      Alan Donovan . resolved

      Ident

      Any reference to t on the RHS is a problem.

      Madeline Kalil

      Done

      Line 187, Patchset 1: // Skip suggesting fixes when the rhs has a call expression, which

      // could panic. Moving a call that panics into the literal will
      // change the program's behavior, because thte program will
      // terminate before the composite literal is constructed.
      Alan Donovan . resolved

      This is true, but I don't think panic is the interesting thing here. If `t = T{...}` expression is populating a local variable then it will disappear during the panic; and if it's a global variable, the panic means you have bigger problems. In general, we can assume that programs shouldn't panic.

      There's a concern that any expression with side effects might mutate the values of the expressions that we move up to the literal; but shouldn't the transformed code keep all the `k: v` pairs in the same order, so there's nothing to worry about?

      A concrete example of the problem we're trying to solve here would help.

      Madeline Kalil

      This was a Gemini-generated edge case that makes theoretical sense to me:
      t = T{...}
      t.a = funcThatMightPanic()

      Here, t will get initialized but a won't be set.

      If we move the assignment up, there is a behavior change:
      t = T{..., a: funcThatMightPanic()}

      because t won't get initialized. But if we are assuming that programs shouldn't panic, then it doesn't make sense to handle this so I'll delete it. And I agree that expressions with side effects are fine because we preserve the original order of the key value exprs.

      Line 199, Patchset 1: // TODO(mkalil): When we allow intervening statements that do not

      // observe t, we will also have to ensure that these assignments do
      Alan Donovan . resolved

      I suggest we don't attempt to do that. Basically, permit t := T{...} immediately followed by one or more t.x = ... assignments, and that's all.

      Madeline Kalil

      Sounds good to me, that makes it easier. Deleted the TODOs

      Line 211, Patchset 1: if err := printer.Fprint(&rhsBuf, pass.Fset, rhs); err != nil {
      Alan Donovan . unresolved

      Remember that reformatting an expression is liable to lose its comments and vertical space. Is it necessary to reformat? All the expressions are in the correct order, so I think we should be able to replace the glue in between them. Something like this:

      ```
      t := T{...}; t.x = v
      ----- --- -
      t := T{..., x: v}
      ```

      (Obviously spaces and semicolons are a little more fiddly. We should keep the newline if that's how the logical semicolon was written in the source.)

      Madeline Kalil
      Yeah, I just thought it would be less error-prone to paste the formatted rhs rather than do the surgical edits and deal with whitespace. In terms of losing comments, I think this only happens when the rhs itself is a struct literal?
      ```
      t12 := A{}
      t12.B = B{
      b: 1, // this comment gets deleted
      }
      ```
      vs
      ```
      t10 := A{}
      t10.a = 1 // this comment is preserved
      t10.b = 2 // this one too
      ```
      I'll come back to this but wanted to push the other changes
      Line 237, Patchset 1: // in the comp lit when we observe code where both patterns apply.
      Alan Donovan . resolved

      Yeah, that's gonna be tricky. I think what you have here is the best we're going to be able to do without heroic effort.

      Madeline Kalil

      Acknowledged

      File go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
      Line 4, Patchset 1:
      import "log"
      Alan Donovan . resolved

      Tip: analysis tests run faster if they don't use imports: `go list -deps log` pulls in 61 packages. I suggest you declare a use(any) function instead of log.Print.

      Madeline Kalil

      Done, thanks.

      Line 140, Patchset 1:}
      Alan Donovan . resolved

      Can we add a test that uses two following statements?

      x = T{...}
      x.a.b = 123
      x.a.c = 456

      I suspect only the first will get fixed, and the user will have to run the tool again to get the second one.

      Madeline Kalil

      I have an outstanding TODO for handling deeply nested exprs, but I added a test case for something like:
      x = T{...}
      x.b = 1
      x.c = 2
      and it produces:
      x = T{..., b: 1, c: 2}

      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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
      Gerrit-Change-Number: 773220
      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: Alan Donovan <adon...@google.com>
      Gerrit-Comment-Date: Mon, 04 May 2026 20:28:39 +0000
      Gerrit-HasComments: Yes
      Gerrit-Has-Labels: Yes
      Comment-In-Reply-To: Alan Donovan <adon...@google.com>
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      May 4, 2026, 5:01:59 PM (7 days ago) May 4
      to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from Alan Donovan and 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:
      • Alan Donovan
      • 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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
      Gerrit-Change-Number: 773220
      Gerrit-PatchSet: 3
      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-Attention: Alan Donovan <adon...@google.com>
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      May 4, 2026, 5:02:05 PM (7 days ago) May 4
      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 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 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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
      Gerrit-Change-Number: 773220
      Gerrit-PatchSet: 3
      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, 04 May 2026 21:02:02 +0000
      Gerrit-HasComments: No
      Gerrit-Has-Labels: Yes
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      May 8, 2026, 3:50:15 PM (3 days ago) May 8
      to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
      Attention needed from Alan Donovan

      Madeline Kalil uploaded new patchset

      Madeline Kalil uploaded patch set #4 to this change.
      Following approvals got outdated and were removed:
      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: newpatchset
      Gerrit-Project: tools
      Gerrit-Branch: master
      Gerrit-Change-Id: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
      Gerrit-Change-Number: 773220
      Gerrit-PatchSet: 4
      unsatisfied_requirement
      open
      diffy

      Madeline Kalil (Gerrit)

      unread,
      May 8, 2026, 3:50:25 PM (3 days ago) May 8
      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 2 comments

      Votes added by Madeline Kalil

      Commit-Queue+1

      2 comments

      File go/analysis/passes/modernize/embedlit.go
      Line 85, Patchset 1: // in the outer lit, we must expand the range to delete the trailing comma.
      Alan Donovan . resolved

      Does the logic below assume that the comma immediately follows the brace?

      Madeline Kalil

      Yes, I thought this was the case in valid code, is that incorrect?

      Madeline Kalil

      Nevermind, commas/whitespace are valid. But we only need this logic in order to handle empty composite lits, and since there's no fields in them to promote, although they can be removed entirely, I don't think we need to handle them at all.

      Line 211, Patchset 1: if err := printer.Fprint(&rhsBuf, pass.Fset, rhs); err != nil {
      Alan Donovan . resolved

      Remember that reformatting an expression is liable to lose its comments and vertical space. Is it necessary to reformat? All the expressions are in the correct order, so I think we should be able to replace the glue in between them. Something like this:

      ```
      t := T{...}; t.x = v
      ----- --- -
      t := T{..., x: v}
      ```

      (Obviously spaces and semicolons are a little more fiddly. We should keep the newline if that's how the logical semicolon was written in the source.)

      Madeline Kalil
      Yeah, I just thought it would be less error-prone to paste the formatted rhs rather than do the surgical edits and deal with whitespace. In terms of losing comments, I think this only happens when the rhs itself is a struct literal?
      ```
      t12 := A{}
      t12.B = B{
      b: 1, // this comment gets deleted
      }
      ```
      vs
      ```
      t10 := A{}
      t10.a = 1 // this comment is preserved
      t10.b = 2 // this one too
      ```
      I'll come back to this but wanted to push the other changes
      Madeline Kalil

      Updated to use surgical edits instead. We have to gather all the assign statements that will be promoted first, and then produce the edits at the end.

      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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
        Gerrit-Change-Number: 773220
        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: Fri, 08 May 2026 19:50:21 +0000
        Gerrit-HasComments: Yes
        Gerrit-Has-Labels: Yes
        Comment-In-Reply-To: Madeline Kalil <mka...@google.com>
        Comment-In-Reply-To: Alan Donovan <adon...@google.com>
        unsatisfied_requirement
        satisfied_requirement
        open
        diffy

        Alan Donovan (Gerrit)

        unread,
        2:03 PM (9 hours ago) 2:03 PM
        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 7 comments

        File go/analysis/passes/modernize/embedlit.go
        Line 47, Patchset 4 (Latest): var (
        edits []analysis.TextEdit

        names []string // names of the embedded field types that can be removed
        compLit = curLit.Node().(*ast.CompositeLit)
        compLitType = info.TypeOf(compLit)
        )
        Alan Donovan . unresolved

        checkA and B are quite long, and are almost completely independent from each other and from their parent function (runEmbedLit); the only things they share are these locals. I think it would be worth breaking the two functions into two top-level functions that each call pass.Report as their last step.

        Line 92, Patchset 4 (Latest): End: end,
        Alan Donovan . unresolved

        inline this var?

        Line 109, Patchset 4 (Latest): _, ok := e.(*ast.KeyValueExpr)
        return ok
        Alan Donovan . unresolved

        `return is[*ast.KeyValueExpr](e)`

        Line 124, Patchset 4 (Latest): if cur.ParentEdgeKind() == edge.AssignStmt_Rhs {
        Alan Donovan . unresolved

        ```
        switch cur.ParentEdgeKind() { case A: case B: default: }
        ```
        avoids evaluating it twice.

        Line 145, Patchset 4 (Latest): type assignInfo struct {
        assign *ast.AssignStmt
        sel *ast.SelectorExpr
        rhs ast.Expr
        }
        Alan Donovan . unresolved

        Rather than allocate a new array of assignInfos for the sequence of statements after every keyed composite literal, you could record the existing statement slice itself (e.g. a `[]Stmt` portion of `BlockStmt.List`, or a pair of Cursors for the first and last statement).

        It's easy to derive the sel (`assign.Lhs[0].(*ast.SelectorExpr)`) and rhs (`assign.Rhs[0]`) fields later.

        Line 183, Patchset 4 (Latest): if id, ok := c.Node().(*ast.Ident); ok {
        Alan Donovan . unresolved

        You can safely assume this because of the type filter passed to Preorder.

        Line 216, Patchset 4 (Latest): src, err := os.ReadFile(filename)
        Alan Donovan . unresolved

        As a general note, it is rarely correct to discard I/O error messages as I/O can fail for a huge variety of reasons that you might want to know about. Better to propagate the error upwards, which in this case means to return it from the error from the Analyzer.run function. This will cause analysis to fail, but that's appropriate, since it should never happen.

        Specifically, an analyzer should never read the file system directly. There's no guarantee that what it reads from the file system matches the parse tree: the tree could have been derived from an edited file via the overlay mechanism; or the file might not exist at all (as in the case of a purely virtualized analysis processing RPC service). Indeed, in the future, the secure analysis sandbox will directly reject attempt to read the file system.

        Instead, Analyzers should call pass.ReadFile to read the contents of one of the files under analysis, at the appropriate version.

        (It's unfortunate that we have to use ReadFile at all--it's a symptom of design problems in the AST--but I think it should be efficient since at this point we committed to reporting a diagnostic.)

        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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
          Gerrit-Change-Number: 773220
          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: Mon, 11 May 2026 18:03:14 +0000
          Gerrit-HasComments: Yes
          Gerrit-Has-Labels: No
          unsatisfied_requirement
          satisfied_requirement
          open
          diffy

          Madeline Kalil (Gerrit)

          unread,
          4:37 PM (6 hours ago) 4:37 PM
          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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
            Gerrit-Change-Number: 773220
            Gerrit-PatchSet: 5
            unsatisfied_requirement
            open
            diffy

            Madeline Kalil (Gerrit)

            unread,
            4:57 PM (6 hours ago) 4:57 PM
            to goph...@pubsubhelper.golang.org, golang-co...@googlegroups.com
            Attention needed from Madeline Kalil

            Madeline Kalil uploaded new patchset

            Madeline Kalil uploaded patch set #6 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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
            Gerrit-Change-Number: 773220
            Gerrit-PatchSet: 6
            unsatisfied_requirement
            open
            diffy

            Madeline Kalil (Gerrit)

            unread,
            4:57 PM (6 hours ago) 4:57 PM
            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 7 comments

            Votes added by Madeline Kalil

            Commit-Queue+1

            7 comments

            File go/analysis/passes/modernize/embedlit.go

            edits []analysis.TextEdit
            names []string // names of the embedded field types that can be removed
            compLit = curLit.Node().(*ast.CompositeLit)
            compLitType = info.TypeOf(compLit)
            )
            Alan Donovan . resolved

            checkA and B are quite long, and are almost completely independent from each other and from their parent function (runEmbedLit); the only things they share are these locals. I think it would be worth breaking the two functions into two top-level functions that each call pass.Report as their last step.

            Madeline Kalil

            Yeah that's better, thanks.

            Line 92, Patchset 4: End: end,
            Alan Donovan . resolved

            inline this var?

            Madeline Kalil

            Done

            Line 109, Patchset 4: _, ok := e.(*ast.KeyValueExpr)
            return ok
            Alan Donovan . resolved

            `return is[*ast.KeyValueExpr](e)`

            Madeline Kalil

            Done

            Line 124, Patchset 4: if cur.ParentEdgeKind() == edge.AssignStmt_Rhs {
            Alan Donovan . resolved

            ```
            switch cur.ParentEdgeKind() { case A: case B: default: }
            ```
            avoids evaluating it twice.

            Madeline Kalil

            Done

            Line 145, Patchset 4: type assignInfo struct {

            assign *ast.AssignStmt
            sel *ast.SelectorExpr
            rhs ast.Expr
            }
            Alan Donovan . resolved

            Rather than allocate a new array of assignInfos for the sequence of statements after every keyed composite literal, you could record the existing statement slice itself (e.g. a `[]Stmt` portion of `BlockStmt.List`, or a pair of Cursors for the first and last statement).

            It's easy to derive the sel (`assign.Lhs[0].(*ast.SelectorExpr)`) and rhs (`assign.Rhs[0]`) fields later.

            Madeline Kalil

            Used a pair of cursors for firstStmt and lastStmt

            Line 183, Patchset 4: if id, ok := c.Node().(*ast.Ident); ok {
            Alan Donovan . resolved

            You can safely assume this because of the type filter passed to Preorder.

            Madeline Kalil

            Done

            Line 216, Patchset 4: src, err := os.ReadFile(filename)
            Alan Donovan . resolved

            As a general note, it is rarely correct to discard I/O error messages as I/O can fail for a huge variety of reasons that you might want to know about. Better to propagate the error upwards, which in this case means to return it from the error from the Analyzer.run function. This will cause analysis to fail, but that's appropriate, since it should never happen.

            Specifically, an analyzer should never read the file system directly. There's no guarantee that what it reads from the file system matches the parse tree: the tree could have been derived from an edited file via the overlay mechanism; or the file might not exist at all (as in the case of a purely virtualized analysis processing RPC service). Indeed, in the future, the secure analysis sandbox will directly reject attempt to read the file system.

            Instead, Analyzers should call pass.ReadFile to read the contents of one of the files under analysis, at the appropriate version.

            (It's unfortunate that we have to use ReadFile at all--it's a symptom of design problems in the AST--but I think it should be efficient since at this point we committed to reporting a diagnostic.)

            Madeline Kalil

            Thank you! I didn't know about pass.ReadFile. Indeed it is unfortunate that we have to read the file but I'm not sure there's a way to handle the comma issue without looking at the actual file content (at least I couldn't think of one).

            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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
              Gerrit-Change-Number: 773220
              Gerrit-PatchSet: 6
              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, 11 May 2026 20:57:52 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: Yes
              Comment-In-Reply-To: Alan Donovan <adon...@google.com>
              unsatisfied_requirement
              satisfied_requirement
              open
              diffy

              Madeline Kalil (Gerrit)

              unread,
              5:02 PM (6 hours ago) 5:02 PM
              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 added 1 comment

              Patchset-level comments
              File-level comment, Patchset 6 (Latest):
              Madeline Kalil . resolved

              Results from this morning of running this modernizer over std: https://go-review.git.corp.google.com/c/go/+/776862

              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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
              Gerrit-Change-Number: 773220
              Gerrit-PatchSet: 6
              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, 11 May 2026 21:02:02 +0000
              Gerrit-HasComments: Yes
              Gerrit-Has-Labels: No
              unsatisfied_requirement
              satisfied_requirement
              open
              diffy

              Alan Donovan (Gerrit)

              unread,
              5:40 PM (5 hours ago) 5:40 PM
              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 16 comments

              File go/analysis/passes/modernize/embedlit.go
              Line 62, Patchset 6 (Latest):func checkA(pass *analysis.Pass, info *types.Info, curLit inspector.Cursor) bool {
              Alan Donovan . unresolved

              Now that these share the modernize package namespace, let's give them slightly more scoped and informative names, e.g. embedlitUnnest and embedlitCombine.

              Line 73, Patchset 6 (Latest): checkLit = func(pass *analysis.Pass, info *types.Info, curLit inspector.Cursor, lit *ast.CompositeLit, litType types.Type) {
              Alan Donovan . unresolved

              Delete these three parameters and litType; they're all already in scope.

              Line 77, Patchset 6 (Latest): if innerLit := isEmbeddedFieldLit(info, litType, kv); innerLit != nil {
              Alan Donovan . unresolved

              complitType

              Line 141, Patchset 6 (Latest): edits []analysis.TextEdit
              Alan Donovan . unresolved

              Move to L273.

              Line 189, Patchset 6 (Latest): for {
              Alan Donovan . unresolved

              stmtloop:

              Line 190, Patchset 6 (Latest): curStmt, ok = curStmt.NextSibling()
              Alan Donovan . unresolved

              var ok bool

              then delete L187

              Line 216, Patchset 4: src, err := os.ReadFile(filename)
              Alan Donovan . unresolved

              As a general note, it is rarely correct to discard I/O error messages as I/O can fail for a huge variety of reasons that you might want to know about. Better to propagate the error upwards, which in this case means to return it from the error from the Analyzer.run function. This will cause analysis to fail, but that's appropriate, since it should never happen.

              Specifically, an analyzer should never read the file system directly. There's no guarantee that what it reads from the file system matches the parse tree: the tree could have been derived from an edited file via the overlay mechanism; or the file might not exist at all (as in the case of a purely virtualized analysis processing RPC service). Indeed, in the future, the secure analysis sandbox will directly reject attempt to read the file system.

              Instead, Analyzers should call pass.ReadFile to read the contents of one of the files under analysis, at the appropriate version.

              (It's unfortunate that we have to use ReadFile at all--it's a symptom of design problems in the AST--but I think it should be efficient since at this point we committed to reporting a diagnostic.)

              Madeline Kalil

              Thank you! I didn't know about pass.ReadFile. Indeed it is unfortunate that we have to read the file but I'm not sure there's a way to handle the comma issue without looking at the actual file content (at least I couldn't think of one).

              Alan Donovan

              Don't forget to handle the error.

              Line 217, Patchset 6 (Latest): rhsCur, _ := curStmt.FindNode(rhs)
              Alan Donovan . unresolved

              No need for Find: you can use `curRhs := curStmt.ChildAt(edge.Assign_Rhs, 0)`.

              Line 224, Patchset 6 (Latest): rhsUsesT = true
              break
              Alan Donovan . unresolved

              break stmtloop

              Line 218, Patchset 6 (Latest): rhsUsesT := false
              for c := range rhsCur.Preorder((*ast.Ident)(nil)) {
              id := c.Node().(*ast.Ident)

              // If the rhs uses a value of t (e.g. t.x = t.y), don't suggest a fix because
              // we can't evaluate t.y when constructing the new literal.
              if info.ObjectOf(id) == tObj {
              rhsUsesT = true
              break
              }
              // Note: we don't need to worry about expressions with side effects
              // changing the behavior when moved inside the comp lit. The order of
              // effects will be preserved because we preserve the order of the key
              // value pairs inside the comp lit.
              }
              if rhsUsesT {
              break
              }
              Alan Donovan . unresolved

              BTW, you could replace this whole loop with `if uses(curRhs, index, tObj) { break } ` if you Require typeindex.

              (Although this function does iterate over all the uses of t, it is very efficient and doesn't allocate.)

              Line 266, Patchset 6 (Latest): if src[i] == ',' {
              Alan Donovan . unresolved

              Is it possible this might be triggered by a comma in a comment?

              Line 265, Patchset 6 (Latest): for i := lastEltOffset; i < rbraceOffset && i < len(src); i++ {
              if src[i] == ',' {
              hasTrailingComma = true
              break
              }
              }
              Alan Donovan . unresolved

              hasTrailingComma = bytes.ContainsByte(src[lastEltOffset:rbraceOffset], ',')

              Line 312, Patchset 6 (Latest): // Replace "=" with ":"
              Alan Donovan . unresolved

              Can you add a test case that uses += to make sure we leave it alone?

              Line 314, Patchset 6 (Latest): Pos: assign.TokPos,
              Alan Donovan . unresolved

              Shouldn't this be expr.End(), so that `t.x = y` becomes `x: t` without a space?

              Line 323, Patchset 6 (Latest): Pos: prevAssign.End(),
              Alan Donovan . unresolved

              prevStmt.Node()

              Line 340, Patchset 6 (Latest): if len(edits) > 0 {
              Alan Donovan . unresolved

              delete (we can't reach here without getting edits)

              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: Ide5fee8d419cc5d6e8fb39c20cf552c3c72bf418
                Gerrit-Change-Number: 773220
                Gerrit-PatchSet: 6
                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: Mon, 11 May 2026 21:40:31 +0000
                Gerrit-HasComments: Yes
                Gerrit-Has-Labels: No
                unsatisfied_requirement
                satisfied_requirement
                open
                diffy
                Reply all
                Reply to author
                Forward
                0 new messages