go/types: implement Identical for *Union types

This aligns with the API proposal (#47916).

Change-Id: I732e5b107e729718ed37e053ad3f434993a97ecd
Reviewed-on: https://go-review.googlesource.com/c/go/+/349413
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/api_test.go b/src/go/types/api_test.go
index 49c054b..4472748 100644
--- a/src/go/types/api_test.go
+++ b/src/go/types/api_test.go
@@ -1621,6 +1621,48 @@
 	}
 }
 
+func TestIdenticalUnions(t *testing.T) {
+	tname := NewTypeName(token.NoPos, nil, "myInt", nil)
+	myInt := NewNamed(tname, Typ[Int], nil)
+	tmap := map[string]*Term{
+		"int":     NewTerm(false, Typ[Int]),
+		"~int":    NewTerm(true, Typ[Int]),
+		"string":  NewTerm(false, Typ[String]),
+		"~string": NewTerm(true, Typ[String]),
+		"myInt":   NewTerm(false, myInt),
+	}
+	makeUnion := func(s string) *Union {
+		parts := strings.Split(s, "|")
+		var terms []*Term
+		for _, p := range parts {
+			term := tmap[p]
+			if term == nil {
+				t.Fatalf("missing term %q", p)
+			}
+			terms = append(terms, term)
+		}
+		return NewUnion(terms)
+	}
+	for _, test := range []struct {
+		x, y string
+		want bool
+	}{
+		// These tests are just sanity checks. The tests for type sets and
+		// interfaces provide much more test coverage.
+		{"int|~int", "~int", true},
+		{"myInt|~int", "~int", true},
+		{"int|string", "string|int", true},
+		{"int|int|string", "string|int", true},
+		{"myInt|string", "int|string", false},
+	} {
+		x := makeUnion(test.x)
+		y := makeUnion(test.y)
+		if got := Identical(x, y); got != test.want {
+			t.Errorf("Identical(%v, %v) = %t", test.x, test.y, got)
+		}
+	}
+}
+
 func TestIssue15305(t *testing.T) {
 	const src = "package p; func f() int16; var _ = f(undef)"
 	fset := token.NewFileSet()
diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go
index 9aa565b..a5d4be9 100644
--- a/src/go/types/predicates.go
+++ b/src/go/types/predicates.go
@@ -6,6 +6,8 @@
 
 package types
 
+import "go/token"
+
 // isNamed reports whether typ has a name.
 // isNamed may be called with types that are not fully set up.
 func isNamed(typ Type) bool {
@@ -225,6 +227,13 @@
 				identical(x.results, y.results, cmpTags, p)
 		}
 
+	case *Union:
+		if y, _ := y.(*Union); y != nil {
+			xset := computeUnionTypeSet(nil, token.NoPos, x)
+			yset := computeUnionTypeSet(nil, token.NoPos, y)
+			return xset.terms.equal(yset.terms)
+		}
+
 	case *Interface:
 		// Two interface types are identical if they describe the same type sets.
 		// With the existing implementation restriction, this simplifies to: