Skip to content

Commit d49017a

Browse files
griesemergopherbot
authored andcommitted
go/types, types2: fix interface unification
When unification of two types succeeds and at least one of them is an interface, we must be more cautious about when to accept the unification, to avoid order dependencies and unexpected inference results. The changes are localized and only affect matching against interfaces; they further restrict what are valid unifications (rather than allowing more code to pass). We may be able to remove some of the restriotions in a future release. See comments in code for a detailed description of the changes. Also, factored out "asInterface" functionality into a function to avoid needless repetition in the code. Fixes #60933. Fixes #60946. Change-Id: I923f7a7c1a22e0f4fd29e441e016e7154429fc5e Reviewed-on: https://go-review.googlesource.com/c/go/+/505396 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Auto-Submit: Robert Griesemer <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent b3ca8d2 commit d49017a

File tree

4 files changed

+203
-22
lines changed

4 files changed

+203
-22
lines changed

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

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,15 @@ func (u *unifier) inferred(tparams []*TypeParam) []Type {
270270
return list
271271
}
272272

273+
// asInterface returns the underlying type of x as an interface if
274+
// it is a non-type parameter interface. Otherwise it returns nil.
275+
func asInterface(x Type) (i *Interface) {
276+
if _, ok := x.(*TypeParam); !ok {
277+
i, _ = under(x).(*Interface)
278+
}
279+
return i
280+
}
281+
273282
// nify implements the core unification algorithm which is an
274283
// adapted version of Checker.identical. For changes to that
275284
// code the corresponding changes should be made here.
@@ -364,11 +373,46 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
364373
if x := u.at(px); x != nil {
365374
// x has an inferred type which must match y
366375
if u.nify(x, y, mode, p) {
367-
// If we have a match, possibly through underlying types,
368-
// and y is a defined type, make sure we record that type
376+
// We have a match, possibly through underlying types.
377+
xi := asInterface(x)
378+
yi := asInterface(y)
379+
_, xn := x.(*Named)
380+
_, yn := y.(*Named)
381+
// If we have two interfaces, what to do depends on
382+
// whether they are named and their method sets.
383+
if xi != nil && yi != nil {
384+
// Both types are interfaces.
385+
// If both types are defined types, they must be identical
386+
// because unification doesn't know which type has the "right" name.
387+
if xn && yn {
388+
return Identical(x, y)
389+
}
390+
// In all other cases, the method sets must match.
391+
// The types unified so we know that corresponding methods
392+
// match and we can simply compare the number of methods.
393+
// TODO(gri) We may be able to relax this rule and select
394+
// the more general interface. But if one of them is a defined
395+
// type, it's not clear how to choose and whether we introduce
396+
// an order dependency or not. Requiring the same method set
397+
// is conservative.
398+
if len(xi.typeSet().methods) != len(yi.typeSet().methods) {
399+
return false
400+
}
401+
} else if xi != nil || yi != nil {
402+
// One but not both of them are interfaces.
403+
// In this case, either x or y could be viable matches for the corresponding
404+
// type parameter, which means choosing either introduces an order dependence.
405+
// Therefore, we must fail unification (go.dev/issue/60933).
406+
return false
407+
}
408+
// If y is a defined type, make sure we record that type
369409
// for type parameter x, which may have until now only
370410
// recorded an underlying type (go.dev/issue/43056).
371-
if _, ok := y.(*Named); ok {
411+
// Either both types are interfaces, or neither type is.
412+
// If both are interfaces, they have the same methods.
413+
// TODO(gri) We probably can do this only for inexact
414+
// unification. Need to find a failure case.
415+
if yn {
372416
u.set(px, y)
373417
}
374418
return true
@@ -398,14 +442,8 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
398442
if enableInterfaceInference && mode&exact == 0 {
399443
// One or both interfaces may be defined types.
400444
// Look under the name, but not under type parameters (go.dev/issue/60564).
401-
var xi *Interface
402-
if _, ok := x.(*TypeParam); !ok {
403-
xi, _ = under(x).(*Interface)
404-
}
405-
var yi *Interface
406-
if _, ok := y.(*TypeParam); !ok {
407-
yi, _ = under(y).(*Interface)
408-
}
445+
xi := asInterface(x)
446+
yi := asInterface(y)
409447
// If we have two interfaces, check the type terms for equivalence,
410448
// and unify common methods if possible.
411449
if xi != nil && yi != nil {

src/go/types/unify.go

Lines changed: 49 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2023 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+
import (
8+
"io"
9+
"os"
10+
)
11+
12+
func g[T any](...T) {}
13+
14+
// Interface and non-interface types do not match.
15+
func _() {
16+
var file *os.File
17+
g(file, io /* ERROR "type io.Writer of io.Discard does not match inferred type *os.File for T" */ .Discard)
18+
g(file, os.Stdout)
19+
}
20+
21+
func _() {
22+
var a *os.File
23+
var b any
24+
g(a, a)
25+
g(a, b /* ERROR "type any of b does not match inferred type *os.File for T" */)
26+
}
27+
28+
var writer interface {
29+
Write(p []byte) (n int, err error)
30+
}
31+
32+
func _() {
33+
var file *os.File
34+
g(file, writer /* ERROR "type interface{Write(p []byte) (n int, err error)} of writer does not match inferred type *os.File for T" */)
35+
g(writer, file /* ERROR "type *os.File of file does not match inferred type interface{Write(p []byte) (n int, err error)} for T" */)
36+
}
37+
38+
// Different named interface types do not match.
39+
func _() {
40+
g(io.ReadWriter(nil), io.ReadWriter(nil))
41+
g(io.ReadWriter(nil), io /* ERROR "does not match" */ .Writer(nil))
42+
g(io.Writer(nil), io /* ERROR "does not match" */ .ReadWriter(nil))
43+
}
44+
45+
// Named and unnamed interface types match if they have the same methods.
46+
func _() {
47+
g(io.Writer(nil), writer)
48+
g(io.ReadWriter(nil), writer /* ERROR "does not match" */ )
49+
}
50+
51+
// There must be no order dependency for named and unnamed interfaces.
52+
func f[T interface{ m(T) }](a, b T) {}
53+
54+
type F interface {
55+
m(F)
56+
}
57+
58+
func _() {
59+
var i F
60+
var j interface {
61+
m(F)
62+
}
63+
64+
// order doesn't matter
65+
f(i, j)
66+
f(j, i)
67+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2023 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 Tn interface{ m() }
8+
type T1 struct{}
9+
type T2 struct{}
10+
11+
func (*T1) m() {}
12+
func (*T2) m() {}
13+
14+
func g[P any](...P) {}
15+
16+
func _() {
17+
var t interface{ m() }
18+
var tn Tn
19+
var t1 *T1
20+
var t2 *T2
21+
22+
// these are ok (interface types only)
23+
g(t, t)
24+
g(t, tn)
25+
g(tn, t)
26+
g(tn, tn)
27+
28+
// these are not ok (interface and non-interface types)
29+
g(t, t1 /* ERROR "does not match" */)
30+
g(t1, t /* ERROR "does not match" */)
31+
g(tn, t1 /* ERROR "does not match" */)
32+
g(t1, tn /* ERROR "does not match" */)
33+
34+
g(t, t1 /* ERROR "does not match" */, t2)
35+
g(t1, t2 /* ERROR "does not match" */, t)
36+
g(tn, t1 /* ERROR "does not match" */, t2)
37+
g(t1, t2 /* ERROR "does not match" */, tn)
38+
}

0 commit comments

Comments
 (0)