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