golang.org/x/tools@v0.21.0/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 "golang.org/x/tools/internal/aliases" 24 ) 25 26 // Changes reports on the differences between the APIs of the old and new packages. 27 // It classifies each difference as either compatible or incompatible (breaking.) For 28 // a detailed discussion of what constitutes an incompatible change, see the package 29 // documentation. 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 // Correspondences between named types. 46 // Even though it is the named types (*types.Named) that correspond, we use 47 // *types.TypeName as a map key because they are canonical. 48 // The values can be either named types or basic types. 49 correspondMap map[*types.TypeName]types.Type 50 51 // Messages. 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 // Old changes. 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 // New additions. 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 // Whole-package satisfaction. 101 // For every old exposed interface oIface and its corresponding new interface nIface... 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 // If nt1 isn't an interface but otn1 is, then that's an incompatibility that 110 // we've already noticed, so there's no need to do anything here. 111 continue 112 } 113 // For every old type that implements oIface, its corresponding new type must implement 114 // nIface. 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 // Here if kind of type changed. 158 d.incompatible(old, "", "changed from %s to %s", 159 objectKindString(old), objectKindString(new)) 160 } 161 162 // Compare two constants. 163 func (d *differ) constChanges(old, new *types.Const) { 164 ot := old.Type() 165 nt := new.Type() 166 // Check for change of type. 167 if !d.correspond(ot, nt) { 168 d.typeChanged(old, "", ot, nt) 169 return 170 } 171 // Check for change of value. 172 // We know the types are the same, so constant.Compare shouldn't panic. 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 // go/types always includes the argument and result names when formatting a signature. 208 // Since these can change without affecting compatibility, we don't want users to 209 // be distracted by them, so we remove them. 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 }