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 + } +)