apidiff: require exported old types to match same-named type in new

Previously, correspondence was sometimes established between completely
unrelated types. This CL tightens the requirements for two exported types
to correspond: the only possibilities are that they have the same name,
or the new type is an alias for the old.

Examples:
	old: type T int    new: type T int
	same name: the T's correspond

	old: type T int    new: type T = U; type U int
	different name, but T is an alias for U.

This might seem to exclude another case:

	old: type T = U; type U int		new: type U int

It seems that T should correspond to U even though the above conditions
aren't met. But in fact U corresponds to U here, not T. Correspondence
is 1:1; we pick the closest match.

This may seem unrelated to the new test case, which reports that two
struct fields are incompatible instead of just one. But the reason that
the old code didn't report one of the fields is that it established a
correspondence between the fields' types, despite their being completely
unrelated.

Fixes golang/go#81523.

Change-Id: I8ab02d13acb606d4ea26fc73fa4d7ee5042d18d7
Reviewed-on: https://go-review.googlesource.com/c/exp/+/836046
Reviewed-by: Hongxiang Jiang <hxjiang@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
index 97b08f2..f29b910 100644
--- a/apidiff/apidiff.go
+++ b/apidiff/apidiff.go
@@ -111,8 +111,7 @@
 type differ struct {
 	old, new *types.Package
 	// Correspondences between named types.
-	// Even though it is the named types (*types.Named) that correspond, we use
-	// *types.TypeName as a map key because they are canonical.
+	// The keys are *types.Named.
 	// The values can be either named types or basic types.
 	correspondMap typeutil.Map
 
diff --git a/apidiff/correspondence.go b/apidiff/correspondence.go
index 8a0ec9a..b32d0d9 100644
--- a/apidiff/correspondence.go
+++ b/apidiff/correspondence.go
@@ -165,6 +165,23 @@
 	if c := d.correspondMap.At(old); c != nil {
 		return types.Identical(c.(types.Type), new)
 	}
+	if oldname.Pkg() == d.old && oldname.Exported() && !isInstantiated(old) {
+		// An exported defined type in old can only correspond to a type in new
+		// if old's name in new still refers to that new type (possibly via an alias).
+		newobj := d.new.Scope().Lookup(oldname.Name())
+		ntn, ok := newobj.(*types.TypeName)
+		if !ok {
+			return false
+		}
+		target := types.Unalias(ntn.Type())
+		if targetNamed, ok := target.(*types.Named); ok && isInstantiated(targetNamed) && old.TypeParams().Len() > 0 {
+			target = targetNamed.Origin()
+		}
+		if !types.Identical(new, target) {
+			return false
+		}
+	}
+
 	// Attempt to establish a correspondence.
 	// Assume the types don't correspond unless they have the same
 	// ID, or are from the old and new packages, respectively.
diff --git a/apidiff/testdata/splitting.go b/apidiff/testdata/splitting.go
index 0653822..99140dd 100644
--- a/apidiff/testdata/splitting.go
+++ b/apidiff/testdata/splitting.go
@@ -97,3 +97,30 @@
 	// i SplitStruct.Databases: changed from *[]uSplit to *[]uSplit2
 	Databases *[]uSplit2
 }
+
+// golang/go#81523: splitting an exported type into two new exported types
+// should report all affected struct fields, not just the second one visited.
+
+// old
+// i ESplit: removed
+type ESplit int
+
+type ExportedSplit struct {
+	F1 ESplit
+	F2 ESplit
+}
+
+// new
+type (
+	// c ESplit1: added
+	ESplit1 int
+	// c ESplit2: added
+	ESplit2 int
+
+	ExportedSplit struct {
+		// i ExportedSplit.F1: changed from ESplit to ESplit1
+		F1 ESplit1
+		// i ExportedSplit.F2: changed from ESplit to ESplit2
+		F2 ESplit2
+	}
+)