diff --git a/src/cmd/compile/internal/types2/compilersupport.go b/src/cmd/compile/internal/types2/compilersupport.go index 1e79bbf9be..31112d4e41 100644 --- a/src/cmd/compile/internal/types2/compilersupport.go +++ b/src/cmd/compile/internal/types2/compilersupport.go @@ -25,10 +25,12 @@ func AsTypeParam(t Type) *TypeParam { return u } -// If t is a type parameter, StructuralType returns the single underlying -// type of all types in the type parameter's type constraint if it exists, -// or nil otherwise. If t is not a type parameter, StructuralType returns -// the underlying type of t. +// If typ is a type parameter, structuralType returns the single underlying +// type of all types in the corresponding type constraint if it exists, or +// nil otherwise. If the type set contains only unrestricted and restricted +// channel types (with identical element types), the single underlying type +// is the restricted channel type if the restrictions are always the same. +// If typ is not a type parameter, structuralType returns the underlying type. func StructuralType(t Type) Type { return structuralType(t) } diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index 169417016f..0b3fe23e80 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -187,29 +187,25 @@ func (check *Checker) unary(x *operand, e *syntax.Operation) { return case syntax.Recv: - var elem Type - if !underIs(x.typ, func(u Type) bool { - ch, _ := u.(*Chan) - if ch == nil { - check.errorf(x, invalidOp+"cannot receive from non-channel %s", x) - return false - } - if ch.dir == SendOnly { - check.errorf(x, invalidOp+"cannot receive from send-only channel %s", x) - return false - } - if elem != nil && !Identical(ch.elem, elem) { - check.errorf(x, invalidOp+"channels of %s must have the same element type", x) - return false - } - elem = ch.elem - return true - }) { + u := structuralType(x.typ) + if u == nil { + check.errorf(x, invalidOp+"cannot receive from %s: no structural type", x) + x.mode = invalid + return + } + ch, _ := u.(*Chan) + if ch == nil { + check.errorf(x, invalidOp+"cannot receive from non-channel %s", x) + x.mode = invalid + return + } + if ch.dir == SendOnly { + check.errorf(x, invalidOp+"cannot receive from send-only channel %s", x) x.mode = invalid return } x.mode = commaok - x.typ = elem + x.typ = ch.elem check.hasCallOrRecv = true return } diff --git a/src/cmd/compile/internal/types2/stmt.go b/src/cmd/compile/internal/types2/stmt.go index 2d41489152..f9c07e38cd 100644 --- a/src/cmd/compile/internal/types2/stmt.go +++ b/src/cmd/compile/internal/types2/stmt.go @@ -408,27 +408,21 @@ func (check *Checker) stmt(ctxt stmtContext, s syntax.Stmt) { if ch.mode == invalid || val.mode == invalid { return } - var elem Type - if !underIs(ch.typ, func(u Type) bool { - uch, _ := u.(*Chan) - if uch == nil { - check.errorf(s, invalidOp+"cannot send to non-channel %s", &ch) - return false - } - if uch.dir == RecvOnly { - check.errorf(s, invalidOp+"cannot send to receive-only channel %s", &ch) - return false - } - if elem != nil && !Identical(uch.elem, elem) { - check.errorf(s, invalidOp+"channels of %s must have the same element type", &ch) - return false - } - elem = uch.elem - return true - }) { + u := structuralType(ch.typ) + if u == nil { + check.errorf(s, invalidOp+"cannot send to %s: no structural type", &ch) return } - check.assignment(&val, elem, "send") + uch, _ := u.(*Chan) + if uch == nil { + check.errorf(s, invalidOp+"cannot send to non-channel %s", &ch) + return + } + if uch.dir == RecvOnly { + check.errorf(s, invalidOp+"cannot send to receive-only channel %s", &ch) + return + } + check.assignment(&val, uch.elem, "send") case *syntax.AssignStmt: lhs := unpackExpr(s.Lhs) diff --git a/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 index 9e7960a474..b1d02efdb5 100644 --- a/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 +++ b/src/cmd/compile/internal/types2/testdata/check/typeparams.go2 @@ -210,7 +210,7 @@ func _[ for _, _ /* ERROR permits only one iteration variable */ = range c1 {} var c2 C2 - for range c2 /* ERROR cannot range over c2.*no structural type */ {} + for range c2 {} var c3 C3 for range c3 /* ERROR receive from send-only channel */ {} diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 index 6cc3801cc9..46ac51ebdd 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2 @@ -12,11 +12,11 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | <-chan T } func _[T any](ch T) { - <-ch // ERROR cannot receive from non-channel + <-ch // ERROR cannot receive from ch .* no structural type } func _[T C0](ch T) { - <-ch // ERROR cannot receive from non-channel + <-ch // ERROR cannot receive from non-channel ch } func _[T C1](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - <-ch // ERROR channels of ch .* must have the same element type + <-ch // ERROR cannot receive from ch .* no structural type } func _[T C4](ch T) { diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45920.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45920.go2 new file mode 100644 index 0000000000..ef9ca9fede --- /dev/null +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue45920.go2 @@ -0,0 +1,17 @@ +// Copyright 2021 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 f1[T any, C chan T | <-chan T](ch C) {} + +func _(ch chan int) { f1(ch) } +func _(ch <-chan int) { f1(ch) } +func _(ch chan<- int) { f1( /* ERROR chan<- int does not satisfy chan T\|<-chan T */ ch) } + +func f2[T any, C chan T | chan<- T](ch C) {} + +func _(ch chan int) { f2(ch) } +func _(ch <-chan int) { f2( /* ERROR <-chan int does not satisfy chan T\|chan<- T */ ch) } +func _(ch chan<- int) { f2(ch) } diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 index 00828eb997..83a8f3a5da 100644 --- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 +++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2 @@ -12,7 +12,7 @@ type C4 interface{ chan int | chan<- int } type C5[T any] interface{ ~chan T | chan<- T } func _[T any](ch T) { - ch /* ERROR cannot send to non-channel */ <- 0 + ch /* ERROR cannot send to ch .* no structural type */ <- 0 } func _[T C0](ch T) { @@ -28,7 +28,7 @@ func _[T C2](ch T) { } func _[T C3](ch T) { - ch /* ERROR channels of ch .* must have the same element type */ <- 0 + ch /* ERROR cannot send to ch .* no structural type */ <- 0 } func _[T C4](ch T) { diff --git a/src/cmd/compile/internal/types2/type.go b/src/cmd/compile/internal/types2/type.go index 64f25c6dac..316e834a77 100644 --- a/src/cmd/compile/internal/types2/type.go +++ b/src/cmd/compile/internal/types2/type.go @@ -27,17 +27,51 @@ func under(t Type) Type { return t } +// If x and y are identical, match returns x. +// If x and y are identical channels but for their direction +// and one of them is unrestricted, match returns the channel +// with the restricted direction. +// In all other cases, match returns nil. +func match(x, y Type) Type { + // Common case: we don't have channels. + if Identical(x, y) { + return x + } + + // We may have channels that differ in direction only. + if x, _ := x.(*Chan); x != nil { + if y, _ := y.(*Chan); y != nil && Identical(x.elem, y.elem) { + // We have channels that differ in direction only. + // If there's an unrestricted channel, select the restricted one. + switch { + case x.dir == SendRecv: + return y + case y.dir == SendRecv: + return x + } + } + } + + // types are different + return nil +} + // If typ is a type parameter, structuralType returns the single underlying // type of all types in the corresponding type constraint if it exists, or -// nil otherwise. If typ is not a type parameter, structuralType returns -// the underlying type. +// nil otherwise. If the type set contains only unrestricted and restricted +// channel types (with identical element types), the single underlying type +// is the restricted channel type if the restrictions are always the same. +// If typ is not a type parameter, structuralType returns the underlying type. func structuralType(typ Type) Type { var su Type if underIs(typ, func(u Type) bool { - if su != nil && !Identical(su, u) { - return false + if su != nil { + u = match(su, u) + if u == nil { + return false + } } - // su == nil || Identical(su, u) + // su == nil || match(su, u) != nil su = u return true }) { @@ -55,10 +89,13 @@ func structuralString(typ Type) Type { if isString(u) { u = NewSlice(universeByte) } - if su != nil && !Identical(su, u) { - return false + if su != nil { + u = match(su, u) + if u == nil { + return false + } } - // su == nil || Identical(su, u) + // su == nil || match(su, u) != nil su = u return true }) {