golang.org/x/tools@v0.21.0/internal/apidiff/apidiff.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  // TODO: test swap corresponding types (e.g. u1 <-> u2 and u2 <-> u1)
     6  // TODO: test exported alias refers to something in another package -- does correspondence work then?
     7  // TODO: CODE COVERAGE
     8  // 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)
     9  // TODO: if you add an unexported method to an exposed interface, you have to check that
    10  //		every exposed type that previously implemented the interface still does. Otherwise
    11  //		an external assignment of the exposed type to the interface type could fail.
    12  // TODO: check constant values: large values aren't representable by some types.
    13  // TODO: Document all the incompatibilities we don't check for.
    14  
    15  package apidiff
    16  
    17  import (
    18  	"fmt"
    19  	"go/constant"
    20  	"go/token"
    21  	"go/types"
    22  
    23  	"golang.org/x/tools/internal/aliases"
    24  )
    25  
    26  // Changes reports on the differences between the APIs of the old and new packages.
    27  // It classifies each difference as either compatible or incompatible (breaking.) For
    28  // a detailed discussion of what constitutes an incompatible change, see the package
    29  // documentation.
    30  func Changes(old, new *types.Package) Report {
    31  	d := newDiffer(old, new)
    32  	d.checkPackage()
    33  	r := Report{}
    34  	for _, m := range d.incompatibles.collect() {
    35  		r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
    36  	}
    37  	for _, m := range d.compatibles.collect() {
    38  		r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
    39  	}
    40  	return r
    41  }
    42  
    43  type differ struct {
    44  	old, new *types.Package
    45  	// Correspondences between named types.
    46  	// Even though it is the named types (*types.Named) that correspond, we use
    47  	// *types.TypeName as a map key because they are canonical.
    48  	// The values can be either named types or basic types.
    49  	correspondMap map[*types.TypeName]types.Type
    50  
    51  	// Messages.
    52  	incompatibles messageSet
    53  	compatibles   messageSet
    54  }
    55  
    56  func newDiffer(old, new *types.Package) *differ {
    57  	return &differ{
    58  		old:           old,
    59  		new:           new,
    60  		correspondMap: map[*types.TypeName]types.Type{},
    61  		incompatibles: messageSet{},
    62  		compatibles:   messageSet{},
    63  	}
    64  }
    65  
    66  func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) {
    67  	addMessage(d.incompatibles, obj, part, format, args)
    68  }
    69  
    70  func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) {
    71  	addMessage(d.compatibles, obj, part, format, args)
    72  }
    73  
    74  func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) {
    75  	ms.add(obj, part, fmt.Sprintf(format, args...))
    76  }
    77  
    78  func (d *differ) checkPackage() {
    79  	// Old changes.
    80  	for _, name := range d.old.Scope().Names() {
    81  		oldobj := d.old.Scope().Lookup(name)
    82  		if !oldobj.Exported() {
    83  			continue
    84  		}
    85  		newobj := d.new.Scope().Lookup(name)
    86  		if newobj == nil {
    87  			d.incompatible(oldobj, "", "removed")
    88  			continue
    89  		}
    90  		d.checkObjects(oldobj, newobj)
    91  	}
    92  	// New additions.
    93  	for _, name := range d.new.Scope().Names() {
    94  		newobj := d.new.Scope().Lookup(name)
    95  		if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
    96  			d.compatible(newobj, "", "added")
    97  		}
    98  	}
    99  
   100  	// Whole-package satisfaction.
   101  	// For every old exposed interface oIface and its corresponding new interface nIface...
   102  	for otn1, nt1 := range d.correspondMap {
   103  		oIface, ok := otn1.Type().Underlying().(*types.Interface)
   104  		if !ok {
   105  			continue
   106  		}
   107  		nIface, ok := nt1.Underlying().(*types.Interface)
   108  		if !ok {
   109  			// If nt1 isn't an interface but otn1 is, then that's an incompatibility that
   110  			// we've already noticed, so there's no need to do anything here.
   111  			continue
   112  		}
   113  		// For every old type that implements oIface, its corresponding new type must implement
   114  		// nIface.
   115  		for otn2, nt2 := range d.correspondMap {
   116  			if otn1 == otn2 {
   117  				continue
   118  			}
   119  			if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
   120  				d.incompatible(otn2, "", "no longer implements %s", objectString(otn1))
   121  			}
   122  		}
   123  	}
   124  }
   125  
   126  func (d *differ) checkObjects(old, new types.Object) {
   127  	switch old := old.(type) {
   128  	case *types.Const:
   129  		if new, ok := new.(*types.Const); ok {
   130  			d.constChanges(old, new)
   131  			return
   132  		}
   133  	case *types.Var:
   134  		if new, ok := new.(*types.Var); ok {
   135  			d.checkCorrespondence(old, "", old.Type(), new.Type())
   136  			return
   137  		}
   138  	case *types.Func:
   139  		switch new := new.(type) {
   140  		case *types.Func:
   141  			d.checkCorrespondence(old, "", old.Type(), new.Type())
   142  			return
   143  		case *types.Var:
   144  			d.compatible(old, "", "changed from func to var")
   145  			d.checkCorrespondence(old, "", old.Type(), new.Type())
   146  			return
   147  
   148  		}
   149  	case *types.TypeName:
   150  		if new, ok := new.(*types.TypeName); ok {
   151  			d.checkCorrespondence(old, "", old.Type(), new.Type())
   152  			return
   153  		}
   154  	default:
   155  		panic("unexpected obj type")
   156  	}
   157  	// Here if kind of type changed.
   158  	d.incompatible(old, "", "changed from %s to %s",
   159  		objectKindString(old), objectKindString(new))
   160  }
   161  
   162  // Compare two constants.
   163  func (d *differ) constChanges(old, new *types.Const) {
   164  	ot := old.Type()
   165  	nt := new.Type()
   166  	// Check for change of type.
   167  	if !d.correspond(ot, nt) {
   168  		d.typeChanged(old, "", ot, nt)
   169  		return
   170  	}
   171  	// Check for change of value.
   172  	// We know the types are the same, so constant.Compare shouldn't panic.
   173  	if !constant.Compare(old.Val(), token.EQL, new.Val()) {
   174  		d.incompatible(old, "", "value changed from %s to %s", old.Val(), new.Val())
   175  	}
   176  }
   177  
   178  func objectKindString(obj types.Object) string {
   179  	switch obj.(type) {
   180  	case *types.Const:
   181  		return "const"
   182  	case *types.Var:
   183  		return "var"
   184  	case *types.Func:
   185  		return "func"
   186  	case *types.TypeName:
   187  		return "type"
   188  	default:
   189  		return "???"
   190  	}
   191  }
   192  
   193  func (d *differ) checkCorrespondence(obj types.Object, part string, old, new types.Type) {
   194  	if !d.correspond(old, new) {
   195  		d.typeChanged(obj, part, old, new)
   196  	}
   197  }
   198  
   199  func (d *differ) typeChanged(obj types.Object, part string, old, new types.Type) {
   200  	old = removeNamesFromSignature(old)
   201  	new = removeNamesFromSignature(new)
   202  	olds := types.TypeString(old, types.RelativeTo(d.old))
   203  	news := types.TypeString(new, types.RelativeTo(d.new))
   204  	d.incompatible(obj, part, "changed from %s to %s", olds, news)
   205  }
   206  
   207  // go/types always includes the argument and result names when formatting a signature.
   208  // Since these can change without affecting compatibility, we don't want users to
   209  // be distracted by them, so we remove them.
   210  func removeNamesFromSignature(t types.Type) types.Type {
   211  	t = aliases.Unalias(t)
   212  	sig, ok := t.(*types.Signature)
   213  	if !ok {
   214  		return t
   215  	}
   216  
   217  	dename := func(p *types.Tuple) *types.Tuple {
   218  		var vars []*types.Var
   219  		for i := 0; i < p.Len(); i++ {
   220  			v := p.At(i)
   221  			vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", aliases.Unalias(v.Type())))
   222  		}
   223  		return types.NewTuple(vars...)
   224  	}
   225  
   226  	return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
   227  }