mirror of
https://github.com/golang/go.git
synced 2026-04-02 17:30:01 +09:00
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:
@@ -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
|
||||
|
||||
@@ -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, ".")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, ".")
|
||||
}
|
||||
|
||||
2
src/internal/types/testdata/check/expr3.go
vendored
2
src/internal/types/testdata/check/expr3.go
vendored
@@ -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}
|
||||
|
||||
39
src/internal/types/testdata/spec/structLits.go
vendored
Normal file
39
src/internal/types/testdata/spec/structLits.go
vendored
Normal 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}
|
||||
)
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user