+++ /dev/null
-// TODO: test swap corresponding types (e.g. u1 <-> u2 and u2 <-> u1)
-// TODO: test exported alias refers to something in another package -- does correspondence work then?
-// TODO: CODE COVERAGE
-// 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)
-// TODO: if you add an unexported method to an exposed interface, you have to check that
-// every exposed type that previously implemented the interface still does. Otherwise
-// an external assignment of the exposed type to the interface type could fail.
-// TODO: check constant values: large values aren't representable by some types.
-// TODO: Document all the incompatibilities we don't check for.
-
-package apidiff
-
-import (
- "fmt"
- "go/constant"
- "go/token"
- "go/types"
-)
-
-// Changes reports on the differences between the APIs of the old and new packages.
-// It classifies each difference as either compatible or incompatible (breaking.) For
-// a detailed discussion of what constitutes an incompatible change, see the package
-// documentation.
-func Changes(old, new *types.Package) Report {
- d := newDiffer(old, new)
- d.checkPackage()
- r := Report{}
- for _, m := range d.incompatibles.collect() {
- r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
- }
- for _, m := range d.compatibles.collect() {
- r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
- }
- return r
-}
-
-type differ struct {
- old, new *types.Package
- // Correspondences between named types.
- // Even though it is the named types (*types.Named) that correspond, we use
- // *types.TypeName as a map key because they are canonical.
- // The values can be either named types or basic types.
- correspondMap map[*types.TypeName]types.Type
-
- // Messages.
- incompatibles messageSet
- compatibles messageSet
-}
-
-func newDiffer(old, new *types.Package) *differ {
- return &differ{
- old: old,
- new: new,
- correspondMap: map[*types.TypeName]types.Type{},
- incompatibles: messageSet{},
- compatibles: messageSet{},
- }
-}
-
-func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) {
- addMessage(d.incompatibles, obj, part, format, args)
-}
-
-func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) {
- addMessage(d.compatibles, obj, part, format, args)
-}
-
-func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) {
- ms.add(obj, part, fmt.Sprintf(format, args...))
-}
-
-func (d *differ) checkPackage() {
- // Old changes.
- for _, name := range d.old.Scope().Names() {
- oldobj := d.old.Scope().Lookup(name)
- if !oldobj.Exported() {
- continue
- }
- newobj := d.new.Scope().Lookup(name)
- if newobj == nil {
- d.incompatible(oldobj, "", "removed")
- continue
- }
- d.checkObjects(oldobj, newobj)
- }
- // New additions.
- for _, name := range d.new.Scope().Names() {
- newobj := d.new.Scope().Lookup(name)
- if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
- d.compatible(newobj, "", "added")
- }
- }
-
- // Whole-package satisfaction.
- // For every old exposed interface oIface and its corresponding new interface nIface...
- for otn1, nt1 := range d.correspondMap {
- oIface, ok := otn1.Type().Underlying().(*types.Interface)
- if !ok {
- continue
- }
- nIface, ok := nt1.Underlying().(*types.Interface)
- if !ok {
- // If nt1 isn't an interface but otn1 is, then that's an incompatibility that
- // we've already noticed, so there's no need to do anything here.
- continue
- }
- // For every old type that implements oIface, its corresponding new type must implement
- // nIface.
- for otn2, nt2 := range d.correspondMap {
- if otn1 == otn2 {
- continue
- }
- if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
- d.incompatible(otn2, "", "no longer implements %s", objectString(otn1))
- }
- }
- }
-}
-
-func (d *differ) checkObjects(old, new types.Object) {
- switch old := old.(type) {
- case *types.Const:
- if new, ok := new.(*types.Const); ok {
- d.constChanges(old, new)
- return
- }
- case *types.Var:
- if new, ok := new.(*types.Var); ok {
- d.checkCorrespondence(old, "", old.Type(), new.Type())
- return
- }
- case *types.Func:
- switch new := new.(type) {
- case *types.Func:
- d.checkCorrespondence(old, "", old.Type(), new.Type())
- return
- case *types.Var:
- d.compatible(old, "", "changed from func to var")
- d.checkCorrespondence(old, "", old.Type(), new.Type())
- return
-
- }
- case *types.TypeName:
- if new, ok := new.(*types.TypeName); ok {
- d.checkCorrespondence(old, "", old.Type(), new.Type())
- return
- }
- default:
- panic("unexpected obj type")
- }
- // Here if kind of type changed.
- d.incompatible(old, "", "changed from %s to %s",
- objectKindString(old), objectKindString(new))
-}
-
-// Compare two constants.
-func (d *differ) constChanges(old, new *types.Const) {
- ot := old.Type()
- nt := new.Type()
- // Check for change of type.
- if !d.correspond(ot, nt) {
- d.typeChanged(old, "", ot, nt)
- return
- }
- // Check for change of value.
- // We know the types are the same, so constant.Compare shouldn't panic.
- if !constant.Compare(old.Val(), token.EQL, new.Val()) {
- d.incompatible(old, "", "value changed from %s to %s", old.Val(), new.Val())
- }
-}
-
-func objectKindString(obj types.Object) string {
- switch obj.(type) {
- case *types.Const:
- return "const"
- case *types.Var:
- return "var"
- case *types.Func:
- return "func"
- case *types.TypeName:
- return "type"
- default:
- return "???"
- }
-}
-
-func (d *differ) checkCorrespondence(obj types.Object, part string, old, new types.Type) {
- if !d.correspond(old, new) {
- d.typeChanged(obj, part, old, new)
- }
-}
-
-func (d *differ) typeChanged(obj types.Object, part string, old, new types.Type) {
- old = removeNamesFromSignature(old)
- new = removeNamesFromSignature(new)
- olds := types.TypeString(old, types.RelativeTo(d.old))
- news := types.TypeString(new, types.RelativeTo(d.new))
- d.incompatible(obj, part, "changed from %s to %s", olds, news)
-}
-
-// go/types always includes the argument and result names when formatting a signature.
-// Since these can change without affecting compatibility, we don't want users to
-// be distracted by them, so we remove them.
-func removeNamesFromSignature(t types.Type) types.Type {
- sig, ok := t.(*types.Signature)
- if !ok {
- return t
- }
-
- dename := func(p *types.Tuple) *types.Tuple {
- var vars []*types.Var
- for i := 0; i < p.Len(); i++ {
- v := p.At(i)
- vars = append(vars, types.NewVar(v.Pos(), v.Pkg(), "", v.Type()))
- }
- return types.NewTuple(vars...)
- }
-
- return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
-}