1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package apidiff
16
17 import (
18 "fmt"
19 "go/constant"
20 "go/token"
21 "go/types"
22
23 "golang.org/x/tools/internal/aliases"
24 )
25
26
27
28
29
30 func Changes(old, new *types.Package) Report {
31 d := newDiffer(old, new)
32 d.checkPackage()
33 r := Report{}
34 for _, m := range d.incompatibles.collect() {
35 r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
36 }
37 for _, m := range d.compatibles.collect() {
38 r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
39 }
40 return r
41 }
42
43 type differ struct {
44 old, new *types.Package
45
46
47
48
49 correspondMap map[*types.TypeName]types.Type
50
51
52 incompatibles messageSet
53 compatibles messageSet
54 }
55
56 func newDiffer(old, new *types.Package) *differ {
57 return &differ{
58 old: old,
59 new: new,
60 correspondMap: map[*types.TypeName]types.Type{},
61 incompatibles: messageSet{},
62 compatibles: messageSet{},
63 }
64 }
65
66 func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) {
67 addMessage(d.incompatibles, obj, part, format, args)
68 }
69
70 func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) {
71 addMessage(d.compatibles, obj, part, format, args)
72 }
73
74 func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) {
75 ms.add(obj, part, fmt.Sprintf(format, args...))
76 }
77
78 func (d *differ) checkPackage() {
79
80 for _, name := range d.old.Scope().Names() {
81 oldobj := d.old.Scope().Lookup(name)
82 if !oldobj.Exported() {
83 continue
84 }
85 newobj := d.new.Scope().Lookup(name)
86 if newobj == nil {
87 d.incompatible(oldobj, "", "removed")
88 continue
89 }
90 d.checkObjects(oldobj, newobj)
91 }
92
93 for _, name := range d.new.Scope().Names() {
94 newobj := d.new.Scope().Lookup(name)
95 if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
96 d.compatible(newobj, "", "added")
97 }
98 }
99
100
101
102 for otn1, nt1 := range d.correspondMap {
103 oIface, ok := otn1.Type().Underlying().(*types.Interface)
104 if !ok {
105 continue
106 }
107 nIface, ok := nt1.Underlying().(*types.Interface)
108 if !ok {
109
110
111 continue
112 }
113
114
115 for otn2, nt2 := range d.correspondMap {
116 if otn1 == otn2 {
117 continue
118 }
119 if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
120 d.incompatible(otn2, "", "no longer implements %s", objectString(otn1))
121 }
122 }
123 }
124 }
125
126 func (d *differ) checkObjects(old, new types.Object) {
127 switch old := old.(type) {
128 case *types.Const:
129 if new, ok := new.(*types.Const); ok {
130 d.constChanges(old, new)
131 return
132 }
133 case *types.Var:
134 if new, ok := new.(*types.Var); ok {
135 d.checkCorrespondence(old, "", old.Type(), new.Type())
136 return
137 }
138 case *types.Func:
139 switch new := new.(type) {
140 case *types.Func:
141 d.checkCorrespondence(old, "", old.Type(), new.Type())
142 return
143 case *types.Var:
144 d.compatible(old, "", "changed from func to var")
145 d.checkCorrespondence(old, "", old.Type(), new.Type())
146 return
147
148 }
149 case *types.TypeName:
150 if new, ok := new.(*types.TypeName); ok {
151 d.checkCorrespondence(old, "", old.Type(), new.Type())
152 return
153 }
154 default:
155 panic("unexpected obj type")
156 }
157
158 d.incompatible(old, "", "changed from %s to %s",
159 objectKindString(old), objectKindString(new))
160 }
161
162
163 func (d *differ) constChanges(old, new *types.Const) {
164 ot := old.Type()
165 nt := new.Type()
166
167 if !d.correspond(ot, nt) {
168 d.typeChanged(old, "", ot, nt)
169 return
170 }
171
172
173 if !constant.Compare(old.Val(), token.EQL, new.Val()) {
174 d.incompatible(old, "", "value changed from %s to %s", old.Val(), new.Val())
175 }
176 }
177
178 func objectKindString(obj types.Object) string {
179 switch obj.(type) {
180 case *types.Const:
181 return "const"
182 case *types.Var:
183 return "var"
184 case *types.Func:
185 return "func"
186 case *types.TypeName:
187 return "type"
188 default:
189 return "???"
190 }
191 }
192
193 func (d *differ) checkCorrespondence(obj types.Object, part string, old, new types.Type) {
194 if !d.correspond(old, new) {
195 d.typeChanged(obj, part, old, new)
196 }
197 }
198
199 func (d *differ) typeChanged(obj types.Object, part string, old, new types.Type) {
200 old = removeNamesFromSignature(old)
201 new = removeNamesFromSignature(new)
202 olds := types.TypeString(old, types.RelativeTo(d.old))
203 news := types.TypeString(new, types.RelativeTo(d.new))
204 d.incompatible(obj, part, "changed from %s to %s", olds, news)
205 }
206
207
208
209
210 func removeNamesFromSignature(t types.Type) types.Type {
211 t = aliases.Unalias(t)
212 sig, ok := t.(*types.Signature)
213 if !ok {
214 return t
215 }
216
217 dename := func(p *types.Tuple) *types.Tuple {
218 var vars []*types.Var
219 for i := 0; i < p.Len(); i++ {
220 v := p.At(i)
221 vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", aliases.Unalias(v.Type())))
222 }
223 return types.NewTuple(vars...)
224 }
225
226 return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
227 }
228
View as plain text