Skip to content

Commit 1f8f2ab

Browse files
griesemergopherbot
authored andcommitted
go/types, types2: remove order dependency in inference involving channels
In inexact unification, when a named type matches against an inferred unnamed type, we change the previously inferred type to the named type. This preserves the type name and assignability. We have to do the same thing when encountering a directional channel: a bidirectional channel can always be assigned to a directional channel but not the other way around. Thus, if we see a directional channel, we must choose the directional channel. This CL extends the previously existing logic for named types to directional channels and also makes the code conditional on inexact unification. The latter is an optimization - if unification is exact, type differences don't exist and updating an already inferred type has no effect. Fixes #62157. Change-Id: I807e3b9f9ab363f9ed848bdb18b2577b1d680ea7 Reviewed-on: https://go-review.googlesource.com/c/go/+/521500 Reviewed-by: Robert Griesemer <[email protected]> Run-TryBot: Robert Griesemer <[email protected]> Reviewed-by: Robert Findley <[email protected]> Auto-Submit: Robert Griesemer <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 22eba6a commit 1f8f2ab

File tree

3 files changed

+194
-22
lines changed

3 files changed

+194
-22
lines changed

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

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -401,18 +401,40 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
401401
// Therefore, we must fail unification (go.dev/issue/60933).
402402
return false
403403
}
404-
// If y is a defined type, make sure we record that type
405-
// for type parameter x, which may have until now only
406-
// recorded an underlying type (go.dev/issue/43056).
407-
// Either both types are interfaces, or neither type is.
408-
// If both are interfaces, they have the same methods.
404+
// If we have inexact unification and one of x or y is a defined type, select the
405+
// defined type. This ensures that in a series of types, all matching against the
406+
// same type parameter, we infer a defined type if there is one, independent of
407+
// order. Type inference or assignment may fail, which is ok.
408+
// Selecting a defined type, if any, ensures that we don't lose the type name;
409+
// and since we have inexact unification, a value of equally named or matching
410+
// undefined type remains assignable (go.dev/issue/43056).
409411
//
410-
// Note: Changing the recorded type for a type parameter to
411-
// a defined type is only ok when unification is inexact.
412-
// But in exact unification, if we have a match, x and y must
413-
// be identical, so changing the recorded type for x is a no-op.
414-
if yn {
415-
u.set(px, y)
412+
// Similarly, if we have inexact unification and there are no defined types but
413+
// channel types, select a directed channel, if any. This ensures that in a series
414+
// of unnamed types, all matching against the same type parameter, we infer the
415+
// directed channel if there is one, independent of order.
416+
// Selecting a directional channel, if any, ensures that a value of another
417+
// inexactly unifying channel type remains assignable (go.dev/issue/62157).
418+
//
419+
// If we have multiple defined channel types, they are either identical or we
420+
// have assignment conflicts, so we can ignore directionality in this case.
421+
//
422+
// If we have defined and literal channel types, a defined type wins to avoid
423+
// order dependencies.
424+
if mode&exact == 0 {
425+
switch {
426+
case xn:
427+
// x is a defined type: nothing to do.
428+
case yn:
429+
// x is not a defined type and y is a defined type: select y.
430+
u.set(px, y)
431+
default:
432+
// Neither x nor y are defined types.
433+
if yc, _ := under(y).(*Chan); yc != nil && yc.dir != SendRecv {
434+
// y is a directed channel type: select y.
435+
u.set(px, y)
436+
}
437+
}
416438
}
417439
return true
418440
}

src/go/types/unify.go

Lines changed: 33 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
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+
func f[T any](...T) T { var x T; return x }
8+
9+
// Test case 1
10+
11+
func _() {
12+
var a chan string
13+
var b <-chan string
14+
f(a, b)
15+
f(b, a)
16+
}
17+
18+
// Test case 2
19+
20+
type F[T any] func(T) bool
21+
22+
func g[T any](T) F[<-chan T] { return nil }
23+
24+
func f1[T any](T, F[T]) {}
25+
func f2[T any](F[T], T) {}
26+
27+
func _() {
28+
var ch chan string
29+
f1(ch, g(""))
30+
f2(g(""), ch)
31+
}
32+
33+
// Test case 3: named and directional types combined
34+
35+
func _() {
36+
type namedA chan int
37+
type namedB chan<- int
38+
39+
var a chan int
40+
var A namedA
41+
var b chan<- int
42+
var B namedB
43+
44+
// Defined types win over channel types irrespective of channel direction.
45+
f(A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
46+
f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A)
47+
48+
f(a, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A)
49+
f(a, A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
50+
f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A, a)
51+
f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, a, A)
52+
f(A, a, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
53+
f(A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, a)
54+
55+
// Unnamed directed channels win over bidirectional channels.
56+
b = f(a, b)
57+
b = f(b, a)
58+
59+
// Defined directed channels win over defined bidirectional channels.
60+
A = f(A, a)
61+
A = f(a, A)
62+
B = f(B, b)
63+
B = f(b, B)
64+
65+
f(a, b, B)
66+
f(a, B, b)
67+
f(b, B, a)
68+
f(b, a, B)
69+
f(B, a, b)
70+
f(B, b, a)
71+
72+
// Differently named channel types conflict irrespective of channel direction.
73+
f(A, B /* ERROR "type namedB of B does not match inferred type namedA for T" */)
74+
f(B, A /* ERROR "type namedA of A does not match inferred type namedB for T" */)
75+
76+
// Ensure that all combinations of directional and
77+
// bidirectional channels with a named directional
78+
// channel lead to the correct (named) directional
79+
// channel.
80+
B = f(a, b)
81+
B = f(a, B)
82+
B = f(b, a)
83+
B = f(B, a)
84+
85+
B = f(a, b, B)
86+
B = f(a, B, b)
87+
B = f(b, B, a)
88+
B = f(b, a, B)
89+
B = f(B, a, b)
90+
B = f(B, b, a)
91+
92+
// verify type error
93+
A = f /* ERROR "cannot use f(B, b, a) (value of type namedB) as namedA value in assignment" */ (B, b, a)
94+
}
95+
96+
// Test case 4: some more combinations
97+
98+
func _() {
99+
type A chan int
100+
type B chan int
101+
type C = chan int
102+
type D = chan<- int
103+
104+
var a A
105+
var b B
106+
var c C
107+
var d D
108+
109+
f(a, b /* ERROR "type B of b does not match inferred type A for T" */, c)
110+
f(c, a, b /* ERROR "type B of b does not match inferred type A for T" */)
111+
f(a, b /* ERROR "type B of b does not match inferred type A for T" */, d)
112+
f(d, a, b /* ERROR "type B of b does not match inferred type A for T" */)
113+
}
114+
115+
// Simplified test case from issue
116+
117+
type Matcher[T any] func(T) bool
118+
119+
func Produces[T any](T) Matcher[<-chan T] { return nil }
120+
121+
func Assert1[T any](Matcher[T], T) {}
122+
func Assert2[T any](T, Matcher[T]) {}
123+
124+
func _() {
125+
var ch chan string
126+
Assert1(Produces(""), ch)
127+
Assert2(ch, Produces(""))
128+
}

0 commit comments

Comments
 (0)