]> Cypherpunks repositories - gostls13.git/commitdiff
go/types, types2: remove order dependency in inference involving channels
authorRobert Griesemer <gri@golang.org>
Mon, 21 Aug 2023 22:50:18 +0000 (15:50 -0700)
committerGopher Robot <gobot@golang.org>
Tue, 29 Aug 2023 23:22:35 +0000 (23:22 +0000)
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 <gri@google.com>
Run-TryBot: Robert Griesemer <gri@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
Auto-Submit: Robert Griesemer <gri@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>

src/cmd/compile/internal/types2/unify.go
src/go/types/unify.go
src/internal/types/testdata/fixedbugs/issue62157.go [new file with mode: 0644]

index 0e4670f376fdf5ea9a92891c846dcaccdecebdbe..5d58e2da13e50c34265d27fdd524084a78c1c5d0 100644 (file)
@@ -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
                        }
index 1467ccef1e9dc5844924e4211cd920951a3bf43c..d8d5cd6f1a696f0505d225ce9c419d964c8aff1c 100644 (file)
@@ -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 (file)
index 0000000..c44f921
--- /dev/null
@@ -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(""))
+}