Skip to content

Commit

Permalink
[release-branch.go1.21] go/types, types2: remove order dependency in …
Browse files Browse the repository at this point in the history
…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>
  • Loading branch information
griesemer authored and gopherbot committed Aug 30, 2023
1 parent 0a95821 commit b120517
Show file tree
Hide file tree
Showing 3 changed files with 194 additions and 22 deletions.
44 changes: 33 additions & 11 deletions src/cmd/compile/internal/types2/unify.go
Expand Up @@ -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
}
Expand Down
44 changes: 33 additions & 11 deletions src/go/types/unify.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

128 changes: 128 additions & 0 deletions 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(""))
}

0 comments on commit b120517

Please sign in to comment.