blob: df016984b32aa5a2dc051600819d7be20aa54a1d [file] [log] [blame] [edit]
// 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
}
}
}