Skip to content

Commit b0d7811

Browse files
committed
apidiff: handle generic types
Two changes were required to handle generic types. First, the code that tries to establish a correspondence between two types could see one types.Type value that came from the type declaration, looking like this: type Foo[T any] and another types.Type value that came from a method argument or return value, looking like this, with no constraint: func (Foo[T]) ... We were using types.Identical in this case, but that function (rightly, according to its narrow definition) returns false when given `Foo[T any]` and `Foo[T]`. So I defined typesEquivalent, which is like types.Identical but succeeds in the above case. The second change was also to the establishCorrespondence method. Previously, any two named types could correspond. With generics, that isn't true if the types have different type parameters. Here we will always have the full types, so we check that there are the same number of type params and they have identical constraints. The type param names don't matter. Also, use cmp.Diff for TestChanges to make it easier to fix broken tests. Also, update the config for go/packages in the apidiff command. Apparently there were breaking changes to that package that made `apidiff -w` fail. Also, fix a gorelease test because the output of apidiff has changed for incompatible generic types. Change-Id: I0a75eb43f3ce4b55748f86a2c33a1cea6d52b35d Reviewed-on: https://go-review.googlesource.com/c/exp/+/411076 Run-TryBot: Jonathan Amsterdam <jba@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Jean de Klerk <deklerk@google.com>
1 parent a51bd04 commit b0d7811

File tree

7 files changed

+117
-9
lines changed

7 files changed

+117
-9
lines changed

apidiff/apidiff_test.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ import (
88
"os"
99
"os/exec"
1010
"path/filepath"
11-
"reflect"
1211
"runtime"
1312
"sort"
1413
"strings"
1514
"testing"
1615

16+
"github.com/google/go-cmp/cmp"
1717
"golang.org/x/tools/go/packages"
1818
)
1919

@@ -40,12 +40,12 @@ func TestChanges(t *testing.T) {
4040
report := Changes(oldpkg.Types, newpkg.Types)
4141

4242
got := report.messages(false)
43-
if !reflect.DeepEqual(got, wanti) {
44-
t.Errorf("incompatibles: got %v\nwant %v\n", got, wanti)
43+
if diff := cmp.Diff(wanti, got); diff != "" {
44+
t.Errorf("incompatibles: mismatch (-want, +got)\n%s", diff)
4545
}
4646
got = report.messages(true)
47-
if !reflect.DeepEqual(got, wantc) {
48-
t.Errorf("compatibles: got %v\nwant %v\n", got, wantc)
47+
if diff := cmp.Diff(wantc, got); diff != "" {
48+
t.Errorf("compatibles: mismatch (-want, +got)\n%s", diff)
4949
}
5050
}
5151

apidiff/correspondence.go

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,75 @@ func (d *differ) establishCorrespondence(old *types.Named, new types.Type) bool
185185
if old.Obj().Pkg() != d.old || newn.Obj().Pkg() != d.new {
186186
return old.Obj().Id() == newn.Obj().Id()
187187
}
188+
// Prior to generics, any two named types could correspond.
189+
// Two named types cannot correspond if their type parameter lists don't match.
190+
if !typeParamListsMatch(old.TypeParams(), newn.TypeParams()) {
191+
return false
192+
}
188193
}
189194
// If there is no correspondence, create one.
190195
d.correspondMap[oldname] = new
191196
// Check that the corresponding types are compatible.
192197
d.checkCompatibleDefined(oldname, old, new)
193198
return true
194199
}
195-
return types.Identical(oldc, new)
200+
return typesEquivalent(oldc, new)
201+
}
202+
203+
// Two list of type parameters match if they are the same length, and
204+
// the constraints of corresponding type parameters are identical.
205+
func typeParamListsMatch(tps1, tps2 *types.TypeParamList) bool {
206+
if tps1.Len() != tps2.Len() {
207+
return false
208+
}
209+
for i := 0; i < tps1.Len(); i++ {
210+
if !types.Identical(tps1.At(i).Constraint(), tps2.At(i).Constraint()) {
211+
return false
212+
}
213+
}
214+
return true
215+
}
216+
217+
// typesEquivalent reports whether two types are identical, or if
218+
// the types have identical type param lists except that one type has nil
219+
// constraints.
220+
//
221+
// This allows us to match a Type from a method receiver or arg to the Type from
222+
// the declaration.
223+
func typesEquivalent(old, new types.Type) bool {
224+
if types.Identical(old, new) {
225+
return true
226+
}
227+
// Handle two types with the same type params, one
228+
// having constraints and one not.
229+
oldn, ok := old.(*types.Named)
230+
if !ok {
231+
return false
232+
}
233+
newn, ok := new.(*types.Named)
234+
if !ok {
235+
return false
236+
}
237+
oldps := oldn.TypeParams()
238+
newps := newn.TypeParams()
239+
if oldps.Len() != newps.Len() {
240+
return false
241+
}
242+
if oldps.Len() == 0 {
243+
// Not generic types.
244+
return false
245+
}
246+
for i := 0; i < oldps.Len(); i++ {
247+
oldp := oldps.At(i)
248+
newp := newps.At(i)
249+
if oldp.Constraint() == nil || newp.Constraint() == nil {
250+
return true
251+
}
252+
if !types.Identical(oldp.Constraint(), newp.Constraint()) {
253+
return false
254+
}
255+
}
256+
return true
196257
}
197258

