gopls/internal/analysis/fillstruct: respect file go version

Direct reference to embedded fields in struct literals is invalid before
Go 1.27.

To prevent generating invalid code, update the fillstruct analyzer to
check the Go version of the file. If the version is less than Go 1.27,
skip filling promoted fields. This fallback behavior causes gopls to
instead suggest filling the direct parent struct, generating valid Go
1.26 nested struct literals.

Also, add build tags and version-specific marker tests for Go 1.26 and
Go 1.27.

For golang/go#78553

Change-Id: I3f915719ec60c796d5f77be72408e1786d3bf680
Reviewed-on: https://go-review.googlesource.com/c/tools/+/786422
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/gopls/internal/analysis/fillstruct/fillstruct.go b/gopls/internal/analysis/fillstruct/fillstruct.go
index 6bdb7b8..bbbd0f7 100644
--- a/gopls/internal/analysis/fillstruct/fillstruct.go
+++ b/gopls/internal/analysis/fillstruct/fillstruct.go
@@ -35,6 +35,7 @@
 	"golang.org/x/tools/internal/astutil"
 	"golang.org/x/tools/internal/typeparams"
 	"golang.org/x/tools/internal/typesinternal"
+	"golang.org/x/tools/internal/versions"
 )
 
 // Diagnose computes a diagnostic for the enclosing struct literal enclosing
@@ -47,6 +48,12 @@
 func Diagnose(curFile inspector.Cursor, start, end token.Pos, pkg *types.Package, info *types.Info) (diags []analysis.Diagnostic) {
 	cur, _, _, _ := astutil.Select(curFile, start, end)
 
+	// Direct reference to embedded fields in struct literals requires Go 1.27+.
+	var supportsEmbedFields bool
+	if v := info.FileVersions[curFile.Node().(*ast.File)]; v != "" {
+		supportsEmbedFields = versions.AtLeast(v, versions.Go1_27)
+	}
+
 	var lits []*ast.CompositeLit
 	for c := range cur.Enclosing((*ast.CompositeLit)(nil)) {
 		lits = append(lits, c.Node().(*ast.CompositeLit))
@@ -133,6 +140,10 @@
 				continue
 			}
 
+			if len(seln.Index()) > 1 && !supportsEmbedFields {
+				continue
+			}
+
 			length := len(seln.Index())
 			if length == 0 {
 				continue
@@ -343,6 +354,12 @@
 
 	// Inv: typ is the possibly-named struct type.
 
+	// Direct reference to embedded fields in struct literals requires Go 1.27+.
+	var supportsEmbedFields bool
+	if v := info.FileVersions[file]; v != "" {
+		supportsEmbedFields = versions.AtLeast(v, versions.Go1_27)
+	}
+
 	// explicit records whether each encountered field is explicitly initialized.
 	// Each non-last field in a selection path is accessed implicitly (false),
 	// and the last field is accessed explicitly (true).
@@ -364,6 +381,10 @@
 			continue
 		}
 
+		if len(seln.Index()) > 1 && !supportsEmbedFields {
+			continue
+		}
+
 		field, ok := seln.Obj().(*types.Var)
 		if !ok {
 			continue
diff --git a/gopls/internal/analysis/fillstruct/testdata/src/issue78553/issue78553.go b/gopls/internal/analysis/fillstruct/testdata/src/issue78553/issue78553.go
index d12a793..d1eb5e4 100644
--- a/gopls/internal/analysis/fillstruct/testdata/src/issue78553/issue78553.go
+++ b/gopls/internal/analysis/fillstruct/testdata/src/issue78553/issue78553.go
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
+//go:build go1.27
+
 package issue78553
 
 type F struct {
diff --git a/gopls/internal/test/marker/testdata/codeaction/fill_struct_go126.txt b/gopls/internal/test/marker/testdata/codeaction/fill_struct_go126.txt
index cf333b0..06b8750 100644
--- a/gopls/internal/test/marker/testdata/codeaction/fill_struct_go126.txt
+++ b/gopls/internal/test/marker/testdata/codeaction/fill_struct_go126.txt
@@ -42,10 +42,17 @@
 	b1 string
 }
 
+// In Go 1.26, direct reference to embedded fields (like b1: "") is invalid.
+// gopls treats "b1" as unknown, does not detect a conflict with b, and
+// suggests filling direct fields a1 and a2.
 var _ = a{
 	b:  b{},
 	b1: "",
-} //@codeaction("}", "refactor.rewrite.fillStruct", err=re"cannot fill both .* and its subfields")
+} //@codeaction("}", "refactor.rewrite.fillStruct", edit=fieldconflict)
+-- @fieldconflict/malformed/fieldconflict.go --
+@@ -19 +19,2 @@
++	a1: "",
++	a2: "",
 -- malformed/embeddedfields.go --
 package malformed
 
@@ -59,13 +66,13 @@
 }
 
 // In Go 1.26, direct reference to embedded fields (like E1: "") is invalid.
-// However, if the user writes it anyway, gopls will still try to fill in the
-// remaining promoted fields as if Go 1.27 were supported.
+// gopls treat "E1" as unknown, does not flatten E, and suggests filling direct
+// fields E, T1, and T2.
 var _ = T{
 	E1: "",
 } //@codeaction("}", "refactor.rewrite.fillStruct", edit=embeddedfieldsgo)
 -- @embeddedfieldsgo/malformed/embeddedfields.go --
 @@ -17 +17,3 @@
-+	E2: "",
++	E:  E{},
 +	T1: "",
 +	T2: "",
diff --git a/gopls/internal/test/marker/testdata/codeaction/fill_struct_issue78553.txt b/gopls/internal/test/marker/testdata/codeaction/fill_struct_go127.txt
similarity index 85%
rename from gopls/internal/test/marker/testdata/codeaction/fill_struct_issue78553.txt
rename to gopls/internal/test/marker/testdata/codeaction/fill_struct_go127.txt
index 5513472..3f3aea1 100644
--- a/gopls/internal/test/marker/testdata/codeaction/fill_struct_issue78553.txt
+++ b/gopls/internal/test/marker/testdata/codeaction/fill_struct_go127.txt
@@ -3,6 +3,7 @@
 
 -- flags --
 -min_go_command=go1.27
+-ignore_extra_diags
 
 -- go.mod --
 module mod.com
@@ -65,3 +66,20 @@
 +	F2: 0,
 +	F:  0,
 +} //@codeaction(re"T{()", "refactor.rewrite.fillStruct", edit=fill_all)
+-- malformed/fieldconflict.go --
+package malformed
+
+type a struct {
+	b
+	a1 string
+	a2 string
+}
+
+type b struct {
+	b1 string
+}
+
+var _ = a{
+	b:  b{},
+	b1: "",
+} //@codeaction("}", "refactor.rewrite.fillStruct", err=re"cannot fill both .* and its subfields")