]> Cypherpunks repositories - gostls13.git/commitdiff
go/types: add CheckExpr function to type-check an expression
authorAlan Donovan <adonovan@google.com>
Thu, 25 Oct 2018 20:44:44 +0000 (16:44 -0400)
committerAlan Donovan <adonovan@google.com>
Wed, 8 May 2019 18:24:30 +0000 (18:24 +0000)
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>
src/go/types/eval.go
src/go/types/eval_test.go

index 8d4db48a9f90fccf7ac6f34e1543d883d01b2d2b..51259604c984c5d6d74274e9341de8349ba866b5 100644 (file)
@@ -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
 }
index d3b3fec66fc04c7caeea0d7b443a7ade217c7d55..d940bf0e80a9cb89956badca19249e2798dd88ae 100644 (file)
@@ -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)
+                               }
+                       }
+               }
+       }
+}