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 | // TODO: test swap corresponding types (e.g. u1 <-> u2 and u2 <-> u1) |
6 | // TODO: test exported alias refers to something in another package -- does correspondence work then? |
7 | // TODO: CODE COVERAGE |
8 | // TODO: note that we may miss correspondences because we bail early when we compare a signature (e.g. when lengths differ; we could do up to the shorter) |
9 | // TODO: if you add an unexported method to an exposed interface, you have to check that |
10 | // every exposed type that previously implemented the interface still does. Otherwise |
11 | // an external assignment of the exposed type to the interface type could fail. |
12 | // TODO: check constant values: large values aren't representable by some types. |
13 | // TODO: Document all the incompatibilities we don't check for. |
14 | |
15 | package apidiff |
16 | |
17 | import ( |
18 | "fmt" |
19 | "go/constant" |
20 | "go/token" |
21 | "go/types" |
22 | ) |
23 | |
24 | // Changes reports on the differences between the APIs of the old and new packages. |
25 | // It classifies each difference as either compatible or incompatible (breaking.) For |
26 | // a detailed discussion of what constitutes an incompatible change, see the package |
27 | // documentation. |
28 | func Changes(old, new *types.Package) Report { |
29 | d := newDiffer(old, new) |
30 | d.checkPackage() |
31 | r := Report{} |
32 | for _, m := range d.incompatibles.collect() { |
33 | r.Changes = append(r.Changes, Change{Message: m, Compatible: false}) |
34 | } |
35 | for _, m := range d.compatibles.collect() { |
36 | r.Changes = append(r.Changes, Change{Message: m, Compatible: true}) |
37 | } |
38 | return r |
39 | } |
40 | |
41 | type differ struct { |
42 | old, new *types.Package |
43 | // Correspondences between named types. |
44 | // Even though it is the named types (*types.Named) that correspond, we use |
45 | // *types.TypeName as a map key because they are canonical. |
46 | // The values can be either named types or basic types. |
47 | correspondMap map[*types.TypeName]types.Type |
48 | |
49 | // Messages. |
50 | incompatibles messageSet |
51 | compatibles messageSet |
52 | } |
53 | |
54 | func newDiffer(old, new *types.Package) *differ { |
55 | return &differ{ |
56 | old: old, |
57 | new: new, |
58 | correspondMap: map[*types.TypeName]types.Type{}, |
59 | incompatibles: messageSet{}, |
60 | compatibles: messageSet{}, |
61 | } |
62 | } |
63 | |
64 | func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) { |
65 | addMessage(d.incompatibles, obj, part, format, args) |
66 | } |
67 | |
68 | func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) { |
69 | addMessage(d.compatibles, obj, part, format, args) |
70 | } |
71 | |
72 | func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) { |
73 | ms.add(obj, part, fmt.Sprintf(format, args...)) |
74 | } |
75 | |
76 | func (d *differ) checkPackage() { |
77 | // Old changes. |
78 | for _, name := range d.old.Scope().Names() { |
79 | oldobj := d.old.Scope().Lookup(name) |
80 | if !oldobj.Exported() { |
81 | continue |
82 | } |
83 | newobj := d.new.Scope().Lookup(name) |
84 | if newobj == nil { |
85 | d.incompatible(oldobj, "", "removed") |
86 | continue |
87 | } |
88 | d.checkObjects(oldobj, newobj) |
89 | } |
90 | // New additions. |
91 | for _, name := range d.new.Scope().Names() { |
92 | newobj := d.new.Scope().Lookup(name) |
93 | if newobj.Exported() && d.old.Scope().Lookup(name) == nil { |
94 | d.compatible(newobj, "", "added") |
95 | } |
96 | } |
97 | |
98 | // Whole-package satisfaction. |
99 | // For every old exposed interface oIface and its corresponding new interface nIface... |
100 | for otn1, nt1 := range d.correspondMap { |
101 | oIface, ok := otn1.Type().Underlying().(*types.Interface) |
102 | if !ok { |
103 | continue |
104 | } |
105 | nIface, ok := nt1.Underlying().(*types.Interface) |
106 | if !ok { |
107 | // If nt1 isn't an interface but otn1 is, then that's an incompatibility that |
108 | // we've already noticed, so there's no need to do anything here. |
109 | continue |
110 | } |
111 | // For every old type that implements oIface, its corresponding new type must implement |
112 | // nIface. |
113 | for otn2, nt2 := range d.correspondMap { |
114 | if otn1 == otn2 { |
115 | continue |
116 | } |
117 | if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) { |
118 | d.incompatible(otn2, "", "no longer implements %s", objectString(otn1)) |
119 | } |
120 | } |
121 | } |
122 | } |
123 | |
124 | func (d *differ) checkObjects(old, new types.Object) { |
125 | switch old := old.(type) { |
126 | case *types.Const: |
127 | if new, ok := new.(*types.Const); ok { |
128 | d.constChanges(old, new) |
129 | return |
130 | } |
131 | case *types.Var: |
132 | if new, ok := new.(*types.Var); ok { |
133 | d.checkCorrespondence(old, "", old.Type(), new.Type()) |
134 | return |
135 | } |
136 | case *types.Func: |
137 | switch new := new.(type) { |
138 | case *types.Func: |
139 | d.checkCorrespondence(old, "", old.Type(), new.Type()) |
140 | return |
141 | case *types.Var: |
142 | d.compatible(old, "", "changed from func to var") |
143 | d.checkCorrespondence(old, "", old.Type(), new.Type()) |
144 | return |
145 | |
146 | } |
147 | case *types.TypeName: |
148 | if new, ok := new.(*types.TypeName); ok { |
149 | d.checkCorrespondence(old, "", old.Type(), new.Type()) |
150 | return |
151 | } |
152 | default: |
153 | panic("unexpected obj type") |
154 | } |
155 | // Here if kind of type changed. |
156 | d.incompatible(old, "", "changed from %s to %s", |
157 | objectKindString(old), objectKindString(new)) |
158 | } |
159 | |
160 | // Compare two constants. |
161 | func (d *differ) constChanges(old, new *types.Const) { |
162 | ot := old.Type() |
163 | nt := new.Type() |
164 | // Check for change of type. |
165 | if !d.correspond(ot, nt) { |
166 | d.typeChanged(old, "", ot, nt) |
167 | return |
168 | } |
169 | // Check for change of value. |
170 | // We know the types are the same, so constant.Compare shouldn't panic. |
171 | if !constant.Compare(old.Val(), token.EQL, new.Val()) { |
172 | d.incompatible(old, "", "value changed from %s to %s", old.Val(), new.Val()) |
173 | } |
174 | } |
175 | |
176 | func objectKindString(obj types.Object) string { |
177 | switch obj.(type) { |
178 | case *types.Const: |
179 | return "const" |
180 | case *types.Var: |
181 | return "var" |
182 | case *types.Func: |
183 | return "func" |
184 | case *types.TypeName: |
185 | return "type" |
186 | default: |
187 | return "???" |
188 | } |
189 | } |
190 | |
191 | func (d *differ) checkCorrespondence(obj types.Object, part string, old, new types.Type) { |
192 | if !d.correspond(old, new) { |
193 | d.typeChanged(obj, part, old, new) |
194 | } |
195 | } |
196 | |
197 | func (d *differ) typeChanged(obj types.Object, part string, old, new types.Type) { |
198 | old = removeNamesFromSignature(old) |
199 | new = removeNamesFromSignature(new) |
200 | olds := types.TypeString(old, types.RelativeTo(d.old)) |
201 | news := types.TypeString(new, types.RelativeTo(d.new)) |
202 | d.incompatible(obj, part, "changed from %s to %s", olds, news) |
203 | } |
204 | |
205 | // go/types always includes the argument and result names when formatting a signature. |
206 | // Since these can change without affecting compatibility, we don't want users to |
207 | // be distracted by them, so we remove them. |
208 | func removeNamesFromSignature(t types.Type) types.Type { |
209 | sig, ok := t.(*types.Signature) |
210 | if !ok { |
211 | return t |
212 | } |
213 | |
214 | dename := func(p *types.Tuple) *types.Tuple { |
215 | var vars []*types.Var |
216 | for i := 0; i < p.Len(); i++ { |
217 | v := p.At(i) |
218 | vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", v.Type())) |
219 | } |
220 | return types.NewTuple(vars...) |
221 | } |
222 | |
223 | return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic()) |
224 | } |
225 |
Members