Skip to content

Commit d98519c

Browse files
maxbjba
authored andcommitted
x/exp/apidiff: add package path to change messages
Following the recent change to add module support to apidiff, it is now no longer the case that all objects reported on in one apidiff execution are in the same package. Therefore, add the package path, made relative to the root of the apidiff execution, to object names when reporting changes. Fixes golang/go#61387 Change-Id: Idd748d46b68c0122f9f28ea3b51cc0519448b672 Reviewed-on: https://go-review.googlesource.com/c/exp/+/512295 TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Jonathan Amsterdam <[email protected]> Run-TryBot: Jonathan Amsterdam <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]>
1 parent 613f0c0 commit d98519c

File tree

5 files changed

+91
-48
lines changed

5 files changed

+91
-48
lines changed

apidiff/apidiff.go

+32-21
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,24 @@ import (
2222
// It classifies each difference as either compatible or incompatible (breaking.) For
2323
// a detailed discussion of what constitutes an incompatible change, see the README.
2424
func Changes(old, new *types.Package) Report {
25+
return changesInternal(old, new, old.Path(), new.Path())
26+
}
27+
28+
// changesInternal contains the core logic for comparing a single package, shared
29+
// between Changes and ModuleChanges. The root package path arguments refer to the
30+
// context of this apidiff invocation - when diffing a single package, they will be
31+
// that package, but when diffing a whole module, they will be the root path of the
32+
// module. This is used to give change messages appropriate context for object names.
33+
// The old and new root must be tracked independently, since each side of the diff
34+
// operation may be a different path.
35+
func changesInternal(old, new *types.Package, oldRootPackagePath, newRootPackagePath string) Report {
2536
d := newDiffer(old, new)
26-
d.checkPackage()
37+
d.checkPackage(oldRootPackagePath)
2738
r := Report{}
28-
for _, m := range d.incompatibles.collect() {
39+
for _, m := range d.incompatibles.collect(oldRootPackagePath, newRootPackagePath) {
2940
r.Changes = append(r.Changes, Change{Message: m, Compatible: false})
3041
}
31-
for _, m := range d.compatibles.collect() {
42+
for _, m := range d.compatibles.collect(oldRootPackagePath, newRootPackagePath) {
3243
r.Changes = append(r.Changes, Change{Message: m, Compatible: true})
3344
}
3445
return r
@@ -54,7 +65,7 @@ func ModuleChanges(old, new *Module) Report {
5465
for n, op := range oldPkgs {
5566
if np, ok := newPkgs[n]; ok {
5667
// shared package, compare surfaces
57-
rr := Changes(op, np)
68+
rr := changesInternal(op, np, old.Path, new.Path)
5869
r.Changes = append(r.Changes, rr.Changes...)
5970
} else {
6071
// old package was removed
@@ -114,19 +125,19 @@ func newDiffer(old, new *types.Package) *differ {
114125
}
115126
}
116127

117-
func (d *differ) incompatible(obj types.Object, part, format string, args ...interface{}) {
128+
func (d *differ) incompatible(obj objectWithSide, part, format string, args ...interface{}) {
118129
addMessage(d.incompatibles, obj, part, format, args)
119130
}
120131

121-
func (d *differ) compatible(obj types.Object, part, format string, args ...interface{}) {
132+
func (d *differ) compatible(obj objectWithSide, part, format string, args ...interface{}) {
122133
addMessage(d.compatibles, obj, part, format, args)
123134
}
124135

125-
func addMessage(ms messageSet, obj types.Object, part, format string, args []interface{}) {
136+
func addMessage(ms messageSet, obj objectWithSide, part, format string, args []interface{}) {
126137
ms.add(obj, part, fmt.Sprintf(format, args...))
127138
}
128139

129-
func (d *differ) checkPackage() {
140+
func (d *differ) checkPackage(oldRootPackagePath string) {
130141
// Old changes.
131142
for _, name := range d.old.Scope().Names() {
132143
oldobj := d.old.Scope().Lookup(name)
@@ -135,7 +146,7 @@ func (d *differ) checkPackage() {
135146
}
136147
newobj := d.new.Scope().Lookup(name)
137148
if newobj == nil {
138-
d.incompatible(oldobj, "", "removed")
149+
d.incompatible(objectWithSide{oldobj, false}, "", "removed")
139150
continue
140151
}
141152
d.checkObjects(oldobj, newobj)
@@ -144,7 +155,7 @@ func (d *differ) checkPackage() {
144155
for _, name := range d.new.Scope().Names() {
145156
newobj := d.new.Scope().Lookup(name)
146157
if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
147-
d.compatible(newobj, "", "added")
158+
d.compatible(objectWithSide{newobj, true}, "", "added")
148159
}
149160
}
150161

@@ -168,7 +179,7 @@ func (d *differ) checkPackage() {
168179
continue
169180
}
170181
if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2, nIface) {
171-
d.incompatible(otn2, "", "no longer implements %s", objectString(otn1))
182+
d.incompatible(objectWithSide{otn2, false}, "", "no longer implements %s", objectString(otn1, oldRootPackagePath))
172183
}
173184
}
174185
}
@@ -183,30 +194,30 @@ func (d *differ) checkObjects(old, new types.Object) {
183194
}
184195
case *types.Var:
185196
if new, ok := new.(*types.Var); ok {
186-
d.checkCorrespondence(old, "", old.Type(), new.Type())
197+
d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
187198
return
188199
}
189200
case *types.Func:
190201
switch new := new.(type) {
191202
case *types.Func:
192-
d.checkCorrespondence(old, "", old.Type(), new.Type())
203+
d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
193204
return
194205
case *types.Var:
195-
d.compatible(old, "", "changed from func to var")
196-
d.checkCorrespondence(old, "", old.Type(), new.Type())
206+
d.compatible(objectWithSide{old, false}, "", "changed from func to var")
207+
d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
197208
return
198209

199210
}
200211
case *types.TypeName:
201212
if new, ok := new.(*types.TypeName); ok {
202-
d.checkCorrespondence(old, "", old.Type(), new.Type())
213+
d.checkCorrespondence(objectWithSide{old, false}, "", old.Type(), new.Type())
203214
return
204215
}
205216
default:
206217
panic("unexpected obj type")
207218
}
208219
// Here if kind of type changed.
209-
d.incompatible(old, "", "changed from %s to %s",
220+
d.incompatible(objectWithSide{old, false}, "", "changed from %s to %s",
210221
objectKindString(old), objectKindString(new))
211222
}
212223

@@ -216,13 +227,13 @@ func (d *differ) constChanges(old, new *types.Const) {
216227
nt := new.Type()
217228
// Check for change of type.
218229
if !d.correspond(ot, nt) {
219-
d.typeChanged(old, "", ot, nt)
230+
d.typeChanged(objectWithSide{old, false}, "", ot, nt)
220231
return
221232
}
222233
// Check for change of value.
223234
// We know the types are the same, so constant.Compare shouldn't panic.
224235
if !constant.Compare(old.Val(), token.EQL, new.Val()) {
225-
d.incompatible(old, "", "value changed from %s to %s", old.Val(), new.Val())
236+
d.incompatible(objectWithSide{old, false}, "", "value changed from %s to %s", old.Val(), new.Val())
226237
}
227238
}
228239

@@ -241,13 +252,13 @@ func objectKindString(obj types.Object) string {
241252
}
242253
}
243254

244-
func (d *differ) checkCorrespondence(obj types.Object, part string, old, new types.Type) {
255+
func (d *differ) checkCorrespondence(obj objectWithSide, part string, old, new types.Type) {
245256
if !d.correspond(old, new) {
246257
d.typeChanged(obj, part, old, new)
247258
}
248259
}
249260

250-
func (d *differ) typeChanged(obj types.Object, part string, old, new types.Type) {
261+
func (d *differ) typeChanged(obj objectWithSide, part string, old, new types.Type) {
251262
old = removeNamesFromSignature(old)
252263
new = removeNamesFromSignature(new)
253264
olds := types.TypeString(old, types.RelativeTo(d.old))

apidiff/apidiff_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ func testModuleChanges(t *testing.T, x packagestest.Exporter) {
5353
t.Fatal("expected some changes, but got none")
5454
}
5555
wanti := []string{
56-
"Version: value changed from 1 to 2",
56+
"./foo.Version: value changed from 1 to 2",
5757
"package example.com/moda/foo/baz: removed",
5858
}
5959
sort.Strings(wanti)
@@ -66,7 +66,7 @@ func testModuleChanges(t *testing.T, x packagestest.Exporter) {
6666
}
6767

6868
wantc := []string{
69-
"Other: added",
69+
"./foo.Other: added",
7070
"package example.com/modb/bar: added",
7171
}
7272
sort.Strings(wantc)

apidiff/compatibility.go

+16-16
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) {
1616

1717
case *types.Struct:
1818
if new, ok := new.(*types.Struct); ok {
19-
d.checkCompatibleStruct(otn, old, new)
19+
d.checkCompatibleStruct(objectWithSide{otn, false}, old, new)
2020
return
2121
}
2222

@@ -36,21 +36,21 @@ func (d *differ) checkCompatible(otn *types.TypeName, old, new types.Type) {
3636
panic("unreachable")
3737

3838
default:
39-
d.checkCorrespondence(otn, "", old, new)
39+
d.checkCorrespondence(objectWithSide{otn, false}, "", old, new)
4040
return
4141

4242
}
4343
// Here if old and new are different kinds of types.
44-
d.typeChanged(otn, "", old, new)
44+
d.typeChanged(objectWithSide{otn, false}, "", old, new)
4545
}
4646

4747
func (d *differ) checkCompatibleChan(otn *types.TypeName, old, new *types.Chan) {
48-
d.checkCorrespondence(otn, ", element type", old.Elem(), new.Elem())
48+
d.checkCorrespondence(objectWithSide{otn, false}, ", element type", old.Elem(), new.Elem())
4949
if old.Dir() != new.Dir() {
5050
if new.Dir() == types.SendRecv {
51-
d.compatible(otn, "", "removed direction")
51+
d.compatible(objectWithSide{otn, false}, "", "removed direction")
5252
} else {
53-
d.incompatible(otn, "", "changed direction")
53+
d.incompatible(objectWithSide{otn, false}, "", "changed direction")
5454
}
5555
}
5656
}
@@ -63,9 +63,9 @@ func (d *differ) checkCompatibleBasic(otn *types.TypeName, old, new *types.Basic
6363
return
6464
}
6565
if compatibleBasics[[2]types.BasicKind{old.Kind(), new.Kind()}] {
66-
d.compatible(otn, "", "changed from %s to %s", old, new)
66+
d.compatible(objectWithSide{otn, false}, "", "changed from %s to %s", old, new)
6767
} else {
68-
d.typeChanged(otn, "", old, new)
68+
d.typeChanged(objectWithSide{otn, false}, "", old, new)
6969
}
7070
}
7171

@@ -118,7 +118,7 @@ func (d *differ) checkCompatibleInterface(otn *types.TypeName, old, new *types.I
118118
// Perform an equivalence check, but with more information.
119119
d.checkMethodSet(otn, old, new, additionsIncompatible)
120120
if u := unexportedMethod(new); u != nil {
121-
d.incompatible(otn, u.Name(), "added unexported method")
121+
d.incompatible(objectWithSide{otn, false}, u.Name(), "added unexported method")
122122
}
123123
}
124124
}
@@ -150,7 +150,7 @@ func unexportedMethod(t *types.Interface) *types.Func {
150150
// struct.
151151
//
152152
// Field tags are ignored: they have no compile-time implications.
153-
func (d *differ) checkCompatibleStruct(obj types.Object, old, new *types.Struct) {
153+
func (d *differ) checkCompatibleStruct(obj objectWithSide, old, new *types.Struct) {
154154
d.checkCompatibleObjectSets(obj, exportedFields(old), exportedFields(new))
155155
d.checkCompatibleObjectSets(obj, exportedSelectableFields(old), exportedSelectableFields(new))
156156
// Removing comparability from a struct is an incompatible change.
@@ -242,7 +242,7 @@ func unambiguousFields(structs []*types.Struct) map[string]*types.Var {
242242

243243
// Anything removed or change from the old set is an incompatible change.
244244
// Anything added to the new set is a compatible change.
245-
func (d *differ) checkCompatibleObjectSets(obj types.Object, old, new map[string]types.Object) {
245+
func (d *differ) checkCompatibleObjectSets(obj objectWithSide, old, new map[string]types.Object) {
246246
for name, oldo := range old {
247247
newo := new[name]
248248
if newo == nil {
@@ -303,16 +303,16 @@ func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addc
303303
if receiverNamedType(oldMethod).Obj() != otn {
304304
part = fmt.Sprintf(", method set of %s", msname)
305305
}
306-
d.incompatible(oldMethod, part, "removed")
306+
d.incompatible(objectWithSide{oldMethod, false}, part, "removed")
307307
} else {
308-
obj := oldMethod
308+
obj := objectWithSide{oldMethod, false}
309309
// If a value method is changed to a pointer method and has a signature
310310
// change, then we can get two messages for the same method definition: one
311311
// for the value method set that says it's removed, and another for the
312312
// pointer method set that says it changed. To keep both messages (since
313313
// messageSet dedups), use newMethod for the second. (Slight hack.)
314314
if !hasPointerReceiver(oldMethod) && hasPointerReceiver(newMethod) {
315-
obj = newMethod
315+
obj = objectWithSide{newMethod, true}
316316
}
317317
d.checkCorrespondence(obj, "", oldMethod.Type(), newMethod.Type())
318318
}
@@ -322,9 +322,9 @@ func (d *differ) checkMethodSet(otn *types.TypeName, oldt, newt types.Type, addc
322322
for name, newMethod := range newMethodSet {
323323
if oldMethodSet[name] == nil {
324324
if addcompat {
325-
d.compatible(newMethod, "", "added")
325+
d.compatible(objectWithSide{newMethod, true}, "", "added")
326326
} else {
327-
d.incompatible(newMethod, "", "added")
327+
d.incompatible(objectWithSide{newMethod, true}, "", "added")
328328
}
329329
}
330330
}

apidiff/messageset.go

+40-8
Original file line numberDiff line numberDiff line change
@@ -10,35 +10,49 @@ import (
1010
"strings"
1111
)
1212

13+
// objectWithSide contains an object, and information on which side (old or new)
14+
// of the comparison it relates to. This matters when need to express the object's
15+
// package path, relative to the root path of the comparison, as the old and new
16+
// sides can have different roots (e.g. comparing somepackage/v2 vs. somepackage/v3).
17+
type objectWithSide struct {
18+
object types.Object
19+
isNew bool
20+
}
21+
1322
// There can be at most one message for each object or part thereof.
1423
// Parts include interface methods and struct fields.
1524
//
1625
// The part thing is necessary. Method (Func) objects have sufficient info, but field
1726
// Vars do not: they just have a field name and a type, without the enclosing struct.
18-
type messageSet map[types.Object]map[string]string
27+
type messageSet map[objectWithSide]map[string]string
1928

2029
// Add a message for obj and part, overwriting a previous message
2130
// (shouldn't happen).
2231
// obj is required but part can be empty.
23-
func (m messageSet) add(obj types.Object, part, msg string) {
32+
func (m messageSet) add(obj objectWithSide, part, msg string) {
2433
s := m[obj]
2534
if s == nil {
2635
s = map[string]string{}
2736
m[obj] = s
2837
}
2938
if f, ok := s[part]; ok && f != msg {
30-
fmt.Printf("! second, different message for obj %s, part %q\n", obj, part)
39+
fmt.Printf("! second, different message for obj %s, isNew %v, part %q\n", obj.object, obj.isNew, part)
3140
fmt.Printf(" first: %s\n", f)
3241
fmt.Printf(" second: %s\n", msg)
3342
}
3443
s[part] = msg
3544
}
3645

37-
func (m messageSet) collect() []string {
46+
func (m messageSet) collect(oldRootPackagePath, newRootPackagePath string) []string {
3847
var s []string
3948
for obj, parts := range m {
49+
rootPackagePath := oldRootPackagePath
50+
if obj.isNew {
51+
rootPackagePath = newRootPackagePath
52+
}
53+
4054
// Format each object name relative to its own package.
41-
objstring := objectString(obj)
55+
objstring := objectString(obj.object, rootPackagePath)
4256
for part, msg := range parts {
4357
var p string
4458

@@ -54,18 +68,36 @@ func (m messageSet) collect() []string {
5468
return s
5569
}
5670

57-
func objectString(obj types.Object) string {
71+
func objectString(obj types.Object, rootPackagePath string) string {
72+
thisPackagePath := obj.Pkg().Path()
73+
74+
var packagePrefix string
75+
if thisPackagePath == rootPackagePath {
76+
// obj is in same package as the diff operation root - no prefix
77+
packagePrefix = ""
78+
} else if strings.HasPrefix(thisPackagePath, rootPackagePath+"/") {
79+
// obj is in a child package compared to the diff operation root - use a
80+
// prefix starting with "./" to emphasise the relative nature
81+
packagePrefix = "./" + thisPackagePath[len(rootPackagePath)+1:] + "."
82+
} else {
83+
// obj is outside the diff operation root - display full path. This can
84+
// happen if there is a need to report a change in a type in an unrelated
85+
// package, because it has been used as the underlying type in a type
86+
// definition in the package being processed, for example.
87+
packagePrefix = thisPackagePath + "."
88+
}
89+
5890
if f, ok := obj.(*types.Func); ok {
5991
sig := f.Type().(*types.Signature)
6092
if recv := sig.Recv(); recv != nil {
6193
tn := types.TypeString(recv.Type(), types.RelativeTo(obj.Pkg()))
6294
if tn[0] == '*' {
6395
tn = "(" + tn + ")"
6496
}
65-
return fmt.Sprintf("%s.%s", tn, obj.Name())
97+
return fmt.Sprintf("%s%s.%s", packagePrefix, tn, obj.Name())
6698
}
6799
}
68-
return obj.Name()
100+
return fmt.Sprintf("%s%s", packagePrefix, obj.Name())
69101
}
70102

71103
func dotjoin(s1, s2 string) string {

cmd/gorelease/testdata/private/break.test

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ success=false
55
-- want --
66
# example.com/private/p
77
## incompatible changes
8-
I.Foo: added
8+
example.com/private/internal/i.I.Foo: added
99

1010
# summary
1111
Cannot suggest a release version.

0 commit comments

Comments
 (0)