mirror of https://github.com/golang/go.git
go/types: add CheckExpr function to type-check an expression
In IDE-like applications (and also in tests), there is often a need to type-check an expression as if it appeared at a particular position in the source code of an already typed-checked package. Eval was added to address this need, but did so only partially, stopping short of exposing a type-annotated expression tree. This makes it impossible to resolve an expression such as new(T).x and discover what Object x refers to. CheckExpr exposes that generality. Eval is now implemented in terms of CheckExpr. This change includes a test that demonstrates the object resolution functionality just described. Historical context: - https://go-review.googlesource.com/c/tools/+/10800 - https://codereview.appspot.com/10748044/ Change-Id: I715ba934b9fc0c9ceb61270e20c5f91f4eff20c3 Reviewed-on: https://go-review.googlesource.com/c/go/+/144677 Run-TryBot: Alan Donovan <adonovan@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
5a2da5624a
commit
19966e9b53
|
|
@ -6,6 +6,7 @@ package types
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
)
|
||||
|
|
@ -16,22 +17,43 @@ import (
|
|||
// 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 {
|
||||
|
|
@ -56,27 +78,22 @@ func Eval(fset *token.FileSet, pkg *Package, pos token.Pos, expr string) (_ Type
|
|||
}
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
package types_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/importer"
|
||||
"go/parser"
|
||||
|
|
@ -199,3 +200,98 @@ func split(s, sep string) (string, string) {
|
|||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue