apidiff: fix confusing changed messages

Depending on the names of types, apidiff could produce confusing
messages about types changing to their own names.

See testdata/order.go for more detail.

Change-Id: I41af43abc5fbda74a54cbe07899ae7fa834f2733
Reviewed-on: https://go-review.googlesource.com/c/exp/+/513676
Reviewed-by: Robert Findley <rfindley@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/apidiff/apidiff.go b/apidiff/apidiff.go
index 92c24fe..06d2e92 100644
--- a/apidiff/apidiff.go
+++ b/apidiff/apidiff.go
@@ -139,7 +139,33 @@
 }
 
 func (d *differ) checkPackage(oldRootPackagePath string) {
-	// Old changes.
+	// Determine what has changed between old and new.
+
+	// First, establish correspondences between types with the same name, before
+	// looking at aliases. This will avoid confusing messages like "T: changed
+	// from T to T", which can happen if a correspondence between an alias
+	// and a named type is established first.
+	// See testdata/order.go.
+	for _, name := range d.old.Scope().Names() {
+		oldobj := d.old.Scope().Lookup(name)
+		if tn, ok := oldobj.(*types.TypeName); ok {
+			if oldn, ok := tn.Type().(*types.Named); ok {
+				if !oldn.Obj().Exported() {
+					continue
+				}
+				// Does new have a named type of the same name? Look up using
+				// the old named type's name, oldn.Obj().Name(), not the
+				// TypeName tn, which may be an alias.
+				newobj := d.new.Scope().Lookup(oldn.Obj().Name())
+				if newobj != nil {
+					d.checkObjects(oldobj, newobj)
+				}
+			}
+		}
+	}
+
+	// Next, look at all exported symbols in the old world and compare them
+	// with the same-named symbols in the new world.
 	for _, name := range d.old.Scope().Names() {
 		oldobj := d.old.Scope().Lookup(name)
 		if !oldobj.Exported() {
@@ -152,7 +178,8 @@
 		}
 		d.checkObjects(oldobj, newobj)
 	}
-	// New additions.
+
+	// Now look at what has been added in the new package.
 	for _, name := range d.new.Scope().Names() {
 		newobj := d.new.Scope().Lookup(name)
 		if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
diff --git a/apidiff/testdata/order.go b/apidiff/testdata/order.go
new file mode 100644
index 0000000..f5b2adf
--- /dev/null
+++ b/apidiff/testdata/order.go
@@ -0,0 +1,37 @@
+package p
+
+// apidiff's output (but not its correctness) could depend on the order
+// in which declarations were visited.
+
+// For example, this always correctly reported that U changed from T to U:
+
+// old
+type U = T
+
+// new
+// i U: changed from T to U
+type U T
+
+// both
+type T int
+
+// But the test below, which is just a renaming of the one above, would report
+// that B was changed from B to B! apidiff was not wrong--there is an
+// incompatibility here--but it expressed itself poorly.
+//
+// This happened because old.A was processed first (Scope.Names returns names
+// sorted), resulting in old.B corresponding with new.A. Later, when old.B
+// was processed, it was matched with new.B. But since there was already a
+// correspondence for old.B, the blame for the incompatibility was put on new.B.
+
+// The fix is to establish the correspondence between same-named types first.
+
+// old
+type A = B
+
+// new
+// i A: changed from B to A
+type A B
+
+// both
+type B int