1 // Copyright 2024 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 package types_test
6 7 import (
8 "fmt"
9 "go/ast"
10 "go/token"
11 "reflect"
12 "regexp"
13 "strings"
14 "testing"
15 16 . "go/types"
17 )
18 19 // TestScopeLookupParent ensures that (*Scope).LookupParent returns
20 // the correct result at various positions with the source.
21 func TestScopeLookupParent(t *testing.T) {
22 fset := token.NewFileSet()
23 imports := make(testImporter)
24 conf := Config{Importer: imports}
25 var info Info
26 makePkg := func(path string, files ...*ast.File) {
27 var err error
28 imports[path], err = conf.Check(path, fset, files, &info)
29 if err != nil {
30 t.Fatal(err)
31 }
32 }
33 34 makePkg("lib", mustParse(fset, "package lib; var X int"))
35 // Each /*name=kind:line*/ comment makes the test look up the
36 // name at that point and checks that it resolves to a decl of
37 // the specified kind and line number. "undef" means undefined.
38 // Note that type switch case clauses with an empty body (but for
39 // comments) need the ";" to ensure that the recorded scope extends
40 // past the comments.
41 mainSrc := `
42 /*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/
43 package main
44 45 import "lib"
46 import . "lib"
47 48 const Pi = 3.1415
49 type T struct{}
50 var Y, _ = lib.X, X
51 52 func F[T *U, U any](param1, param2 int) /*param1=undef*/ (res1 /*res1=undef*/, res2 int) /*param1=var:12*/ /*res1=var:12*/ /*U=typename:12*/ {
53 const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/
54 type /*t=undef*/ t /*t=typename:14*/ *t
55 print(Y) /*Y=var:10*/
56 x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y
57 var F = /*F=func:12*/ F[*int, int] /*F=var:17*/ ; _ = F
58 59 var a []int
60 for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x }
61 62 var i interface{}
63 switch y := i.(type) { /*y=undef*/
64 case /*y=undef*/ int /*y=undef*/ : /*y=var:23*/ ;
65 case float32, /*y=undef*/ float64 /*y=undef*/ : /*y=var:23*/ ;
66 default /*y=undef*/ : /*y=var:23*/
67 println(y)
68 }
69 /*y=undef*/
70 71 switch int := i.(type) {
72 case /*int=typename:0*/ int /*int=typename:0*/ : /*int=var:31*/
73 println(int)
74 default /*int=typename:0*/ : /*int=var:31*/ ;
75 }
76 77 _ = param1
78 _ = res1
79 return
80 }
81 /*main=undef*/
82 `
83 84 info.Uses = make(map[*ast.Ident]Object)
85 f := mustParse(fset, mainSrc)
86 makePkg("main", f)
87 mainScope := imports["main"].Scope()
88 rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`)
89 for _, group := range f.Comments {
90 for _, comment := range group.List {
91 // Parse the assertion in the comment.
92 m := rx.FindStringSubmatch(comment.Text)
93 if m == nil {
94 t.Errorf("%s: bad comment: %s",
95 fset.Position(comment.Pos()), comment.Text)
96 continue
97 }
98 name, want := m[1], m[2]
99 100 // Look up the name in the innermost enclosing scope.
101 inner := mainScope.Innermost(comment.Pos())
102 if inner == nil {
103 t.Errorf("%s: at %s: can't find innermost scope",
104 fset.Position(comment.Pos()), comment.Text)
105 continue
106 }
107 got := "undef"
108 if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil {
109 kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
110 got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line)
111 }
112 if got != want {
113 t.Errorf("%s: at %s: %s resolved to %s, want %s",
114 fset.Position(comment.Pos()), comment.Text, name, got, want)
115 }
116 }
117 }
118 119 // Check that for each referring identifier,
120 // a lookup of its name on the innermost
121 // enclosing scope returns the correct object.
122 123 for id, wantObj := range info.Uses {
124 inner := mainScope.Innermost(id.Pos())
125 if inner == nil {
126 t.Errorf("%s: can't find innermost scope enclosing %q",
127 fset.Position(id.Pos()), id.Name)
128 continue
129 }
130 131 // Exclude selectors and qualified identifiers---lexical
132 // refs only. (Ideally, we'd see if the AST parent is a
133 // SelectorExpr, but that requires PathEnclosingInterval
134 // from golang.org/x/tools/go/ast/astutil.)
135 if id.Name == "X" {
136 continue
137 }
138 139 _, gotObj := inner.LookupParent(id.Name, id.Pos())
140 if gotObj != wantObj {
141 // Print the scope tree of mainScope in case of error.
142 var printScopeTree func(indent string, s *Scope)
143 printScopeTree = func(indent string, s *Scope) {
144 t.Logf("%sscope %s %v-%v = %v",
145 indent,
146 ScopeComment(s),
147 s.Pos(),
148 s.End(),
149 s.Names())
150 for i := range s.NumChildren() {
151 printScopeTree(indent+" ", s.Child(i))
152 }
153 }
154 printScopeTree("", mainScope)
155 156 t.Errorf("%s: Scope(%s).LookupParent(%s@%v) got %v, want %v [scopePos=%v]",
157 fset.Position(id.Pos()),
158 ScopeComment(inner),
159 id.Name,
160 id.Pos(),
161 gotObj,
162 wantObj,
163 ObjectScopePos(wantObj))
164 continue
165 }
166 }
167 }
168