1
0
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:
Robert Griesemer 2013-07-12 11:03:34 -07:00
parent 5ec27f6da9
commit 32ed9615b3
6 changed files with 269 additions and 47 deletions

View File

@ -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...)

View File

@ -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
View 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
View 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

View File

@ -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()
}

View File

@ -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"),