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:
Robert Griesemer 2015-06-05 16:52:52 -07:00
parent a6d2a4254e
commit 665374f1c8
15 changed files with 271 additions and 170 deletions

View File

@ -161,7 +161,7 @@ func (check *Checker) assignVar(lhs ast.Expr, x *operand) Type {
var v *Var var v *Var
var v_used bool var v_used bool
if ident != nil { 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) v, _ = obj.(*Var)
if v != nil { if v != nil {
v_used = v.used v_used = v.used
@ -314,8 +314,13 @@ func (check *Checker) shortVarDecl(pos token.Pos, lhs, rhs []ast.Expr) {
// declare new variables // declare new variables
if len(newVars) > 0 { 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 { for _, obj := range newVars {
check.declare(scope, nil, obj) // recordObject already called check.declare(scope, nil, obj, scopePos) // recordObject already called
} }
} else { } else {
check.softErrorf(pos, "no new variables on left side of :=") check.softErrorf(pos, "no new variables on left side of :=")

View File

@ -280,7 +280,7 @@ func (check *Checker) selector(x *operand, e *ast.SelectorExpr) {
// can only appear in qualified identifiers which are mapped to // can only appear in qualified identifiers which are mapped to
// selector expressions. // selector expressions.
if ident, ok := e.X.(*ast.Ident); ok { 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 { if pkg, _ := obj.(*PkgName); pkg != nil {
assert(pkg.pkg == check.pkg) assert(pkg.pkg == check.pkg)
check.recordUse(ident, pkg) check.recordUse(ident, pkg)

View File

@ -84,6 +84,7 @@ type Checker struct {
// context within which the current object is type-checked // context within which the current object is type-checked
// (valid only for the duration of type-checking a specific object) // (valid only for the duration of type-checking a specific object)
context context
pos token.Pos // if valid, identifiers are looked up as if at position pos (used by Eval)
// debugging // debugging
indent int // indentation for tracing indent int // indentation for tracing

View File

@ -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 // spec: "The blank identifier, represented by the underscore
// character _, may be used in a declaration like any other // character _, may be used in a declaration like any other
// identifier but the declaration does not introduce a new // 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) check.reportAltDecl(alt)
return return
} }
obj.setScopePos(pos)
} }
if id != nil { if id != nil {
check.recordDef(id, obj) check.recordDef(id, obj)
@ -348,8 +349,13 @@ func (check *Checker) declStmt(decl ast.Decl) {
check.arityMatch(s, last) 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 { for i, name := range s.Names {
check.declare(check.scope, name, lhs[i]) check.declare(check.scope, name, lhs[i], scopePos)
} }
case token.VAR: case token.VAR:
@ -395,8 +401,10 @@ func (check *Checker) declStmt(decl ast.Decl) {
// declare all variables // declare all variables
// (only at this point are the variable scopes (parents) set) // (only at this point are the variable scopes (parents) set)
scopePos := s.End() // see constant declarations
for i, name := range s.Names { 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: default:
@ -405,7 +413,11 @@ func (check *Checker) declStmt(decl ast.Decl) {
case *ast.TypeSpec: case *ast.TypeSpec:
obj := NewTypeName(s.Name.Pos(), pkg, s.Name.Name, nil) 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) check.typeDecl(obj, s.Type, nil, nil)
default: default:

View File

@ -2,44 +2,30 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// This file implements New, Eval and EvalNode.
package types package types
import ( import (
"fmt" "fmt"
"go/ast"
"go/parser" "go/parser"
"go/token" "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 // Eval returns the type and, if constant, the value for the
// expression or type literal string str evaluated in scope. // expression expr, evaluated at position pos of package pkg,
// If the expression contains function literals, the function // which must have been derived from type-checking an AST with
// bodies are ignored (though they must be syntactically correct). // 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 // If pkg == nil, the Universe scope is used and the provided
// scope is ignored. Otherwise, the scope must belong to the // position pos is ignored. If pkg != nil, and pos is invalid,
// package (either the package scope, or nested within the // the package scope is used. Otherwise, pos must belong to the
// package scope). // package.
// //
// An error is returned if the scope is incorrect, the string // An error is returned if pos is not within the package or
// has syntax errors, or if it cannot be evaluated in the scope. // if the node cannot be evaluated.
// Position info for objects in the result type is undefined.
// //
// Note: Eval should not be used instead of running Check to compute // Note: Eval should not be used instead of running Check to compute
// types and values, but in addition to Check. Eval will re-evaluate // 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 // level untyped constants will return an untyped type rather then the
// respective context-specific type. // respective context-specific type.
// //
func Eval(str string, pkg *Package, scope *Scope) (TypeAndValue, error) { func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (tv TypeAndValue, err error) {
node, err := parser.ParseExpr(str) // determine scope
if err != nil { var scope *Scope
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
if pkg == nil { if pkg == nil {
scope = Universe scope = Universe
pos = token.NoPos
} else if !pos.IsValid() {
scope = pkg.scope
} else { } 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 s := scope
for s != nil && s != pkg.scope { for s != nil && s != pkg.scope {
s = s.parent s = s.parent
} }
// s == nil || s == pkg.scope // s == nil || s == pkg.scope
if s == nil { 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 // initialize checker
check := NewChecker(nil, fset, pkg, nil) check := NewChecker(nil, fset, pkg, nil)
check.scope = scope check.scope = scope
check.pos = pos
defer check.handleBailout(&err) defer check.handleBailout(&err)
// evaluate node // evaluate node
var x operand var x operand
check.rawExpr(&x, node, nil) check.rawExpr(&x, node, nil)
return TypeAndValue{x.mode, x.typ, x.val}, nil return TypeAndValue{x.mode, x.typ, x.val}, err
} }

View File

@ -17,14 +17,14 @@ import (
. "golang.org/x/tools/go/types" . "golang.org/x/tools/go/types"
) )
func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, typStr, valStr string) { func testEval(t *testing.T, fset *token.FileSet, pkg *Package, pos token.Pos, expr string, typ Type, typStr, valStr string) {
gotTv, err := Eval(str, pkg, scope) gotTv, err := Eval(fset, pkg, pos, expr)
if err != nil { if err != nil {
t.Errorf("Eval(%q) failed: %s", str, err) t.Errorf("Eval(%q) failed: %s", expr, err)
return return
} }
if gotTv.Type == nil { 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 return
} }
@ -32,14 +32,14 @@ func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, ty
if typ != nil { if typ != nil {
// we have a type, check identity // we have a type, check identity
if !Identical(gotTv.Type, typ) { 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 return
} }
} else { } else {
// we have a string, compare type string // we have a string, compare type string
gotStr := gotTv.Type.String() gotStr := gotTv.Type.String()
if gotStr != typStr { 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 return
} }
} }
@ -50,19 +50,21 @@ func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, ty
gotStr = gotTv.Value.String() gotStr = gotTv.Value.String()
} }
if gotStr != valStr { 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) { func TestEvalBasic(t *testing.T) {
fset := token.NewFileSet()
for _, typ := range Typ[Bool : String+1] { 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) { func TestEvalComposite(t *testing.T) {
fset := token.NewFileSet()
for _, test := range independentTestTypes { 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"`, `"abc" <= "bcd"`,
`len([10]struct{}{}) == 2*5`, `len([10]struct{}{}) == 2*5`,
} }
fset := token.NewFileSet()
for _, test := range tests { 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) skipSpecialPlatforms(t)
src := ` // The contents of /*-style comments are of the form
package p // expr => value, type
import "fmt" // where value may be the empty string.
import m "math" // Each expr is evaluated at the position of the comment
const c = 3.0 // and the result is compared with the expected value
type T []int // and type.
func f(a int, s string) float64 { 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") fmt.Println("calling f")
_ = m.Pi // use package math _ = m.Pi // use package math
const d int = c + 1 const d int = c + 1
var x int var x int
x = a + len(s) x = a + len(s)
return float64(x) 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() 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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
pkg, err := Check("p", fset, []*ast.File{file}) for _, file := range files {
if err != nil { for _, group := range file.Comments {
t.Fatal(err) for _, comment := range group.List {
} s := comment.Text
if len(s) >= 4 && s[:2] == "/*" && s[len(s)-2:] == "*/" {
pkgScope := pkg.Scope() str, typ := split(s[2:len(s)-2], ", ")
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, ", ")
str, val := split(str, "=>") str, val := split(str, "=>")
testEval(t, pkg, funcScope, str, nil, typ, val) testEval(t, fset, pkg, comment.Pos(), str, nil, typ, val)
}
}
}
} }
} }

View File

@ -12,7 +12,7 @@ import (
// labels checks correct label use in body. // labels checks correct label use in body.
func (check *Checker) labels(body *ast.BlockStmt) { func (check *Checker) labels(body *ast.BlockStmt) {
// set of all labels in this body // 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) fwdJumps := check.blockBranches(all, nil, nil, body.List)

View File

@ -45,6 +45,12 @@ type Object interface {
// sameId reports whether obj.Id() and Id(pkg, name) are the same. // sameId reports whether obj.Id() and Id(pkg, name) are the same.
sameId(pkg *Package, name string) bool 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 // Id returns name if it is exported, otherwise it
@ -80,6 +86,7 @@ type object struct {
name string name string
typ Type typ Type
order_ uint32 order_ uint32
scopePos_ token.Pos
} }
func (obj *object) Parent() *Scope { return obj.parent } 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) Id() string { return Id(obj.pkg, obj.name) }
func (obj *object) String() string { panic("abstract") } func (obj *object) String() string { panic("abstract") }
func (obj *object) order() uint32 { return obj.order_ } 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) 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 { func (obj *object) sameId(pkg *Package, name string) bool {
// spec: // spec:
@ -125,7 +134,7 @@ type PkgName struct {
} }
func NewPkgName(pos token.Pos, pkg *Package, name string, imported *Package) *PkgName { 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. // 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 { 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 } 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 { 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). // 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 { 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 { 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 { 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 } 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 { if sig != nil {
typ = sig 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 // FullName returns the package- or receiver-type-qualified name of

View File

@ -4,7 +4,10 @@
package types package types
import "fmt" import (
"fmt"
"go/token"
)
// A Package describes a Go package. // A Package describes a Go package.
type Package struct { type Package struct {
@ -23,7 +26,7 @@ func NewPackage(path, name string) *Package {
if name == "_" { if name == "_" {
panic("invalid package 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} return &Package{path: path, name: name, scope: scope}
} }

View File

@ -108,7 +108,7 @@ func (check *Checker) declarePkgObj(ident *ast.Ident, obj Object, d *declInfo) {
return return
} }
check.declare(check.pkg.scope, ident, obj) check.declare(check.pkg.scope, ident, obj, token.NoPos)
check.objMap[obj] = d check.objMap[obj] = d
obj.setOrder(uint32(len(check.objMap))) obj.setOrder(uint32(len(check.objMap)))
} }
@ -153,7 +153,7 @@ func (check *Checker) collectObjects() {
// but there is no corresponding package object. // but there is no corresponding package object.
check.recordDef(file.Name, nil) 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) check.recordScope(file, fileScope)
for _, decl := range file.Decls { for _, decl := range file.Decls {
@ -232,7 +232,7 @@ func (check *Checker) collectObjects() {
// information because the same package - found // information because the same package - found
// via Config.Packages - may be dot-imported in // via Config.Packages - may be dot-imported in
// another package!) // another package!)
check.declare(fileScope, nil, obj) check.declare(fileScope, nil, obj, token.NoPos)
check.recordImplicit(s, obj) check.recordImplicit(s, obj)
} }
} }
@ -241,7 +241,7 @@ func (check *Checker) collectObjects() {
check.addUnusedDotImport(fileScope, imp, s.Pos()) check.addUnusedDotImport(fileScope, imp, s.Pos())
} else { } else {
// declare imported package object in file scope // declare imported package object in file scope
check.declare(fileScope, nil, obj) check.declare(fileScope, nil, obj, token.NoPos)
} }
case *ast.ValueSpec: case *ast.ValueSpec:
@ -331,7 +331,7 @@ func (check *Checker) collectObjects() {
check.softErrorf(obj.pos, "missing function body") check.softErrorf(obj.pos, "missing function body")
} }
} else { } else {
check.declare(pkg.scope, d.Name, obj) check.declare(pkg.scope, d.Name, obj, token.NoPos)
} }
} else { } else {
// method // method

View File

@ -31,7 +31,7 @@ func (check *Checker) isTerminating(s ast.Stmt, label string) bool {
// the predeclared (possibly parenthesized) panic() function is terminating // the predeclared (possibly parenthesized) panic() function is terminating
if call, _ := unparen(s.X).(*ast.CallExpr); call != nil { if call, _ := unparen(s.X).(*ast.CallExpr); call != nil {
if id, _ := call.Fun.(*ast.Ident); id != 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 { if b, _ := obj.(*Builtin); b != nil && b.id == _Panic {
return true return true
} }

View File

@ -9,6 +9,7 @@ package types
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"go/token"
"io" "io"
"sort" "sort"
"strings" "strings"
@ -24,14 +25,15 @@ import (
type Scope struct { type Scope struct {
parent *Scope parent *Scope
children []*Scope children []*Scope
comment string // for debugging only
elems map[string]Object // lazily allocated 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 // NewScope returns a new, empty scope contained in the given parent
// scope, if any. The comment is for debugging only. // scope, if any. The comment is for debugging only.
func NewScope(parent *Scope, comment string) *Scope { func NewScope(parent *Scope, pos, end token.Pos, comment string) *Scope {
s := &Scope{parent: parent, comment: comment} s := &Scope{parent, nil, nil, pos, end, comment}
// don't add children to Universe scope! // don't add children to Universe scope!
if parent != nil && parent != Universe { if parent != nil && parent != Universe {
parent.children = append(parent.children, s) 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 // 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 // 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 // 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 // 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 // time (see Insert, below). This can only happen for dot-imported objects
// whose scope is the scope of the package that exported them. // 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 { 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 return s, obj
} }
} }
@ -106,6 +110,36 @@ func (s *Scope) Insert(obj Object) Object {
return nil 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, // WriteTo writes a string representation of the scope to w,
// with the scope elements sorted by name. // with the scope elements sorted by name.
// The level of indentation is controlled by n >= 0, with // The level of indentation is controlled by n >= 0, with

View File

@ -23,6 +23,10 @@ func (check *Checker) funcBody(decl *declInfo, name string, sig *Signature, body
defer fmt.Println("--- <end>") 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 // save/restore current context and setup function context
// (and use 0 indentation at function start) // (and use 0 indentation at function start)
defer func(ctxt context, indent int) { 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) { 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.recordScope(s, scope)
check.scope = 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) // 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." // with the same name as a result parameter is in scope at the place of the return."
for _, obj := range res.vars { 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(s.Pos(), "result parameter %s not in scope at return", obj.name)
check.errorf(alt.Pos(), "\tinner declaration of %s", obj) check.errorf(alt.Pos(), "\tinner declaration of %s", obj)
// ok to continue // ok to continue
@ -513,7 +517,11 @@ func (check *Checker) stmt(ctxt stmtContext, s ast.Stmt) {
T = x.typ T = x.typ
} }
obj := NewVar(lhs.Pos(), check.pkg, lhs.Name, T) 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) check.recordImplicit(clause, obj)
// For the "declared but not used" error, all lhs variables act as // 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'. // 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 // declare variables
if len(vars) > 0 { if len(vars) > 0 {
for _, obj := range vars { 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 { } else {
check.error(s.TokPos, "no new variables on left side of :=") check.error(s.TokPos, "no new variables on left side of :=")

View File

@ -23,7 +23,7 @@ func (check *Checker) ident(x *operand, e *ast.Ident, def *Named, path []*TypeNa
x.mode = invalid x.mode = invalid
x.expr = e x.expr = e
scope, obj := check.scope.LookupParent(e.Name) scope, obj := check.scope.LookupParent(e.Name, check.pos)
if obj == nil { if obj == nil {
if e.Name == "_" { if e.Name == "_" {
check.errorf(e.Pos(), "cannot use _ as value or type") 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. // funcType type-checks a function or method type.
func (check *Checker) funcType(sig *Signature, recvPar *ast.FieldList, ftyp *ast.FuncType) { 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) check.recordScope(ftyp, scope)
recvList, _ := check.collectParams(scope, recvPar, false) recvList, _ := check.collectParams(scope, recvPar, false)
@ -414,7 +414,7 @@ func (check *Checker) collectParams(scope *Scope, list *ast.FieldList, variadicO
// ok to continue // ok to continue
} }
par := NewParam(name.Pos(), check.pkg, name.Name, typ) 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) params = append(params, par)
} }
named = true named = true

View File

@ -177,7 +177,7 @@ func DefPredeclaredTestFuncs() {
} }
func init() { func init() {
Universe = NewScope(nil, "universe") Universe = NewScope(nil, token.NoPos, token.NoPos, "universe")
Unsafe = NewPackage("unsafe", "unsafe") Unsafe = NewPackage("unsafe", "unsafe")
Unsafe.complete = true Unsafe.complete = true