Skip to content

Commit b75e492

Browse files
committed
go/types,types2: delay the check for conflicting struct field names
In #52529, we observed that checking types for duplicate fields and methods during method collection can result in incorrect early expansion of the base type. Fix this by delaying the check for duplicate fields. Notably, we can't delay the check for duplicate methods as we must preserve the invariant that added method names are unique. After this change, it may be possible in the presence of errors to have a type-checked type containing a method name that conflicts with a field name. With the previous logic conflicting methods would have been skipped. This is a change in behavior, but only for invalid code. Preserving the existing behavior would likely require delaying method collection, which could have more significant consequences. As a result of this change, the compiler test fixedbugs/issue28268.go started passing with types2, being previously marked as broken. The fix was not actually related to the duplicate method error, but rather the fact that we stopped reporting redundant errors on the calls to x.b() and x.E(), because they are now (valid!) methods. Fixes #52529 Change-Id: I850ce85c6ba76d79544f46bfd3deb8538d8c7d00 Reviewed-on: https://go-review.googlesource.com/c/go/+/403455 Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent a41e37f commit b75e492

File tree

5 files changed

+107
-38
lines changed

5 files changed

+107
-38
lines changed

src/cmd/compile/internal/types2/decl.go

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -636,14 +636,12 @@ func (check *Checker) collectMethods(obj *TypeName) {
636636
base, _ := obj.typ.(*Named) // shouldn't fail but be conservative
637637
if base != nil {
638638
assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type
639-
u := base.under()
640-
if t, _ := u.(*Struct); t != nil {
641-
for _, fld := range t.fields {
642-
if fld.name != "_" {
643-
assert(mset.insert(fld) == nil)
644-
}
645-
}
646-
}
639+
640+
// See issue #52529: we must delay the expansion of underlying here, as
641+
// base may not be fully set-up.
642+
check.later(func() {
643+
check.checkFieldUniqueness(base)
644+
}).describef(obj, "verifying field uniqueness for %v", base)
647645

648646
// Checker.Files may be called multiple times; additional package files
649647
// may add methods to already type-checked types. Add pre-existing methods
@@ -662,17 +660,10 @@ func (check *Checker) collectMethods(obj *TypeName) {
662660
assert(m.name != "_")
663661
if alt := mset.insert(m); alt != nil {
664662
var err error_
665-
switch alt.(type) {
666-
case *Var:
667-
err.errorf(m.pos, "field and method with the same name %s", m.name)
668-
case *Func:
669-
if check.conf.CompilerErrorMessages {
670-
err.errorf(m.pos, "%s.%s redeclared in this block", obj.Name(), m.name)
671-
} else {
672-
err.errorf(m.pos, "method %s already declared for %s", m.name, obj)
673-
}
674-
default:
675-
unreachable()
663+
if check.conf.CompilerErrorMessages {
664+
err.errorf(m.pos, "%s.%s redeclared in this block", obj.Name(), m.name)
665+
} else {
666+
err.errorf(m.pos, "method %s already declared for %s", m.name, obj)
676667
}
677668
err.recordAltDecl(alt)
678669
check.report(&err)
@@ -686,6 +677,36 @@ func (check *Checker) collectMethods(obj *TypeName) {
686677
}
687678
}
688679

680+
func (check *Checker) checkFieldUniqueness(base *Named) {
681+
if t, _ := base.under().(*Struct); t != nil {
682+
var mset objset
683+
for i := 0; i < base.methods.Len(); i++ {
684+
m := base.methods.At(i, nil)
685+
assert(m.name != "_")
686+
assert(mset.insert(m) == nil)
687+
}
688+
689+
// Check that any non-blank field names of base are distinct from its
690+
// method names.
691+
for _, fld := range t.fields {
692+
if fld.name != "_" {
693+
if alt := mset.insert(fld); alt != nil {
694+
// Struct fields should already be unique, so we should only
695+
// encounter an alternate via collision with a method name.
696+
_ = alt.(*Func)
697+
698+
// For historical consistency, we report the primary error on the
699+
// method, and the alt decl on the field.
700+
var err error_
701+
err.errorf(alt, "field and method with the same name %s", fld.name)
702+
err.recordAltDecl(fld)
703+
check.report(&err)
704+
}
705+
}
706+
}
707+
}
708+
}
709+
689710
func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
690711
assert(obj.typ == nil)
691712

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
type Foo[P any] struct {
8+
_ *Bar[P]
9+
}
10+
11+
type Bar[Q any] Foo[Q]
12+
13+
func (v *Bar[R]) M() {
14+
_ = (*Foo[R])(v)
15+
}

src/go/types/decl.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -712,14 +712,12 @@ func (check *Checker) collectMethods(obj *TypeName) {
712712
base, _ := obj.typ.(*Named) // shouldn't fail but be conservative
713713
if base != nil {
714714
assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type
715-
u := base.under()
716-
if t, _ := u.(*Struct); t != nil {
717-
for _, fld := range t.fields {
718-
if fld.name != "_" {
719-
assert(mset.insert(fld) == nil)
720-
}
721-
}
722-
}
715+
716+
// See issue #52529: we must delay the expansion of underlying here, as
717+
// base may not be fully set-up.
718+
check.later(func() {
719+
check.checkFieldUniqueness(base)
720+
}).describef(obj, "verifying field uniqueness for %v", base)
723721

724722
// Checker.Files may be called multiple times; additional package files
725723
// may add methods to already type-checked types. Add pre-existing methods
@@ -737,14 +735,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
737735
// to it must be unique."
738736
assert(m.name != "_")
739737
if alt := mset.insert(m); alt != nil {
740-
switch alt.(type) {
741-
case *Var:
742-
check.errorf(m, _DuplicateFieldAndMethod, "field and method with the same name %s", m.name)
743-
case *Func:
744-
check.errorf(m, _DuplicateMethod, "method %s already declared for %s", m.name, obj)
745-
default:
746-
unreachable()
747-
}
738+
check.errorf(m, _DuplicateMethod, "method %s already declared for %s", m.name, obj)
748739
check.reportAltDecl(alt)
749740
continue
750741
}
@@ -756,6 +747,34 @@ func (check *Checker) collectMethods(obj *TypeName) {
756747
}
757748
}
758749

750+
func (check *Checker) checkFieldUniqueness(base *Named) {
751+
if t, _ := base.under().(*Struct); t != nil {
752+
var mset objset
753+
for i := 0; i < base.methods.Len(); i++ {
754+
m := base.methods.At(i, nil)
755+
assert(m.name != "_")
756+
assert(mset.insert(m) == nil)
757+
}
758+
759+
// Check that any non-blank field names of base are distinct from its
760+
// method names.
761+
for _, fld := range t.fields {
762+
if fld.name != "_" {
763+
if alt := mset.insert(fld); alt != nil {
764+
// Struct fields should already be unique, so we should only
765+
// encounter an alternate via collision with a method name.
766+
_ = alt.(*Func)
767+
768+
// For historical consistency, we report the primary error on the
769+
// method, and the alt decl on the field.
770+
check.errorf(alt, _DuplicateFieldAndMethod, "field and method with the same name %s", fld.name)
771+
check.reportAltDecl(fld)
772+
}
773+
}
774+
}
775+
}
776+
}
777+
759778
func (check *Checker) funcDecl(obj *Func, decl *declInfo) {
760779
assert(obj.typ == nil)
761780

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package p
6+
7+
type Foo[P any] struct {
8+
_ *Bar[P]
9+
}
10+
11+
type Bar[Q any] Foo[Q]
12+
13+
func (v *Bar[R]) M() {
14+
_ = (*Foo[R])(v)
15+
}

test/run.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,7 +1950,6 @@ var types2Failures = setOf(
19501950
"fixedbugs/issue18419.go", // types2 reports no field or method member, but should say unexported
19511951
"fixedbugs/issue20233.go", // types2 reports two instead of one error (preference: 1.17 compiler)
19521952
"fixedbugs/issue20245.go", // types2 reports two instead of one error (preference: 1.17 compiler)
1953-
"fixedbugs/issue28268.go", // types2 reports follow-on errors (preference: 1.17 compiler)
19541953
"fixedbugs/issue31053.go", // types2 reports "unknown field" instead of "cannot refer to unexported field"
19551954
)
19561955

@@ -2022,11 +2021,11 @@ func setOf(keys ...string) map[string]bool {
20222021
//
20232022
// For example, the following string:
20242023
//
2025-
// a b:"c d" 'e''f' "g\""
2024+
// a b:"c d" 'e''f' "g\""
20262025
//
20272026
// Would be parsed as:
20282027
//
2029-
// []string{"a", "b:c d", "ef", `g"`}
2028+
// []string{"a", "b:c d", "ef", `g"`}
20302029
//
20312030
// [copied from src/go/build/build.go]
20322031
func splitQuoted(s string) (r []string, err error) {

0 commit comments

Comments
 (0)