1
2
3
4
5
6
7
8
9
10
11 package apidiff
12
13 import (
14 "fmt"
15 "go/constant"
16 "go/token"
17 "go/types"
18 "strings"
19
20 "golang.org/x/tools/go/types/typeutil"
21 )
22
23
24
25
26 func Changes(old, new *types.Package) Report {
27 return changesInternal(old, new, old.Path(), new.Path())
28 }
29
30
31
32
33
34
35
36
37 func changesInternal(old, new *types.Package, oldRootPackagePath, newRootPackagePath string) Report {
38 d := newDiffer(old, new)
39 d.checkPackage(oldRootPackagePath)
40 r := Report{}
41 for _, m := range d.incompatibles.collect(oldRootPackagePath, newRootPackagePath) {
42 r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
43 }
44 for _, m := range d.compatibles.collect(oldRootPackagePath, newRootPackagePath) {
45 r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
46 }
47 return r
48 }
49
50
51
52
53
54 func ModuleChanges(old, new *Module) Report {
55 var r Report
56
57 oldPkgs := make(map[string]*types.Package)
58 for _, p := range old.Packages {
59 oldPkgs[old.relativePath(p)] = p
60 }
61
62 newPkgs := make(map[string]*types.Package)
63 for _, p := range new.Packages {
64 newPkgs[new.relativePath(p)] = p
65 }
66
67 for n, op := range oldPkgs {
68 if np, ok := newPkgs[n]; ok {
69
70 rr := changesInternal(op, np, old.Path, new.Path)
71 r.Changes = append(r.Changes, rr.Changes...)
72 } else {
73
74 r.Changes = append(r.Changes, packageChange(op, "removed", false))
75 }
76 }
77
78 for n, np := range newPkgs {
79 if _, ok := oldPkgs[n]; !ok {
80
81 r.Changes = append(r.Changes, packageChange(np, "added", true))
82 }
83 }
84
85 return r
86 }
87
88 func packageChange(p *types.Package, change string, compatible bool) Change {
89 return Change{
90 Message: fmt.Sprintf("package %s: %s", p.Path(), change),
91 Compatible: compatible,
92 }
93 }
94
95
96
97 type Module struct {
98 Path string
99 Packages []*types.Package
100 }
101
102
103 func (m *Module) relativePath(p *types.Package) string {
104 return strings.TrimPrefix(p.Path(), m.Path)
105 }
106
107 type differ struct {
108 old, new *types.Package
109
110
111
112
113 correspondMap typeutil.Map
114
115
116 incompatibles messageSet
117 compatibles messageSet
118 }
119
120 func newDiffer(old, new *types.Package) *differ {
121 return &differ{
122 old: old,
123 new: new,
124 incompatibles: messageSet{},
125 compatibles: messageSet{},
126 }
127 }
128
129 func (d *differ) incompatible(obj objectWithSide, part, format string, args ...interface{}) {
130 addMessage(d.incompatibles, obj, part, format, args)
131 }
132
133 func (d *differ) compatible(obj objectWithSide, part, format string, args ...interface{}) {
134 addMessage(d.compatibles, obj, part, format, args)
135 }
136
137 func addMessage(ms messageSet, obj objectWithSide, part, format string, args []interface{}) {
138 ms.add(obj, part, fmt.Sprintf(format, args...))
139 }
140
141 func (d *differ) checkPackage(oldRootPackagePath string) {
142
143
144
145
146
147
148
149 for _, name := range d.old.Scope().Names() {
150 oldobj := d.old.Scope().Lookup(name)
151 if tn, ok := oldobj.(*types.TypeName); ok {
152 if oldn, ok := tn.Type().(*types.Named); ok {
153 if !oldn.Obj().Exported() {
154 continue
155 }
156
157
158
159 newobj := d.new.Scope().Lookup(oldn.Obj().Name())
160 if newobj != nil {
161 d.checkObjects(oldobj, newobj)
162 }
163 }
164 }
165 }
166
167
168
169 for _, name := range d.old.Scope().Names() {
170 oldobj := d.old.Scope().Lookup(name)
171 if !oldobj.Exported() {
172 continue
173 }
174 newobj := d.new.Scope().Lookup(name)
175 if newobj == nil {
176 d.incompatible(objectWithSide{oldobj, false}, "", "removed")
177 continue
178 }
179 d.checkObjects(oldobj, newobj)
180 }
181
182
183 for _, name := range d.new.Scope().Names() {
184 newobj := d.new.Scope().Lookup(name)
185 if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
186 d.compatible(objectWithSide{newobj, true}, "", "added")
187 }
188 }
189
190
191
192 d.correspondMap.Iterate(func(k1 types.Type, v1 any) {
193 ot1 := k1.(*types.Named)
194 otn1 := ot1.Obj()
195 nt1 := v1.(types.Type)
196 oIface, ok := otn1.Type().Underlying().(*types.Interface)
197 if !ok {
198 return
199 }
200 nIface, ok := nt1.Underlying().(*types.Interface)
201 if !ok {
202
203
204 return
205 }
206
207
208 d.correspondMap.Iterate(func(k2 types.Type, v2 any) {
209 ot2 := k2.(*types.Named)
210 otn2 := ot2.Obj()
211 nt2 := v2.(types.Type)
212 if otn1 == otn2 {
213 return
214 }
215 if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
216
217
218 d.incompatible(objectWithSide{otn2, false}, "", "no longer implements %s", objectString(otn1, oldRootPackagePath))
219 }
220 })
221 })
222 }
223
224 func (d *differ) checkObjects(old, new types.Object) {
225 switch old := old.(type) {
226 case *types.Const:
227 if new, ok := new.(*types.Const); ok {
228 d.constChanges(old, new)
229 return
230 }
231 case *types.Var:
232 if new, ok := new.(*types.Var); ok {
233 d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
234 return
235 }
236 case *types.Func:
237 switch new := new.(type) {
238 case *types.Func:
239 d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
240 return
241 case *types.Var:
242 d.compatible(objectWithSide{old, false}, "", "changed from func to var")
243 d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
244 return
245
246 }
247 case *types.TypeName:
248 if new, ok := new.(*types.TypeName); ok {
249 d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
250 return
251 }
252 default:
253 panic("unexpected obj type")
254 }
255
256 d.incompatible(objectWithSide{old, false}, "", "changed from %s to %s",
257 objectKindString(old), objectKindString(new))
258 }
259
260
261 func (d *differ) constChanges(old, new *types.Const) {
262 ot := old.Type()
263 nt := new.Type()
264
265 if !d.correspond(ot, nt) {
266 d.typeChanged(objectWithSide{old, false}, "", ot, nt)
267 return
268 }
269
270
271 if !constant.Compare(old.Val(), token.EQL, new.Val()) {
272 d.incompatible(objectWithSide{old, false}, "", "value changed from %s to %s", old.Val(), new.Val())
273 }
274 }
275
276 func objectKindString(obj types.Object) string {
277 switch obj.(type) {
278 case *types.Const:
279 return "const"
280 case *types.Var:
281 return "var"
282 case *types.Func:
283 return "func"
284 case *types.TypeName:
285 return "type"
286 default:
287 return "???"
288 }
289 }
290
291 func (d *differ) checkCorrespondence(obj objectWithSide, part string, old, new types.Type) {
292 if !d.correspond(old, new) {
293 d.typeChanged(obj, part, old, new)
294 }
295 }
296
297 func (d *differ) typeChanged(obj objectWithSide, part string, old, new types.Type) {
298 old = removeNamesFromSignature(old)
299 new = removeNamesFromSignature(new)
300 olds := types.TypeString(old, types.RelativeTo(d.old))
301 news := types.TypeString(new, types.RelativeTo(d.new))
302 d.incompatible(obj, part, "changed from %s to %s", olds, news)
303 }
304
305
306
307
308 func removeNamesFromSignature(t types.Type) types.Type {
309 sig, ok := t.(*types.Signature)
310 if !ok {
311 return t
312 }
313
314 dename := func(p *types.Tuple) *types.Tuple {
315 var vars []*types.Var
316 for i := 0; i < p.Len(); i++ {
317 v := p.At(i)
318 vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", v.Type()))
319 }
320 return types.NewTuple(vars...)
321 }
322
323 return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
324 }
325
View as plain text