| // Copyright 2024 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package types_test |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/token" |
| "reflect" |
| "regexp" |
| "strings" |
| "testing" |
| |
| . "go/types" |
| ) |
| |
| // TestScopeLookupParent ensures that (*Scope).LookupParent returns |
| // the correct result at various positions with the source. |
| func TestScopeLookupParent(t *testing.T) { |
| fset := token.NewFileSet() |
| imports := make(testImporter) |
| conf := Config{Importer: imports} |
| var info Info |
| makePkg := func(path string, files ...*ast.File) { |
| var err error |
| imports[path], err = conf.Check(path, fset, files, &info) |
| if err != nil { |
| t.Fatal(err) |
| } |
| } |
| |
| makePkg("lib", mustParse(fset, "package lib; var X int")) |
| // Each /*name=kind:line*/ comment makes the test look up the |
| // name at that point and checks that it resolves to a decl of |
| // the specified kind and line number. "undef" means undefined. |
| // Note that type switch case clauses with an empty body (but for |
| // comments) need the ";" to ensure that the recorded scope extends |
| // past the comments. |
| mainSrc := ` |
| /*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ |
| package main |
| |
| import "lib" |
| import . "lib" |
| |
| const Pi = 3.1415 |
| type T struct{} |
| var Y, _ = lib.X, X |
| |
| 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*/ { |
| const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ |
| type /*t=undef*/ t /*t=typename:14*/ *t |
| print(Y) /*Y=var:10*/ |
| x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y |
| var F = /*F=func:12*/ F[*int, int] /*F=var:17*/ ; _ = F |
| |
| var a []int |
| for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } |
| |
| var i interface{} |
| switch y := i.(type) { /*y=undef*/ |
| case /*y=undef*/ int /*y=undef*/ : /*y=var:23*/ ; |
| case float32, /*y=undef*/ float64 /*y=undef*/ : /*y=var:23*/ ; |
| default /*y=undef*/ : /*y=var:23*/ |
| println(y) |
| } |
| /*y=undef*/ |
| |
| switch int := i.(type) { |
| case /*int=typename:0*/ int /*int=typename:0*/ : /*int=var:31*/ |
| println(int) |
| default /*int=typename:0*/ : /*int=var:31*/ ; |
| } |
| |
| _ = param1 |
| _ = res1 |
| return |
| } |
| /*main=undef*/ |
| ` |
| |
| info.Uses = make(map[*ast.Ident]Object) |
| f := mustParse(fset, mainSrc) |
| makePkg("main", f) |
| mainScope := imports["main"].Scope() |
| rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) |
| for _, group := range f.Comments { |
| for _, comment := range group.List { |
| // Parse the assertion in the comment. |
| m := rx.FindStringSubmatch(comment.Text) |
| if m == nil { |
| t.Errorf("%s: bad comment: %s", |
| fset.Position(comment.Pos()), comment.Text) |
| continue |
| } |
| name, want := m[1], m[2] |
| |
| // Look up the name in the innermost enclosing scope. |
| inner := mainScope.Innermost(comment.Pos()) |
| if inner == nil { |
| t.Errorf("%s: at %s: can't find innermost scope", |
| fset.Position(comment.Pos()), comment.Text) |
| continue |
| } |
| got := "undef" |
| if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil { |
| kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types.")) |
| got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line) |
| } |
| if got != want { |
| t.Errorf("%s: at %s: %s resolved to %s, want %s", |
| fset.Position(comment.Pos()), comment.Text, name, got, want) |
| } |
| } |
| } |
| |
| // Check that for each referring identifier, |
| // a lookup of its name on the innermost |
| // enclosing scope returns the correct object. |
| |
| for id, wantObj := range info.Uses { |
| inner := mainScope.Innermost(id.Pos()) |
| if inner == nil { |
| t.Errorf("%s: can't find innermost scope enclosing %q", |
| fset.Position(id.Pos()), id.Name) |
| continue |
| } |
| |
| // Exclude selectors and qualified identifiers---lexical |
| // refs only. (Ideally, we'd see if the AST parent is a |
| // SelectorExpr, but that requires PathEnclosingInterval |
| // from golang.org/x/tools/go/ast/astutil.) |
| if id.Name == "X" { |
| continue |
| } |
| |
| _, gotObj := inner.LookupParent(id.Name, id.Pos()) |
| if gotObj != wantObj { |
| // Print the scope tree of mainScope in case of error. |
| var printScopeTree func(indent string, s *Scope) |
| printScopeTree = func(indent string, s *Scope) { |
| t.Logf("%sscope %s %v-%v = %v", |
| indent, |
| ScopeComment(s), |
| s.Pos(), |
| s.End(), |
| s.Names()) |
| for i := range s.NumChildren() { |
| printScopeTree(indent+" ", s.Child(i)) |
| } |
| } |
| printScopeTree("", mainScope) |
| |
| t.Errorf("%s: Scope(%s).LookupParent(%s@%v) got %v, want %v [scopePos=%v]", |
| fset.Position(id.Pos()), |
| ScopeComment(inner), |
| id.Name, |
| id.Pos(), |
| gotObj, |
| wantObj, |
| ObjectScopePos(wantObj)) |
| continue |
| } |
| } |
| } |