198259
func (d *differ) sortedMethods(iface *types.Interface) []*types.Func {

apidiff/testdata/tests.go

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -672,7 +672,7 @@ type S7 struct {
672672
E string
673673
}
674674

675-
//////////////// Method sets
675+
//// Method sets
676676

677677
// old
678678
type SM struct {
@@ -925,3 +925,45 @@ type j4 = k4
925925
// i Vj4: changed from k4 to j4
926926
// e.g. p.Vj4 = p.Vk4
927927
type j4 k4
928+
929+
//// Generics
930+
931+
// old
932+
type G[T any] []T
933+
934+
// new
935+
// OK: param name change
936+
type G[A any] []A
937+
938+
// old
939+
type GM[A, B comparable] map[A]B
940+
941+
// new
942+
// i GM: changed from map[A]B to map[B]A
943+
type GM[A, B comparable] map[B]A
944+
945+
// old
946+
type GT[V any] struct {
947+
}
948+
949+
func (GT[V]) M(*GT[V]) {}
950+
951+
// new
952+
// OK
953+
type GT[V any] struct {
954+
}
955+
956+
func (GT[V]) M(*GT[V]) {}
957+
958+
// old
959+
type GT2[V any] struct {
960+
}
961+
962+
func (GT2[V]) M(*GT2[V]) {}
963+
964+
// new
965+
// i GT2: changed from GT2[V any] to GT2[V comparable]
966+
type GT2[V comparable] struct {
967+
}
968+
969+
func (GT2[V]) M(*GT2[V]) {}

cmd/apidiff/main.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,9 @@ func mustLoadPackage(importPath string) *packages.Package {
9797
}
9898

9999
func loadPackage(importPath string) (*packages.Package, error) {
100-
cfg := &packages.Config{Mode: packages.LoadTypes}
100+
cfg := &packages.Config{Mode: packages.LoadTypes |
101+
packages.NeedName | packages.NeedTypes | packages.NeedImports | packages.NeedDeps,
102+
}
101103
pkgs, err := packages.Load(cfg, importPath)
102104
if err != nil {
103105
return nil, err

cmd/gorelease/testdata/generics/changed_param.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ base=v0.0.1
33
-- want --
44
# example.com/generics/a
55
## incompatible changes
6-
Foo[V any].Value: changed from func() V to func() any
6+
Foo: changed from Foo[V any] to Foo[V Number]
77
## compatible changes
88
Number: added
99

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ require (
88
)
99

1010
require (
11+
github.com/google/go-cmp v0.5.8 // indirect
1112
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 // indirect
1213
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
1314
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
2+
github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
13
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57 h1:LQmS1nU0twXLA96Kt7U9qtHJEbBk3z6Q0V4UXjZkpr4=
24
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
35
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3 h1:kQgndtyPBW/JIYERgdxfwMYh3AVStj88WQTlNDi2a+o=

0 commit comments

Comments
 (0)