Skip to content

Commit ee402e9

Browse files
griesemergopherbot
authored andcommitted
go/types, types2: use exact unification for component types
This change defines two unification modes used to control unification: - assign set when unifying types involved in an assignment - exact if set, types unify if they can be made identical Currently, unification is inexact: when a defined type is compared against a type literal, the underlying type of the defined type is considered. When channel types are compared, the channel direction is ignored. And when defined types are compared where one (or both) are interfaces, interface unification is used. By contrast, exact unification requires types to match exactly: if they can be unified, the types must be identical (with suitable type arguments). Exact unification is required when comparing component types. For instance, when unifying func(x P) with func(x Q), the two signatures unify only if P is identical to Q per Go's assignment rules. Until now we have ignored exact unification and made due with inexact unification everywhere, even for component types. In some cases this led to infinite recursions in the unifier, which we guarded against with a depth limit (and unification failure). Go's assignmemt rules allow inexact matching at the top-level but require exact matching for element types. This change passes 'assign' to the unifier when unifying parameter against argument types because those follow assignment rules. When comparing constraints, inexact unification is used as before. In 'assign' mode, when comparing element types, the unifyier is called recursively, this time with the 'exact' mode set, causing element types to be compared exactly. If unification succeeds for element types, they are identical (with suitable type arguments). This change fixes #60460. It also fixes a bug in the test for issue #60377. We also don't need to rely anymore on the recursion depth limit (a temporary fix) for #59740. Finally, because we use exact unification when comparing element types which are channels, errors caused by assignment failures (due to inexact inference which succeeded when it shouldn't have) now produce the correct inference error. Fixes #60460. For #60377. For #59740. Change-Id: Icb6a9b4dbd34294f99328a06d52135cb499cab85 Reviewed-on: https://go-review.googlesource.com/c/go/+/498895 Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Reviewed-by: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent f78483e commit ee402e9

File tree

7 files changed

+159
-33
lines changed

7 files changed

