Repository: goplus/gop
Pull request #1486 (2023-10-22 16:43:27)

parser: ParseExprFrom/ParseExpr; typesutil.Check: ident/selectorExpr rec.Type


Patch:
diff --git a/cl/expr.go b/cl/expr.go
index 4c918fa3..8e24089a 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -155,6 +155,12 @@ find:
 	}
 	if rec := ctx.recorder(); rec != nil {
 		rec.Use(ident, o)
+		e := ctx.cb.Get(-1)
+		tv := types.TypeAndValue{Type: e.Type, Value: e.CVal}
+		if !fvalue {
+			tv.Type = o.Type()
+		}
+		rec.Type(ident, tv)
 	}
 	return
 }
@@ -368,6 +374,10 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 		}
 	default:
 		compileExpr(ctx, v.X)
+		if rec := ctx.recorder(); rec != nil {
+			e := ctx.cb.Get(-1)
+			rec.Type(v.X, types.TypeAndValue{Type: e.Type, Value: e.CVal})
+		}
 	}
 	if err := compileMember(ctx, v, v.Sel.Name, flags); err != nil {
 		panic(err)
diff --git a/parser/interface.go b/parser/interface.go
index c88c4a43..3ed93a1c 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -49,6 +49,8 @@ const (
 	ParseGoPlusClass Mode = 1 << 17
 )
 
+// -----------------------------------------------------------------------------
+
 // ParseFile parses the source code of a single Go source file and returns
 // the corresponding ast.File node. The source code may be provided via
 // the filename of the source file, or via the src parameter.
@@ -73,9 +75,9 @@ func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 	}
 
 	// get source
-	text, err := readSource(src)
+	text, err := readSourceLocal(filename, src)
 	if err != nil {
-		return nil, err
+		return
 	}
 
 	var p parser
@@ -108,3 +110,61 @@ func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 
 	return
 }
+
+// -----------------------------------------------------------------------------
+
+// ParseExprFrom is a convenience function for parsing an expression.
+// The arguments have the same meaning as for ParseFile, but the source must
+// be a valid Go (type or value) expression. Specifically, fset must not
+// be nil.
+//
+// If the source couldn't be read, the returned AST is nil and the error
+// indicates the specific failure. If the source was read but syntax
+// errors were found, the result is a partial AST (with ast.Bad* nodes
+// representing the fragments of erroneous source code). Multiple errors
+// are returned via a scanner.ErrorList which is sorted by source position.
+func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (expr ast.Expr, err error) {
+	// get source
+	text, err := readSourceLocal(filename, src)
+	if err != nil {
+		return
+	}
+
+	var p parser
+	defer func() {
+		if e := recover(); e != nil {
+			// resume same panic if it's not a bailout
+			if _, ok := e.(bailout); !ok {
+				panic(e)
+			}
+		}
+		p.errors.Sort()
+		err = p.errors.Err()
+	}()
+
+	// parse expr
+	p.init(fset, filename, text, mode)
+	expr = p.parseRHS()
+
+	// If a semicolon was inserted, consume it;
+	// report an error if there's more tokens.
+	if p.tok == token.SEMICOLON && p.lit == "\n" {
+		p.next()
+	}
+	p.expect(token.EOF)
+
+	return
+}
+
+// ParseExpr is a convenience function for obtaining the AST of an expression x.
+// The position information recorded in the AST is undefined. The filename used
+// in error messages is the empty string.
+//
+// If syntax errors were found, the result is a partial AST (with ast.Bad* nodes
+// representing the fragments of erroneous source code). Multiple errors are
+// returned via a scanner.ErrorList which is sorted by source position.
+func ParseExpr(x string) (ast.Expr, error) {
+	return ParseExprFrom(token.NewFileSet(), "", []byte(x), 0)
+}
+
+// -----------------------------------------------------------------------------
diff --git a/parser/parser.go b/parser/parser.go
index 481f9f3a..43bb6efe 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -381,7 +381,8 @@ func (p *parser) next() {
 }
 
 // A bailout panic is raised to indicate early termination.
