1 | // Copyright 2019 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package apidiff |
6 | |
7 | import ( |
8 | "fmt" |
9 | "go/types" |
10 | "reflect" |
11 | ) |
12 | |
13 | func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) { |
14 | switch old := old.(type) { |
15 | case *types.Interface: |
16 | if new, ok := new.(*types.Interface); ok { |
17 | d.checkCompatibleInterface(otn, old, new) |
18 | return |
19 | } |
20 | |
21 | case *types.Struct: |
22 | if new, ok := new.(*types.Struct); ok { |
23 | d.checkCompatibleStruct(otn, old, new) |
24 | return |
25 | } |
26 | |
27 | case *types.Chan: |
28 | if new, ok := new.(*types.Chan); ok { |
29 | d.checkCompatibleChan(otn, old, new) |
30 | return |
31 | } |
32 | |
33 | case *types.Basic: |
34 | if new, ok := new.(*types.Basic); ok { |
35 | d.checkCompatibleBasic(otn, old, new) |
36 | return |
37 | } |
38 | |
39 | case *types.Named: |
40 | panic("unreachable") |
41 | |
42 | default: |
43 | d.checkCorrespondence(otn, "", old, new) |
44 | return |
45 | |
46 | } |
47 | // Here if old and new are different kinds of types. |
48 | d.typeChanged(otn, "", old, new) |
49 | } |
50 | |
51 | func (d *differ) checkCompatibleChan(otn *types.TypeName, old, new *types.Chan) { |
52 | d.checkCorrespondence(otn, ", element type", old.Elem(), new.Elem()) |
53 | if old.Dir() != new.Dir() { |
54 | if new.Dir() == types.SendRecv { |
55 | d.compatible(otn, "", "removed direction") |
56 | } else { |
57 | d.incompatible(otn, "", "changed direction") |
58 | } |
59 | } |
60 | } |
61 | |
62 | func (d *differ) checkCompatibleBasic(otn *types.TypeName, old, new *types.Basic) { |
63 | // Certain changes to numeric types are compatible. Approximately, the info must |
64 | // be the same, and the new values must be a superset of the old. |
65 | if old.Kind() == new.Kind() { |
66 | // old and new are identical |
67 | return |
68 | } |
69 | if compatibleBasics[[2]types.BasicKind{old.Kind(), new.Kind()}] { |
70 | d.compatible(otn, "", "changed from %s to %s", old, new) |
71 | } else { |
72 | d.typeChanged(otn, "", old, new) |
73 | } |
74 | } |
75 | |
76 | // All pairs (old, new) of compatible basic types. |
77 | var compatibleBasics = map[[2]types.BasicKind]bool{ |
78 | {types.Uint8, types.Uint16}: true, |
79 | {types.Uint8, types.Uint32}: true, |
80 | {types.Uint8, types.Uint}: true, |
81 | {types.Uint8, types.Uint64}: true, |
82 | {types.Uint16, types.Uint32}: true, |
83 | {types.Uint16, types.Uint}: true, |
84 | {types.Uint16, types.Uint64}: true, |
85 | {types.Uint32, types.Uint}: true, |
86 | {types.Uint32, types.Uint64}: true, |
87 | {types.Uint, types.Uint64}: true, |
88 | {types.Int8, types.Int16}: true, |
89 | {types.Int8, types.Int32}: true, |
90 | {types.Int8, types.Int}: true, |
91 | {types.Int8, types.Int64}: true, |
92 | {types.Int16, types.Int32}: true, |
93 | {types.Int16, types.Int}: true, |
94 | {types.Int16, types.Int64}: true, |
95 | {types.Int32, types.Int}: true, |
96 | {types.Int32, types.Int64}: true, |
97 | {types.Int, types.Int64}: true, |
98 | {types.Float32, types.Float64}: true, |
99 | {types.Complex64, types.Complex128}: true, |
100 | } |
101 | |
102 | // Interface compatibility: |
103 | // If the old interface has an unexported method, the new interface is compatible |
104 | // if its exported method set is a superset of the old. (Users could not implement, |
105 | // only embed.) |
106 | // |
107 | // If the old interface did not have an unexported method, the new interface is |
108 | // compatible if its exported method set is the same as the old, and it has no |
109 | // unexported methods. (Adding an unexported method makes the interface |
110 | // unimplementable outside the package.) |
111 | // |
112 | // TODO: must also check that if any methods were added or removed, every exposed |
113 | // type in the package that implemented the interface in old still implements it in |
114 | // new. Otherwise external assignments could fail. |
115 | func (d *differ) checkCompatibleInterface(otn *types.TypeName, old, new *types.Interface) { |
116 | // Method sets are checked in checkCompatibleDefined. |
117 | |
118 | // Does the old interface have an unexported method? |
119 | if unexportedMethod(old) != nil { |
120 | d.checkMethodSet(otn, old, new, additionsCompatible) |
121 | } else { |
122 | // Perform an equivalence check, but with more information. |
123 | d.checkMethodSet(otn, old, new, additionsIncompatible) |
124 | if u := unexportedMethod(new); u != nil { |
125 | d.incompatible(otn, u.Name(), "added unexported method") |
126 | } |
127 | } |
128 | } |
129 | |
130 | // Return an unexported method from the method set of t, or nil if there are none. |
131 | func unexportedMethod(t *types.Interface) *types.Func { |
132 | for i := 0; i < t.NumMethods(); i++ { |
133 | if m := t.Method(i); !m.Exported() { |
134 | return m |
135 | } |
136 | } |
137 | return nil |
138 | } |
139 | |
140 | // We need to check three things for structs: |
141 | // 1. The set of exported fields must be compatible. This ensures that keyed struct |
142 | // literals continue to compile. (There is no compatibility guarantee for unkeyed |
143 | // struct literals.) |
144 | // 2. The set of exported *selectable* fields must be compatible. This includes the exported |
145 | // fields of all embedded structs. This ensures that selections continue to compile. |
146 | // 3. If the old struct is comparable, so must the new one be. This ensures that equality |
147 | // expressions and uses of struct values as map keys continue to compile. |
148 | // |
149 | // An unexported embedded struct can't appear in a struct literal outside the |
150 | // package, so it doesn't have to be present, or have the same name, in the new |
151 | // struct. |
152 | // |
153 | // Field tags are ignored: they have no compile-time implications. |
154 | func (d *differ) checkCompatibleStruct(obj types.Object, old, new *types.Struct) { |
155 | d.checkCompatibleObjectSets(obj, exportedFields(old), exportedFields(new)) |
156 | d.checkCompatibleObjectSets(obj, exportedSelectableFields(old), exportedSelectableFields(new)) |
157 | // Removing comparability from a struct is an incompatible change. |
158 | if types.Comparable(old) && !types.Comparable(new) { |
159 | d.incompatible(obj, "", "old is comparable, new is not") |
160 | } |
161 | } |
162 | |
163 | // exportedFields collects all the immediate fields of the struct that are exported. |
164 | // This is also the set of exported keys for keyed struct literals. |
165 | func exportedFields(s *types.Struct) map[string]types.Object { |
166 | m := map[string]types.Object{} |
167 | for i := 0; i < s.NumFields(); i++ { |
168 | f := s.Field(i) |
169 | if f.Exported() { |
170 | m[f.Name()] = f |
171 | } |
172 | } |
173 | return m |
174 | } |
175 | |
176 | // exportedSelectableFields collects all the exported fields of the struct, including |
177 | // exported fields of embedded structs. |
178 | // |
179 | // We traverse the struct breadth-first, because of the rule that a lower-depth field |
180 | // shadows one at a higher depth. |
181 | func exportedSelectableFields(s *types.Struct) map[string]types.Object { |
182 | var ( |
183 | m = map[string]types.Object{} |
184 | next []*types.Struct // embedded structs at the next depth |
185 | seen []*types.Struct // to handle recursive embedding |
186 | ) |
187 | for cur := []*types.Struct{s}; len(cur) > 0; cur, next = next, nil { |
188 | seen = append(seen, cur...) |
189 | // We only want to consider unambiguous fields. Ambiguous fields (where there |
190 | // is more than one field of the same name at the same level) are legal, but |
191 | // cannot be selected. |
192 | for name, f := range unambiguousFields(cur) { |
193 | // Record an exported field we haven't seen before. If we have seen it, |
194 | // it occurred a lower depth, so it shadows this field. |
195 | if f.Exported() && m[name] == nil { |
196 | m[name] = f |
197 | } |
198 | // Remember embedded structs for processing at the next depth, |
199 | // but only if we haven't seen the struct at this depth or above. |
200 | if !f.Anonymous() { |
201 | continue |
202 | } |
203 | t := f.Type().Underlying() |
204 | if p, ok := t.(*types.Pointer); ok { |
205 | t = p.Elem().Underlying() |
206 | } |
207 | if t, ok := t.(*types.Struct); ok && !contains(seen, t) { |
208 | next = append(next, t) |
209 | } |
210 | } |
211 | } |
212 | return m |
213 | } |
214 | |
215 | func contains(ts []*types.Struct, t *types.Struct) bool { |
216 | for _, s := range ts { |
217 | if types.Identical(s, t) { |
218 | return true |
219 | } |
220 | } |
221 | return false |
222 | } |
223 | |
224 | // Given a set of structs at the same depth, the unambiguous fields are the ones whose |
225 | // names appear exactly once. |
226 | func unambiguousFields(structs []*types.Struct) map[string]*types.Var { |
227 | fields := map[string]*types.Var{} |
228 | seen := map[string]bool{} |
229 | for _, s := range structs { |
230 | for i := 0; i < s.NumFields(); i++ { |
231 | f := s.Field(i) |
232 | name := f.Name() |
233 | if seen[name] { |
234 | delete(fields, name) |
235 | } else { |
236 | seen[name] = true |
237 | fields[name] = f |
238 | } |
239 | } |
240 | } |
241 | return fields |
242 | } |
243 | |
244 | // Anything removed or change from the old set is an incompatible change. |
245 | // Anything added to the new set is a compatible change. |
246 | func (d *differ) checkCompatibleObjectSets(obj types.Object, old, new map[string]types.Object) { |
247 | for name, oldo := range old { |
248 | newo := new[name] |
249 | if newo == nil { |
250 | d.incompatible(obj, name, "removed") |
251 | } else { |
252 | d.checkCorrespondence(obj, name, oldo.Type(), newo.Type()) |
253 | } |
254 | } |
255 | for name := range new { |
256 | if old[name] == nil { |
257 | d.compatible(obj, name, "added") |
258 | } |
259 | } |
260 | } |
261 | |
262 | func (d *differ) checkCompatibleDefined(otn *types.TypeName, old *types.Named, new types.Type) { |
263 | // We've already checked that old and new correspond. |
264 | d.checkCompatible(otn, old.Underlying(), new.Underlying()) |
265 | // If there are different kinds of types (e.g. struct and interface), don't bother checking |
266 | // the method sets. |
267 | if reflect.TypeOf(old.Underlying()) != reflect.TypeOf(new.Underlying()) { |
268 | return |
269 | } |
270 | // Interface method sets are checked in checkCompatibleInterface. |
271 | if _, ok := old.Underlying().(*types.Interface); ok { |
272 | return |
273 | } |
274 | |
275 | // A new method set is compatible with an old if the new exported methods are a superset of the old. |
276 | d.checkMethodSet(otn, old, new, additionsCompatible) |
277 | d.checkMethodSet(otn, types.NewPointer(old), types.NewPointer(new), additionsCompatible) |
278 | } |
279 | |
280 | const ( |
281 | additionsCompatible = true |
282 | additionsIncompatible = false |
283 | ) |
284 | |
285 | func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addcompat bool) { |
286 | // TODO: find a way to use checkCompatibleObjectSets for this. |
287 | oldMethodSet := exportedMethods(oldt) |
288 | newMethodSet := exportedMethods(newt) |
289 | msname := otn.Name() |
290 | if _, ok := oldt.(*types.Pointer); ok { |
291 | msname = "*" + msname |
292 | } |
293 | for name, oldMethod := range oldMethodSet { |
294 | newMethod := newMethodSet[name] |
295 | if newMethod == nil { |
296 | var part string |
297 | // Due to embedding, it's possible that the method's receiver type is not |
298 | // the same as the defined type whose method set we're looking at. So for |
299 | // a type T with removed method M that is embedded in some other type U, |
300 | // we will generate two "removed" messages for T.M, one for its own type |
301 | // T and one for the embedded type U. We want both messages to appear, |
302 | // but the messageSet dedup logic will allow only one message for a given |
303 | // object. So use the part string to distinguish them. |
304 | if receiverNamedType(oldMethod).Obj() != otn { |
305 | part = fmt.Sprintf(", method set of %s", msname) |
306 | } |
307 | d.incompatible(oldMethod, part, "removed") |
308 | } else { |
309 | obj := oldMethod |
310 | // If a value method is changed to a pointer method and has a signature |
311 | // change, then we can get two messages for the same method definition: one |
312 | // for the value method set that says it's removed, and another for the |
313 | // pointer method set that says it changed. To keep both messages (since |
314 | // messageSet dedups), use newMethod for the second. (Slight hack.) |
315 | if !hasPointerReceiver(oldMethod) && hasPointerReceiver(newMethod) { |
316 | obj = newMethod |
317 | } |
318 | d.checkCorrespondence(obj, "", oldMethod.Type(), newMethod.Type()) |
319 | } |
320 | } |
321 | |
322 | // Check for added methods. |
323 | for name, newMethod := range newMethodSet { |
324 | if oldMethodSet[name] == nil { |
325 | if addcompat { |
326 | d.compatible(newMethod, "", "added") |
327 | } else { |
328 | d.incompatible(newMethod, "", "added") |
329 | } |
330 | } |
331 | } |
332 | } |
333 | |
334 | // exportedMethods collects all the exported methods of type's method set. |
335 | func exportedMethods(t types.Type) map[string]types.Object { |
336 | m := map[string]types.Object{} |
337 | ms := types.NewMethodSet(t) |
338 | for i := 0; i < ms.Len(); i++ { |
339 | obj := ms.At(i).Obj() |
340 | if obj.Exported() { |
341 | m[obj.Name()] = obj |
342 | } |
343 | } |
344 | return m |
345 | } |
346 | |
347 | func receiverType(method types.Object) types.Type { |
348 | return method.Type().(*types.Signature).Recv().Type() |
349 | } |
350 | |
351 | func receiverNamedType(method types.Object) *types.Named { |
352 | switch t := receiverType(method).(type) { |
353 | case *types.Pointer: |
354 | return t.Elem().(*types.Named) |
355 | case *types.Named: |
356 | return t |
357 | default: |
358 | panic("unreachable") |
359 | } |
360 | } |
361 | |
362 | func hasPointerReceiver(method types.Object) bool { |
363 | _, ok := receiverType(method).(*types.Pointer) |
364 | return ok |
365 | } |
366 |
Members