go/analysis/passes/modernize: embedlit - skip if no selections are embedded

If we have a type with no embedded fields, like:

type T struct {
   x, y int
}

t := T{}
t.x = 1

We should not suggest combining the assignment into
t := T{x: 1}

because this is not directly related to the Go1.27 language
feature of direct access to embedded struct fields.

If at least one of the selections references an embedded field,
we should still suggest a modernization, like:

type T struct {
  x int
  Y
}

type Y struct {
  y int
}

t := T{}
t.x = 1 // non-embedded reference
t.y = 2 // embedded reference

=>
t := T{x: 1, y: 2}

Change-Id: I8a1b83c8e60a6dc6f1cf609e8d6e5ba18b0f9225
Reviewed-on: https://go-review.googlesource.com/c/tools/+/797560
Auto-Submit: Madeline Kalil <mkalil@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/go/analysis/passes/modernize/embedlit.go b/go/analysis/passes/modernize/embedlit.go
index f8b6dfc..4061758 100644
--- a/go/analysis/passes/modernize/embedlit.go
+++ b/go/analysis/passes/modernize/embedlit.go
@@ -241,7 +241,8 @@
 		tObj = info.ObjectOf(lhs)
 		// Marks the contiguous block of embedded field assign statements that will
 		// be moved into the struct initialization.
-		firstStmt, lastStmt inspector.Cursor
+		firstStmt, lastStmt  inspector.Cursor
+		hasEmbeddedSelection bool
 	)
 stmtloop:
 	for {
@@ -272,6 +273,15 @@
 		if obj != tObj {
 			break
 		}
+		// The selection is from an embedded field if it directly
+		// assigns an embedded struct field (t.B = B{...}) or if
+		// the length of the index path is greater than one.
+		seln := info.Selections[sel]
+		if v, ok := seln.Obj().(*types.Var); ok && v.Embedded() ||
+			len(seln.Index()) > 1 {
+			hasEmbeddedSelection = true
+		}
+
 		rhsCur := curStmt.ChildAt(edge.AssignStmt_Rhs, 0)
 		if uses(index, rhsCur, tObj) {
 			break
@@ -294,7 +304,8 @@
 		lastStmt = curStmt
 	}
 
-	if !firstStmt.Valid() {
+	if !firstStmt.Valid() || !hasEmbeddedSelection {
+		// We should not suggest a fix if none of the selections are from embedded fields.
 		return nil
 	}
 
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 19a47ee..6c9ac33 100644
--- a/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
+++ b/go/analysis/passes/modernize/testdata/src/embedlit/embedlit_go127.go
@@ -63,6 +63,11 @@
 
 type U struct {
 	x int
+	V
+}
+
+type V struct {
+	x, y int
 }
 
 var (
@@ -222,6 +227,17 @@
 	var v1, v2 = A{}, A{} // nope, multi-declaration
 	v2.e = 1
 	_ = v1
+
+	t17 := U{}
+	t17.x = 1 // nope: not embedded
+
+	t18 := V{}
+	t18.x = 1
+	t18.y = 2 // nope: not embedded
+
+	t19 := U{} // want "embedded field assignment can be moved to struct literal"
+	t19.y = 2
+	t19.x = 1
 }
 
 func foo() int {
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 6d8e437..48f0593 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
@@ -63,6 +63,11 @@
 
 type U struct {
 	x int
+	V
+}
+
+type V struct {
+	x, y int
 }
 
 var (
@@ -208,6 +213,17 @@
 	var v1, v2 = A{}, A{} // nope, multi-declaration
 	v2.e = 1
 	_ = v1
+	
+	t17 := U{}
+	t17.x = 1 // nope: not embedded
+
+	t18 := V{}
+	t18.x = 1
+	t18.y = 2 // nope: not embedded
+
+	t19 := U{ // want "embedded field assignment can be moved to struct literal"
+				y: 2,
+				x: 1}
 }
 
 func foo() int {