.gitignore added
[dotfiles/.git] / .config / coc / extensions / coc-go-data / tools / pkg / mod / honnef.co / go / tools@v0.1.1 / go / ir / source_test.go
1 // Copyright 2013 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 //lint:file-ignore SA1019 go/ssa's test suite is built around the deprecated go/loader. We'll leave fixing that to upstream.
6
7 package ir_test
8
9 // This file defines tests of source-level debugging utilities.
10
11 import (
12         "fmt"
13         "go/ast"
14         "go/constant"
15         "go/parser"
16         "go/token"
17         "go/types"
18         "io/ioutil"
19         "os"
20         "runtime"
21         "strings"
22         "testing"
23
24         "honnef.co/go/tools/go/ir"
25         "honnef.co/go/tools/go/ir/irutil"
26
27         "golang.org/x/tools/go/ast/astutil"
28         "golang.org/x/tools/go/expect"
29         "golang.org/x/tools/go/loader"
30 )
31
32 func TestObjValueLookup(t *testing.T) {
33         if runtime.GOOS == "android" {
34                 t.Skipf("no testdata directory on %s", runtime.GOOS)
35         }
36
37         conf := loader.Config{ParserMode: parser.ParseComments}
38         src, err := ioutil.ReadFile("testdata/objlookup.go")
39         if err != nil {
40                 t.Fatal(err)
41         }
42         readFile := func(filename string) ([]byte, error) { return src, nil }
43         f, err := conf.ParseFile("testdata/objlookup.go", src)
44         if err != nil {
45                 t.Fatal(err)
46         }
47         conf.CreateFromFiles("main", f)
48
49         // Maps each var Ident (represented "name:linenum") to the
50         // kind of ir.Value we expect (represented "Constant", "&Alloc").
51         expectations := make(map[string]string)
52
53         // Each note of the form @ir(x, "BinOp") in testdata/objlookup.go
54         // specifies an expectation that an object named x declared on the
55         // same line is associated with an an ir.Value of type *ir.BinOp.
56         notes, err := expect.ExtractGo(conf.Fset, f)
57         if err != nil {
58                 t.Fatal(err)
59         }
60         for _, n := range notes {
61                 if n.Name != "ir" {
62                         t.Errorf("%v: unexpected note type %q, want \"ir\"", conf.Fset.Position(n.Pos), n.Name)
63                         continue
64                 }
65                 if len(n.Args) != 2 {
66                         t.Errorf("%v: ir has %d args, want 2", conf.Fset.Position(n.Pos), len(n.Args))
67                         continue
68                 }
69                 ident, ok := n.Args[0].(expect.Identifier)
70                 if !ok {
71                         t.Errorf("%v: got %v for arg 1, want identifier", conf.Fset.Position(n.Pos), n.Args[0])
72                         continue
73                 }
74                 exp, ok := n.Args[1].(string)
75                 if !ok {
76                         t.Errorf("%v: got %v for arg 2, want string", conf.Fset.Position(n.Pos), n.Args[1])
77                         continue
78                 }
79                 p, _, err := expect.MatchBefore(conf.Fset, readFile, n.Pos, string(ident))
80                 if err != nil {
81                         t.Error(err)
82                         continue
83                 }
84                 pos := conf.Fset.Position(p)
85                 key := fmt.Sprintf("%s:%d", ident, pos.Line)
86                 expectations[key] = exp
87         }
88
89         iprog, err := conf.Load()
90         if err != nil {
91                 t.Error(err)
92                 return
93         }
94
95         prog := irutil.CreateProgram(iprog, 0 /*|ir.PrintFunctions*/)
96         mainInfo := iprog.Created[0]
97         mainPkg := prog.Package(mainInfo.Pkg)
98         mainPkg.SetDebugMode(true)
99         mainPkg.Build()
100
101         var varIds []*ast.Ident
102         var varObjs []*types.Var
103         for id, obj := range mainInfo.Defs {
104                 // Check invariants for func and const objects.
105                 switch obj := obj.(type) {
106                 case *types.Func:
107                         checkFuncValue(t, prog, obj)
108
109                 case *types.Const:
110                         checkConstValue(t, prog, obj)
111
112                 case *types.Var:
113                         if id.Name == "_" {
114                                 continue
115                         }
116                         varIds = append(varIds, id)
117                         varObjs = append(varObjs, obj)
118                 }
119         }
120         for id, obj := range mainInfo.Uses {
121                 if obj, ok := obj.(*types.Var); ok {
122                         varIds = append(varIds, id)
123                         varObjs = append(varObjs, obj)
124                 }
125         }
126
127         // Check invariants for var objects.
128         // The result varies based on the specific Ident.
129         for i, id := range varIds {
130                 obj := varObjs[i]
131                 ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos())
132                 pos := prog.Fset.Position(id.Pos())
133                 exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
134                 if exp == "" {
135                         t.Errorf("%s: no expectation for var ident %s ", pos, id.Name)
136                         continue
137                 }
138                 wantAddr := false
139                 if exp[0] == '&' {
140                         wantAddr = true
141                         exp = exp[1:]
142                 }
143                 checkVarValue(t, prog, mainPkg, ref, obj, exp, wantAddr)
144         }
145 }
146
147 func checkFuncValue(t *testing.T, prog *ir.Program, obj *types.Func) {
148         fn := prog.FuncValue(obj)
149         // fmt.Printf("FuncValue(%s) = %s\n", obj, fn) // debugging
150         if fn == nil {
151                 if obj.Name() != "interfaceMethod" {
152                         t.Errorf("FuncValue(%s) == nil", obj)
153                 }
154                 return
155         }
156         if fnobj := fn.Object(); fnobj != obj {
157                 t.Errorf("FuncValue(%s).Object() == %s; value was %s",
158                         obj, fnobj, fn.Name())
159                 return
160         }
161         if !types.Identical(fn.Type(), obj.Type()) {
162                 t.Errorf("FuncValue(%s).Type() == %s", obj, fn.Type())
163                 return
164         }
165 }
166
167 func checkConstValue(t *testing.T, prog *ir.Program, obj *types.Const) {
168         c := prog.ConstValue(obj)
169         // fmt.Printf("ConstValue(%s) = %s\n", obj, c) // debugging
170         if c == nil {
171                 t.Errorf("ConstValue(%s) == nil", obj)
172                 return
173         }
174         if !types.Identical(c.Type(), obj.Type()) {
175                 t.Errorf("ConstValue(%s).Type() == %s", obj, c.Type())
176                 return
177         }
178         if obj.Name() != "nil" {
179                 if !constant.Compare(c.Value, token.EQL, obj.Val()) {
180                         t.Errorf("ConstValue(%s).Value (%s) != %s",
181                                 obj, c.Value, obj.Val())
182                         return
183                 }
184         }
185 }
186
187 func checkVarValue(t *testing.T, prog *ir.Program, pkg *ir.Package, ref []ast.Node, obj *types.Var, expKind string, wantAddr bool) {
188         // The prefix of all assertions messages.
189         prefix := fmt.Sprintf("VarValue(%s @ L%d)",
190                 obj, prog.Fset.Position(ref[0].Pos()).Line)
191
192         v, gotAddr := prog.VarValue(obj, pkg, ref)
193
194         // Kind is the concrete type of the ir Value.
195         gotKind := "nil"
196         if v != nil {
197                 gotKind = fmt.Sprintf("%T", v)[len("*ir."):]
198         }
199
200         // fmt.Printf("%s = %v (kind %q; expect %q) wantAddr=%t gotAddr=%t\n", prefix, v, gotKind, expKind, wantAddr, gotAddr) // debugging
201
202         // Check the kinds match.
203         // "nil" indicates expected failure (e.g. optimized away).
204         if expKind != gotKind {
205                 t.Errorf("%s concrete type == %s, want %s", prefix, gotKind, expKind)
206         }
207
208         // Check the types match.
209         // If wantAddr, the expected type is the object's address.
210         if v != nil {
211                 expType := obj.Type()
212                 if wantAddr {
213                         expType = types.NewPointer(expType)
214                         if !gotAddr {
215                                 t.Errorf("%s: got value, want address", prefix)
216                         }
217                 } else if gotAddr {
218                         t.Errorf("%s: got address, want value", prefix)
219                 }
220                 if !types.Identical(v.Type(), expType) {
221                         t.Errorf("%s.Type() == %s, want %s", prefix, v.Type(), expType)
222                 }
223         }
224 }
225
226 // Ensure that, in debug mode, we can determine the ir.Value
227 // corresponding to every ast.Expr.
228 func TestValueForExpr(t *testing.T) {
229         testValueForExpr(t, "testdata/valueforexpr.go")
230 }
231
232 func testValueForExpr(t *testing.T, testfile string) {
233         if runtime.GOOS == "android" {
234                 t.Skipf("no testdata dir on %s", runtime.GOOS)
235         }
236
237         conf := loader.Config{ParserMode: parser.ParseComments}
238         f, err := conf.ParseFile(testfile, nil)
239         if err != nil {
240                 t.Error(err)
241                 return
242         }
243         conf.CreateFromFiles("main", f)
244
245         iprog, err := conf.Load()
246         if err != nil {
247                 t.Error(err)
248                 return
249         }
250
251         mainInfo := iprog.Created[0]
252
253         prog := irutil.CreateProgram(iprog, 0)
254         mainPkg := prog.Package(mainInfo.Pkg)
255         mainPkg.SetDebugMode(true)
256         mainPkg.Build()
257
258         if false {
259                 // debugging
260                 for _, mem := range mainPkg.Members {
261                         if fn, ok := mem.(*ir.Function); ok {
262                                 fn.WriteTo(os.Stderr)
263                         }
264                 }
265         }
266
267         var parenExprs []*ast.ParenExpr
268         ast.Inspect(f, func(n ast.Node) bool {
269                 if n != nil {
270                         if e, ok := n.(*ast.ParenExpr); ok {
271                                 parenExprs = append(parenExprs, e)
272                         }
273                 }
274                 return true
275         })
276
277         notes, err := expect.ExtractGo(prog.Fset, f)
278         if err != nil {
279                 t.Fatal(err)
280         }
281         for _, n := range notes {
282                 want := n.Name
283                 if want == "nil" {
284                         want = "<nil>"
285                 }
286                 position := prog.Fset.Position(n.Pos)
287                 var e ast.Expr
288                 for _, paren := range parenExprs {
289                         if paren.Pos() > n.Pos {
290                                 e = paren.X
291                                 break
292                         }
293                 }
294                 if e == nil {
295                         t.Errorf("%s: note doesn't precede ParenExpr: %q", position, want)
296                         continue
297                 }
298
299                 path, _ := astutil.PathEnclosingInterval(f, n.Pos, n.Pos)
300                 if path == nil {
301                         t.Errorf("%s: can't find AST path from root to comment: %s", position, want)
302                         continue
303                 }
304
305                 fn := ir.EnclosingFunction(mainPkg, path)
306                 if fn == nil {
307                         t.Errorf("%s: can't find enclosing function", position)
308                         continue
309                 }
310
311                 v, gotAddr := fn.ValueForExpr(e) // (may be nil)
312                 got := strings.TrimPrefix(fmt.Sprintf("%T", v), "*ir.")
313                 if got != want {
314                         t.Errorf("%s: got value %q, want %q", position, got, want)
315                 }
316                 if v != nil {
317                         T := v.Type()
318                         if gotAddr {
319                                 T = T.Underlying().(*types.Pointer).Elem() // deref
320                         }
321                         if !types.Identical(T, mainInfo.TypeOf(e)) {
322                                 t.Errorf("%s: got type %s, want %s", position, mainInfo.TypeOf(e), T)
323                         }
324                 }
325         }
326 }
327
328 // findInterval parses input and returns the [start, end) positions of
329 // the first occurrence of substr in input.  f==nil indicates failure;
330 // an error has already been reported in that case.
331 //
332 func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
333         f, err := parser.ParseFile(fset, "<input>", input, 0)
334         if err != nil {
335                 t.Errorf("parse error: %s", err)
336                 return
337         }
338
339         i := strings.Index(input, substr)
340         if i < 0 {
341                 t.Errorf("%q is not a substring of input", substr)
342                 f = nil
343                 return
344         }
345
346         filePos := fset.File(f.Package)
347         return f, filePos.Pos(i), filePos.Pos(i + len(substr))
348 }
349
350 func TestEnclosingFunction(t *testing.T) {
351         tests := []struct {
352                 input  string // the input file
353                 substr string // first occurrence of this string denotes interval
354                 fn     string // name of expected containing function
355         }{
356                 // We use distinctive numbers as syntactic landmarks.
357
358                 // Ordinary function:
359                 {`package main
360                   func f() { println(1003) }`,
361                         "100", "main.f"},
362                 // Methods:
363                 {`package main
364                   type T int
365                   func (t T) f() { println(200) }`,
366                         "200", "(main.T).f"},
367                 // Function literal:
368                 {`package main
369                   func f() { println(func() { print(300) }) }`,
370                         "300", "main.f$1"},
371                 // Doubly nested
372                 {`package main
373                   func f() { println(func() { print(func() { print(350) })})}`,
374                         "350", "main.f$1$1"},
375                 // Implicit init for package-level var initializer.
376                 {"package main; var a = 400", "400", "main.init"},
377                 // No code for constants:
378                 {"package main; const a = 500", "500", "(none)"},
379                 // Explicit init()
380                 {"package main; func init() { println(600) }", "600", "main.init#1"},
381                 // Multiple explicit init functions:
382                 {`package main
383                   func init() { println("foo") }
384                   func init() { println(800) }`,
385                         "800", "main.init#2"},
386                 // init() containing FuncLit.
387                 {`package main
388                   func init() { println(func(){print(900)}) }`,
389                         "900", "main.init#1$1"},
390         }
391         for _, test := range tests {
392                 conf := loader.Config{Fset: token.NewFileSet()}
393                 f, start, end := findInterval(t, conf.Fset, test.input, test.substr)
394                 if f == nil {
395                         continue
396                 }
397                 path, exact := astutil.PathEnclosingInterval(f, start, end)
398                 if !exact {
399                         t.Errorf("EnclosingFunction(%q) not exact", test.substr)
400                         continue
401                 }
402
403                 conf.CreateFromFiles("main", f)
404
405                 iprog, err := conf.Load()
406                 if err != nil {
407                         t.Error(err)
408                         continue
409                 }
410                 prog := irutil.CreateProgram(iprog, 0)
411                 pkg := prog.Package(iprog.Created[0].Pkg)
412                 pkg.Build()
413
414                 name := "(none)"
415                 fn := ir.EnclosingFunction(pkg, path)
416                 if fn != nil {
417                         name = fn.String()
418                 }
419
420                 if name != test.fn {
421                         t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
422                                 test.substr, test.input, name, test.fn)
423                         continue
424                 }
425
426                 // While we're here: test HasEnclosingFunction.
427                 if has := ir.HasEnclosingFunction(pkg, path); has != (fn != nil) {
428                         t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
429                                 test.substr, test.input, has, fn != nil)
430                         continue
431                 }
432         }
433 }