Skip to content

Commit dd44895

Browse files
committed
go/types: correctly compute method set of some recursive interfaces
R=go1.11. The existing algorithm for type-checking interfaces breaks down in complex cases of recursive types, e.g.: package issue21804 type ( _ interface{ m(B) } A interface{ a(D) } B interface{ A } C interface{ B } D interface{ C } ) var _ A = C(nil) The underlying problem is that the method set of C is computed by following a chain of embedded interfaces at a point when the method set for one of those interfaces is not yet complete. A more general problem is largely avoided by topological sorting of interfaces depending on their dependencies on embedded interfaces (but not method parameters). This change fixes this problem by fundamentally changing how interface method sets are computed: Instead of determining them based on recursively type-checking embedded interfaces, the new approach computes the method sets of interfaces separately, based on syntactic structure and name resolution; and using type- checked types only when readily available (e.g., for local types which can at best refer to themselves, and imported interfaces). This approach avoids cyclic dependencies arising in the method sets by separating the collection of embedded methods (which fundamentally cannot have cycles in correct programs) and type- checking of the method's signatures (which may have arbitrary cycles). As a consequence, type-checking interfaces can rely on the pre-computed method sets which makes the code simpler: Type- checking of embedded interface types doesn't have to happen anymore during interface construction since we already have all methods and now is delayed to the end of type-checking. Also, the topological sort of global interfaces is not needed anymore. Fixes #18395. Change-Id: I0f849ac9568e87a32c9c27bbf8fab0e2bac9ebb1 Reviewed-on: https://go-review.googlesource.com/79575 Reviewed-by: Alan Donovan <[email protected]>
1 parent 4c4ce3d commit dd44895

File tree

11 files changed

+723
-264
lines changed

11 files changed

+723
-264
lines changed

src/go/types/check.go

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,12 @@ type Checker struct {
8585
files []*ast.File // package files
8686
unusedDotImports map[*Scope]map[*Package]token.Pos // positions of unused dot-imported packages for each file scope
8787

88-
firstErr error // first error encountered
89-
methods map[string][]*Func // maps type names to associated methods
90-
untyped map[ast.Expr]exprInfo // map of expressions without final type
91-
funcs []funcInfo // list of functions to type-check
92-
delayed []func() // delayed checks requiring fully setup types
88+
firstErr error // first error encountered
89+
methods map[string][]*Func // maps package scope type names to associated non-blank, non-interface methods
90+
interfaces map[*TypeName]*ifaceInfo // maps interface type names to corresponding interface infos
91+
untyped map[ast.Expr]exprInfo // map of expressions without final type
92+
funcs []funcInfo // list of functions to type-check
93+
delayed []func() // delayed checks requiring fully setup types
9394

9495
// context within which the current object is type-checked
9596
// (valid only for the duration of type-checking a specific object)
@@ -186,6 +187,7 @@ func (check *Checker) initFiles(files []*ast.File) {
186187

187188
check.firstErr = nil
188189
check.methods = nil
190+
check.interfaces = nil
189191
check.untyped = nil
190192
check.funcs = nil
191193
check.delayed = nil
@@ -236,19 +238,21 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
236238

237239
check.collectObjects()
238240

239-
check.packageObjects(check.resolveOrder())
241+
check.packageObjects()
240242

241243
check.functionBodies()
242244

243245
check.initOrder()
244246

245-
if !check.conf.DisableUnusedImportCheck {
246-
check.unusedImports()
247+
// perform delayed checks
248+
// (cannot use range - delayed checks may add more delayed checks;
249+
// e.g., when type-checking delayed embedded interfaces)
250+
for i := 0; i < len(check.delayed); i++ {
251+
check.delayed[i]()
247252
}
248253

249-
// perform delayed checks
250-
for _, f := range check.delayed {
251-
f()
254+
if !check.conf.DisableUnusedImportCheck {
255+
check.unusedImports()
252256
}
253257

254258
check.recordUntyped()

src/go/types/check_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ var tests = [][]string{
6161
{"testdata/cycles2.src"},
6262
{"testdata/cycles3.src"},
6363
{"testdata/cycles4.src"},
64+
{"testdata/cycles5.src"},
6465
{"testdata/init0.src"},
6566
{"testdata/init1.src"},
6667
{"testdata/init2.src"},

src/go/types/decl.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@ func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object, pos token
3737
}
3838
}
3939

40+
// pathString returns a string of the form a->b-> ... ->g for a path [a, b, ... g].
41+
func pathString(path []*TypeName) string {
42+
var s string
43+
for i, p := range path {
44+
if i > 0 {
45+
s += "->"
46+
}
47+
s += p.Name()
48+
}
49+
return s
50+
}
51+
4052
// objDecl type-checks the declaration of obj in its respective (file) context.
4153
// See check.typ for the details on def and path.
4254
func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
@@ -45,7 +57,7 @@ func (check *Checker) objDecl(obj Object, def *Named, path []*TypeName) {
4557
}
4658

4759
if trace {
48-
check.trace(obj.Pos(), "-- declaring %s", obj.Name())
60+
check.trace(obj.Pos(), "-- declaring %s (path = %s)", obj.Name(), pathString(path))
4961
check.indent++
5062
defer func() {
5163
check.indent--

0 commit comments

Comments
 (0)