-type bailout struct{}
+type bailout struct {
+}
 
 func (p *parser) error(pos token.Pos, msg string) {
 	epos := p.file.Position(pos)
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 372c880b..77643083 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -22,6 +22,7 @@ import (
 	"fmt"
 	"io"
 	"io/fs"
+	"os"
 	"path"
 	"path/filepath"
 	"strings"
@@ -164,7 +165,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 		if isClass {
 			mode |= ParseGoPlusClass
 		}
-		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || filter(conf.Filter, d)) {
+		if !strings.HasPrefix(fname, "_") && (conf.Filter == nil || filter(d, conf.Filter)) {
 			filename := fs.Join(path, fname)
 			if useGoParser {
 				if filedata, err := fs.ReadFile(filename); err == nil {
@@ -193,7 +194,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, path string, conf Config) (p
 	return
 }
 
-func filter(fn func(fs.FileInfo) bool, d fs.DirEntry) bool {
+func filter(d fs.DirEntry, fn func(fs.FileInfo) bool) bool {
 	fi, err := d.Info()
 	return err != nil || fn(fi)
 }
@@ -254,12 +255,7 @@ func ParseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 
 // ParseFSFile parses the source code of a single Go+ source file and returns the corresponding ast.File node.
 func ParseFSFile(fset *token.FileSet, fs FileSystem, filename string, src interface{}, mode Mode) (f *ast.File, err error) {
-	var code []byte
-	if src == nil {
-		code, err = fs.ReadFile(filename)
-	} else {
-		code, err = readSource(src)
-	}
+	code, err := readSourceFS(fs, filename, src)
 	if err != nil {
 		return
 	}
@@ -287,4 +283,21 @@ func readSource(src interface{}) ([]byte, error) {
 	return nil, errInvalidSource
 }
 
+// If src != nil, readSource converts src to a []byte if possible;
+// otherwise it returns an error. If src == nil, readSource returns
+// the result of reading the file specified by filename.
+func readSourceLocal(filename string, src interface{}) ([]byte, error) {
+	if src != nil {
+		return readSource(src)
+	}
+	return os.ReadFile(filename)
+}
+
+func readSourceFS(fs FileSystem, filename string, src interface{}) ([]byte, error) {
+	if src != nil {
+		return readSource(src)
+	}
+	return fs.ReadFile(filename)
+}
+
 // -----------------------------------------------------------------------------
diff --git a/x/typesutil/gopinfo.go b/x/typesutil/gopinfo.go
index 2550b276..cf6ed2c4 100644
--- a/x/typesutil/gopinfo.go
+++ b/x/typesutil/gopinfo.go
@@ -183,6 +183,9 @@ type gopRecorder struct {
 // only in the Defs map, and identifiers denoting packages in
 // qualified identifiers are collected in the Uses map.
 func (info gopRecorder) Type(e ast.Expr, tv types.TypeAndValue) {
+	if debugVerbose {
+		log.Println("==> Type:", e, tv.Type)
+	}
 	info.Types[e] = tv
 }
 


Test patch:
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 1aed0e3f..76fe912d 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -17,14 +17,34 @@
 package parser
 
 import (
+	"io/fs"
 	"testing"
 
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/token"
+	fsx "github.com/qiniu/x/http/fs"
 )
 
 // -----------------------------------------------------------------------------
 
+func TestFilter(t *testing.T) {
+	d := fsx.NewFileInfo("foo.go", 10)
+	if filter(d, func(fi fs.FileInfo) bool {
+		return false
+	}) {
+		t.Fatal("TestFilter failed")
+	}
+}
+
+func TestAssert(t *testing.T) {
+	defer func() {
+		if e := recover(); e != "go/parser internal error: panic msg" {
+			t.Fatal("TestAssert:", e)
+		}
+	}()
+	assert(false, "panic msg")
+}
+
 func TestExt(t *testing.T) {
 	cases := [][2]string{
 		{"t.spx.gox", ".spx"},
@@ -64,6 +84,20 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) {
 	}
 }
 
+func testErrCodeParseExpr(t *testing.T, code string, errExp, panicExp string) {
+	defer func() {
+		if e := recover(); e != nil {
+			if panicMsg(e) != panicExp {
+				t.Fatal("testErrCodeParseExpr panic:", e)
+			}
+		}
+	}()
+	_, err := ParseExpr(code)
+	if err == nil || err.Error() != errExp {
+		t.Fatal("testErrCodeParseExpr error:", err)
+	}
+}
+
 func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
 	defer func() {
 		if e := recover(); e != nil {
@@ -120,6 +154,24 @@ test "hello", (x, "y") => {
 `, `/foo/bar.gop:3:19: expected 'IDENT', found "y"`, ``)
 }
 
+func TestErrTooManyParseExpr(t *testing.T) {
+	testErrCodeParseExpr(t, `func() int {
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+  var
+}()
+`, `3:3: expected 'IDENT', found 'var' (and 10 more errors)`, ``)
+}
+
 func TestErrTooMany(t *testing.T) {
 	testErrCode(t, `
 func f() { var }
@@ -133,6 +185,7 @@ func h() { var }
 func h() { var }
 func h() { var }
 func h() { var }
+func h() { var }
 `, `/foo/bar.gop:2:16: expected 'IDENT', found '}' (and 10 more errors)`, ``)
 }
 
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index 10fcc025..a5f398a3 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -40,6 +40,16 @@ func init() {
 	SetDebug(DbgFlagAll)
 }
 
+func TestParseExprFrom(t *testing.T) {
+	fset := token.NewFileSet()
+	if _, err := ParseExprFrom(fset, "/foo/bar/not-exists", nil, 0); err == nil {
+		t.Fatal("ParseExprFrom: no error?")
+	}
+	if _, err := ParseExpr("1+1\n;"); err == nil || err.Error() != "2:1: expected 'EOF', found ';'" {
+		t.Fatal("ParseExpr:", err)
+	}
+}
+
 func TestReadSource(t *testing.T) {
 	buf := bytes.NewBuffer(nil)
 	if _, err := readSource(buf); err != nil {
@@ -52,6 +62,9 @@ func TestReadSource(t *testing.T) {
 	if _, err := readSource(0); err == nil {
 		t.Fatal("readSource int failed: no error?")
 	}
+	if _, err := readSourceLocal("/foo/bar/not-exists", nil); err == nil {
+		t.Fatal("readSourceLocal int failed: no error?")
+	}
 }
 
 func TestParseFiles(t *testing.T) {