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