gopls/internal/golang/completion: support go1.27 embedded T{f: ...}

This CL causes completion to offer as candidates the names of
fields promoted from embedded structs, if the source file
uses go1.27 or later.

+ tests

Fixes golang/go#78553

Change-Id: Id346e0a5862c7a937e3e5efdeeaeb3e6b035057f
Reviewed-on: https://go-review.googlesource.com/c/tools/+/786780
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/gopls/internal/golang/completion/completion.go b/gopls/internal/golang/completion/completion.go
index fc8a2f8..411e708 100644
--- a/gopls/internal/golang/completion/completion.go
+++ b/gopls/internal/golang/completion/completion.go
@@ -14,6 +14,7 @@
 	"go/scanner"
 	"go/token"
 	"go/types"
+	"iter"
 	"math"
 	"slices"
 	"sort"
@@ -1809,38 +1810,81 @@
 func (c *completer) structLiteralFieldName(ctx context.Context) error {
 	clInfo := c.enclosingCompositeLiteral
 
-	// Mark fields of the composite literal that have already been set,
-	// except for the current field.
-	addedFields := make(map[*types.Var]bool)
-	for _, el := range clInfo.cl.Elts {
-		if kvExpr, ok := el.(*ast.KeyValueExpr); ok {
-			if clInfo.kv == kvExpr {
-				continue
-			}
+	if t, ok := clInfo.clType.Underlying().(*types.Struct); ok {
 
-			if key, ok := kvExpr.Key.(*ast.Ident); ok {
-				if used, ok := c.pkg.TypesInfo().Uses[key]; ok {
-					if usedVar, ok := used.(*types.Var); ok {
-						addedFields[usedVar] = true
+		// Collect selection indices of all existing
+		// fields specified by the struct literal.
+		existing := make(map[types.Object][]int)
+		for _, elt := range clInfo.cl.Elts {
+			if kv, ok := elt.(*ast.KeyValueExpr); ok {
+				if key, ok := kv.Key.(*ast.Ident); ok && clInfo.kv != kv {
+					seln, ok := types.LookupSelection(clInfo.clType, true, c.pkg.Types(), key.Name)
+					if ok {
+						existing[seln.Obj()] = seln.Index()
 					}
 				}
 			}
 		}
-	}
 
-	// Add struct fields.
-	if t, ok := types.Unalias(clInfo.clType).(*types.Struct); ok {
-		const deltaScore = 0.0001
-		for i := range t.NumFields() {
-			field := t.Field(i)
-			if !addedFields[field] {
-				c.deepState.enqueue(candidate{
-					obj:   field,
-					score: highScore - float64(i)*deltaScore,
-				})
+		// fields returns the sequence of candidate fields of struct type t.
+		fields := func(t *types.Struct) iter.Seq[*types.Var] {
+			// go1.27 permits promoted fields in struct literals.
+			deep := versions.AtLeast(c.goversion, versions.Go1_27)
+
+			return func(yield func(*types.Var) bool) {
+				var collect func(t *types.Struct) bool
+				collect = func(t *types.Struct) bool {
+					for f := range t.Fields() {
+						if !yield(f) {
+							return false
+						}
+						if deep && f.Anonymous() {
+							if inner, ok := f.Type().Underlying().(*types.Struct); ok && !collect(inner) {
+								return false
+							}
+						}
+					}
+					return true
+				}
+				collect(t)
 			}
 		}
 
+		// conflict reports whether one field index
+		// sequence is a prefix (ancestor) of the other.
+		conflict := func(a, b []int) bool {
+			for i := range min(len(a), len(b)) {
+				if a[i] != b[i] {
+					return false
+				}
+			}
+			return true
+		}
+
+	fieldloop:
+		for f := range fields(t) {
+			seln, ok := types.LookupSelection(clInfo.clType, true, c.pkg.Types(), f.Name())
+			if !ok || seln.Obj() != f {
+				continue // candidate's name is shadowed or ambiguous here
+			}
+
+			// Reject candidates that conflict with existing fields.
+			for _, indices := range existing {
+				if conflict(seln.Index(), indices) {
+					continue fieldloop
+				}
+			}
+
+			const deltaScore = 0.0001
+			const depthPenalty = 0.01
+			depth := len(seln.Index())
+			fieldIdx := seln.Index()[depth-1]
+			c.deepState.enqueue(candidate{
+				obj:   seln.Obj(),
+				score: highScore - float64(depth-1)*depthPenalty - float64(fieldIdx)*deltaScore,
+			})
+		}
+
 		// Fall through and add lexical completions if we aren't
 		// certain we are in the key part of a key-value pair.
 		if !clInfo.maybeInFieldName {
diff --git a/gopls/internal/test/marker/testdata/completion/issue78553.txt b/gopls/internal/test/marker/testdata/completion/issue78553.txt
index 23e813a..34fb489 100644
--- a/gopls/internal/test/marker/testdata/completion/issue78553.txt
+++ b/gopls/internal/test/marker/testdata/completion/issue78553.txt
@@ -4,9 +4,6 @@
 -- flags --
 -min_go_command=go1.27
 
--- skip --
-Skipping as this feature is not yet implemented. Ref: go.dev/issues/78553
-
 -- go.mod --
 module mod.com
 
@@ -16,21 +13,32 @@
 package main
 
 type E1 struct {
-	A int //@item(fieldA, "A", "int", "field")
+	A int //@item(fieldA, "A", "int", "field"),item(structE1, "E1", "struct{...}", "struct")
 }
 
 type E2 struct {
-	E1
+	E1    //@item(fieldE1, "E1", "E1", "field"),item(structE2, "E2", "struct{...}", "struct")
 	B int //@item(fieldB, "B", "int", "field")
 }
 
 type T struct {
-	E2    //@item(fieldE2, "E2", "E2", "field")
+	E2    //@item(fieldE2, "E2", "E2", "field"),item(structT, "T", "struct{...}", "struct")
 	C int //@item(fieldC, "C", "int", "field")
 }
 
+//@item(literalE2, "E2{}", "", "var")
+//@item(funcMain, "main", "func()", "func")
+
 func main() {
 	_ = T{
-		//@complete("", fieldA, fieldB, fieldC, fieldE2)
+		// Fields are suggested in breadth-first order:
+		// T fields (E2, C), then E2 fields (E1, B), then E1 fields (A).
+
+		//@complete("", fieldE2, fieldC, fieldE1, fieldB, fieldA, literalE2, funcMain, structE1, structE2, structT)
+	}
+
+	_ = T{
+		A: 1,
+		//@complete("", fieldC, fieldB)
 	}
 }
diff --git a/gopls/internal/test/marker/testdata/completion/issue78553_before127.txt b/gopls/internal/test/marker/testdata/completion/issue78553_before127.txt
new file mode 100644
index 0000000..0e4ec31
--- /dev/null
+++ b/gopls/internal/test/marker/testdata/completion/issue78553_before127.txt
@@ -0,0 +1,28 @@
+Test for completion on promoted fields in struct literals before Go 1.27.
+Ref: go.dev/issues/78553
+
+-- go.mod --
+module mod.com
+
+go 1.26
+
+-- main.go --
+package main
+
+type E1 struct {
+	A int //@item(fieldA, "A", "int", "field")
+}
+
+type T struct {
+	E1    //@item(fieldE1, "E1", "E1", "field"),item(structE1, "E1", "struct{...}", "struct")
+	C int //@item(fieldC, "C", "int", "field"),item(structT, "T", "struct{...}", "struct")
+}
+
+//@item(literalE1, "E1{}", "", "var")
+//@item(funcMain, "main", "func()", "func")
+
+func main() {
+	_ = T{
+		//@complete("", fieldE1, fieldC, literalE1, funcMain, structE1, structT)
+	}
+}