diff --git a/go/types/api.go b/go/types/api.go index 06e1c7b6d5..881ca8961f 100644 --- a/go/types/api.go +++ b/go/types/api.go @@ -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...) diff --git a/go/types/check.go b/go/types/check.go index fe154ae5fb..f4d2213759 100644 --- a/go/types/check.go +++ b/go/types/check.go @@ -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) == "." { diff --git a/go/types/eval.go b/go/types/eval.go new file mode 100644 index 0000000000..dfff1118ab --- /dev/null +++ b/go/types/eval.go @@ -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 +} diff --git a/go/types/eval_test.go b/go/types/eval_test.go new file mode 100644 index 0000000000..cf73de3885 --- /dev/null +++ b/go/types/eval_test.go @@ -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 diff --git a/go/types/scope.go b/go/types/scope.go index 604c2d4543..19a7730592 100644 --- a/go/types/scope.go +++ b/go/types/scope.go @@ -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() } diff --git a/go/types/types_test.go b/go/types/types_test.go index d9a948c5c5..178939c9b5 100644 --- a/go/types/types_test.go +++ b/go/types/types_test.go @@ -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"),