diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go index 4f041323532a0..4e9c77161584f 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 646eb3e339079..6680d97b86e49 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 0000000000000..c44f921f44bf4 --- /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("")) +}