Skip to content

Commit 20f0bcb

Browse files
committed
go/types: don't clone interface methods when embedding them
https://golang.org/cl/191257 significantly changed (and simplified) the computation of interface method sets with embedded interfaces. Specifically, when adding methods from an embedded interface, those method objects (Func Objects) were cloned so that they could have a different source position (the embedding position rather than the original method position) for better error messages. This causes problems for code that depends on the identity of method objects that represent the same method, embedded or not. This CL avoids the cloning. Instead, while computing the method set of an interface, a position map is carried along that tracks embedding positions. The map is not needed anymore after type- checking. Updates #34421. Change-Id: I8ce188136c76fa70fba686711167db29a049f46d Reviewed-on: https://go-review.googlesource.com/c/go/+/196561 Reviewed-by: Matthew Dempsky <[email protected]>
1 parent 78e5288 commit 20f0bcb

File tree

2 files changed

+66
-11
lines changed

2 files changed

+66
-11
lines changed

src/go/types/object_test.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@
44

55
package types
66

7-
import "testing"
7+
import (
8+
"go/ast"
9+
"go/parser"
10+
"go/token"
11+
"testing"
12+
)
813

914
func TestIsAlias(t *testing.T) {
1015
check := func(obj *TypeName, want bool) {
@@ -42,3 +47,40 @@ func TestIsAlias(t *testing.T) {
4247
check(test.name, test.alias)
4348
}
4449
}
50+
51+
// TestEmbeddedMethod checks that an embedded method is represented by
52+
// the same Func Object as the original method. See also issue #34421.
53+
func TestEmbeddedMethod(t *testing.T) {
54+
const src = `package p; type I interface { error }`
55+
56+
// type-check src
57+
fset := token.NewFileSet()
58+
f, err := parser.ParseFile(fset, "", src, 0)
59+
if err != nil {
60+
t.Fatalf("parse failed: %s", err)
61+
}
62+
var conf Config
63+
pkg, err := conf.Check(f.Name.Name, fset, []*ast.File{f}, nil)
64+
if err != nil {
65+
t.Fatalf("typecheck failed: %s", err)
66+
}
67+
68+
// get original error.Error method
69+
eface := Universe.Lookup("error")
70+
orig, _, _ := LookupFieldOrMethod(eface.Type(), false, nil, "Error")
71+
if orig == nil {
72+
t.Fatalf("original error.Error not found")
73+
}
74+
75+
// get embedded error.Error method
76+
iface := pkg.Scope().Lookup("I")
77+
embed, _, _ := LookupFieldOrMethod(iface.Type(), false, nil, "Error")
78+
if embed == nil {
79+
t.Fatalf("embedded error.Error not found")
80+
}
81+
82+
// original and embedded Error object should be identical
83+
if orig != embed {
84+
t.Fatalf("%s (%p) != %s (%p)", orig, orig, embed, embed)
85+
}
86+
}

src/go/types/typexpr.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -557,28 +557,43 @@ func (check *Checker) completeInterface(ityp *Interface) {
557557

558558
ityp.allMethods = markComplete // avoid infinite recursion
559559

560-
var methods []*Func
560+
// Methods of embedded interfaces are collected unchanged; i.e., the identity
561+
// of a method I.m's Func Object of an interface I is the same as that of
562+
// the method m in an interface that embeds interface I. On the other hand,
563+
// if a method is embedded via multiple overlapping embedded interfaces, we
564+
// don't provide a guarantee which "original m" got chosen for the embedding
565+
// interface. See also issue #34421.
566+
//
567+
// If we don't care to provide this identity guarantee anymore, instead of
568+
// reusing the original method in embeddings, we can clone the method's Func
569+
// Object and give it the position of a corresponding embedded interface. Then
570+
// we can get rid of the mpos map below and simply use the cloned method's
571+
// position.
572+
561573
var seen objset
562-
addMethod := func(m *Func, explicit bool) {
574+
var methods []*Func
575+
mpos := make(map[*Func]token.Pos) // method specification or method embedding position, for good error messages
576+
addMethod := func(pos token.Pos, m *Func, explicit bool) {
563577
switch other := seen.insert(m); {
564578
case other == nil:
565579
methods = append(methods, m)
580+
mpos[m] = pos
566581
case explicit:
567-
check.errorf(m.pos, "duplicate method %s", m.name)
568-
check.reportAltDecl(other)
582+
check.errorf(pos, "duplicate method %s", m.name)
583+
check.errorf(mpos[other.(*Func)], "\tother declaration of %s", m.name) // secondary error, \t indented
569584
default:
570585
// check method signatures after all types are computed (issue #33656)
571586
check.atEnd(func() {
572587
if !check.identical(m.typ, other.Type()) {
573-
check.errorf(m.pos, "duplicate method %s", m.name)
574-
check.reportAltDecl(other)
588+
check.errorf(pos, "duplicate method %s", m.name)
589+
check.errorf(mpos[other.(*Func)], "\tother declaration of %s", m.name) // secondary error, \t indented
575590
}
576591
})
577592
}
578593
}
579594

580595
for _, m := range ityp.methods {
581-
addMethod(m, true)
596+
addMethod(m.pos, m, true)
582597
}
583598

584599
posList := check.posMap[ityp]
@@ -587,9 +602,7 @@ func (check *Checker) completeInterface(ityp *Interface) {
587602
typ := underlying(typ).(*Interface)
588603
check.completeInterface(typ)
589604
for _, m := range typ.allMethods {
590-
copy := *m
591-
copy.pos = pos // preserve embedding position
592-
addMethod(&copy, false)
605+
addMethod(pos, m, false) // use embedding position pos rather than m.pos
593606
}
594607
}
595608

0 commit comments

Comments
 (0)