summaryrefslogtreecommitdiffstats
path: root/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/apidiff/apidiff.go
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/apidiff/apidiff.go220
1 files changed, 220 insertions, 0 deletions
diff --git a/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/apidiff/apidiff.go b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/apidiff/apidiff.go
new file mode 100644
index 0000000..76669d8
--- /dev/null
+++ b/dependencies/pkg/mod/golang.org/x/exp@v0.0.0-20220613132600-b0d781184e0d/apidiff/apidiff.go
@@ -0,0 +1,220 @@
+// 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())
+}