mirror of
https://github.com/golang/go
synced 2024-11-18 18:04:46 -07:00
go.tools/go/types: implement New, Eval and EvalNode
Eval and EvalNode permit the evaluation of an expression or type literal string (or AST node in case of EvalNode) within a given context (package and scope). Also: - track nested (children) scopes for all scopes - provide a mechanism to iterate over nested scopes - permit recursive printing of scopes TODO: more tests R=adonovan CC=golang-dev https://golang.org/cl/10748044
This commit is contained in:
parent
5ec27f6da9
commit
32ed9615b3
@ -54,7 +54,7 @@ import (
|
||||
)
|
||||
|
||||
// A Context specifies the supporting context for type checking.
|
||||
// An empty Context is a ready-to-use default context.
|
||||
// The zero value for a Context is a ready-to-use default context.
|
||||
type Context struct {
|
||||
// If Error != nil, it is called with each error found
|
||||
// during type checking. The error strings of errors with
|
||||
@ -145,7 +145,7 @@ func (ctxt *Context) Check(path string, fset *token.FileSet, files ...*ast.File)
|
||||
return check(ctxt, path, fset, files...)
|
||||
}
|
||||
|
||||
// Check is shorthand for ctxt.Check where ctxt is a default (empty) context.
|
||||
// Check is shorthand for ctxt.Check where ctxt is a default context.
|
||||
func Check(path string, fset *token.FileSet, files ...*ast.File) (*Package, error) {
|
||||
var ctxt Context
|
||||
return ctxt.Check(path, fset, files...)
|
||||
|
@ -58,6 +58,17 @@ type checker struct {
|
||||
indent int // indentation for tracing
|
||||
}
|
||||
|
||||
func newChecker(ctxt *Context, fset *token.FileSet, pkg *Package) *checker {
|
||||
return &checker{
|
||||
ctxt: ctxt,
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
methods: make(map[*TypeName]*Scope),
|
||||
conversions: make(map[*ast.CallExpr]bool),
|
||||
untyped: make(map[ast.Expr]exprInfo),
|
||||
}
|
||||
}
|
||||
|
||||
func (check *checker) callIdent(id *ast.Ident, obj Object) {
|
||||
if f := check.ctxt.Ident; f != nil {
|
||||
assert(id != nil)
|
||||
@ -93,6 +104,22 @@ func (check *checker) later(f *Func, sig *Signature, body *ast.BlockStmt) {
|
||||
// A bailout panic is raised to indicate early termination.
|
||||
type bailout struct{}
|
||||
|
||||
func (check *checker) handleBailout(err *error) {
|
||||
switch p := recover().(type) {
|
||||
case nil, bailout:
|
||||
// normal return or early exit
|
||||
*err = check.firsterr
|
||||
default:
|
||||
// unexpected panic: don't crash clients
|
||||
if debug {
|
||||
check.dump("INTERNAL PANIC: %v", p)
|
||||
panic(p)
|
||||
}
|
||||
// TODO(gri) add a test case for this scenario
|
||||
*err = fmt.Errorf("types internal error: %v", p)
|
||||
}
|
||||
}
|
||||
|
||||
func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.File) (pkg *Package, err error) {
|
||||
pkg = &Package{
|
||||
path: pkgPath,
|
||||
@ -100,32 +127,8 @@ func check(ctxt *Context, pkgPath string, fset *token.FileSet, files ...*ast.Fil
|
||||
imports: make(map[string]*Package),
|
||||
}
|
||||
|
||||
// initialize checker
|
||||
check := checker{
|
||||
ctxt: ctxt,
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
methods: make(map[*TypeName]*Scope),
|
||||
conversions: make(map[*ast.CallExpr]bool),
|
||||
untyped: make(map[ast.Expr]exprInfo),
|
||||
}
|
||||
|
||||
// handle panics
|
||||
defer func() {
|
||||
switch p := recover().(type) {
|
||||
case nil, bailout:
|
||||
// normal return or early exit
|
||||
err = check.firsterr
|
||||
default:
|
||||
// unexpected panic: don't crash clients
|
||||
if debug {
|
||||
check.dump("INTERNAL PANIC: %v", p)
|
||||
panic(p)
|
||||
}
|
||||
// TODO(gri) add a test case for this scenario
|
||||
err = fmt.Errorf("types internal error: %v", p)
|
||||
}
|
||||
}()
|
||||
check := newChecker(ctxt, fset, pkg)
|
||||
defer check.handleBailout(&err)
|
||||
|
||||
// we need a reasonable path to continue
|
||||
if path.Clean(pkgPath) == "." {
|
||||
|
118
go/types/eval.go
Normal file
118
go/types/eval.go
Normal file
@ -0,0 +1,118 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// This file implements New, Eval and EvalNode.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
|
||||
"code.google.com/p/go.tools/go/exact"
|
||||
)
|
||||
|
||||
// TODO(gri) Calling IsIdentity on struct or interface types created
|
||||
// with New or Eval in the Universe context will crash if the types
|
||||
// contain non-exported fields or methods because those require a non-
|
||||
// nil package. The type checker should disallow non-exported methods
|
||||
// and fields in types in Universe scope.
|
||||
|
||||
// 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 {
|
||||
typ, _, err := Eval(str, nil, nil)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return typ
|
||||
}
|
||||
|
||||
// TODO(gri): Try to find a better name than Eval. Not everybody
|
||||
// agrees that it's the best name for the functionality provided.
|
||||
// Change EvalNode in correspondence.
|
||||
|
||||
// Eval returns the type and, if constant, the value for the
|
||||
// expression or type literal string str evaluated in scope.
|
||||
//
|
||||
// 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).
|
||||
//
|
||||
// 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.
|
||||
//
|
||||
// Note: Eval should not be used instead of running Check to compute
|
||||
// types and values, but in addition to Check. Eval will re-evaluate
|
||||
// its argument each time, and it also does not know about the context
|
||||
// in which an expression is used (e.g., an assignment). Thus, top-
|
||||
// level untyped constants will return an untyped type rather then the
|
||||
// respective context-specific type.
|
||||
//
|
||||
func Eval(str string, pkg *Package, scope *Scope) (typ Type, val exact.Value, err error) {
|
||||
node, err := parser.ParseExpr(str)
|
||||
if err != nil {
|
||||
return nil, nil, 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) (typ Type, val exact.Value, err error) {
|
||||
// verify package/scope relationship
|
||||
if pkg == nil {
|
||||
scope = Universe
|
||||
} else {
|
||||
s := scope
|
||||
for s != nil && s != pkg.scope {
|
||||
s = s.parent
|
||||
}
|
||||
// s == nil || s == pkg.scope
|
||||
if s == nil {
|
||||
return nil, nil, fmt.Errorf("scope does not belong to package %s", pkg.name)
|
||||
}
|
||||
}
|
||||
|
||||
// initialize checker
|
||||
var ctxt Context
|
||||
check := newChecker(&ctxt, fset, pkg)
|
||||
check.topScope = scope
|
||||
defer check.handleBailout(&err)
|
||||
|
||||
// evaluate node
|
||||
var x operand
|
||||
check.exprOrType(&x, node)
|
||||
switch x.mode {
|
||||
case invalid, novalue, typexprn:
|
||||
fallthrough
|
||||
default:
|
||||
unreachable() // or bailed out with error
|
||||
case constant:
|
||||
val = x.val
|
||||
fallthrough
|
||||
case typexpr, variable, value, valueok:
|
||||
typ = x.typ
|
||||
}
|
||||
|
||||
return
|
||||
}
|
60
go/types/eval_test.go
Normal file
60
go/types/eval_test.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2013 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.
|
||||
|
||||
// This file contains tests for Eval.
|
||||
|
||||
package types
|
||||
|
||||
import "testing"
|
||||
|
||||
func testEval(t *testing.T, pkg *Package, scope *Scope, str string, typ Type, typStr, valStr string) {
|
||||
gotTyp, gotVal, err := Eval(str, pkg, scope)
|
||||
if err != nil {
|
||||
t.Errorf("Eval(%s) failed: %s", str, err)
|
||||
return
|
||||
}
|
||||
if gotTyp == nil {
|
||||
t.Errorf("Eval(%s) got nil type but no error", str)
|
||||
return
|
||||
}
|
||||
|
||||
// compare types
|
||||
if typ != nil {
|
||||
// we have a type, check identity
|
||||
if !IsIdentical(gotTyp, typ) {
|
||||
t.Errorf("Eval(%s) got type %s, want %s", str, gotTyp, typ)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// we have a string, compare type string
|
||||
gotStr := gotTyp.String()
|
||||
if gotStr != typStr {
|
||||
t.Errorf("Eval(%s) got type %s, want %s", str, gotStr, typStr)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// compare values
|
||||
gotStr := ""
|
||||
if gotVal != nil {
|
||||
gotStr = gotVal.String()
|
||||
}
|
||||
if gotStr != valStr {
|
||||
t.Errorf("Eval(%s) got value %s, want %s", str, gotStr, valStr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalBasic(t *testing.T) {
|
||||
for _, typ := range Typ[Bool : String+1] {
|
||||
testEval(t, nil, nil, typ.name, typ, "", "")
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalComposite(t *testing.T) {
|
||||
for _, test := range testTypes {
|
||||
testEval(t, nil, nil, test.src, nil, test.str, "")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(gri) expand
|
@ -8,24 +8,33 @@ import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// TODO(gri) Provide scopes with a name or other mechanism so that
|
||||
// objects can use that information for better printing.
|
||||
|
||||
// A Scope maintains a set of objects and a link to its containing (parent)
|
||||
// scope. Objects may be inserted and looked up by name, or by package path
|
||||
// and name. A nil *Scope acts like an empty scope for operations that do not
|
||||
// modify the scope or access a scope's parent scope.
|
||||
// A Scope maintains a set of objects and links to its containing
|
||||
// (parent) and contained (children) scopes.
|
||||
// Objects may be inserted and looked up by name, or by package path
|
||||
// and name. A nil *Scope acts like an empty scope for operations that
|
||||
// do not modify the scope or access a scope's parent scope.
|
||||
type Scope struct {
|
||||
parent *Scope
|
||||
entries []Object
|
||||
node ast.Node
|
||||
parent *Scope
|
||||
children []*Scope
|
||||
entries []Object
|
||||
node ast.Node
|
||||
}
|
||||
|
||||
// NewScope returns a new, empty scope.
|
||||
// NewScope returns a new, empty scope contained in the given parent
|
||||
// scope, if any.
|
||||
func NewScope(parent *Scope) *Scope {
|
||||
return &Scope{parent: parent}
|
||||
scope := &Scope{parent: parent}
|
||||
if parent != nil {
|
||||
parent.children = append(parent.children, scope)
|
||||
}
|
||||
return scope
|
||||
}
|
||||
|
||||
// Parent returns the scope's containing (parent) scope.
|
||||
@ -61,6 +70,20 @@ func (s *Scope) At(i int) Object {
|
||||
return s.entries[i]
|
||||
}
|
||||
|
||||
// NumChildren() returns the number of scopes nested in s.
|
||||
// If s == nil, the result is 0.
|
||||
func (s *Scope) NumChildren() int {
|
||||
if s == nil {
|
||||
return 0
|
||||
}
|
||||
return len(s.children)
|
||||
}
|
||||
|
||||
// Child returns the i'th child scope for 0 <= i < NumChildren().
|
||||
func (s *Scope) Child(i int) *Scope {
|
||||
return s.children[i]
|
||||
}
|
||||
|
||||
// Lookup returns the object in scope s with the given package
|
||||
// and name if such an object exists; otherwise the result is nil.
|
||||
// A nil scope acts like an empty scope, and parent scopes are ignored.
|
||||
@ -132,16 +155,34 @@ func (s *Scope) Insert(obj Object) Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scope) WriteTo(w io.Writer, n int, recurse bool) {
|
||||
const ind = ". "
|
||||
indn := strings.Repeat(ind, n)
|
||||
|
||||
if s.NumEntries() == 0 {
|
||||
fmt.Fprintf(w, "%sscope %p {}\n", indn, s)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%sscope %p {\n", indn, s)
|
||||
indn1 := indn + ind
|
||||
for _, obj := range s.entries {
|
||||
fmt.Fprintf(w, "%s%s\n", indn1, obj)
|
||||
}
|
||||
|
||||
if recurse {
|
||||
for _, s := range s.children {
|
||||
fmt.Fprintln(w)
|
||||
s.WriteTo(w, n+1, recurse)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s}", indn)
|
||||
}
|
||||
|
||||
// String returns a string representation of the scope, for debugging.
|
||||
func (s *Scope) String() string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "scope %p {", s)
|
||||
if s.NumEntries() > 0 {
|
||||
fmt.Fprintln(&buf)
|
||||
for _, obj := range s.entries {
|
||||
fmt.Fprintf(&buf, "\t%s\n", obj)
|
||||
}
|
||||
}
|
||||
fmt.Fprintf(&buf, "}\n")
|
||||
s.WriteTo(&buf, 0, false)
|
||||
return buf.String()
|
||||
}
|
||||
|
@ -56,8 +56,8 @@ var testTypes = []testEntry{
|
||||
}`, `struct{x int; y int; z float32 "foo"}`},
|
||||
{`struct {
|
||||
string
|
||||
elems []T
|
||||
}`, `struct{string; elems []p.T}`},
|
||||
elems []complex128
|
||||
}`, `struct{string; elems []complex128}`},
|
||||
|
||||
// pointers
|
||||
dup("*int"),
|
||||
|
Loading…
Reference in New Issue
Block a user