go/types: add pos parameter to Eval; remove New, EvalNode
Scopes now have "extent" information; that is they provide a range [scope.Pos(), scope.End()) which describes the source text range covered by the scope. It requires that the incoming AST has correct position information; also the extent for the Universe and for package scopes is not set (positions are invalid). Objects have a new (currently unexported) scopePos position, which is the position at which the object becomes visible inside its *Scope. Scope.LookupParent takes an addition parameter pos. If valid, an identifier is looked up as if found at position pos. This can be used to find the object corresponding to an identifier at position pos after scopes have been completely populated (and thus may contain the same identifier which may be defined only later in the source text). Fixes #9980. Change-Id: Icb49c44c5c3d4b93c0718ce2a769ec468877709d Reviewed-on: https://go-review.googlesource.com/10800 Reviewed-by: Alan Donovan <adonovan@google.com>
This commit is contained in:
parent
a6d2a4254e
commit
665374f1c8
|
@ -161,7 +161,7 @@ func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
|
|||
var v *Var
|
||||
var v_used bool
|
||||
if ident != nil {
|
||||
if _, obj := check.scope.LookupParent(ident.Name); obj != nil {
|
||||
if _, obj := check.scope.LookupParent(ident.Name, token.NoPos); obj != nil {
|
||||
v, _ = obj.(*Var)
|
||||
if v != nil {
|
||||
v_used = v.used
|
||||
|
@ -314,8 +314,13 @@ func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) {
|
|||
|
||||
// declare new variables
|
||||
if len(newVars) > 0 {
|
||||
// spec: "The scope of a constant or variable identifier declared inside
|
||||
// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
|
||||
// for short variable declarations) and ends at the end of the innermost
|
||||
// containing block."
|
||||
scopePos := rhs[len(rhs)-1].End()
|
||||
for _, obj := range newVars {
|
||||
check.declare(scope, nil, obj) // recordObject already called
|
||||
check.declare(scope, nil, obj, scopePos) // recordObject already called
|
||||
}
|
||||
} else {
|
||||
check.softErrorf(pos, "no new variables on left side of :=")
|
||||
|
|
|
@ -280,7 +280,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
|
|||
// can only appear in qualified identifiers which are mapped to
|
||||
// selector expressions.
|
||||
if ident, ok := e.X.(*ast.Ident); ok {
|
||||
_, obj := check.scope.LookupParent(ident.Name)
|
||||
_, obj := check.scope.LookupParent(ident.Name, check.pos)
|
||||
if pkg, _ := obj.(*PkgName); pkg != nil {
|
||||
assert(pkg.pkg == check.pkg)
|
||||
check.recordUse(ident, pkg)
|
||||
|
|
|
@ -84,6 +84,7 @@ type Checker struct {
|
|||
// context within which the current object is type-checked
|
||||
// (valid only for the duration of type-checking a specific object)
|
||||
context
|
||||
pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval)
|
||||
|
||||
// debugging
|
||||
indent int // indentation for tracing
|
||||
|
|
|
@ -20,7 +20,7 @@ func (check *Checker) reportAltDecl(obj Object) {
|
|||
}
|
||||
}
|
||||
|
||||
func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object) {
|
||||
func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object, pos token.Pos) {
|
||||
// spec: "The blank identifier, represented by the underscore
|
||||
// character _, may be used in a declaration like any other
|
||||
// identifier but the declaration does not introduce a new
|
||||
|
@ -31,6 +31,7 @@ func (check *Checker) declare(scope *Scope, id *ast.Ident, obj Object) {
|
|||
check.reportAltDecl(alt)
|
||||
return
|
||||
}
|
||||
obj.setScopePos(pos)
|
||||
}
|
||||
if id != nil {
|
||||
check.recordDef(id, obj)
|
||||
|
@ -348,8 +349,13 @@ func (check *Checker) declStmt(decl ast.Decl) {
|
|||
|
||||
check.arityMatch(s, last)
|
||||
|
||||
// spec: "The scope of a constant or variable identifier declared
|
||||
// inside a function begins at the end of the ConstSpec or VarSpec
|
||||
// (ShortVarDecl for short variable declarations) and ends at the
|
||||
// end of the innermost containing block."
|
||||
scopePos := s.End()
|
||||
for i, name := range s.Names {
|
||||
check.declare(check.scope, name, lhs[i])
|
||||
check.declare(check.scope, name, lhs[i], scopePos)
|
||||
}
|
||||
|
||||
case token.VAR:
|
||||
|
@ -395,8 +401,10 @@ func (check *Checker) declStmt(decl ast.Decl) {
|
|||
|
||||
// declare all variables
|
||||
// (only at this point are the variable scopes (parents) set)
|
||||
scopePos := s.End() // see constant declarations
|
||||
for i, name := range s.Names {
|
||||
check.declare(check.scope, name, lhs0[i])
|
||||
// see constant declarations
|
||||
check.declare(check.scope, name, lhs0[i], scopePos)
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -405,7 +413,11 @@ func (check *Checker) declStmt(decl ast.Decl) {
|
|||
|
||||
case *ast.TypeSpec:
|
||||
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil)
|
||||
check.declare(check.scope, s.Name, obj)
|
||||
// spec: "The scope of a type identifier declared inside a function
|
||||
// begins at the identifier in the TypeSpec and ends at the end of
|
||||
// the innermost containing block."
|
||||
scopePos := s.Name.Pos()
|
||||
check.declare(check.scope, s.Name, obj, scopePos)
|
||||
check.typeDecl(obj, s.Type, nil, nil)
|
||||
|
||||
default:
|
||||
|
|
|
@ -2,44 +2,30 @@
|
|||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file implements New, Eval and EvalNode.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// New is a convenience function to create a new type from a given
|
||||
// expression or type literal string evaluated in Universe scope.
|
||||
// New(str) is shorthand for Eval(str, nil, nil), but only returns
|
||||
// the type result, and panics in case of an error.
|
||||
// Position info for objects in the result type is undefined.
|
||||
//
|
||||
func New(str string) Type {
|
||||
tv, err := Eval(str, nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tv.Type
|
||||
}
|
||||
|
||||
// Eval returns the type and, if constant, the value for the
|
||||
// expression or type literal string str evaluated in scope.
|
||||
// If the expression contains function literals, the function
|
||||
// bodies are ignored (though they must be syntactically correct).
|
||||
// expression expr, evaluated at position pos of package pkg,
|
||||
// which must have been derived from type-checking an AST with
|
||||
// complete position information relative to the provided file
|
||||
// set.
|
||||
//
|
||||
// If the expression contains function literals, their bodies
|
||||
// are ignored (i.e., the bodies are not type-checked).
|
||||
//
|
||||
// If pkg == nil, the Universe scope is used and the provided
|
||||
// scope is ignored. Otherwise, the scope must belong to the
|
||||
// package (either the package scope, or nested within the
|
||||
// package scope).
|
||||
// position pos is ignored. If pkg != nil, and pos is invalid,
|
||||
// the package scope is used. Otherwise, pos must belong to the
|
||||
// package.
|
||||
//
|
||||
// An error is returned if the scope is incorrect, the string
|
||||
// has syntax errors, or if it cannot be evaluated in the scope.
|
||||
// Position info for objects in the result type is undefined.
|
||||
// An error is returned if pos is not within the package or
|
||||
// if the node cannot be evaluated.
|
||||
//
|
||||
// Note: Eval should not be used instead of running Check to compute
|
||||
// types and values, but in addition to Check. Eval will re-evaluate
|
||||
|
@ -48,48 +34,54 @@ func New(str string) Type {
|
|||
// level untyped constants will return an untyped type rather then the
|
||||
// respective context-specific type.
|
||||
//
|
||||
func Eval(str string, pkg *Package, scope *Scope) (TypeAndValue, error) {
|
||||
node, err := parser.ParseExpr(str)
|
||||
if err != nil {
|
||||
return TypeAndValue{}, err
|
||||
}
|
||||
|
||||
// Create a file set that looks structurally identical to the
|
||||
// one created by parser.ParseExpr for correct error positions.
|
||||
fset := token.NewFileSet()
|
||||
fset.AddFile("", len(str), fset.Base()).SetLinesForContent([]byte(str))
|
||||
|
||||
return EvalNode(fset, node, pkg, scope)
|
||||
}
|
||||
|
||||
// EvalNode is like Eval but instead of string it accepts
|
||||
// an expression node and respective file set.
|
||||
//
|
||||
// An error is returned if the scope is incorrect
|
||||
// if the node cannot be evaluated in the scope.
|
||||
//
|
||||
func EvalNode(fset *token.FileSet, node ast.Expr, pkg *Package, scope *Scope) (tv TypeAndValue, err error) {
|
||||
// verify package/scope relationship
|
||||
func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (tv TypeAndValue, err error) {
|
||||
// determine scope
|
||||
var scope *Scope
|
||||
if pkg == nil {
|
||||
scope = Universe
|
||||
pos = token.NoPos
|
||||
} else if !pos.IsValid() {
|
||||
scope = pkg.scope
|
||||
} else {
|
||||
// The package scope extent (position information) may be
|
||||
// incorrect (files spread accross a wide range of fset
|
||||
// positions) - ignore it and just consider its children
|
||||
// (file scopes).
|
||||
for _, fscope := range pkg.scope.children {
|
||||
if scope = fscope.Innermost(pos); scope != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if scope == nil || debug {
|
||||
s := scope
|
||||
for s != nil && s != pkg.scope {
|
||||
s = s.parent
|
||||
}
|
||||
// s == nil || s == pkg.scope
|
||||
if s == nil {
|
||||
return TypeAndValue{}, fmt.Errorf("scope does not belong to package %s", pkg.name)
|
||||
return TypeAndValue{}, fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse expressions
|
||||
// BUG(gri) In case of type-checking errors below, the type checker
|
||||
// doesn't have the correct file set for expr. The correct
|
||||
// solution requires a ParseExpr that uses the incoming
|
||||
// file set fset.
|
||||
node, err := parser.ParseExpr(expr)
|
||||
if err != nil {
|
||||
return TypeAndValue{}, err
|
||||
}
|
||||
|
||||
// initialize checker
|
||||
check := NewChecker(nil, fset, pkg, nil)
|
||||
check.scope = scope
|
||||
check.pos = pos
|
||||
defer check.handleBailout(&err)
|
||||
|
||||
// evaluate node
|
||||
var x operand
|
||||
check.rawExpr(&x, node, nil)
|
||||
return TypeAndValue{x.mode, x.typ, x.val}, nil
|
||||
return TypeAndValue{x.mode, x.typ, x.val}, err
|
||||
}
|
||||
|
|
|
@ -17,14 +17,14 @@ import (
|
|||
. "golang.org/x/tools/go/types"
|
||||
)
|
||||
|
||||
func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, typStr, valStr string) {
|
||||
gotTv, err := Eval(str, pkg, scope)
|
||||
func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) {
|
||||
gotTv, err := Eval(fset, pkg, pos, expr)
|
||||
if err != nil {
|
||||
t.Errorf("Eval(%q) failed: %s", str, err)
|
||||
t.Errorf("Eval(%q) failed: %s", expr, err)
|
||||
return
|
||||
}
|
||||
if gotTv.Type == nil {
|
||||
t.Errorf("Eval(%q) got nil type but no error", str)
|
||||
t.Errorf("Eval(%q) got nil type but no error", expr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -32,14 +32,14 @@ func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, ty
|
|||
if typ != nil {
|
||||
// we have a type, check identity
|
||||
if !Identical(gotTv.Type, typ) {
|
||||
t.Errorf("Eval(%q) got type %s, want %s", str, gotTv.Type, typ)
|
||||
t.Errorf("Eval(%q) got type %s, want %s", expr, gotTv.Type, typ)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// we have a string, compare type string
|
||||
gotStr := gotTv.Type.String()
|
||||
if gotStr != typStr {
|
||||
t.Errorf("Eval(%q) got type %s, want %s", str, gotStr, typStr)
|
||||
t.Errorf("Eval(%q) got type %s, want %s", expr, gotStr, typStr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -50,19 +50,21 @@ func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, ty
|
|||
gotStr = gotTv.Value.String()
|
||||
}
|
||||
if gotStr != valStr {
|
||||
t.Errorf("Eval(%q) got value %s, want %s", str, gotStr, valStr)
|
||||
t.Errorf("Eval(%q) got value %s, want %s", expr, gotStr, valStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBasic(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
for _, typ := range Typ[Bool : String+1] {
|
||||
testEval(t, nil, nil, typ.Name(), typ, "", "")
|
||||
testEval(t, fset, nil, token.NoPos, typ.Name(), typ, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalComposite(t *testing.T) {
|
||||
fset := token.NewFileSet()
|
||||
for _, test := range independentTestTypes {
|
||||
testEval(t, nil, nil, test.src, nil, test.str, "")
|
||||
testEval(t, fset, nil, token.NoPos, test.src, nil, test.str, "")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -77,69 +79,99 @@ func TestEvalArith(t *testing.T) {
|
|||
`"abc" <= "bcd"`,
|
||||
`len([10]struct{}{}) == 2*5`,
|
||||
}
|
||||
fset := token.NewFileSet()
|
||||
for _, test := range tests {
|
||||
testEval(t, nil, nil, test, Typ[UntypedBool], "", "true")
|
||||
testEval(t, fset, nil, token.NoPos, test, Typ[UntypedBool], "", "true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalContext(t *testing.T) {
|
||||
func TestEvalPos(t *testing.T) {
|
||||
skipSpecialPlatforms(t)
|
||||
|
||||
src := `
|
||||
package p
|
||||
import "fmt"
|
||||
import m "math"
|
||||
const c = 3.0
|
||||
type T []int
|
||||
func f(a int, s string) float64 {
|
||||
// The contents of /*-style comments are of the form
|
||||
// expr => value, type
|
||||
// where value may be the empty string.
|
||||
// Each expr is evaluated at the position of the comment
|
||||
// and the result is compared with the expected value
|
||||
// and type.
|
||||
var sources = []string{
|
||||
`
|
||||
package p
|
||||
import "fmt"
|
||||
import m "math"
|
||||
const c = 3.0
|
||||
type T []int
|
||||
func f(a int, s string) float64 {
|
||||
fmt.Println("calling f")
|
||||
_ = m.Pi // use package math
|
||||
const d int = c + 1
|
||||
var x int
|
||||
x = a + len(s)
|
||||
return float64(x)
|
||||
}
|
||||
`
|
||||
/* true => true, untyped bool */
|
||||
/* fmt.Println => , func(a ...interface{}) (n int, err error) */
|
||||
/* c => 3, untyped float */
|
||||
/* T => , p.T */
|
||||
/* a => , int */
|
||||
/* s => , string */
|
||||
/* d => 4, int */
|
||||
/* x => , int */
|
||||
/* d/c => 1, int */
|
||||
/* c/2 => 3/2, untyped float */
|
||||
/* m.Pi < m.E => false, untyped bool */
|
||||
}
|
||||
`,
|
||||
`
|
||||
package p
|
||||
/* c => 3, untyped float */
|
||||
type T1 /* T1 => , p.T1 */ struct {}
|
||||
var v1 /* v1 => , int */ = 42
|
||||
func /* f1 => , func(v1 float64) */ f1(v1 float64) {
|
||||
/* f1 => , func(v1 float64) */
|
||||
/* v1 => , float64 */
|
||||
var c /* c => 3, untyped float */ = "foo" /* c => , string */
|
||||
{
|
||||
var c struct {
|
||||
c /* c => , string */ int
|
||||
}
|
||||
/* c => , struct{c int} */
|
||||
_ = c
|
||||
}
|
||||
_ = func(a, b, c int) /* c => , string */ {
|
||||
/* c => , int */
|
||||
}
|
||||
_ = c
|
||||
type FT /* FT => , p.FT */ interface{}
|
||||
}
|
||||
`,
|
||||
}
|
||||
|
||||
fset := token.NewFileSet()
|
||||
file, err := parser.ParseFile(fset, "p", src, 0)
|
||||
var files []*ast.File
|
||||
for i, src := range sources {
|
||||
file, err := parser.ParseFile(fset, "p", src, parser.ParseComments)
|
||||
if err != nil {
|
||||
t.Fatalf("could not parse file %d: %s", i, err)
|
||||
}
|
||||
files = append(files, file)
|
||||
}
|
||||
|
||||
pkg, err := Check("p", fset, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkg, err := Check("p", fset, []*ast.File{file})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
pkgScope := pkg.Scope()
|
||||
if n := pkgScope.NumChildren(); n != 1 {
|
||||
t.Fatalf("got %d file scopes, want 1", n)
|
||||
}
|
||||
|
||||
fileScope := pkgScope.Child(0)
|
||||
if n := fileScope.NumChildren(); n != 1 {
|
||||
t.Fatalf("got %d functions scopes, want 1", n)
|
||||
}
|
||||
|
||||
funcScope := fileScope.Child(0)
|
||||
|
||||
var tests = []string{
|
||||
`true => true, untyped bool`,
|
||||
`fmt.Println => , func(a ...interface{}) (n int, err error)`,
|
||||
`c => 3, untyped float`,
|
||||
`T => , p.T`,
|
||||
`a => , int`,
|
||||
`s => , string`,
|
||||
`d => 4, int`,
|
||||
`x => , int`,
|
||||
`d/c => 1, int`,
|
||||
`c/2 => 3/2, untyped float`,
|
||||
`m.Pi < m.E => false, untyped bool`,
|
||||
}
|
||||
for _, test := range tests {
|
||||
str, typ := split(test, ", ")
|
||||
for _, file := range files {
|
||||
for _, group := range file.Comments {
|
||||
for _, comment := range group.List {
|
||||
s := comment.Text
|
||||
if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" {
|
||||
str, typ := split(s[2:len(s)-2], ", ")
|
||||
str, val := split(str, "=>")
|
||||
testEval(t, pkg, funcScope, str, nil, typ, val)
|
||||
testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
// labels checks correct label use in body.
|
||||
func (check *Checker) labels(body *ast.BlockStmt) {
|
||||
// set of all labels in this body
|
||||
all := NewScope(nil, "label")
|
||||
all := NewScope(nil, body.Pos(), body.End(), "label")
|
||||
|
||||
fwdJumps := check.blockBranches(all, nil, nil, body.List)
|
||||
|
||||
|
|
|
@ -45,6 +45,12 @@ type Object interface {
|
|||
|
||||
// sameId reports whether obj.Id() and Id(pkg, name) are the same.
|
||||
sameId(pkg *Package, name string) bool
|
||||
|
||||
// scopePos returns the start position of the scope of this Object
|
||||
scopePos() token.Pos
|
||||
|
||||
// setScopePos sets the start position of the scope for this Object.
|
||||
setScopePos(pos token.Pos)
|
||||
}
|
||||
|
||||
// Id returns name if it is exported, otherwise it
|
||||
|
@ -80,6 +86,7 @@ type object struct {
|
|||
name string
|
||||
typ Type
|
||||
order_ uint32
|
||||
scopePos_ token.Pos
|
||||
}
|
||||
|
||||
func (obj *object) Parent() *Scope { return obj.parent }
|
||||
|
@ -91,9 +98,11 @@ func (obj *object) Exported() bool { return ast.IsExported(obj.name) }
|
|||
func (obj *object) Id() string { return Id(obj.pkg, obj.name) }
|
||||
func (obj *object) String() string { panic("abstract") }
|
||||
func (obj *object) order() uint32 { return obj.order_ }
|
||||
func (obj *object) scopePos() token.Pos { return obj.scopePos_ }
|
||||
|
||||
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
|
||||
func (obj *object) setParent(parent *Scope) { obj.parent = parent }
|
||||
func (obj *object) setOrder(order uint32) { assert(order > 0); obj.order_ = order }
|
||||
func (obj *object) setScopePos(pos token.Pos) { obj.scopePos_ = pos }
|
||||
|
||||
func (obj *object) sameId(pkg *Package, name string) bool {
|
||||
// spec:
|
||||
|
@ -125,7 +134,7 @@ type PkgName struct {
|
|||
}
|
||||
|
||||
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName {
|
||||
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0}, imported, false}
|
||||
return &PkgName{object{nil, pos, pkg, name, Typ[Invalid], 0, token.NoPos}, imported, false}
|
||||
}
|
||||
|
||||
// Imported returns the package that was imported.
|
||||
|
@ -140,7 +149,7 @@ type Const struct {
|
|||
}
|
||||
|
||||
func NewConst(pos token.Pos, pkg *Package, name string, typ Type, val exact.Value) *Const {
|
||||
return &Const{object{nil, pos, pkg, name, typ, 0}, val, false}
|
||||
return &Const{object{nil, pos, pkg, name, typ, 0, token.NoPos}, val, false}
|
||||
}
|
||||
|
||||
func (obj *Const) Val() exact.Value { return obj.val }
|
||||
|
@ -151,7 +160,7 @@ type TypeName struct {
|
|||
}
|
||||
|
||||
func NewTypeName(pos token.Pos, pkg *Package, name string, typ Type) *TypeName {
|
||||
return &TypeName{object{nil, pos, pkg, name, typ, 0}}
|
||||
return &TypeName{object{nil, pos, pkg, name, typ, 0, token.NoPos}}
|
||||
}
|
||||
|
||||
// A Variable represents a declared variable (including function parameters and results, and struct fields).
|
||||
|
@ -164,15 +173,15 @@ type Var struct {
|
|||
}
|
||||
|
||||
func NewVar(pos token.Pos, pkg *Package, name string, typ Type) *Var {
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0}}
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}}
|
||||
}
|
||||
|
||||
func NewParam(pos token.Pos, pkg *Package, name string, typ Type) *Var {
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0}, used: true} // parameters are always 'used'
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, used: true} // parameters are always 'used'
|
||||
}
|
||||
|
||||
func NewField(pos token.Pos, pkg *Package, name string, typ Type, anonymous bool) *Var {
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0}, anonymous: anonymous, isField: true}
|
||||
return &Var{object: object{nil, pos, pkg, name, typ, 0, token.NoPos}, anonymous: anonymous, isField: true}
|
||||
}
|
||||
|
||||
func (obj *Var) Anonymous() bool { return obj.anonymous }
|
||||
|
@ -192,7 +201,7 @@ func NewFunc(pos token.Pos, pkg *Package, name string, sig *Signature) *Func {
|
|||
if sig != nil {
|
||||
typ = sig
|
||||
}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0}}
|
||||
return &Func{object{nil, pos, pkg, name, typ, 0, token.NoPos}}
|
||||
}
|
||||
|
||||
// FullName returns the package- or receiver-type-qualified name of
|
||||
|
|
|
@ -4,7 +4,10 @@
|
|||
|
||||
package types
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"go/token"
|
||||
)
|
||||
|
||||
// A Package describes a Go package.
|
||||
type Package struct {
|
||||
|
@ -23,7 +26,7 @@ func NewPackage(path, name string) *Package {
|
|||
if name == "_" {
|
||||
panic("invalid package name _")
|
||||
}
|
||||
scope := NewScope(Universe, fmt.Sprintf("package %q", path))
|
||||
scope := NewScope(Universe, token.NoPos, token.NoPos, fmt.Sprintf("package %q", path))
|
||||
return &Package{path: path, name: name, scope: scope}
|
||||
}
|
||||
|
||||
|
|
|
@ -108,7 +108,7 @@ func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) {
|
|||
return
|
||||
}
|
||||
|
||||
check.declare(check.pkg.scope, ident, obj)
|
||||
check.declare(check.pkg.scope, ident, obj, token.NoPos)
|
||||
check.objMap[obj] = d
|
||||
obj.setOrder(uint32(len(check.objMap)))
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ func (check *Checker) collectObjects() {
|
|||
// but there is no corresponding package object.
|
||||
check.recordDef(file.Name, nil)
|
||||
|
||||
fileScope := NewScope(check.pkg.scope, check.filename(fileNo))
|
||||
fileScope := NewScope(check.pkg.scope, file.Pos(), file.End(), check.filename(fileNo))
|
||||
check.recordScope(file, fileScope)
|
||||
|
||||
for _, decl := range file.Decls {
|
||||
|
@ -232,7 +232,7 @@ func (check *Checker) collectObjects() {
|
|||
// information because the same package - found
|
||||
// via Config.Packages - may be dot-imported in
|
||||
// another package!)
|
||||
check.declare(fileScope, nil, obj)
|
||||
check.declare(fileScope, nil, obj, token.NoPos)
|
||||
check.recordImplicit(s, obj)
|
||||
}
|
||||
}
|
||||
|
@ -241,7 +241,7 @@ func (check *Checker) collectObjects() {
|
|||
check.addUnusedDotImport(fileScope, imp, s.Pos())
|
||||
} else {
|
||||
// declare imported package object in file scope
|
||||
check.declare(fileScope, nil, obj)
|
||||
check.declare(fileScope, nil, obj, token.NoPos)
|
||||
}
|
||||
|
||||
case *ast.ValueSpec:
|
||||
|
@ -331,7 +331,7 @@ func (check *Checker) collectObjects() {
|
|||
check.softErrorf(obj.pos, "missing function body")
|
||||
}
|
||||
} else {
|
||||
check.declare(pkg.scope, d.Name, obj)
|
||||
check.declare(pkg.scope, d.Name, obj, token.NoPos)
|
||||
}
|
||||
} else {
|
||||
// method
|
||||
|
|
|
@ -31,7 +31,7 @@ func (check *Checker) isTerminating(s ast.Stmt, label string) bool {
|
|||
// the predeclared (possibly parenthesized) panic() function is terminating
|
||||
if call, _ := unparen(s.X).(*ast.CallExpr); call != nil {
|
||||
if id, _ := call.Fun.(*ast.Ident); id != nil {
|
||||
if _, obj := check.scope.LookupParent(id.Name); obj != nil {
|
||||
if _, obj := check.scope.LookupParent(id.Name, token.NoPos); obj != nil {
|
||||
if b, _ := obj.(*Builtin); b != nil && b.id == _Panic {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ package types
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/token"
|
||||
"io"
|
||||
"sort"
|
||||
"strings"
|
||||
|
@ -24,14 +25,15 @@ import (
|
|||
type Scope struct {
|
||||
parent *Scope
|
||||
children []*Scope
|
||||
comment string // for debugging only
|
||||
elems map[string]Object // lazily allocated
|
||||
pos, end token.Pos // scope extent; may be invalid
|
||||
comment string // for debugging only
|
||||
}
|
||||
|
||||
// NewScope returns a new, empty scope contained in the given parent
|
||||
// scope, if any. The comment is for debugging only.
|
||||
func NewScope(parent *Scope, comment string) *Scope {
|
||||
s := &Scope{parent: parent, comment: comment}
|
||||
func NewScope(parent *Scope, pos, end token.Pos, comment string) *Scope {
|
||||
s := &Scope{parent, nil, nil, pos, end, comment}
|
||||
// don't add children to Universe scope!
|
||||
if parent != nil && parent != Universe {
|
||||
parent.children = append(parent.children, s)
|
||||
|
@ -71,15 +73,17 @@ func (s *Scope) Lookup(name string) Object {
|
|||
|
||||
// LookupParent follows the parent chain of scopes starting with s until
|
||||
// it finds a scope where Lookup(name) returns a non-nil object, and then
|
||||
// returns that scope and object. If no such scope exists, the result is (nil, nil).
|
||||
// returns that scope and object. If a valid position pos is provided,
|
||||
// only objects that were declared at or before pos are considered.
|
||||
// If no such scope and object exists, the result is (nil, nil).
|
||||
//
|
||||
// Note that obj.Parent() may be different from the returned scope if the
|
||||
// object was inserted into the scope and already had a parent at that
|
||||
// time (see Insert, below). This can only happen for dot-imported objects
|
||||
// whose scope is the scope of the package that exported them.
|
||||
func (s *Scope) LookupParent(name string) (*Scope, Object) {
|
||||
func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) {
|
||||
for ; s != nil; s = s.parent {
|
||||
if obj := s.elems[name]; obj != nil {
|
||||
if obj := s.elems[name]; obj != nil && (!pos.IsValid() || obj.scopePos() <= pos) {
|
||||
return s, obj
|
||||
}
|
||||
}
|
||||
|
@ -106,6 +110,36 @@ func (s *Scope) Insert(obj Object) Object {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Pos and End describe the scope's source code extent [pos, end).
|
||||
// The results are guaranteed to be valid only if the type-checked
|
||||
// AST has complete position information. The extent is undefined
|
||||
// for Universe and package scopes.
|
||||
func (s *Scope) Pos() token.Pos { return s.pos }
|
||||
func (s *Scope) End() token.Pos { return s.end }
|
||||
|
||||
// Contains returns true if pos is within the scope's extent.
|
||||
// The result is guaranteed to be valid only if the type-checked
|
||||
// AST has complete position information.
|
||||
func (s *Scope) Contains(pos token.Pos) bool {
|
||||
return s.pos <= pos && pos < s.end
|
||||
}
|
||||
|
||||
// Innermost returns the innermost (child) scope containing
|
||||
// pos. If pos is not within any scope, the result is nil.
|
||||
// The result is guaranteed to be valid only if the type-checked
|
||||
// AST has complete position information.
|
||||
func (s *Scope) Innermost(pos token.Pos) *Scope {
|
||||
if s.Contains(pos) {
|
||||
for _, s := range s.children {
|
||||
if s.Contains(pos) {
|
||||
return s.Innermost(pos)
|
||||
}
|
||||
}
|
||||
return s
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WriteTo writes a string representation of the scope to w,
|
||||
// with the scope elements sorted by name.
|
||||
// The level of indentation is controlled by n >= 0, with
|
||||
|
|
|
@ -23,6 +23,10 @@ func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body
|
|||
defer fmt.Println("--- <end>")
|
||||
}
|
||||
|
||||
// set function scope extent
|
||||
sig.scope.pos = body.Pos()
|
||||
sig.scope.end = body.End()
|
||||
|
||||
// save/restore current context and setup function context
|
||||
// (and use 0 indentation at function start)
|
||||
defer func(ctxt context, indent int) {
|
||||
|
@ -119,7 +123,7 @@ func (check *Checker) multipleDefaults(list []ast.Stmt) {
|
|||
}
|
||||
|
||||
func (check *Checker) openScope(s ast.Stmt, comment string) {
|
||||
scope := NewScope(check.scope, comment)
|
||||
scope := NewScope(check.scope, s.Pos(), s.End(), comment)
|
||||
check.recordScope(s, scope)
|
||||
check.scope = scope
|
||||
}
|
||||
|
@ -329,7 +333,7 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
// list in a "return" statement if a different entity (constant, type, or variable)
|
||||
// with the same name as a result parameter is in scope at the place of the return."
|
||||
for _, obj := range res.vars {
|
||||
if _, alt := check.scope.LookupParent(obj.name); alt != nil && alt != obj {
|
||||
if _, alt := check.scope.LookupParent(obj.name, check.pos); alt != nil && alt != obj {
|
||||
check.errorf(s.Pos(), "result parameter %s not in scope at return", obj.name)
|
||||
check.errorf(alt.Pos(), "\tinner declaration of %s", obj)
|
||||
// ok to continue
|
||||
|
@ -513,7 +517,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
T = x.typ
|
||||
}
|
||||
obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T)
|
||||
check.declare(check.scope, nil, obj)
|
||||
scopePos := clause.End()
|
||||
if len(clause.Body) > 0 {
|
||||
scopePos = clause.Body[0].Pos()
|
||||
}
|
||||
check.declare(check.scope, nil, obj, scopePos)
|
||||
check.recordImplicit(clause, obj)
|
||||
// For the "declared but not used" error, all lhs variables act as
|
||||
// one; i.e., if any one of them is 'used', all of them are 'used'.
|
||||
|
@ -704,7 +712,12 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
|
|||
// declare variables
|
||||
if len(vars) > 0 {
|
||||
for _, obj := range vars {
|
||||
check.declare(check.scope, nil /* recordDef already called */, obj)
|
||||
// spec: "The scope of a constant or variable identifier declared inside
|
||||
// a function begins at the end of the ConstSpec or VarSpec (ShortVarDecl
|
||||
// for short variable declarations) and ends at the end of the innermost
|
||||
// containing block."
|
||||
scopePos := s.End()
|
||||
check.declare(check.scope, nil /* recordDef already called */, obj, scopePos)
|
||||
}
|
||||
} else {
|
||||
check.error(s.TokPos, "no new variables on left side of :=")
|
||||
|
|
|
@ -23,7 +23,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
|
|||
x.mode = invalid
|
||||
x.expr = e
|
||||
|
||||
scope, obj := check.scope.LookupParent(e.Name)
|
||||
scope, obj := check.scope.LookupParent(e.Name, check.pos)
|
||||
if obj == nil {
|
||||
if e.Name == "_" {
|
||||
check.errorf(e.Pos(), "cannot use _ as value or type")
|
||||
|
@ -143,7 +143,7 @@ func (check *Checker) typ(e ast.Expr) Type {
|
|||
|
||||
// funcType type-checks a function or method type.
|
||||
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) {
|
||||
scope := NewScope(check.scope, "function")
|
||||
scope := NewScope(check.scope, token.NoPos, token.NoPos, "function")
|
||||
check.recordScope(ftyp, scope)
|
||||
|
||||
recvList, _ := check.collectParams(scope, recvPar, false)
|
||||
|
@ -414,7 +414,7 @@ func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
|
|||
// ok to continue
|
||||
}
|
||||
par := NewParam(name.Pos(), check.pkg, name.Name, typ)
|
||||
check.declare(scope, name, par)
|
||||
check.declare(scope, name, par, scope.pos)
|
||||
params = append(params, par)
|
||||
}
|
||||
named = true
|
||||
|
|
|
@ -177,7 +177,7 @@ func DefPredeclaredTestFuncs() {
|
|||
}
|
||||
|
||||
func init() {
|
||||
Universe = NewScope(nil, "universe")
|
||||
Universe = NewScope(nil, token.NoPos, token.NoPos, "universe")
|
||||
Unsafe = NewPackage("unsafe", "unsafe")
|
||||
Unsafe.complete = true
|
||||
|
||||
|
|
Loading…
Reference in New Issue