+159
-33
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
155155
// Function parameters are always typed. Arguments may be untyped.
156156
// Collect the indices of untyped arguments and handle them later.
157157
if isTyped(arg.typ) {
158-
if !u.unify(par.typ, arg.typ, 0) {
158+
if !u.unify(par.typ, arg.typ, assign) {
159159
errorf("type", par.typ, arg.typ, arg)
160160
return nil
161161
}
@@ -340,7 +340,7 @@ func (check *Checker) infer(pos syntax.Pos, tparams []*TypeParam, targs []Type,
340340
arg := args[i]
341341
typ := Default(arg.typ)
342342
assert(isTyped(typ))
343-
if !u.unify(tpar, typ, 0) {
343+
if !u.unify(tpar, typ, assign) {
344344
errorf("default type", tpar, typ, arg)
345345
return nil
346346
}

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

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const (
4747
// Whether to panic when unificationDepthLimit is reached.
4848
// If disabled, a recursion depth overflow results in a (quiet)
4949
// unification failure.
50-
panicAtUnificationDepthLimit = false // go.dev/issue/59740
50+
panicAtUnificationDepthLimit = true
5151

5252
// If enableCoreTypeUnification is set, unification will consider
5353
// the core types, if any, of non-local (unbound) type parameters.
@@ -109,8 +109,24 @@ func newUnifier(tparams []*TypeParam, targs []Type) *unifier {
109109
// unifyMode controls the behavior of the unifier.
110110
type unifyMode uint
111111

112+
const (
113+
// If assign is set, we are unifying types involved in an assignment:
114+
// they may match inexactly at the top, but element types must match
115+
// exactly.
116+
assign unifyMode = 1 << iota
117+
118+
// If exact is set, types unify if they are identical (or can be
119+
// made identical with suitable arguments for type parameters).
120+
// Otherwise, a named type and a type literal unify if their
121+
// underlying types unify, channel directions are ignored, and
122+
// if there is an interface, the other type must implement the
123+
// interface.
124+
exact
125+
)
126+
112127
// unify attempts to unify x and y and reports whether it succeeded.
113128
// As a side-effect, types may be inferred for type parameters.
129+
// The mode parameter controls how types are compared.
114130
func (u *unifier) unify(x, y Type, mode unifyMode) bool {
115131
return u.nify(x, y, mode, nil)
116132
}
@@ -284,11 +300,11 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
284300
}
285301

286302
// Unification will fail if we match a defined type against a type literal.
287-
// Per the (spec) assignment rules, assignments of values to variables with
303+
// If we are matching types in an assignment, at the top-level, types with
288304
// the same type structure are permitted as long as at least one of them
289305
// is not a defined type. To accommodate for that possibility, we continue
290306
// unification with the underlying type of a defined type if the other type
291-
// is a type literal.
307+
// is a type literal. This is controlled by the exact unification mode.
292308
// We also continue if the other type is a basic type because basic types
293309
// are valid underlying types and may appear as core types of type constraints.
294310
// If we exclude them, inferred defined types for type parameters may not
@@ -300,7 +316,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
300316
// we will fail at function instantiation or argument assignment time.
301317
//
302318
// If we have at least one defined type, there is one in y.
303-
if ny, _ := y.(*Named); ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
319+
if ny, _ := y.(*Named); mode&exact == 0 && ny != nil && isTypeLit(x) && !(enableInterfaceInference && IsInterface(x)) {
304320
if traceInference {
305321
u.tracef("%s ≡ under %s", x, ny)
306322
}
@@ -365,7 +381,11 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
365381
}
366382

367383
// Type elements (array, slice, etc. elements) use emode for unification.
384+
// Element types must match exactly if the types are used in an assignment.
368385
emode := mode
386+
if mode&assign != 0 {
387+
emode |= exact
388+
}
369389

370390
// If EnableInterfaceInference is set and both types are interfaces, one
371391
// interface must have a subset of the methods of the other and corresponding
@@ -613,9 +633,11 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
613633
}
614634

615635
case *Chan:
616-
// Two channel types unify if their value types unify.
636+
// Two channel types unify if their value types unify
637+
// and if they have the same direction.
638+
// The channel direction is ignored for inexact unification.
617639
if y, ok := y.(*Chan); ok {
618-
return u.nify(x.elem, y.elem, emode, p)
640+
return (mode&exact == 0 || x.dir == y.dir) && u.nify(x.elem, y.elem, emode, p)
619641
}
620642

621643
case *Named:
@@ -625,7 +647,7 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
625647
// If one or both named types are interfaces, the types unify if the
626648
// respective methods unify (per the rules for interface unification).
627649
if y, ok := y.(*Named); ok {
628-
if enableInterfaceInference {
650+
if enableInterfaceInference && mode&exact == 0 {
629651
xi, _ := x.under().(*Interface)
630652
yi, _ := y.under().(*Interface)
631653
// If one or both of x and y are interfaces, use interface unification.

src/go/types/infer.go

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/go/types/unify.go

Lines changed: 29 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/internal/types/testdata/examples/functions.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -150,15 +150,15 @@ func _() {
150150
var send func(chan<- int)
151151

152152
ffboth(both)
153-
ffboth(recv /* ERROR "cannot use" */ )
154-
ffboth(send /* ERROR "cannot use" */ )
153+
ffboth(recv /* ERROR "does not match" */ )
154+
ffboth(send /* ERROR "does not match" */ )
155155

156-
ffrecv(both /* ERROR "cannot use" */ )
156+
ffrecv(both /* ERROR "does not match" */ )
157157
ffrecv(recv)
158-
ffrecv(send /* ERROR "cannot use" */ )
158+
ffrecv(send /* ERROR "does not match" */ )
159159

160-
ffsend(both /* ERROR "cannot use" */ )
161-
ffsend(recv /* ERROR "cannot use" */ )
160+
ffsend(both /* ERROR "does not match" */ )
161+
ffsend(recv /* ERROR "does not match" */ )
162162
ffsend(send)
163163
}
164164

src/internal/types/testdata/fixedbugs/issue60377.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,20 +61,14 @@ func _() {
6161
}
6262

6363
// This is similar to the first example but here T1 is a component
64-
// of a func type. In this case we should be able to infer a type
65-
// argument for P because component types must be identical even
66-
// in the case of interfaces.
67-
// This is a short-coming of type inference at the moment, but it
68-
// is better to not be able to infer a type here (we can always
69-
// supply one), than to infer the wrong type in other cases (see
70-
// below). Finally, if we decide to accept go.dev/issues/8082,
71-
// the behavior here is correct.
64+
// of a func type. In this case types must match exactly: P must
65+
// match int.
7266

7367
func g5[P any](func(T1[P])) {}
7468

7569
func _() {
7670
var f func(T1[int])
77-
g5 /* ERROR "cannot infer P" */ (f)
71+
g5(f)
7872
g5[int](f)
7973
g5[string](f /* ERROR "cannot use f (variable of type func(T1[int])) as func(T1[string]) value in argument to g5[string]" */)
8074
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
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+
// Simplified (representative) test case.
8+
9+
func _() {
10+
f(R1{})
11+
}
12+
13+
func f[T any](R[T]) {}
14+
15+
type R[T any] interface {
16+
m(R[T])
17+
}
18+
19+
type R1 struct{}
20+
21+
func (R1) m(R[int]) {}
22+
23+
// Test case from issue.
24+
25+
func _() {
26+
r := newTestRules()
27+
NewSet(r)
28+
r2 := newTestRules2()
29+
NewSet(r2)
30+
}
31+
32+
type Set[T any] struct {
33+
rules Rules[T]
34+
}
35+
36+
func NewSet[T any](rules Rules[T]) Set[T] {
37+
return Set[T]{
38+
rules: rules,
39+
}
40+
}
41+
42+
func (s Set[T]) Copy() Set[T] {
43+
return NewSet(s.rules)
44+
}
45+
46+
type Rules[T any] interface {
47+
Hash(T) int
48+
Equivalent(T, T) bool
49+
SameRules(Rules[T]) bool
50+
}
51+
52+
type testRules struct{}
53+
54+
func newTestRules() Rules[int] {
55+
return testRules{}
56+
}
57+
58+
func (r testRules) Hash(val int) int {
59+
return val % 16
60+
}
61+
62+
func (r testRules) Equivalent(val1 int, val2 int) bool {
63+
return val1 == val2
64+
}
65+
66+
func (r testRules) SameRules(other Rules[int]) bool {
67+
_, ok := other.(testRules)
68+
return ok
69+
}
70+
71+
type testRules2 struct{}
72+
73+
func newTestRules2() Rules[string] {
74+
return testRules2{}
75+
}
76+
77+
func (r testRules2) Hash(val string) int {
78+
return 16
79+
}
80+
81+
func (r testRules2) Equivalent(val1 string, val2 string) bool {
82+
return val1 == val2
83+
}
84+
85+
func (r testRules2) SameRules(other Rules[string]) bool {
86+
_, ok := other.(testRules2)
87+
return ok
88+
}

0 commit comments

Comments
 (0)