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 11package apidiff 12 13import ( 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. 24func Changes(old, new *types.Package) Report { 25 d := newDiffer(old, new) 26 d.checkPackage() 27 r := Report{} 28 for _, m := range d.incompatibles.collect() { 29 r.Changes = append(r.Changes, Change{Message: m, Compatible: false}) 30 } 31 for _, m := range d.compatibles.collect() { 32 r.Changes = append(r.Changes, Change{Message: m, Compatible: true}) 33 } 34 return r 35} 36 37type 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 50func 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 60func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) { 61 addMessage(d.incompatibles, obj, part, format, args) 62} 63 64func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) { 65 addMessage(d.compatibles, obj, part, format, args) 66} 67 68func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) { 69 ms.add(obj, part, fmt.Sprintf(format, args...)) 70} 71 72func (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 120func (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. 157func (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 172func 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 187func (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 193func (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. 204func 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} 221