github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/internal/apidiff/compatibility.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 package apidiff 6 7 import ( 8 "fmt" 9 "go/types" 10 "reflect" 11 ) 12 13 func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) { 14 switch old := old.(type) { 15 case *types.Interface: 16 if new, ok := new.(*types.Interface); ok { 17 d.checkCompatibleInterface(otn, old, new) 18 return 19 } 20 21 case *types.Struct: 22 if new, ok := new.(*types.Struct); ok { 23 d.checkCompatibleStruct(otn, old, new) 24 return 25 } 26 27 case *types.Chan: 28 if new, ok := new.(*types.Chan); ok { 29 d.checkCompatibleChan(otn, old, new) 30 return 31 } 32 33 case *types.Basic: 34 if new, ok := new.(*types.Basic); ok { 35 d.checkCompatibleBasic(otn, old, new) 36 return 37 } 38 39 case *types.Named: 40 panic("unreachable") 41 42 default: 43 d.checkCorrespondence(otn, "", old, new) 44 return 45 46 } 47 // Here if old and new are different kinds of types. 48 d.typeChanged(otn, "", old, new) 49 } 50 51 func (d *differ) checkCompatibleChan(otn *types.TypeName, old, new *types.Chan) { 52 d.checkCorrespondence(otn, ", element type", old.Elem(), new.Elem()) 53 if old.Dir() != new.Dir() { 54 if new.Dir() == types.SendRecv { 55 d.compatible(otn, "", "removed direction") 56 } else { 57 d.incompatible(otn, "", "changed direction") 58 } 59 } 60 } 61 62 func (d *differ) checkCompatibleBasic(otn *types.TypeName, old, new *types.Basic) { 63 // Certain changes to numeric types are compatible. Approximately, the info must 64 // be the same, and the new values must be a superset of the old. 65 if old.Kind() == new.Kind() { 66 // old and new are identical 67 return 68 } 69 if compatibleBasics[[2]types.BasicKind{old.Kind(), new.Kind()}] { 70 d.compatible(otn, "", "changed from %s to %s", old, new) 71 } else { 72 d.typeChanged(otn, "", old, new) 73 } 74 } 75 76 // All pairs (old, new) of compatible basic types. 77 var compatibleBasics = map[[2]types.BasicKind]bool{ 78 {types.Uint8, types.Uint16}: true, 79 {types.Uint8, types.Uint32}: true, 80 {types.Uint8, types.Uint}: true, 81 {types.Uint8, types.Uint64}: true, 82 {types.Uint16, types.Uint32}: true, 83 {types.Uint16, types.Uint}: true, 84 {types.Uint16, types.Uint64}: true, 85 {types.Uint32, types.Uint}: true, 86 {types.Uint32, types.Uint64}: true, 87 {types.Uint, types.Uint64}: true, 88 {types.Int8, types.Int16}: true, 89 {types.Int8, types.Int32}: true, 90 {types.Int8, types.Int}: true, 91 {types.Int8, types.Int64}: true, 92 {types.Int16, types.Int32}: true, 93 {types.Int16, types.Int}: true, 94 {types.Int16, types.Int64}: true, 95 {types.Int32, types.Int}: true, 96 {types.Int32, types.Int64}: true, 97 {types.Int, types.Int64}: true, 98 {types.Float32, types.Float64}: true, 99 {types.Complex64, types.Complex128}: true, 100 } 101 102 // Interface compatibility: 103 // If the old interface has an unexported method, the new interface is compatible 104 // if its exported method set is a superset of the old. (Users could not implement, 105 // only embed.) 106 // 107 // If the old interface did not have an unexported method, the new interface is 108 // compatible if its exported method set is the same as the old, and it has no 109 // unexported methods. (Adding an unexported method makes the interface 110 // unimplementable outside the package.) 111 // 112 // TODO: must also check that if any methods were added or removed, every exposed 113 // type in the package that implemented the interface in old still implements it in 114 // new. Otherwise external assignments could fail. 115 func (d *differ) checkCompatibleInterface(otn *types.TypeName, old, new *types.Interface) { 116 // Method sets are checked in checkCompatibleDefined. 117 118 // Does the old interface have an unexported method? 119 if unexportedMethod(old) != nil { 120 d.checkMethodSet(otn, old, new, additionsCompatible) 121 } else { 122 // Perform an equivalence check, but with more information. 123 d.checkMethodSet(otn, old, new, additionsIncompatible) 124 if u := unexportedMethod(new); u != nil { 125 d.incompatible(otn, u.Name(), "added unexported method") 126 } 127 } 128 } 129 130 // Return an unexported method from the method set of t, or nil if there are none. 131 func unexportedMethod(t *types.Interface) *types.Func { 132 for i := 0; i < t.NumMethods(); i++ { 133 if m := t.Method(i); !m.Exported() { 134 return m 135 } 136 } 137 return nil 138 } 139 140 // We need to check three things for structs: 141 // 1. The set of exported fields must be compatible. This ensures that keyed struct 142 // literals continue to compile. (There is no compatibility guarantee for unkeyed 143 // struct literals.) 144 // 2. The set of exported *selectable* fields must be compatible. This includes the exported 145 // fields of all embedded structs. This ensures that selections continue to compile. 146 // 3. If the old struct is comparable, so must the new one be. This ensures that equality 147 // expressions and uses of struct values as map keys continue to compile. 148 // 149 // An unexported embedded struct can't appear in a struct literal outside the 150 // package, so it doesn't have to be present, or have the same name, in the new 151 // struct. 152 // 153 // Field tags are ignored: they have no compile-time implications. 154 func (d *differ) checkCompatibleStruct(obj types.Object, old, new *types.Struct) { 155 d.checkCompatibleObjectSets(obj, exportedFields(old), exportedFields(new)) 156 d.checkCompatibleObjectSets(obj, exportedSelectableFields(old), exportedSelectableFields(new)) 157 // Removing comparability from a struct is an incompatible change. 158 if types.Comparable(old) && !types.Comparable(new) { 159 d.incompatible(obj, "", "old is comparable, new is not") 160 } 161 } 162 163 // exportedFields collects all the immediate fields of the struct that are exported. 164 // This is also the set of exported keys for keyed struct literals. 165 func exportedFields(s *types.Struct) map[string]types.Object { 166 m := map[string]types.Object{} 167 for i := 0; i < s.NumFields(); i++ { 168 f := s.Field(i) 169 if f.Exported() { 170 m[f.Name()] = f 171 } 172 } 173 return m 174 } 175 176 // exportedSelectableFields collects all the exported fields of the struct, including 177 // exported fields of embedded structs. 178 // 179 // We traverse the struct breadth-first, because of the rule that a lower-depth field 180 // shadows one at a higher depth. 181 func exportedSelectableFields(s *types.Struct) map[string]types.Object { 182 var ( 183 m = map[string]types.Object{} 184 next []*types.Struct // embedded structs at the next depth 185 seen []*types.Struct // to handle recursive embedding 186 ) 187 for cur := []*types.Struct{s}; len(cur) > 0; cur, next = next, nil { 188 seen = append(seen, cur...) 189 // We only want to consider unambiguous fields. Ambiguous fields (where there 190 // is more than one field of the same name at the same level) are legal, but 191 // cannot be selected. 192 for name, f := range unambiguousFields(cur) { 193 // Record an exported field we haven't seen before. If we have seen it, 194 // it occurred a lower depth, so it shadows this field. 195 if f.Exported() && m[name] == nil { 196 m[name] = f 197 } 198 // Remember embedded structs for processing at the next depth, 199 // but only if we haven't seen the struct at this depth or above. 200 if !f.Anonymous() { 201 continue 202 } 203 t := f.Type().Underlying() 204 if p, ok := t.(*types.Pointer); ok { 205 t = p.Elem().Underlying() 206 } 207 if t, ok := t.(*types.Struct); ok && !contains(seen, t) { 208 next = append(next, t) 209 } 210 } 211 } 212 return m 213 } 214 215 func contains(ts []*types.Struct, t *types.Struct) bool { 216 for _, s := range ts { 217 if types.Identical(s, t) { 218 return true 219 } 220 } 221 return false 222 } 223 224 // Given a set of structs at the same depth, the unambiguous fields are the ones whose 225 // names appear exactly once. 226 func unambiguousFields(structs []*types.Struct) map[string]*types.Var { 227 fields := map[string]*types.Var{} 228 seen := map[string]bool{} 229 for _, s := range structs { 230 for i := 0; i < s.NumFields(); i++ { 231 f := s.Field(i) 232 name := f.Name() 233 if seen[name] { 234 delete(fields, name) 235 } else { 236 seen[name] = true 237 fields[name] = f 238 } 239 } 240 } 241 return fields 242 } 243 244 // Anything removed or change from the old set is an incompatible change. 245 // Anything added to the new set is a compatible change. 246 func (d *differ) checkCompatibleObjectSets(obj types.Object, old, new map[string]types.Object) { 247 for name, oldo := range old { 248 newo := new[name] 249 if newo == nil { 250 d.incompatible(obj, name, "removed") 251 } else { 252 d.checkCorrespondence(obj, name, oldo.Type(), newo.Type()) 253 } 254 } 255 for name := range new { 256 if old[name] == nil { 257 d.compatible(obj, name, "added") 258 } 259 } 260 } 261 262 func (d *differ) checkCompatibleDefined(otn *types.TypeName, old *types.Named, new types.Type) { 263 // We've already checked that old and new correspond. 264 d.checkCompatible(otn, old.Underlying(), new.Underlying()) 265 // If there are different kinds of types (e.g. struct and interface), don't bother checking 266 // the method sets. 267 if reflect.TypeOf(old.Underlying()) != reflect.TypeOf(new.Underlying()) { 268 return 269 } 270 // Interface method sets are checked in checkCompatibleInterface. 271 if _, ok := old.Underlying().(*types.Interface); ok { 272 return 273 } 274 275 // A new method set is compatible with an old if the new exported methods are a superset of the old. 276 d.checkMethodSet(otn, old, new, additionsCompatible) 277 d.checkMethodSet(otn, types.NewPointer(old), types.NewPointer(new), additionsCompatible) 278 } 279 280 const ( 281 additionsCompatible = true 282 additionsIncompatible = false 283 ) 284 285 func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addcompat bool) { 286 // TODO: find a way to use checkCompatibleObjectSets for this. 287 oldMethodSet := exportedMethods(oldt) 288 newMethodSet := exportedMethods(newt) 289 msname := otn.Name() 290 if _, ok := oldt.(*types.Pointer); ok { 291 msname = "*" + msname 292 } 293 for name, oldMethod := range oldMethodSet { 294 newMethod := newMethodSet[name] 295 if newMethod == nil { 296 var part string 297 // Due to embedding, it's possible that the method's receiver type is not 298 // the same as the defined type whose method set we're looking at. So for 299 // a type T with removed method M that is embedded in some other type U, 300 // we will generate two "removed" messages for T.M, one for its own type 301 // T and one for the embedded type U. We want both messages to appear, 302 // but the messageSet dedup logic will allow only one message for a given 303 // object. So use the part string to distinguish them. 304 if receiverNamedType(oldMethod).Obj() != otn { 305 part = fmt.Sprintf(", method set of %s", msname) 306 } 307 d.incompatible(oldMethod, part, "removed") 308 } else { 309 obj := oldMethod 310 // If a value method is changed to a pointer method and has a signature 311 // change, then we can get two messages for the same method definition: one 312 // for the value method set that says it's removed, and another for the 313 // pointer method set that says it changed. To keep both messages (since 314 // messageSet dedups), use newMethod for the second. (Slight hack.) 315 if !hasPointerReceiver(oldMethod) && hasPointerReceiver(newMethod) { 316 obj = newMethod 317 } 318 d.checkCorrespondence(obj, "", oldMethod.Type(), newMethod.Type()) 319 } 320 } 321 322 // Check for added methods. 323 for name, newMethod := range newMethodSet { 324 if oldMethodSet[name] == nil { 325 if addcompat { 326 d.compatible(newMethod, "", "added") 327 } else { 328 d.incompatible(newMethod, "", "added") 329 } 330 } 331 } 332 } 333 334 // exportedMethods collects all the exported methods of type's method set. 335 func exportedMethods(t types.Type) map[string]types.Object { 336 m := map[string]types.Object{} 337 ms := types.NewMethodSet(t) 338 for i := 0; i < ms.Len(); i++ { 339 obj := ms.At(i).Obj() 340 if obj.Exported() { 341 m[obj.Name()] = obj 342 } 343 } 344 return m 345 } 346 347 func receiverType(method types.Object) types.Type { 348 return method.Type().(*types.Signature).Recv().Type() 349 } 350 351 func receiverNamedType(method types.Object) *types.Named { 352 switch t := receiverType(method).(type) { 353 case *types.Pointer: 354 return t.Elem().(*types.Named) 355 case *types.Named: 356 return t 357 default: 358 panic("unreachable") 359 } 360 } 361 362 func hasPointerReceiver(method types.Object) bool { 363 _, ok := receiverType(method).(*types.Pointer) 364 return ok 365 }