cmd/compile/internal/types2: better error message for missing ~ in constraint

If a constraint could be satisfied if one of its type elements
had a ~, provide this information in the error message.

Fixes #49179.

Change-Id: I59f1a855a0646ad7254a978420b0334f1f52ec22
Reviewed-on: https://go-review.googlesource.com/c/go/+/366758
Trust: Robert Griesemer <gri@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go
index 3f5fc56..f9423dd 100644
--- a/src/cmd/compile/internal/types2/instantiate.go
+++ b/src/cmd/compile/internal/types2/instantiate.go
@@ -238,9 +238,28 @@
 	}
 
 	// Otherwise, V's type must be included in the iface type set.
-	if !Ti.typeSet().includes(V) {
-		// TODO(gri) report which type is missing
-		return errorf("%s does not implement %s", V, T)
+	var alt Type
+	if Ti.typeSet().is(func(t *term) bool {
+		if !t.includes(V) {
+			// If V ∉ t.typ but V ∈ ~t.typ then remember this type
+			// so we can suggest it as an alternative in the error
+			// message.
+			if alt == nil && !t.tilde && Identical(t.typ, under(t.typ)) {
+				tt := *t
+				tt.tilde = true
+				if tt.includes(V) {
+					alt = t.typ
+				}
+			}
+			return true
+		}
+		return false
+	}) {
+		if alt != nil {
+			return errorf("%s does not implement %s (possibly missing ~ for %s in constraint %s)", V, T, alt, T)
+		} else {
+			return errorf("%s does not implement %s", V, T)
+		}
 	}
 
 	return nil
diff --git a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49179.go2 b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49179.go2
index 7cba52a..75bea18 100644
--- a/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49179.go2
+++ b/src/cmd/compile/internal/types2/testdata/fixedbugs/issue49179.go2
@@ -4,6 +4,24 @@
 
 package p
 
+func f1[P int | string]()            {}
+func f2[P ~int | string | float64]() {}
+func f3[P int](x P)                  {}
+
+type myInt int
+type myFloat float64
+
+func _() {
+	_ = f1[int]
+	_ = f1[myInt /* ERROR possibly missing ~ for int in constraint int\|string */]
+	_ = f2[myInt]
+	_ = f2[myFloat /* ERROR possibly missing ~ for float64 in constraint int\|string|float64 */]
+	var x myInt
+	f3( /* ERROR myInt does not implement int \(possibly missing ~ for int in constraint int\) */ x)
+}
+
+// test case from the issue
+
 type SliceConstraint[T any] interface {
 	[]T
 }
@@ -15,5 +33,5 @@
 type MySlice []int
 
 func f(s MySlice) {
-	Map[MySlice /* ERROR MySlice does not implement SliceConstraint\[int\] */, int](s, nil)
+	Map[MySlice /* ERROR MySlice does not implement SliceConstraint\[int\] \(possibly missing ~ for \[\]int in constraint SliceConstraint\[int\]\) */, int](s, nil)
 }
diff --git a/src/cmd/compile/internal/types2/typeset.go b/src/cmd/compile/internal/types2/typeset.go
index a55e9d1..eaf614d 100644
--- a/src/cmd/compile/internal/types2/typeset.go
+++ b/src/cmd/compile/internal/types2/typeset.go
@@ -108,6 +108,8 @@
 func (s *_TypeSet) singleType() Type { return s.terms.singleType() }
 
 // includes reports whether t ∈ s.
+// TODO(gri) This function is not used anywhere anymore. Remove once we
+//           are clear that we don't need it elsewhere in the future.
 func (s *_TypeSet) includes(t Type) bool { return s.terms.includes(t) }
 
 // subsetOf reports whether s1 ⊆ s2.