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")