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