GoPLS Viewer

Home|gopls/go/types/objectpath/objectpath_test.go
1// Copyright 2018 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
5package objectpath_test
6
7import (
8    "bytes"
9    "fmt"
10    "go/ast"
11    "go/importer"
12    "go/parser"
13    "go/token"
14    "go/types"
15    "strings"
16    "testing"
17
18    "golang.org/x/tools/go/buildutil"
19    "golang.org/x/tools/go/gcexportdata"
20    "golang.org/x/tools/go/loader"
21    "golang.org/x/tools/go/types/objectpath"
22)
23
24func TestPaths(t *testing.T) {
25    pkgs := map[string]map[string]string{
26        "b": {"b.go"`
27package b
28
29import "a"
30
31const C = a.Int(0)
32
33func F(a, b, c int, d a.T)
34
35type T struct{ A int; b int; a.T }
36
37func (T) M() *interface{ f() }
38
39type U T
40
41type A = struct{ x int }
42
43var V []*a.T
44
45type M map[struct{x int}]struct{y int}
46
47func unexportedFunc()
48type unexportedType struct{}
49
50type S struct{t struct{x int}}
51type R []struct{y int}
52type Q [2]struct{z int}
53`},
54        "a": {"a.go"`
55package a
56
57type Int int
58
59type T struct{x, y int}
60
61`},
62    }
63    paths := []pathTest{
64        // Good paths
65        {"b""C""const b.C a.Int"""},
66        {"b""F""func b.F(a int, b int, c int, d a.T)"""},
67        {"b""F.PA0""var a int"""},
68        {"b""F.PA1""var b int"""},
69        {"b""F.PA2""var c int"""},
70        {"b""F.PA3""var d a.T"""},
71        {"b""T""type b.T struct{A int; b int; a.T}"""},
72        {"b""T.O""type b.T struct{A int; b int; a.T}"""},
73        {"b""T.UF0""field A int"""},
74        {"b""T.UF1""field b int"""},
75        {"b""T.UF2""field T a.T"""},
76        {"b""U.UF2""field T a.T"""}, // U.U... are aliases for T.U...
77        {"b""A""type b.A = struct{x int}"""},
78        {"b""A.F0""field x int"""},
79        {"b""V""var b.V []*a.T"""},
80        {"b""M""type b.M map[struct{x int}]struct{y int}"""},
81        {"b""M.UKF0""field x int"""},
82        {"b""M.UEF0""field y int"""},
83        {"b""T.M0""func (b.T).M() *interface{f()}"""}, // concrete method
84        {"b""T.M0.RA0""var  *interface{f()}"""},       // parameter
85        {"b""T.M0.RA0.EM0""func (interface).f()"""},   // interface method
86        {"b""unexportedType""type b.unexportedType struct{}"""},
87        {"b""S.UF0.F0""field x int"""},
88        {"b""R.UEF0""field y int"""},
89        {"b""Q.UEF0""field z int"""},
90        {"a""T""type a.T struct{x int; y int}"""},
91        {"a""T.UF0""field x int"""},
92
93        // Bad paths
94        {"b""""""empty path"},
95        {"b""missing"""`package b does not contain "missing"`},
96        {"b""F.U""""invalid path: ends with 'U', want [AFMO]"},
97        {"b""F.PA3.O""""path denotes type a.T struct{x int; y int}, which belongs to a different package"},
98        {"b""F.PA!"""`invalid path: bad numeric operand "" for code 'A'`},
99        {"b""F.PA3.UF0""""path denotes field x int, which belongs to a different package"},
100        {"b""F.PA3.UF5""""field index 5 out of range [0-2)"},
101        {"b""V.EE""""invalid path: ends with 'E', want [AFMO]"},
102        {"b""F..O""""invalid path: unexpected '.' in type context"},
103        {"b""T.OO""""invalid path: code 'O' in object context"},
104        {"b""T.EO""""cannot apply 'E' to b.T (got *types.Named, want pointer, slice, array, chan or map)"},
105        {"b""A.O""""cannot apply 'O' to struct{x int} (got *types.Struct, want named or type param)"},
106        {"b""A.UF0""""cannot apply 'U' to struct{x int} (got *types.Struct, want named)"},
107        {"b""M.UPO""""cannot apply 'P' to map[struct{x int}]struct{y int} (got *types.Map, want signature)"},
108        {"b""C.O""""path denotes type a.Int int, which belongs to a different package"},
109        {"b""T.M9""""method index 9 out of range [0-1)"},
110        {"b""M.UF0""""cannot apply 'F' to map[struct{x int}]struct{y int} (got *types.Map, want struct)"},
111        {"b""V.KO""""cannot apply 'K' to []*a.T (got *types.Slice, want map)"},
112        {"b""V.A4""""cannot apply 'A' to []*a.T (got *types.Slice, want tuple)"},
113        {"b""V.RA0""""cannot apply 'R' to []*a.T (got *types.Slice, want signature)"},
114        {"b""F.PA4""""tuple index 4 out of range [0-4)"},
115        {"b""F.XO""""invalid path: unknown code 'X'"},
116    }
117    conf := loader.Config{Buildbuildutil.FakeContext(pkgs)}
118    conf.Import("a")
119    conf.Import("b")
120    progerr := conf.Load()
121    if err != nil {
122        t.Fatal(err)
123    }
124
125    for _test := range paths {
126        if err := testPath(progtest); err != nil {
127            t.Error(err)
128        }
129    }
130
131    // bad objects
132    bInfo := prog.Imported["b"]
133    for _test := range []struct {
134        obj     types.Object
135        wantErr string
136    }{
137        {types.Universe.Lookup("nil"), "predeclared nil has no path"},
138        {types.Universe.Lookup("len"), "predeclared builtin len has no path"},
139        {types.Universe.Lookup("int"), "predeclared type int has no path"},
140        {bInfo.Implicits[bInfo.Files[0].Imports[0]], "no path for package a"}, // import "a"
141        {bInfo.Pkg.Scope().Lookup("unexportedFunc"), "no path for non-exported func b.unexportedFunc()"},
142    } {
143        patherr := objectpath.For(test.obj)
144        if err == nil {
145            t.Errorf("Object(%s) = %q, want error"test.objpath)
146            continue
147        }
148        if err.Error() != test.wantErr {
149            t.Errorf("Object(%s) error was %q, want %q"test.objerrtest.wantErr)
150            continue
151        }
152    }
153}
154
155type pathTest struct {
156    pkg     string
157    path    objectpath.Path
158    wantobj string
159    wantErr string
160}
161
162func testPath(prog *loader.Programtest pathTesterror {
163    // We test objectpath by enumerating a set of paths
164    // and ensuring that Path(pkg, Object(pkg, path)) == path.
165    //
166    // It might seem more natural to invert the test:
167    // identify a set of objects and for each one,
168    // ensure that Object(pkg, Path(pkg, obj)) == obj.
169    // However, for most interesting test cases there is no
170    // easy way to identify the object short of applying
171    // a series of destructuring operations to pkg---which
172    // is essentially what objectpath.Object does.
173    // (We do a little of that when testing bad paths, below.)
174    //
175    // The downside is that the test depends on the path encoding.
176    // The upside is that the test exercises the encoding.
177
178    pkg := prog.Imported[test.pkg].Pkg
179    // check path -> object
180    objerr := objectpath.Object(pkgtest.path)
181    if (test.wantErr != "") != (err != nil) {
182        return fmt.Errorf("Object(%s, %q) returned error %q, want %q"pkg.Path(), test.patherrtest.wantErr)
183    }
184    if test.wantErr != "" {
185        if got := stripSubscripts(err.Error()); got != test.wantErr {
186            return fmt.Errorf("Object(%s, %q) error was %q, want %q",
187                pkg.Path(), test.pathgottest.wantErr)
188        }
189        return nil
190    }
191    // Inv: err == nil
192
193    if objString := stripSubscripts(obj.String()); objString != test.wantobj {
194        return fmt.Errorf("Object(%s, %q) = %s, want %s"pkg.Path(), test.pathobjStringtest.wantobj)
195    }
196    if obj.Pkg() != pkg {
197        return fmt.Errorf("Object(%s, %q) = %v, which belongs to package %s",
198            pkg.Path(), test.pathobjobj.Pkg().Path())
199    }
200
201    // check object -> path
202    path2err := objectpath.For(obj)
203    if err != nil {
204        return fmt.Errorf("For(%v) failed: %v, want %q"objerrtest.path)
205    }
206    // We do not require that test.path == path2. Aliases are legal.
207    // But we do require that Object(path2) finds the same object.
208    obj2err := objectpath.Object(pkgpath2)
209    if err != nil {
210        return fmt.Errorf("Object(%s, %q) failed: %v (roundtrip from %q)"pkg.Path(), path2errtest.path)
211    }
212    if obj2 != obj {
213        return fmt.Errorf("Object(%s, For(obj)) != obj: got %s, obj is %s (path1=%q, path2=%q)"pkg.Path(), obj2objtest.pathpath2)
214    }
215    return nil
216}
217
218// stripSubscripts removes type parameter id subscripts.
219//
220// TODO(rfindley): remove this function once subscripts are removed from the
221// type parameter type string.
222func stripSubscripts(s stringstring {
223    var runes []rune
224    for _r := range s {
225        // For debugging/uniqueness purposes, TypeString on a type parameter adds a
226        // subscript corresponding to the type parameter's unique id. This is going
227        // to be removed, but in the meantime we skip the subscript runes to get a
228        // deterministic output.
229        if '₀' <= r && r < '₀'+10 {
230            continue // trim type parameter subscripts
231        }
232        runes = append(runesr)
233    }
234    return string(runes)
235}
236
237// TestSourceAndExportData uses objectpath to compute a correspondence
238// of objects between two versions of the same package, one loaded from
239// source, the other from export data.
240func TestSourceAndExportData(t *testing.T) {
241    const src = `
242package p
243
244type I int
245
246func (I) F() *struct{ X, Y int } {
247    return nil
248}
249
250type Foo interface {
251    Method() (string, func(int) struct{ X int })
252}
253
254var X chan struct{ Z int }
255var Z map[string]struct{ A int }
256`
257
258    // Parse source file and type-check it as a package, "src".
259    fset := token.NewFileSet()
260    ferr := parser.ParseFile(fset"src.go"src0)
261    if err != nil {
262        t.Fatal(err)
263    }
264    conf := types.Config{Importerimporter.For("source"nil)}
265    info := &types.Info{
266        Defsmake(map[*ast.Ident]types.Object),
267    }
268    srcpkgerr := conf.Check("src/p"fset, []*ast.File{f}, info)
269    if err != nil {
270        t.Fatal(err)
271    }
272
273    // Export binary export data then reload it as a new package, "bin".
274    var buf bytes.Buffer
275    if err := gcexportdata.Write(&buffsetsrcpkg); err != nil {
276        t.Fatal(err)
277    }
278
279    imports := make(map[string]*types.Package)
280    binpkgerr := gcexportdata.Read(&buffsetimports"bin/p")
281    if err != nil {
282        t.Fatal(err)
283    }
284
285    // Now find the correspondences between them.
286    for _srcobj := range info.Defs {
287        if srcobj == nil {
288            continue // e.g. package declaration
289        }
290        if _ok := srcobj.(*types.PkgName); ok {
291            continue // PkgName has no objectpath
292        }
293
294        patherr := objectpath.For(srcobj)
295        if err != nil {
296            t.Errorf("For(%v): %v"srcobjerr)
297            continue
298        }
299        binobjerr := objectpath.Object(binpkgpath)
300        if err != nil {
301            t.Errorf("Object(%s, %q): %v"binpkg.Path(), patherr)
302            continue
303        }
304
305        // Check the object strings match.
306        // (We can't check that types are identical because the
307        // objects belong to different type-checker realms.)
308        srcstr := objectString(srcobj)
309        binstr := objectString(binobj)
310        if srcstr != binstr {
311            t.Errorf("ObjectStrings do not match: Object(For(%q)) = %s, want %s",
312                pathsrcstrbinstr)
313            continue
314        }
315    }
316}
317
318func objectString(obj types.Objectstring {
319    s := types.ObjectString(obj, (*types.Package).Name)
320
321    // The printing of interface methods changed in go1.11.
322    // This work-around makes the specific test pass with earlier versions.
323    s = strings.Replace(s"func (interface).Method""func (p.Foo).Method", -1)
324
325    return s
326}
327
328// TestOrdering uses objectpath over two Named types with the same method
329// names but in a different source order and checks that objectpath is the
330// same for methods with the same name.
331func TestOrdering(t *testing.T) {
332    pkgs := map[string]map[string]string{
333        "p": {"p.go"`
334package p
335
336type T struct{ A int }
337
338func (T) M() { }
339func (T) N() { }
340func (T) X() { }
341func (T) Y() { }
342`},
343        "q": {"q.go"`
344package q
345
346type T struct{ A int }
347
348func (T) N() { }
349func (T) M() { }
350func (T) Y() { }
351func (T) X() { }
352`}}
353    conf := loader.Config{Buildbuildutil.FakeContext(pkgs)}
354    conf.Import("p")
355    conf.Import("q")
356    progerr := conf.Load()
357    if err != nil {
358        t.Fatal(err)
359    }
360    p := prog.Imported["p"].Pkg
361    q := prog.Imported["q"].Pkg
362
363    // From here, the objectpaths generated for p and q should be the
364    // same. If they are not, then we are generating an ordering that is
365    // dependent on the declaration of the types within the file.
366    for _test := range []struct {
367        path objectpath.Path
368    }{
369        {"T.M0"},
370        {"T.M1"},
371        {"T.M2"},
372        {"T.M3"},
373    } {
374        pobjerr := objectpath.Object(ptest.path)
375        if err != nil {
376            t.Errorf("Object(%s) failed in a1: %v"test.patherr)
377            continue
378        }
379        qobjerr := objectpath.Object(qtest.path)
380        if err != nil {
381            t.Errorf("Object(%s) failed in a2: %v"test.patherr)
382            continue
383        }
384        if pobj.Name() != pobj.Name() {
385            t.Errorf("Objects(%s) not equal, got a1 = %v, a2 = %v"test.pathpobj.Name(), qobj.Name())
386        }
387    }
388}
389
MembersX
TestPaths.err
TestSourceAndExportData
TestOrdering.q
TestOrdering.RangeStmt_11264.BlockStmt.qobj
TestPaths.t
TestSourceAndExportData.imports
TestOrdering.RangeStmt_11264.test
testPath.test
testPath.objString
stripSubscripts.s
TestSourceAndExportData.err
TestPaths.paths
TestPaths.RangeStmt_4328.test
pathTest.wantobj
testPath.prog
pathTest
testPath.path2
TestSourceAndExportData.RangeStmt_9208.BlockStmt.binobj
TestOrdering.pkgs
parser
token
gcexportdata
TestPaths.RangeStmt_4182.BlockStmt.err
TestSourceAndExportData.srcpkg
TestOrdering.conf
TestOrdering.p
TestPaths.conf
pathTest.wantErr
testPath.obj
TestSourceAndExportData.RangeStmt_9208.BlockStmt.srcstr
importer
TestPaths.RangeStmt_4328.BlockStmt.err
TestSourceAndExportData.RangeStmt_9208.BlockStmt.err
TestPaths.pkgs
testPath.BlockStmt.got
TestSourceAndExportData.fset
TestSourceAndExportData.info
TestSourceAndExportData.binpkg
ast
stripSubscripts
stripSubscripts.runes
TestOrdering
TestOrdering.prog
TestPaths.RangeStmt_4328.BlockStmt.path
testPath.err
TestSourceAndExportData.t
TestSourceAndExportData.f
TestSourceAndExportData.conf
TestSourceAndExportData.RangeStmt_9208.BlockStmt.binstr
objectString
objectString.obj
TestOrdering.t
TestOrdering.err
pathTest.path
TestSourceAndExportData.src
TestSourceAndExportData.buf
TestSourceAndExportData.RangeStmt_9208.srcobj
TestPaths.RangeStmt_4182.test
testPath
TestOrdering.RangeStmt_11264.BlockStmt.err
bytes
TestPaths
TestPaths.prog
testPath.pkg
testPath.obj2
stripSubscripts.RangeStmt_7567.r
TestSourceAndExportData.RangeStmt_9208.BlockStmt.path
TestOrdering.RangeStmt_11264.BlockStmt.pobj
pathTest.pkg
objectString.s
Members
X