1
2
3
4
5 package apidiff
6
7 import (
8 "fmt"
9 "go/types"
10 "reflect"
11
12 "golang.org/x/tools/internal/aliases"
13 "golang.org/x/tools/internal/typesinternal"
14 )
15
16 func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) {
17 old = aliases.Unalias(old)
18 new = aliases.Unalias(new)
19 switch old := old.(type) {
20 case *types.Interface:
21 if new, ok := new.(*types.Interface); ok {
22 d.checkCompatibleInterface(otn, old, new)
23 return
24 }
25
26 case *types.Struct:
27 if new, ok := new.(*types.Struct); ok {
28 d.checkCompatibleStruct(otn, old, new)
29 return
30 }
31
32 case *types.Chan:
33 if new, ok := new.(*types.Chan); ok {
34 d.checkCompatibleChan(otn, old, new)
35 return
36 }
37
38 case *types.Basic:
39 if new, ok := new.(*types.Basic); ok {
40 d.checkCompatibleBasic(otn, old, new)
41 return
42 }
43
44 case *types.Named:
45 panic("unreachable")
46
47 default:
48 d.checkCorrespondence(otn, "", old, new)
49 return
50
51 }
52
53 d.typeChanged(otn, "", old, new)
54 }
55
56 func (d *differ) checkCompatibleChan(otn *types.TypeName, old, new *types.Chan) {
57 d.checkCorrespondence(otn, ", element type", old.Elem(), new.Elem())
58 if old.Dir() != new.Dir() {
59 if new.Dir() == types.SendRecv {
60 d.compatible(otn, "", "removed direction")
61 } else {
62 d.incompatible(otn, "", "changed direction")
63 }
64 }
65 }
66
67 func (d *differ) checkCompatibleBasic(otn *types.TypeName, old, new *types.Basic) {
68
69
70 if old.Kind() == new.Kind() {
71
72 return
73 }
74 if compatibleBasics[[2]types.BasicKind{old.Kind(), new.Kind()}] {
75 d.compatible(otn, "", "changed from %s to %s", old, new)
76 } else {
77 d.typeChanged(otn, "", old, new)
78 }
79 }
80
81
82 var compatibleBasics = map[[2]types.BasicKind]bool{
83 {types.Uint8, types.Uint16}: true,
84 {types.Uint8, types.Uint32}: true,
85 {types.Uint8, types.Uint}: true,
86 {types.Uint8, types.Uint64}: true,
87 {types.Uint16, types.Uint32}: true,
88 {types.Uint16, types.Uint}: true,
89 {types.Uint16, types.Uint64}: true,
90 {types.Uint32, types.Uint}: true,
91 {types.Uint32, types.Uint64}: true,
92 {types.Uint, types.Uint64}: true,
93 {types.Int8, types.Int16}: true,
94 {types.Int8, types.Int32}: true,
95 {types.Int8, types.Int}: true,
96 {types.Int8, types.Int64}: true,
97 {types.Int16, types.Int32}: true,
98 {types.Int16, types.Int}: true,
99 {types.Int16, types.Int64}: true,
100 {types.Int32, types.Int}: true,
101 {types.Int32, types.Int64}: true,
102 {types.Int, types.Int64}: true,
103 {types.Float32, types.Float64}: true,
104 {types.Complex64, types.Complex128}: true,
105 }
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 func (d *differ) checkCompatibleInterface(otn *types.TypeName, old, new *types.Interface) {
121
122
123
124 if unexportedMethod(old) != nil {
125 d.checkMethodSet(otn, old, new, additionsCompatible)
126 } else {
127
128 d.checkMethodSet(otn, old, new, additionsIncompatible)
129 if u := unexportedMethod(new); u != nil {
130 d.incompatible(otn, u.Name(), "added unexported method")
131 }
132 }
133 }
134
135
136 func unexportedMethod(t *types.Interface) *types.Func {
137 for i := 0; i < t.NumMethods(); i++ {
138 if m := t.Method(i); !m.Exported() {
139 return m
140 }
141 }
142 return nil
143 }
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159 func (d *differ) checkCompatibleStruct(obj types.Object, old, new *types.Struct) {
160 d.checkCompatibleObjectSets(obj, exportedFields(old), exportedFields(new))
161 d.checkCompatibleObjectSets(obj, exportedSelectableFields(old), exportedSelectableFields(new))
162
163 if types.Comparable(old) && !types.Comparable(new) {
164 d.incompatible(obj, "", "old is comparable, new is not")
165 }
166 }
167
168
169
170 func exportedFields(s *types.Struct) map[string]types.Object {
171 m := map[string]types.Object{}
172 for i := 0; i < s.NumFields(); i++ {
173 f := s.Field(i)
174 if f.Exported() {
175 m[f.Name()] = f
176 }
177 }
178 return m
179 }
180
181
182
183
184
185
186 func exportedSelectableFields(s *types.Struct) map[string]types.Object {
187 var (
188 m = map[string]types.Object{}
189 next []*types.Struct
190 seen []*types.Struct
191 )
192 for cur := []*types.Struct{s}; len(cur) > 0; cur, next = next, nil {
193 seen = append(seen, cur...)
194
195
196
197 for name, f := range unambiguousFields(cur) {
198
199
200 if f.Exported() && m[name] == nil {
201 m[name] = f
202 }
203
204
205 if !f.Anonymous() {
206 continue
207 }
208 t := f.Type().Underlying()
209 if p, ok := t.(*types.Pointer); ok {
210 t = p.Elem().Underlying()
211 }
212 if t, ok := t.(*types.Struct); ok && !contains(seen, t) {
213 next = append(next, t)
214 }
215 }
216 }
217 return m
218 }
219
220 func contains(ts []*types.Struct, t *types.Struct) bool {
221 for _, s := range ts {
222 if types.Identical(s, t) {
223 return true
224 }
225 }
226 return false
227 }
228
229
230
231 func unambiguousFields(structs []*types.Struct) map[string]*types.Var {
232 fields := map[string]*types.Var{}
233 seen := map[string]bool{}
234 for _, s := range structs {
235 for i := 0; i < s.NumFields(); i++ {
236 f := s.Field(i)
237 name := f.Name()
238 if seen[name] {
239 delete(fields, name)
240 } else {
241 seen[name] = true
242 fields[name] = f
243 }
244 }
245 }
246 return fields
247 }
248
249
250
251 func (d *differ) checkCompatibleObjectSets(obj types.Object, old, new map[string]types.Object) {
252 for name, oldo := range old {
253 newo := new[name]
254 if newo == nil {
255 d.incompatible(obj, name, "removed")
256 } else {
257 d.checkCorrespondence(obj, name, oldo.Type(), newo.Type())
258 }
259 }
260 for name := range new {
261 if old[name] == nil {
262 d.compatible(obj, name, "added")
263 }
264 }
265 }
266
267 func (d *differ) checkCompatibleDefined(otn *types.TypeName, old *types.Named, new types.Type) {
268
269 d.checkCompatible(otn, old.Underlying(), new.Underlying())
270
271
272 if reflect.TypeOf(old.Underlying()) != reflect.TypeOf(new.Underlying()) {
273 return
274 }
275
276 if types.IsInterface(old) {
277 return
278 }
279
280
281 d.checkMethodSet(otn, old, new, additionsCompatible)
282 d.checkMethodSet(otn, types.NewPointer(old), types.NewPointer(new), additionsCompatible)
283 }
284
285 const (
286 additionsCompatible = true
287 additionsIncompatible = false
288 )
289
290 func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addcompat bool) {
291
292 oldMethodSet := exportedMethods(oldt)
293 newMethodSet := exportedMethods(newt)
294 msname := otn.Name()
295 if _, ok := aliases.Unalias(oldt).(*types.Pointer); ok {
296 msname = "*" + msname
297 }
298 for name, oldMethod := range oldMethodSet {
299 newMethod := newMethodSet[name]
300 if newMethod == nil {
301 var part string
302
303
304
305
306
307
308
309 recv := oldMethod.Type().(*types.Signature).Recv()
310 if _, named := typesinternal.ReceiverNamed(recv); named.Obj() != otn {
311 part = fmt.Sprintf(", method set of %s", msname)
312 }
313 d.incompatible(oldMethod, part, "removed")
314 } else {
315 obj := oldMethod
316
317
318
319
320
321 if !hasPointerReceiver(oldMethod) && hasPointerReceiver(newMethod) {
322 obj = newMethod
323 }
324 d.checkCorrespondence(obj, "", oldMethod.Type(), newMethod.Type())
325 }
326 }
327
328
329 for name, newMethod := range newMethodSet {
330 if oldMethodSet[name] == nil {
331 if addcompat {
332 d.compatible(newMethod, "", "added")
333 } else {
334 d.incompatible(newMethod, "", "added")
335 }
336 }
337 }
338 }
339
340
341 func exportedMethods(t types.Type) map[string]*types.Func {
342 m := make(map[string]*types.Func)
343 ms := types.NewMethodSet(t)
344 for i := 0; i < ms.Len(); i++ {
345 obj := ms.At(i).Obj().(*types.Func)
346 if obj.Exported() {
347 m[obj.Name()] = obj
348 }
349 }
350 return m
351 }
352
353 func hasPointerReceiver(method *types.Func) bool {
354 isptr, _ := typesinternal.ReceiverNamed(method.Type().(*types.Signature).Recv())
355 return isptr
356 }
357
View as plain text