GoPLS Viewer

Home|gopls/internal/apidiff/apidiff.go
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
15package apidiff
16
17import (
18    "fmt"
19    "go/constant"
20    "go/token"
21    "go/types"
22)
23
24// Changes reports on the differences between the APIs of the old and new packages.
25// It classifies each difference as either compatible or incompatible (breaking.) For
26// a detailed discussion of what constitutes an incompatible change, see the package
27// documentation.
28func Changes(oldnew *types.PackageReport {
29    d := newDiffer(oldnew)
30    d.checkPackage()
31    r := Report{}
32    for _m := range d.incompatibles.collect() {
33        r.Changes = append(r.ChangesChange{MessagemCompatiblefalse})
34    }
35    for _m := range d.compatibles.collect() {
36        r.Changes = append(r.ChangesChange{MessagemCompatibletrue})
37    }
38    return r
39}
40
41type differ struct {
42    oldnew *types.Package
43    // Correspondences between named types.
44    // Even though it is the named types (*types.Named) that correspond, we use
45    // *types.TypeName as a map key because they are canonical.
46    // The values can be either named types or basic types.
47    correspondMap map[*types.TypeName]types.Type
48
49    // Messages.
50    incompatibles messageSet
51    compatibles   messageSet
52}
53
54func newDiffer(oldnew *types.Package) *differ {
55    return &differ{
56        old:           old,
57        new:           new,
58        correspondMap: map[*types.TypeName]types.Type{},
59        incompatiblesmessageSet{},
60        compatibles:   messageSet{},
61    }
62}
63
64func (d *differincompatible(obj types.Objectpartformat stringargs ...interface{}) {
65    addMessage(d.incompatiblesobjpartformatargs)
66}
67
68func (d *differcompatible(obj types.Objectpartformat stringargs ...interface{}) {
69    addMessage(d.compatiblesobjpartformatargs)
70}
71
72func addMessage(ms messageSetobj types.Objectpartformat stringargs []interface{}) {
73    ms.add(objpartfmt.Sprintf(formatargs...))
74}
75
76func (d *differcheckPackage() {
77    // Old changes.
78    for _name := range d.old.Scope().Names() {
79        oldobj := d.old.Scope().Lookup(name)
80        if !oldobj.Exported() {
81            continue
82        }
83        newobj := d.new.Scope().Lookup(name)
84        if newobj == nil {
85            d.incompatible(oldobj"""removed")
86            continue
87        }
88        d.checkObjects(oldobjnewobj)
89    }
90    // New additions.
91    for _name := range d.new.Scope().Names() {
92        newobj := d.new.Scope().Lookup(name)
93        if newobj.Exported() && d.old.Scope().Lookup(name) == nil {
94            d.compatible(newobj"""added")
95        }
96    }
97
98    // Whole-package satisfaction.
99    // For every old exposed interface oIface and its corresponding new interface nIface...
100    for otn1nt1 := range d.correspondMap {
101        oIfaceok := otn1.Type().Underlying().(*types.Interface)
102        if !ok {
103            continue
104        }
105        nIfaceok := nt1.Underlying().(*types.Interface)
106        if !ok {
107            // If nt1 isn't an interface but otn1 is, then that's an incompatibility that
108            // we've already noticed, so there's no need to do anything here.
109            continue
110        }
111        // For every old type that implements oIface, its corresponding new type must implement
112        // nIface.
113        for otn2nt2 := range d.correspondMap {
114            if otn1 == otn2 {
115                continue
116            }
117            if types.Implements(otn2.Type(), oIface) && !types.Implements(nt2nIface) {
118                d.incompatible(otn2"""no longer implements %s"objectString(otn1))
119            }
120        }
121    }
122}
123
124func (d *differcheckObjects(oldnew types.Object) {
125    switch old := old.(type) {
126    case *types.Const:
127        if newok := new.(*types.Const); ok {
128            d.constChanges(oldnew)
129            return
130        }
131    case *types.Var:
132        if newok := new.(*types.Var); ok {
133            d.checkCorrespondence(old""old.Type(), new.Type())
134            return
135        }
136    case *types.Func:
137        switch new := new.(type) {
138        case *types.Func:
139            d.checkCorrespondence(old""old.Type(), new.Type())
140            return
141        case *types.Var:
142            d.compatible(old"""changed from func to var")
143            d.checkCorrespondence(old""old.Type(), new.Type())
144            return
145
146        }
147    case *types.TypeName:
148        if newok := new.(*types.TypeName); ok {
149            d.checkCorrespondence(old""old.Type(), new.Type())
150            return
151        }
152    default:
153        panic("unexpected obj type")
154    }
155    // Here if kind of type changed.
156    d.incompatible(old"""changed from %s to %s",
157        objectKindString(old), objectKindString(new))
158}
159
160// Compare two constants.
161func (d *differconstChanges(oldnew *types.Const) {
162    ot := old.Type()
163    nt := new.Type()
164    // Check for change of type.
165    if !d.correspond(otnt) {
166        d.typeChanged(old""otnt)
167        return
168    }
169    // Check for change of value.
170    // We know the types are the same, so constant.Compare shouldn't panic.
171    if !constant.Compare(old.Val(), token.EQLnew.Val()) {
172        d.incompatible(old"""value changed from %s to %s"old.Val(), new.Val())
173    }
174}
175
176func objectKindString(obj types.Objectstring {
177    switch obj.(type) {
178    case *types.Const:
179        return "const"
180    case *types.Var:
181        return "var"
182    case *types.Func:
183        return "func"
184    case *types.TypeName:
185        return "type"
186    default:
187        return "???"
188    }
189}
190
191func (d *differcheckCorrespondence(obj types.Objectpart stringoldnew types.Type) {
192    if !d.correspond(oldnew) {
193        d.typeChanged(objpartoldnew)
194    }
195}
196
197func (d *differtypeChanged(obj types.Objectpart stringoldnew types.Type) {
198    old = removeNamesFromSignature(old)
199    new = removeNamesFromSignature(new)
200    olds := types.TypeString(oldtypes.RelativeTo(d.old))
201    news := types.TypeString(newtypes.RelativeTo(d.new))
202    d.incompatible(objpart"changed from %s to %s"oldsnews)
203}
204
205// go/types always includes the argument and result names when formatting a signature.
206// Since these can change without affecting compatibility, we don't want users to
207// be distracted by them, so we remove them.
208func removeNamesFromSignature(t types.Typetypes.Type {
209    sigok := t.(*types.Signature)
210    if !ok {
211        return t
212    }
213
214    dename := func(p *types.Tuple) *types.Tuple {
215        var vars []*types.Var
216        for i := 0i < p.Len(); i++ {
217            v := p.At(i)
218            vars = append(varstypes.NewVar(v.Pos(), v.Pkg(), ""v.Type()))
219        }
220        return types.NewTuple(vars...)
221    }
222
223    return types.NewSignature(sig.Recv(), dename(sig.Params()), dename(sig.Results()), sig.Variadic())
224}
225
MembersX
Changes.d
differ.checkObjects.d
differ.incompatible.args
differ.compatible.d
differ.checkPackage.RangeStmt_2738.name
differ.checkPackage.RangeStmt_2738.BlockStmt.newobj
differ.typeChanged.part
addMessage.obj
differ.checkPackage.RangeStmt_3037.BlockStmt.newobj
differ.checkCorrespondence.d
differ.typeChanged.new
objectKindString.obj
differ.checkCorrespondence.old
differ.old
differ.compatible.obj
differ.checkPackage.RangeStmt_3350.nt1
differ.checkPackage.RangeStmt_3350.BlockStmt.RangeStmt_3812.otn2
differ.checkObjects
differ.checkCorrespondence.new
differ.new
newDiffer
differ.compatible
differ.compatible.format
differ.checkPackage.d
differ.compatible.args
differ.constChanges
differ.constChanges.old
differ.typeChanged.olds
differ.checkCorrespondence.part
removeNamesFromSignature.BlockStmt.BlockStmt.v
differ.incompatible.obj
addMessage.format
addMessage.args
differ.checkPackage.RangeStmt_3350.otn1
objectKindString
Changes.r
newDiffer.new
differ.incompatible.part
token
Changes.old
Changes.RangeStmt_1490.m
differ
removeNamesFromSignature.BlockStmt.vars
differ.incompatibles
newDiffer.old
differ.typeChanged.d
differ.typeChanged.obj
differ.typeChanged.old
fmt
addMessage.part
differ.checkPackage
differ.checkPackage.RangeStmt_2738.BlockStmt.oldobj
differ.checkPackage.RangeStmt_3350.BlockStmt.RangeStmt_3812.nt2
constant
differ.incompatible.d
differ.incompatible
differ.incompatible.format
addMessage
differ.checkCorrespondence.obj
differ.typeChanged
removeNamesFromSignature
removeNamesFromSignature.t
removeNamesFromSignature.BlockStmt.i
differ.constChanges.nt
differ.checkCorrespondence
differ.typeChanged.news
types
Changes.new
differ.correspondMap
addMessage.ms
differ.constChanges.new
differ.compatibles
differ.constChanges.d
differ.checkObjects.new
differ.constChanges.ot
Changes
Changes.RangeStmt_1369.m
differ.compatible.part
differ.checkPackage.RangeStmt_3037.name
differ.checkObjects.old
Members
X