| 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