From: Robert Griesemer Date: Wed, 10 Nov 2021 20:11:03 +0000 (-0800) Subject: cmd/compile/internal/types2: slightly relax notion of structural type X-Git-Tag: go1.18beta1~395 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=96c94c2c831a5c074d33e2b7b553e91eb602e6bd;p=gostls13.git cmd/compile/internal/types2: slightly relax notion of structural type If we have all channel types in a constraint, there is no structural type if they don't all have the same channel direction (and identical element types, of course). By allowing different channel types for the purposes of the structural type, as long as there is not a send-only _and_ a receive- only channel in the type set, we make it possible to find a useful, if restricted by channel direction, structural type where before there was none. So if we have unrestricted and send-only channels, the structural type is the send-only channel, and vice versa. For all operations on channels that rely on a structural type, it's always ok to have an unrestricted channel, so this is not affecting their behavior. But it makes those operations more flexible in the presence of type parameters containing mixed channel types. For constraint type inference, where we currently may not infer a channel at all, this change allows us to infer a more restricted channel (send- or receive-only). If the inferred channel type is a valid type argument we win; if not we haven't lost anything. Use structuralType for send and receive operations and adjust related error messages (the error message that change are the ones involving type parameters, so historic error messages are preserved). Fixes #45920. Change-Id: If3a64d29c37e7734d3163df330f8b02dd032bc60 Reviewed-on: https://go-review.googlesource.com/c/go/+/363075 Trust: Robert Griesemer Run-TryBot: Robert Griesemer Reviewed-by: Robert Findley --- 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 }) {