import (
"fmt"
+ "go/ast"
"go/parser"
"go/token"
)
// complete position information relative to the provided file
// set.
//
+// The meaning of the parameters fset, pkg, and pos is the
+// same as in CheckExpr. An error is returned if expr cannot
+// be parsed successfully, or the resulting expr AST cannot be
+// type-checked.
+func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ TypeAndValue, err error) {
+ // parse expressions
+ node, err := parser.ParseExprFrom(fset, "eval", expr, 0)
+ if err != nil {
+ return TypeAndValue{}, err
+ }
+
+ info := &Info{
+ Types: make(map[ast.Expr]TypeAndValue),
+ }
+ err = CheckExpr(fset, pkg, pos, node, info)
+ return info.Types[node], err
+}
+
+// CheckExpr type checks the expression expr as if it had appeared at
+// position pos of package pkg. Type information about the expression
+// is recorded in info.
+//
// If pkg == nil, the Universe scope is used and the provided
// 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 pos is not within the package or
-// if the node cannot be evaluated.
+// if the node cannot be type-checked.
//
-// 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.
+// Note: Eval and CheckExpr should not be used instead of running Check
+// to compute types and values, but in addition to Check, as these
+// functions ignore 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(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ TypeAndValue, err error) {
+func CheckExpr(fset *token.FileSet, pkg *Package, pos token.Pos, expr ast.Expr, info *Info) (err error) {
// determine scope
var scope *Scope
if pkg == nil {
}
// s == nil || s == pkg.scope
if s == nil {
- return TypeAndValue{}, fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name)
+ return fmt.Errorf("no position %s found in package %s", fset.Position(pos), pkg.name)
}
}
}
- // parse expressions
- node, err := parser.ParseExprFrom(fset, "eval", expr, 0)
- if err != nil {
- return TypeAndValue{}, err
- }
-
// initialize checker
- check := NewChecker(nil, fset, pkg, nil)
+ check := NewChecker(nil, fset, pkg, info)
check.scope = scope
check.pos = pos
defer check.handleBailout(&err)
// evaluate node
var x operand
- check.rawExpr(&x, node, nil)
+ check.rawExpr(&x, expr, nil)
check.processDelayed(0) // incl. all functions
+ check.recordUntyped()
- return TypeAndValue{x.mode, x.typ, x.val}, nil
+ return nil
}
package types_test
import (
+ "fmt"
"go/ast"
"go/importer"
"go/parser"
i := strings.Index(s, sep)
return strings.TrimSpace(s[:i]), strings.TrimSpace(s[i+len(sep):])
}
+
+func TestCheckExpr(t *testing.T) {
+ testenv.MustHaveGoBuild(t)
+
+ // Each comment has the form /* expr => object */:
+ // expr is an identifier or selector expression that is passed
+ // to CheckExpr at the position of the comment, and object is
+ // the string form of the object it denotes.
+ const src = `
+package p
+
+import "fmt"
+
+const c = 3.0
+type T []int
+type S struct{ X int }
+
+func f(a int, s string) S {
+ /* fmt.Println => func fmt.Println(a ...interface{}) (n int, err error) */
+ /* fmt.Stringer.String => func (fmt.Stringer).String() string */
+ fmt.Println("calling f")
+
+ var fmt struct{ Println int }
+ /* fmt => var fmt struct{Println int} */
+ /* fmt.Println => field Println int */
+ /* f(1, "").X => field X int */
+ fmt.Println = 1
+
+ /* append => builtin append */
+
+ /* new(S).X => field X int */
+
+ return S{}
+}`
+
+ fset := token.NewFileSet()
+ f, err := parser.ParseFile(fset, "p", src, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ conf := Config{Importer: importer.Default()}
+ pkg, err := conf.Check("p", fset, []*ast.File{f}, nil)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ checkExpr := func(pos token.Pos, str string) (Object, error) {
+ expr, err := parser.ParseExprFrom(fset, "eval", str, 0)
+ if err != nil {
+ return nil, err
+ }
+
+ info := &Info{
+ Uses: make(map[*ast.Ident]Object),
+ Selections: make(map[*ast.SelectorExpr]*Selection),
+ }
+ if err := CheckExpr(fset, pkg, pos, expr, info); err != nil {
+ return nil, fmt.Errorf("CheckExpr(%q) failed: %s", str, err)
+ }
+ switch expr := expr.(type) {
+ case *ast.Ident:
+ if obj, ok := info.Uses[expr]; ok {
+ return obj, nil
+ }
+ case *ast.SelectorExpr:
+ if sel, ok := info.Selections[expr]; ok {
+ return sel.Obj(), nil
+ }
+ if obj, ok := info.Uses[expr.Sel]; ok {
+ return obj, nil // qualified identifier
+ }
+ }
+ return nil, fmt.Errorf("no object for %s", str)
+ }
+
+ for _, group := range f.Comments {
+ for _, comment := range group.List {
+ s := comment.Text
+ if len(s) >= 4 && strings.HasPrefix(s, "/*") && strings.HasSuffix(s, "*/") {
+ pos := comment.Pos()
+ expr, wantObj := split(s[2:len(s)-2], "=>")
+ obj, err := checkExpr(pos, expr)
+ if err != nil {
+ t.Errorf("%s: %s", fset.Position(pos), err)
+ continue
+ }
+ if obj.String() != wantObj {
+ t.Errorf("%s: checkExpr(%s) = %s, want %v",
+ fset.Position(pos), expr, obj, wantObj)
+ }
+ }
+ }
+ }
+}