]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/types2: slightly relax notion of structural type
authorRobert Griesemer <gri@golang.org>
Wed, 10 Nov 2021 20:11:03 +0000 (12:11 -0800)
committerRobert Griesemer <gri@golang.org>
Wed, 10 Nov 2021 21:59:23 +0000 (21:59 +0000)
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 <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
src/cmd/compile/internal/types2/compilersupport.go
src/cmd/compile/internal/types2/expr.go
src/cmd/compile/internal/types2/stmt.go
src/cmd/compile/internal/types2/testdata/check/typeparams.go2
src/cmd/compile/internal/types2/testdata/fixedbugs/issue43671.go2
src/cmd/compile/internal/types2/testdata/fixedbugs/issue45920.go2 [new file with mode: 0644]
src/cmd/compile/internal/types2/testdata/fixedbugs/issue47115.go2
src/cmd/compile/internal/types2/type.go

index 1e79bbf9bee8bef9c8fb1d6c0c2cb6756e946665..31112d4e419ba2e70923734cf29c97e4e63d0b29 100644 (file)
@@ -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)
 }
index 169417016f880ebc12cd7f94ea5e549940fd1e1a..0b3fe23e8039a2a32b70c360e5e6b7a3e6c65244 100644 (file)
@@ -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
        }
index 2d414891528454c62e59b47d4e2f86159329f272..f9c07e38cdc4046521586171550a0ef737f2e5e0 100644 (file)
@@ -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)
index 9e7960a474485b6f9e34df40b1c3f27d123cbc13..b1d02efdb554898ed71b874cfa8d4caf72fc67b1 100644 (file)
@@ -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 */ {}
index 6cc3801cc96b8b38ed8ef4c9ea6e4a8ad6c54081..46ac51ebdd2df27b2007ce66c953b460e693c4e8 100644 (file)
@@ -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 (file)
index 0000000..ef9ca9f
--- /dev/null
@@ -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) }
index 00828eb997be5cfe834b3f8ea5679c038b9633c9..83a8f3a5da64356cf10b747692018c379e712939 100644 (file)
@@ -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) {
index 64f25c6dac35b9ce38bcb53b317c6aade8ec5909..316e834a77efa40bcfbb0195d8733d8df430fd8e 100644 (file)
@@ -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
        }) {