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  }