github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/apidiff/apidiff.go (about) 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 }