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  }