go/types, types2: allow promoted field names as keys in struct literals

For #9859.

Change-Id: Ie4823e726ab43fe2ce1e96c279f63dad6c1f9873
Reviewed-on: https://go-review.googlesource.com/c/go/+/751702
Reviewed-by: Mark Freeman <markfreeman@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
Robert Griesemer
2026-03-04 19:01:53 -08:00
parent 86b5e678e8
commit 9f7e98d263
8 changed files with 144 additions and 57 deletions

View File

@@ -160,7 +160,7 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type
fields := utyp.fields
if _, ok := e.ElemList[0].(*syntax.KeyValueExpr); ok {
// all elements must have keys
visited := make([]bool, len(fields))
visited := make(trie[*Var])
for _, e := range e.ElemList {
kv, _ := e.(*syntax.KeyValueExpr)
if kv == nil {
@@ -175,26 +175,41 @@ func (check *Checker) compositeLit(x *operand, e *syntax.CompositeLit, hint Type
check.errorf(kv, InvalidLitField, "invalid field name %s in struct literal", kv.Key)
continue
}
i := fieldIndex(fields, check.pkg, key.Value, false)
if i < 0 {
var alt Object
if j := fieldIndex(fields, check.pkg, key.Value, true); j >= 0 {
alt = fields[j]
}
obj, index, indirect := lookupFieldOrMethod(utyp, false, check.pkg, key.Value, false)
if obj == nil {
alt, _, _ := lookupFieldOrMethod(utyp, false, check.pkg, key.Value, true)
msg := check.lookupError(base, key.Value, alt, true)
check.error(kv.Key, MissingLitField, msg)
continue
}
fld := fields[i]
fld, _ := obj.(*Var)
if fld == nil {
check.errorf(kv.Key, MissingLitField, "%s is not a field", kv.Key)
continue
}
if check.allowVersion(go1_27) {
if indirect {
check.errorf(kv.Key, InvalidLitField, "invalid implicit pointer indirection to reach %s", kv.Key)
continue
}
} else {
if len(index) > 1 {
check.errorf(kv.Key, InvalidLitField, "cannot use promoted field %s in struct literal of type %s", fieldPath(utyp, index), base)
continue
}
}
check.recordUse(key, fld)
etyp := fld.typ
check.assignment(x, etyp, "struct literal")
// 0 <= i < len(fields)
if visited[i] {
check.errorf(kv, DuplicateLitField, "duplicate field name %s in struct literal", key.Value)
continue
if alt, n := visited.insert(index, fld); n != 0 {
if fld == alt {
check.errorf(kv, DuplicateLitField, "duplicate field name %s in struct literal", fld.name)
} else if n < len(index) {
check.errorf(kv, DuplicateLitField, "cannot specify promoted field %s and enclosing embedded field %s", fld.name, alt.name)
} else { // n > len(index)
check.errorf(kv, DuplicateLitField, "cannot specify embedded field %s and enclosed promoted field %s", fld.name, alt.name)
}
}
visited[i] = true
}
} else {
// no element must have a key

View File

@@ -6,7 +6,10 @@
package types2
import "bytes"
import (
"bytes"
"strings"
)
// LookupSelection selects the field or method whose ID is Id(pkg,
// name), on a value of type T. If addressable is set, T is the type
@@ -645,19 +648,6 @@ func concat(list []int, i int) []int {
return append(t, i)
}
// fieldIndex returns the index for the field with matching package and name, or a value < 0.
// See Object.sameId for the meaning of foldCase.
func fieldIndex(fields []*Var, pkg *Package, name string, foldCase bool) int {
if name != "_" {
for i, f := range fields {
if f.sameId(pkg, name, foldCase) {
return i
}
}
}
return -1
}
// methodIndex returns the index of and method with matching package and name, or (-1, nil).
// See Object.sameId for the meaning of foldCase.
func methodIndex(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
@@ -670,3 +660,22 @@ func methodIndex(methods []*Func, pkg *Package, name string, foldCase bool) (int
}
return -1, nil
}
// Given a (possibly pointer to a) struct type and field index sequence,
// fieldPath returns the dot-separated concatenated field names for the
// given index sequence (e.g. "a.b.c").
// Use for error reporting etc. where speed is not important.
func fieldPath(typ Type, index []int) string {
var names []string
for _, i := range index {
u, ok := derefStructPtr(typ).Underlying().(*Struct)
if !ok {
// should not happen if index is valid for typ
break
}
fld := u.Field(i)
names = append(names, fld.name)
typ = fld.typ
}
return strings.Join(names, ".")
}

View File

@@ -164,7 +164,7 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) {
fields := utyp.fields
if _, ok := e.Elts[0].(*ast.KeyValueExpr); ok {
// all elements must have keys
visited := make([]bool, len(fields))
visited := make(trie[*Var])
for _, e := range e.Elts {
kv, _ := e.(*ast.KeyValueExpr)
if kv == nil {
@@ -179,26 +179,41 @@ func (check *Checker) compositeLit(x *operand, e *ast.CompositeLit, hint Type) {
check.errorf(kv, InvalidLitField, "invalid field name %s in struct literal", kv.Key)
continue
}
i := fieldIndex(fields, check.pkg, key.Name, false)
if i < 0 {
var alt Object
if j := fieldIndex(fields, check.pkg, key.Name, true); j >= 0 {
alt = fields[j]
}
obj, index, indirect := lookupFieldOrMethod(utyp, false, check.pkg, key.Name, false)
if obj == nil {
alt, _, _ := lookupFieldOrMethod(utyp, false, check.pkg, key.Name, true)
msg := check.lookupError(base, key.Name, alt, true)
check.error(kv.Key, MissingLitField, msg)
continue
}
fld := fields[i]
fld, _ := obj.(*Var)
if fld == nil {
check.errorf(kv.Key, MissingLitField, "%s is not a field", kv.Key)
continue
}
if check.allowVersion(go1_27) {
if indirect {
check.errorf(kv.Key, InvalidLitField, "invalid implicit pointer indirection to reach %s", kv.Key)
continue
}
} else {
if len(index) > 1 {
check.errorf(kv.Key, InvalidLitField, "cannot use promoted field %s in struct literal of type %s", fieldPath(utyp, index), base)
continue
}
}
check.recordUse(key, fld)
etyp := fld.typ
check.assignment(x, etyp, "struct literal")
// 0 <= i < len(fields)
if visited[i] {
check.errorf(kv, DuplicateLitField, "duplicate field name %s in struct literal", key.Name)
continue
if alt, n := visited.insert(index, fld); n != 0 {
if fld == alt {
check.errorf(kv, DuplicateLitField, "duplicate field name %s in struct literal", fld.name)
} else if n < len(index) {
check.errorf(kv, DuplicateLitField, "cannot specify promoted field %s and enclosing embedded field %s", fld.name, alt.name)
} else { // n > len(index)
check.errorf(kv, DuplicateLitField, "cannot specify embedded field %s and enclosed promoted field %s", fld.name, alt.name)
}
}
visited[i] = true
}
} else {
// no element must have a key

View File

@@ -9,7 +9,10 @@
package types
import "bytes"
import (
"bytes"
"strings"
)
// LookupSelection selects the field or method whose ID is Id(pkg,
// name), on a value of type T. If addressable is set, T is the type
@@ -648,19 +651,6 @@ func concat(list []int, i int) []int {
return append(t, i)
}
// fieldIndex returns the index for the field with matching package and name, or a value < 0.
// See Object.sameId for the meaning of foldCase.
func fieldIndex(fields []*Var, pkg *Package, name string, foldCase bool) int {
if name != "_" {
for i, f := range fields {
if f.sameId(pkg, name, foldCase) {
return i
}
}
}
return -1
}
// methodIndex returns the index of and method with matching package and name, or (-1, nil).
// See Object.sameId for the meaning of foldCase.
func methodIndex(methods []*Func, pkg *Package, name string, foldCase bool) (int, *Func) {
@@ -673,3 +663,22 @@ func methodIndex(methods []*Func, pkg *Package, name string, foldCase bool) (int
}
return -1, nil
}
// Given a (possibly pointer to a) struct type and field index sequence,
// fieldPath returns the dot-separated concatenated field names for the
// given index sequence (e.g. "a.b.c").
// Use for error reporting etc. where speed is not important.
func fieldPath(typ Type, index []int) string {
var names []string
for _, i := range index {
u, ok := derefStructPtr(typ).Underlying().(*Struct)
if !ok {
// should not happen if index is valid for typ
break
}
fld := u.Field(i)
names = append(names, fld.name)
typ = fld.typ
}
return strings.Join(names, ".")
}

View File

@@ -187,7 +187,7 @@ func struct_literals() {
_ = T1{1 /* ERROR "invalid field name" */ : 0}
_ = T1{a: 0, s: "foo", u: 0, a /* ERROR "duplicate field" */: 10}
_ = T1{a: "foo" /* ERRORx `cannot use .* in struct literal` */ }
_ = T1{c /* ERROR "unknown field" */ : 0}
_ = T1{c: 0} // not an error anymore since Go 1.27
_ = T1{T0: { /* ERROR "missing type" */ }} // struct literal element type may not be elided
_ = T1{T0: T0{}}
_ = T1{T0 /* ERROR "invalid field name" */ .a: 0}

View File

@@ -0,0 +1,39 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package p
type A struct {
a int
B
C
*D
}
type B struct {
b int
C
}
type C struct {
c int
D
}
type D struct {
x, y, z int
}
var (
_ = &A{}
_ = &A{a: 0}
_ = A{A /* ERROR "unknown field A in struct literal of type A, but does have a" */ : 0}
_ = A{X /* ERROR "unknown field X in struct literal of type A, but does have x" */ : 0}
_ = A{a: 0, b: 0, c: 0}
_ = A{x /* ERROR "pointer indirection" */ : 0}
_ = B{b: 0, c: 0, x: 0, y: 0, z: 0}
_ = B{C: C{}, x /* ERROR "cannot specify promoted field x and enclosing embedded field C" */ : 0}
_ = A{b: 0, B /* ERROR "cannot specify embedded field B and enclosed promoted field b" */ : B{}}
_ = A{B: B{}, b /* ERROR "cannot specify promoted field b and enclosing embedded field B" */ : 0}
)

View File

@@ -1,4 +1,4 @@
// errorcheck
// errorcheck -lang=go1.26
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style

View File

@@ -1,4 +1,4 @@
// errorcheck
// errorcheck -lang=go1.26
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style