aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2023-08-21 15:50:18 -0700
committerGopher Robot <gobot@golang.org>2023-08-30 21:35:21 +0000
commitb120517ffd415a6a12835b916df7505001cfe583 (patch)
treeba6cc73673bac1f9220c572f706065ab66f9d913
parent0a9582163c3392ea0e51cadec9ee5fc5c46cfeee (diff)
downloadgo-b120517ffd415a6a12835b916df7505001cfe583.tar.gz
go-b120517ffd415a6a12835b916df7505001cfe583.zip
[release-branch.go1.21] 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 #62205. Change-Id: I807e3b9f9ab363f9ed848bdb18b2577b1d680ea7 Reviewed-on: https://go-review.googlesource.com/c/go/+/524256 Run-TryBot: Robert Griesemer <gri@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Auto-Submit: Dmitri Shuralyov <dmitshur@google.com> Reviewed-by: Robert Griesemer <gri@google.com> Reviewed-by: Robert Findley <rfindley@google.com>
-rw-r--r--src/cmd/compile/internal/types2/unify.go44
-rw-r--r--src/go/types/unify.go44
-rw-r--r--src/internal/types/testdata/fixedbugs/issue62157.go128
3 files changed, 194 insertions, 22 deletions
diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go
index 4f04132353..4e9c771615 100644
--- a/src/cmd/compile/internal/types2/unify.go
+++ b/src/cmd/compile/internal/types2/unify.go
@@ -401,18 +401,40 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// Therefore, we must fail unification (go.dev/issue/60933).
return false
}
- // If y is a defined type, make sure we record that type
- // for type parameter x, which may have until now only
- // recorded an underlying type (go.dev/issue/43056).
- // Either both types are interfaces, or neither type is.
- // If both are interfaces, they have the same methods.
+ // If we have inexact unification and one of x or y is a defined type, select the
+ // defined type. This ensures that in a series of types, all matching against the
+ // same type parameter, we infer a defined type if there is one, independent of
+ // order. Type inference or assignment may fail, which is ok.
+ // Selecting a defined type, if any, ensures that we don't lose the type name;
+ // and since we have inexact unification, a value of equally named or matching
+ // undefined type remains assignable (go.dev/issue/43056).
//
- // Note: Changing the recorded type for a type parameter to
- // a defined type is only ok when unification is inexact.
- // But in exact unification, if we have a match, x and y must
- // be identical, so changing the recorded type for x is a no-op.
- if yn {
- u.set(px, y)
+ // Similarly, if we have inexact unification and there are no defined types but
+ // channel types, select a directed channel, if any. This ensures that in a series
+ // of unnamed types, all matching against the same type parameter, we infer the
+ // directed channel if there is one, independent of order.
+ // Selecting a directional channel, if any, ensures that a value of another
+ // inexactly unifying channel type remains assignable (go.dev/issue/62157).
+ //
+ // If we have multiple defined channel types, they are either identical or we
+ // have assignment conflicts, so we can ignore directionality in this case.
+ //
+ // If we have defined and literal channel types, a defined type wins to avoid
+ // order dependencies.
+ if mode&exact == 0 {
+ switch {
+ case xn:
+ // x is a defined type: nothing to do.
+ case yn:
+ // x is not a defined type and y is a defined type: select y.
+ u.set(px, y)
+ default:
+ // Neither x nor y are defined types.
+ if yc, _ := under(y).(*Chan); yc != nil && yc.dir != SendRecv {
+ // y is a directed channel type: select y.
+ u.set(px, y)
+ }
+ }
}
return true
}
diff --git a/src/go/types/unify.go b/src/go/types/unify.go
index 646eb3e339..6680d97b86 100644
--- a/src/go/types/unify.go
+++ b/src/go/types/unify.go
@@ -403,18 +403,40 @@ func (u *unifier) nify(x, y Type, mode unifyMode, p *ifacePair) (result bool) {
// Therefore, we must fail unification (go.dev/issue/60933).
return false
}
- // If y is a defined type, make sure we record that type
- // for type parameter x, which may have until now only
- // recorded an underlying type (go.dev/issue/43056).
- // Either both types are interfaces, or neither type is.
- // If both are interfaces, they have the same methods.
+ // If we have inexact unification and one of x or y is a defined type, select the
+ // defined type. This ensures that in a series of types, all matching against the
+ // same type parameter, we infer a defined type if there is one, independent of
+ // order. Type inference or assignment may fail, which is ok.
+ // Selecting a defined type, if any, ensures that we don't lose the type name;
+ // and since we have inexact unification, a value of equally named or matching
+ // undefined type remains assignable (go.dev/issue/43056).
//
- // Note: Changing the recorded type for a type parameter to
- // a defined type is only ok when unification is inexact.
- // But in exact unification, if we have a match, x and y must
- // be identical, so changing the recorded type for x is a no-op.
- if yn {
- u.set(px, y)
+ // Similarly, if we have inexact unification and there are no defined types but
+ // channel types, select a directed channel, if any. This ensures that in a series
+ // of unnamed types, all matching against the same type parameter, we infer the
+ // directed channel if there is one, independent of order.
+ // Selecting a directional channel, if any, ensures that a value of another
+ // inexactly unifying channel type remains assignable (go.dev/issue/62157).
+ //
+ // If we have multiple defined channel types, they are either identical or we
+ // have assignment conflicts, so we can ignore directionality in this case.
+ //
+ // If we have defined and literal channel types, a defined type wins to avoid
+ // order dependencies.
+ if mode&exact == 0 {
+ switch {
+ case xn:
+ // x is a defined type: nothing to do.
+ case yn:
+ // x is not a defined type and y is a defined type: select y.
+ u.set(px, y)
+ default:
+ // Neither x nor y are defined types.
+ if yc, _ := under(y).(*Chan); yc != nil && yc.dir != SendRecv {
+ // y is a directed channel type: select y.
+ u.set(px, y)
+ }
+ }
}
return true
}
diff --git a/src/internal/types/testdata/fixedbugs/issue62157.go b/src/internal/types/testdata/fixedbugs/issue62157.go
new file mode 100644
index 0000000000..c44f921f44
--- /dev/null
+++ b/src/internal/types/testdata/fixedbugs/issue62157.go
@@ -0,0 +1,128 @@
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package p
+
+func f[T any](...T) T { var x T; return x }
+
+// Test case 1
+
+func _() {
+ var a chan string
+ var b <-chan string
+ f(a, b)
+ f(b, a)
+}
+
+// Test case 2
+
+type F[T any] func(T) bool
+
+func g[T any](T) F[<-chan T] { return nil }
+
+func f1[T any](T, F[T]) {}
+func f2[T any](F[T], T) {}
+
+func _() {
+ var ch chan string
+ f1(ch, g(""))
+ f2(g(""), ch)
+}
+
+// Test case 3: named and directional types combined
+
+func _() {
+ type namedA chan int
+ type namedB chan<- int
+
+ var a chan int
+ var A namedA
+ var b chan<- int
+ var B namedB
+
+ // Defined types win over channel types irrespective of channel direction.
+ f(A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
+ f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A)
+
+ f(a, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A)
+ f(a, A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
+ f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, A, a)
+ f(b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, a, A)
+ f(A, a, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */)
+ f(A, b /* ERROR "cannot use b (variable of type chan<- int) as namedA value in argument to f" */, a)
+
+ // Unnamed directed channels win over bidirectional channels.
+ b = f(a, b)
+ b = f(b, a)
+
+ // Defined directed channels win over defined bidirectional channels.
+ A = f(A, a)
+ A = f(a, A)
+ B = f(B, b)
+ B = f(b, B)
+
+ f(a, b, B)
+ f(a, B, b)
+ f(b, B, a)
+ f(b, a, B)
+ f(B, a, b)
+ f(B, b, a)
+
+ // Differently named channel types conflict irrespective of channel direction.
+ f(A, B /* ERROR "type namedB of B does not match inferred type namedA for T" */)
+ f(B, A /* ERROR "type namedA of A does not match inferred type namedB for T" */)
+
+ // Ensure that all combinations of directional and
+ // bidirectional channels with a named directional
+ // channel lead to the correct (named) directional
+ // channel.
+ B = f(a, b)
+ B = f(a, B)
+ B = f(b, a)
+ B = f(B, a)
+
+ B = f(a, b, B)
+ B = f(a, B, b)
+ B = f(b, B, a)
+ B = f(b, a, B)
+ B = f(B, a, b)
+ B = f(B, b, a)
+
+ // verify type error
+ A = f /* ERROR "cannot use f(B, b, a) (value of type namedB) as namedA value in assignment" */ (B, b, a)
+}
+
+// Test case 4: some more combinations
+
+func _() {
+ type A chan int
+ type B chan int
+ type C = chan int
+ type D = chan<- int
+
+ var a A
+ var b B
+ var c C
+ var d D
+
+ f(a, b /* ERROR "type B of b does not match inferred type A for T" */, c)
+ f(c, a, b /* ERROR "type B of b does not match inferred type A for T" */)
+ f(a, b /* ERROR "type B of b does not match inferred type A for T" */, d)
+ f(d, a, b /* ERROR "type B of b does not match inferred type A for T" */)
+}
+
+// Simplified test case from issue
+
+type Matcher[T any] func(T) bool
+
+func Produces[T any](T) Matcher[<-chan T] { return nil }
+
+func Assert1[T any](Matcher[T], T) {}
+func Assert2[T any](T, Matcher[T]) {}
+
+func _() {
+ var ch chan string
+ Assert1(Produces(""), ch)
+ Assert2(ch, Produces(""))
+}