go/types: factor out slice elem computation for copy built-in

This is a port of CL 357413 to go/types. Some test constraints are also
updated to remove 'interface', to coincide with the corresponding test
data file in types2.

Change-Id: I5248190501c2e4381eb7625f8d4fb269301d6e16
Reviewed-on: https://go-review.googlesource.com/c/go/+/359138
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
diff --git a/src/go/types/builtins.go b/src/go/types/builtins.go
index 3e2c994..29a8339 100644
--- a/src/go/types/builtins.go
+++ b/src/go/types/builtins.go
@@ -330,33 +330,22 @@
 
 	case _Copy:
 		// copy(x, y []T) int
-		var dst Type
-		if t := asSlice(x.typ); t != nil {
-			dst = t.elem
-		}
+		dst, _ := singleUnder(x.typ).(*Slice)
 
 		var y operand
 		arg(&y, 1)
 		if y.mode == invalid {
 			return
 		}
-		var src Type
-		switch t := optype(y.typ).(type) {
-		case *Basic:
-			if isString(y.typ) {
-				src = universeByte
-			}
-		case *Slice:
-			src = t.elem
-		}
+		src, _ := singleUnderString(y.typ).(*Slice)
 
 		if dst == nil || src == nil {
 			check.invalidArg(x, _InvalidCopy, "copy expects slice arguments; found %s and %s", x, &y)
 			return
 		}
 
-		if !Identical(dst, src) {
-			check.invalidArg(x, _InvalidCopy, "arguments to copy %s and %s have different element types %s and %s", x, &y, dst, src)
+		if !Identical(dst.elem, src.elem) {
+			check.errorf(x, _InvalidCopy, "arguments to copy %s and %s have different element types %s and %s", x, &y, dst.elem, src.elem)
 			return
 		}
 
@@ -783,6 +772,46 @@
 	return true
 }
 
+// If typ is a type parameter, single under returns the single underlying
+// type of all types in the corresponding type constraint if it exists, or
+// nil if it doesn't exist. If typ is not a type parameter, singleUnder
+// just returns the underlying type.
+func singleUnder(typ Type) Type {
+	var su Type
+	if underIs(typ, func(u Type) bool {
+		if su != nil && !Identical(su, u) {
+			return false
+		}
+		// su == nil || Identical(su, u)
+		su = u
+		return true
+	}) {
+		return su
+	}
+	return nil
+}
+
+// singleUnderString is like singleUnder but also considers []byte and
+// string as "identical". In this case, if successful, the result is always
+// []byte.
+func singleUnderString(typ Type) Type {
+	var su Type
+	if underIs(typ, func(u Type) bool {
+		if isString(u) {
+			u = NewSlice(universeByte)
+		}
+		if su != nil && !Identical(su, u) {
+			return false
+		}
+		// su == nil || Identical(su, u)
+		su = u
+		return true
+	}) {
+		return su
+	}
+	return nil
+}
+
 // hasVarSize reports if the size of type t is variable due to type parameters.
 func hasVarSize(t Type) bool {
 	switch t := under(t).(type) {
diff --git a/src/go/types/testdata/check/builtins.go2 b/src/go/types/testdata/check/builtins.go2
index fb912a19..f9b6ec7 100644
--- a/src/go/types/testdata/check/builtins.go2
+++ b/src/go/types/testdata/check/builtins.go2
@@ -51,7 +51,7 @@
 	copy(x /* ERROR copy expects slice arguments */ , y)
 }
 
-func _[T interface{~[]byte}](x, y T) {
+func _[T ~[]byte](x, y T) {
 	copy(x, y)
 	copy(x, "foo")
 	copy("foo" /* ERROR expects slice arguments */ , y)
@@ -66,20 +66,26 @@
 	copy(y /* ERROR different element types */ , x3)
 }
 
-func _[T interface{~[]E}, E any](x T, y []E) {
+func _[T ~[]E, E any](x T, y []E) {
 	copy(x, y)
 	copy(x /* ERROR different element types */ , "foo")
 }
 
-func _[T interface{~string}](x []byte, y T) {
+func _[T ~string](x []byte, y T) {
 	copy(x, y)
 	copy(y /* ERROR expects slice arguments */ , x)
 }
 
-func _[T interface{~[]byte|~string}](x T, y []byte) {
+func _[T ~[]byte|~string](x T, y []byte) {
 	copy(x /* ERROR expects slice arguments */ , y)
-	// TODO(gri) should this be valid?
-	copy(y /* ERROR expects slice arguments */ , x)
+	copy(y, x)
+}
+
+type L0 []int
+type L1 []int
+
+func _[T L0 | L1](x, y T) {
+	copy(x, y)
 }
 
 // delete