scope2_test.go raw

   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