From b0cb94b8069150bd0eee2b6ee34da7e01172b042 Mon Sep 17 00:00:00 2001 From: Jonathan Amsterdam Date: Tue, 25 Jul 2023 13:26:45 -0400 Subject: [PATCH] apidiff: clarify and add tests Explain the type-splitting cases better. Add a test where an alias changes incompatibly. Change-Id: Ib7ce4c8dd7a827e72bf50ea29bd480996f06ff8e Reviewed-on: https://go-review.googlesource.com/c/exp/+/513135 Run-TryBot: Jonathan Amsterdam Reviewed-by: Robert Findley TryBot-Result: Gopher Robot --- apidiff/testdata/aliases.go | 15 ++++++++ apidiff/testdata/splitting.go | 71 +++++++++++++++++++++++------------ 2 files changed, 61 insertions(+), 25 deletions(-) create mode 100644 apidiff/testdata/aliases.go diff --git a/apidiff/testdata/aliases.go b/apidiff/testdata/aliases.go new file mode 100644 index 000000000..2f68ea8fe --- /dev/null +++ b/apidiff/testdata/aliases.go @@ -0,0 +1,15 @@ +package p + +// Here the same alias refers to different types in old and new. +// We correctly detect the problem, but the message is poor. + +// both +type t1 int +type t2 bool + +// old +type A = t1 + +// new +// i t1: changed from int to bool +type A = t2 diff --git a/apidiff/testdata/splitting.go b/apidiff/testdata/splitting.go index a27539745..2ef6b06d4 100644 --- a/apidiff/testdata/splitting.go +++ b/apidiff/testdata/splitting.go @@ -1,56 +1,77 @@ package p // Splitting types +// +// In the old world, there is one type with two names, one of which is an alias. +// In the new world, there are two distinct types. +// +// That is an incompatible change, because client code like +// +// var v *T1 = new(T2) +// +// will succeed in the old world, where T1 and T2 name the same type, +// but fail in the new world. -// OK: in both old and new, {J1, K1, L1} name the same type. +// OK: in both old and new, A, B, and C all name the same type. // old type ( - J1 = K1 - K1 = L1 - L1 int + A = B + B = C + C int ) // new type ( - J1 = K1 - K1 int - L1 = J1 + A = B + B int + C = A ) -// Old has one type, K2; new has J2 and K2. +// An example of splitting: + +// Old has one type, D; new has E and D. // both -type K2 int +type D int // old -type J2 = K2 +type E = D // new -// i K2: changed from K2 to K2 -type J2 K2 // old K2 corresponds with new J2 -// old K2 also corresponds with new K2: problem +// i E: changed from D to E +type E D // old D corresponds with new E +// old D also corresponds with new D: problem + +// Here we have a benign split. +// f and g are the same type in old and different types in new. +// But clients have no way of constructing an expression of type f, +// so they cannot write code that breaks. // both -type k3 int +type f int -var Vj3 j3 // expose j3 +var Vg g // expose g // old -type j3 = k3 +type g = f // new -// OK: k3 isn't exposed -type j3 k3 +// OK: f isn't exposed +type g f + +// Here we have another incompatible split, even +// though the type names are unexported. The problem +// is that both names are exposed via exported variables. // both -type k4 int +type h int -var Vj4 j4 // expose j4 -var VK4 k4 // expose k4 +var Vj j // expose j +var Vh h // expose h // old -type j4 = k4 +type j = h // new -// i Vj4: changed from k4 to j4 -// e.g. p.Vj4 = p.Vk4 -type j4 k4 +// i Vj: changed from h to j +// e.g. p.Vj = p.Vh +type j h