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