Repository: goplus/gop
Pull request #1556 (2023-11-25 02:48:52)

v1.1.12


Patch:
diff --git a/VERSION b/VERSION
deleted file mode 100644
index ac84f8c3..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.10
diff --git a/cl/compile.go b/cl/compile.go
index bdb9d3d2..24837727 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -22,12 +22,14 @@ import (
 	"go/types"
 	"log"
 	"os"
+	"path/filepath"
 	"reflect"
 	"sort"
 	"strings"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/ast/fromgo"
+	"github.com/goplus/gop/cl/internal/typesutil"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/cpackages"
@@ -204,6 +206,9 @@ type Config struct {
 	// RelativePath = true means to generate file line comments with relative file path.
 	RelativePath bool
 
+	// FileLineRoot set means generate file line comments start with root path.
+	FileLineRoot string
+
 	// NoAutoGenMain = true means not to auto generate main func is no entry.
 	NoAutoGenMain bool
 
@@ -220,7 +225,7 @@ type goxRecorder struct {
 
 // Member maps identifiers to the objects they denote.
 func (p *goxRecorder) Member(id ast.Node, obj types.Object) {
-	tv := types.TypeAndValue{Type: obj.Type()}
+	tv := typesutil.NewTypeAndValueForObject(obj)
 	switch v := id.(type) {
 	case *ast.SelectorExpr:
 		sel := v.Sel
@@ -369,6 +374,9 @@ type pkgCtx struct {
 	generics map[string]bool // generic type record
 	idents   []*ast.Ident    // toType ident recored
 	inInst   int             // toType in generic instance
+
+	fileLineRoot       string // comment file line start root
+	fileLineWorkingDir string // comment file line working dir
 }
 
 type pkgImp struct {
@@ -509,6 +517,10 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		fset: fset,
 		syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool),
 	}
+	if conf.FileLineRoot != "" {
+		ctx.fileLineRoot, _ = filepath.Abs(conf.FileLineRoot)
+		ctx.fileLineWorkingDir, _ = filepath.Abs(workingDir)
+	}
 	confGox := &gox.Config{
 		Types:           conf.Types,
 		Fset:            fset,
diff --git a/cl/expr.go b/cl/expr.go
index 00d90fe9..b8dc8c84 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -100,7 +100,11 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef,
 			sig := fn.Ancestor().Type().(*types.Signature)
 			if recv := sig.Recv(); recv != nil {
 				ctx.cb.Val(recv)
-				if compileMember(ctx, ident, name, flags&^clCommandWithoutArgs) == nil { // class member object
+				chkFlag := flags &^ clCommandWithoutArgs
+				if chkFlag&clIdentSelectorExpr != 0 {
+					chkFlag = clIdentCanAutoCall
+				}
+				if compileMember(ctx, ident, name, chkFlag) == nil { // class member object
 					return
 				}
 				ctx.cb.InternalStack().PopN(1)
@@ -158,12 +162,11 @@ find:
 		e := ctx.cb.Get(-1)
 		if oldo != nil && gox.IsTypeEx(e.Type) {
 			rec.Use(ident, oldo)
+			rec.Type(ident, typesutil.NewTypeAndValueForObject(oldo))
 			return
 		}
 		rec.Use(ident, o)
-		typ, _ := gox.DerefType(e.Type)
-		tv := typesutil.NewTypeAndValue(typ, e.CVal)
-		rec.Type(ident, tv)
+		rec.Type(ident, typesutil.NewTypeAndValueForObject(o))
 	}
 	return
 }
@@ -199,6 +202,7 @@ func compileExprLHS(ctx *blockCtx, expr ast.Expr) {
 		compileIndexExprLHS(ctx, v)
 	case *ast.SelectorExpr:
 		compileSelectorExprLHS(ctx, v)
+		recordTypesVariable(ctx, v, -1)
 	case *ast.StarExpr:
 		compileStarExprLHS(ctx, v)
 	default:
@@ -210,6 +214,21 @@ func twoValue(inFlags []int) bool {
 	return inFlags != nil && (inFlags[0]&clCallWithTwoValue) != 0
 }
 
+func recordTypesValue(ctx *blockCtx, expr ast.Expr, n int) {
+	if rec := ctx.recorder(); rec != nil {
+		e := ctx.cb.Get(n)
+		rec.Type(expr, typesutil.NewTypeAndValueForValue(e.Type, e.CVal))
+	}
+}
+
+func recordTypesVariable(ctx *blockCtx, expr ast.Expr, n int) {
+	if rec := ctx.recorder(); rec != nil {
+		e := ctx.cb.Get(n)
+		t, _ := gox.DerefType(e.Type)
+		rec.Type(expr, typesutil.NewTypeAndValueForVariable(t))
+	}
+}
+
 func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 	switch v := expr.(type) {
 	case *ast.Ident:
@@ -220,6 +239,7 @@ func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 		compileIdent(ctx, v, flags)
 	case *ast.BasicLit:
 		compileBasicLit(ctx, v)
+		recordTypesValue(ctx, v, -1)
 	case *ast.CallExpr:
 		flags := 0
 		if inFlags != nil {
@@ -234,8 +254,10 @@ func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
 		compileSelectorExpr(ctx, v, flags)
 	case *ast.BinaryExpr:
 		compileBinaryExpr(ctx, v)
+		recordTypesValue(ctx, v, -1)
 	case *ast.UnaryExpr:
 		compileUnaryExpr(ctx, v, twoValue(inFlags))
+		recordTypesValue(ctx, v, -1)
 	case *ast.FuncLit:
 		compileFuncLit(ctx, v)
 	case *ast.CompositeLit:
@@ -359,10 +381,6 @@ func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr) {
 		}
 	default:
 		compileExpr(ctx, v.X)
-		if rec := ctx.recorder(); rec != nil {
-			e := ctx.cb.Get(-1)
-			rec.Type(v.X, typesutil.NewTypeAndValue(e.Type, e.CVal))
-		}
 	}
 	ctx.cb.MemberRef(v.Sel.Name, v)
 }
@@ -383,7 +401,7 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 		compileExpr(ctx, v.X)
 		if rec := ctx.recorder(); rec != nil {
 			e := ctx.cb.Get(-1)
-			rec.Type(v.X, typesutil.NewTypeAndValue(e.Type, e.CVal))
+			rec.Type(v.X, typesutil.NewTypeAndValueForType(e.Type))
 		}
 	}
 	if err := compileMember(ctx, v, v.Sel.Name, flags); err != nil {
@@ -544,6 +562,9 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 	for fn != nil {
 		err := compileCallArgs(fn, fnt, ctx, v, ellipsis, flags)
 		if err == nil {
+			if rec := ctx.recorder(); rec != nil {
+				rec.Type(v, typesutil.NewTypeAndValueForCallResult(ctx.cb.Get(-1).Type))
+			}
 			break
 		}
 		if fn.next == nil {
@@ -794,6 +815,9 @@ func compileStructLitInKeyVal(ctx *blockCtx, elts []ast.Expr, t *types.Struct, t
 			err := ctx.newCodeErrorf(name.Pos(), "%s undefined (type %v has no field or method %s)", src, typ, name.Name)
 			panic(err)
 		}
+		if rec := ctx.recorder(); rec != nil {
+			rec.Use(name, t.Field(idx))
+		}
 		switch expr := kv.Value.(type) {
 		case *ast.LambdaExpr, *ast.LambdaExpr2:
 			sig := checkLambdaFuncType(ctx, expr, t.Field(idx).Type(), clLambaField, kv.Key)
@@ -878,6 +902,9 @@ func compileCompositeLit(ctx *blockCtx, v *ast.CompositeLit, expected types.Type
 	}
 	if t, ok := underlying.(*types.Struct); ok && kind == compositeLitKeyVal {
 		compileStructLitInKeyVal(ctx, v.Elts, t, typ, v)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Type(v, typesutil.NewTypeAndValueForValue(typ, nil))
+		}
 		if hasPtr {
 			ctx.cb.UnaryOp(gotoken.AND)
 		}
@@ -892,6 +919,10 @@ func compileCompositeLit(ctx *blockCtx, v *ast.CompositeLit, expected types.Type
 		ctx.cb.MapLit(nil, n<<1)
 		return
 	}
+	if rec := ctx.recorder(); rec != nil {
+		rec.Type(v.Type, typesutil.NewTypeAndValueForType(typ))
+		rec.Type(v, typesutil.NewTypeAndValueForValue(typ, nil))
+	}
 	switch underlying.(type) {
 	case *types.Slice:
 		ctx.cb.SliceLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 23ee6fdc..4622a029 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -25,6 +25,7 @@ import (
 	"strconv"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl/internal/typesutil"
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 )
@@ -112,7 +113,12 @@ func toParam(ctx *blockCtx, fld *ast.Field, args []*gox.Param) []*gox.Param {
 
 // -----------------------------------------------------------------------------
 
-func toType(ctx *blockCtx, typ ast.Expr) types.Type {
+func toType(ctx *blockCtx, typ ast.Expr) (t types.Type) {
+	if rec := ctx.recorder(); rec != nil {
+		defer func() {
+			rec.Type(typ, typesutil.NewTypeAndValueForType(t))
+		}()
+	}
 	switch v := typ.(type) {
 	case *ast.Ident:
 		ctx.idents = append(ctx.idents, v)
@@ -228,6 +234,7 @@ func toIdentType(ctx *blockCtx, ident *ast.Ident) (ret types.Type) {
 		defer func() {
 			if obj != nil {
 				rec.Use(ident, obj)
+				rec.Type(ident, typesutil.NewTypeAndValueForObject(obj))
 			}
 		}()
 	}
@@ -341,7 +348,11 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			}
 		}
 	}
-	return types.NewStruct(fields, tags)
+	st := types.NewStruct(fields, tags)
+	if rec != nil {
+		rec.Type(v, typesutil.NewTypeAndValueForType(st))
+	}
+	return st
 }
 
 func toFieldTag(v *ast.BasicLit) string {
diff --git a/cl/internal/spx/pkg/pkg.go b/cl/internal/spx/pkg/pkg.go
index 649406b5..9c77e7e7 100644
--- a/cl/internal/spx/pkg/pkg.go
+++ b/cl/internal/spx/pkg/pkg.go
@@ -5,8 +5,8 @@ var (
 )
 
 type Vector struct {
-	x int
-	y int
+	X int
+	Y int
 }
 
 func NewVector(x, y int) *Vector {
@@ -14,10 +14,14 @@ func NewVector(x, y int) *Vector {
 }
 
 func (v *Vector) Add__0(x int, y int) {
-	v.x += x
-	v.y += y
+	v.X += x
+	v.Y += y
 }
 
 func (v *Vector) Add__1(o *Vector) {
-	v.Add__0(o.x, o.y)
+	v.Add__0(o.X, o.Y)
+}
+
+func (v *Vector) Self() *Vector {
+	return v
 }
diff --git a/cl/internal/spx/sprite.go b/cl/internal/spx/sprite.go
index 00963bb8..b6b0615f 100644
--- a/cl/internal/spx/sprite.go
+++ b/cl/internal/spx/sprite.go
@@ -22,6 +22,15 @@ import (
 
 type Sprite struct {
 	pos pkg.Vector
+	Entry
+}
+
+type Entry struct {
+	vec pkg.Vector
+}
+
+func (p *Entry) Vector() *pkg.Vector {
+	return &p.vec
 }
 
 func (p *Sprite) SetCostume(costume interface{}) {
diff --git a/cl/internal/typesutil/api.go b/cl/internal/typesutil/api.go
index e5827a9d..000ce81f 100644
--- a/cl/internal/typesutil/api.go
+++ b/cl/internal/typesutil/api.go
@@ -49,13 +49,66 @@ type TypeAndValue struct {
 	Value constant.Value
 }
 
-func NewTypeAndValue(typ types.Type, val constant.Value) (ret types.TypeAndValue) {
-	if t, ok := typ.(*gox.TypeType); ok {
-		ret.Type = t.Type()
-		(*TypeAndValue)(unsafe.Pointer(&ret)).mode = typexpr
+func NewTypeAndValueForType(typ types.Type) (ret types.TypeAndValue) {
+	switch t := typ.(type) {
+	case *gox.TypeType:
+		typ = t.Type()
+	}
+	ret.Type = types.Default(typ)
+	(*TypeAndValue)(unsafe.Pointer(&ret)).mode = typexpr
+	return
+}
+
+func NewTypeAndValueForValue(typ types.Type, val constant.Value) (ret types.TypeAndValue) {
+	var mode operandMode
+	if val != nil {
+		mode = constant_
 	} else {
-		ret.Type = typ
-		ret.Value = val
+		mode = value
+	}
+	ret.Type = types.Default(typ)
+	ret.Value = val
+	(*TypeAndValue)(unsafe.Pointer(&ret)).mode = mode
+	return
+}
+
+func NewTypeAndValueForVariable(typ types.Type) (ret types.TypeAndValue) {
+	ret.Type = typ
+	(*TypeAndValue)(unsafe.Pointer(&ret)).mode = variable
+	return
+}
+
+func NewTypeAndValueForCallResult(typ types.Type) (ret types.TypeAndValue) {
+	if typ == nil {
+		ret.Type = &types.Tuple{}
+		(*TypeAndValue)(unsafe.Pointer(&ret)).mode = novalue
+		return
+	}
+	ret.Type = typ
+	(*TypeAndValue)(unsafe.Pointer(&ret)).mode = value
+	return
+}
+
+func NewTypeAndValueForObject(obj types.Object) (ret types.TypeAndValue) {
+	var mode operandMode
+	var val constant.Value
+	switch v := obj.(type) {
+	case *types.Const:
+		mode = constant_
+		val = v.Val()
+	case *types.TypeName:
+		mode = typexpr
+	case *types.Var:
+		mode = variable
+	case *types.Func:
+		mode = value
+	case *types.Builtin:
+		mode = builtin
+	case *types.Nil:
+		mode = value
 	}
+	ret.Type = obj.Type()
+	ret.Value = val
+	(*TypeAndValue)(unsafe.Pointer(&ret)).mode = mode
 	return
 }
diff --git a/cl/stmt.go b/cl/stmt.go
index b236c61c..805646b5 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -42,11 +42,23 @@ func relFile(dir string, file string) string {
 	return file
 }
 
+func offsetFileLine(ctx *blockCtx, file string) string {
+	if !filepath.IsAbs(file) {
+		file = filepath.Join(ctx.fileLineWorkingDir, file)
+	}
+	if ret, err := filepath.Rel(ctx.fileLineRoot, file); err == nil {
+		return ret
+	}
+	return file
+}
+
 func commentStmt(ctx *blockCtx, stmt ast.Stmt) {
 	if ctx.fileLine {
 		start := stmt.Pos()
 		pos := ctx.fset.Position(start)
-		if ctx.relativePath {
+		if ctx.fileLineRoot != "" {
+			pos.Filename = offsetFileLine(ctx, pos.Filename)
+		} else if ctx.relativePath {
 			pos.Filename = relFile(ctx.targetDir, pos.Filename)
 		}
 		line := fmt.Sprintf("\n//line %s:%d:1", pos.Filename, pos.Line)
@@ -58,10 +70,12 @@ func commentStmt(ctx *blockCtx, stmt ast.Stmt) {
 }
 
 func commentFunc(ctx *blockCtx, fn *gox.Func, decl *ast.FuncDecl) {
-	if ctx.fileLine {
-		start := decl.Name.Pos()
+	start := decl.Name.Pos()
+	if ctx.fileLine && start != token.NoPos {
 		pos := ctx.fset.Position(start)
-		if ctx.relativePath {
+		if ctx.fileLineRoot != "" {
+			pos.Filename = offsetFileLine(ctx, pos.Filename)
+		} else if ctx.relativePath {
 			pos.Filename = relFile(ctx.targetDir, pos.Filename)
 		}
 		var line string
diff --git a/cmd/internal/clean/clean.go b/cmd/internal/clean/clean.go
index 12d448ec..a06c5fce 100644
--- a/cmd/internal/clean/clean.go
+++ b/cmd/internal/clean/clean.go
@@ -18,7 +18,6 @@ package clean
 
 import (
 	"fmt"
-	"io/ioutil"
 	"log"
 	"os"
 	"path/filepath"
@@ -28,15 +27,15 @@ import (
 )
 
 const (
-	autoGenFile      = "gop_autogen.go"
-	autoGenTestFile  = "gop_autogen_test.go"
-	autoGen2TestFile = "gop_autogen2_test.go"
+	autoGenFileSuffix = "_autogen.go"
+	autoGenTestFile   = "gop_autogen_test.go"
+	autoGen2TestFile  = "gop_autogen2_test.go"
 )
 
 // -----------------------------------------------------------------------------
 
-func cleanAGFiles(dir string) {
-	fis, err := ioutil.ReadDir(dir)
+func cleanAGFiles(dir string, execAct bool) {
+	fis, err := os.ReadDir(dir)
 	if err != nil {
 		return
 	}
@@ -48,25 +47,34 @@ func cleanAGFiles(dir string) {
 		if fi.IsDir() {
 			pkgDir := filepath.Join(dir, fname)
 			if fname == ".gop" {
-				removeGopDir(pkgDir)
+				removeGopDir(pkgDir, execAct)
 			} else {
-				cleanAGFiles(pkgDir)
+				cleanAGFiles(pkgDir, execAct)
 			}
 			continue
 		}
+		if strings.HasSuffix(fname, autoGenFileSuffix) {
+			file := filepath.Join(dir, fname)
+			fmt.Printf("Cleaning %s ...\n", file)
+			if execAct {
+				os.Remove(file)
+			}
+		}
 	}
-	autogens := []string{autoGenFile, autoGenTestFile, autoGen2TestFile}
+	autogens := []string{autoGenTestFile, autoGen2TestFile}
 	for _, autogen := range autogens {
 		file := filepath.Join(dir, autogen)
 		if _, err = os.Stat(file); err == nil {
 			fmt.Printf("Cleaning %s ...\n", file)
-			os.Remove(file)
+			if execAct {
+				os.Remove(file)
+			}
 		}
 	}
 }
 
-func removeGopDir(dir string) {
-	fis, err := ioutil.ReadDir(dir)
+func removeGopDir(dir string, execAct bool) {
+	fis, err := os.ReadDir(dir)
 	if err != nil {
 		return
 	}
@@ -75,23 +83,29 @@ func removeGopDir(dir string) {
 		if strings.HasSuffix(fname, ".gop.go") {
 			genfile := filepath.Join(dir, fname)
 			fmt.Printf("Cleaning %s ...\n", genfile)
-			os.Remove(genfile)
+			if execAct {
+				os.Remove(genfile)
+			}
 		}
 	}
-	os.Remove(dir)
+	if execAct {
+		os.Remove(dir)
+	}
 }
 
 // -----------------------------------------------------------------------------
 
 // Cmd - gop clean
 var Cmd = &base.Command{
-	UsageLine: "gop clean [-v] <gopSrcDir>",
+	UsageLine: "gop clean [flags] <gopSrcDir>",
 	Short:     "Clean all Go+ auto generated files",
 }
 
 var (
 	flag = &Cmd.Flag
-	_    = flag.Bool("v", false, "print verbose information.")
+
+	_        = flag.Bool("v", false, "print verbose information.")
+	testMode = flag.Bool("t", false, "test mode: display files to clean but don't clean them.")
 )
 
 func init() {
@@ -109,7 +123,7 @@ func runCmd(_ *base.Command, args []string) {
 	} else {
 		dir = flag.Arg(0)
 	}
-	cleanAGFiles(dir)
+	cleanAGFiles(dir, !*testMode)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cmd/internal/env/env.go b/cmd/internal/env/env.go
index 0fcb86a7..43febb85 100644
--- a/cmd/internal/env/env.go
+++ b/cmd/internal/env/env.go
@@ -27,6 +27,7 @@ import (
 
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/env"
+	"github.com/goplus/gop/x/gocmd"
 	"github.com/goplus/mod"
 	"github.com/goplus/mod/modcache"
 )
@@ -71,6 +72,7 @@ func runCmd(_ *base.Command, args []string) {
 	gopEnv["BUILDDATE"] = env.BuildDate()
 	gopEnv["GOPVERSION"] = env.Version()
 	gopEnv["GOPROOT"] = env.GOPROOT()
+	gopEnv["GOP_GOCMD"] = gocmd.Name()
 	gopEnv["GOMODCACHE"] = modcache.GOMODCACHE
 	gopEnv["GOPMOD"], _ = mod.GOPMOD("", 0)
 	gopEnv["HOME"] = env.HOME()
diff --git a/gengo.go b/gengo.go
index 66eb0e23..0e233736 100644
--- a/gengo.go
+++ b/gengo.go
@@ -138,7 +138,7 @@ func genGoEntry(list *errors.List, path string, d fs.DirEntry, conf *Config, fla
 
 func genGoSingleFile(file string, conf *Config, flags GenFlags) (err error) {
 	dir, fname := filepath.Split(file)
-	autogen := dir + strings.TrimSuffix(fname, ".gop") + "_augogen.go"
+	autogen := dir + strings.TrimSuffix(fname, ".gop") + "_autogen.go"
 	if (flags & GenFlagPrompt) != 0 {
 		fmt.Fprintln(os.Stderr, "GenGo", file, "...")
 	}
diff --git a/go.mod b/go.mod
index f9c82170..07ae1813 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,11 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.17
-	github.com/goplus/gox v1.13.0
+	github.com/goplus/c2go v0.7.18
+	github.com/goplus/gox v1.13.1
 	github.com/goplus/mod v0.11.9
 	github.com/qiniu/x v1.13.1
-	golang.org/x/tools v0.14.0
+	golang.org/x/tools v0.15.0
 )
 
 require (
@@ -16,5 +16,5 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	golang.org/x/mod v0.14.0 // indirect
-	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/sys v0.14.0 // indirect
 )
diff --git a/go.sum b/go.sum
index 682743b0..c9d92aff 100644
--- a/go.sum
+++ b/go.sum
@@ -4,10 +4,10 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.17 h1:q/b0Fv1ggJUo0E0ZN/3t8zoE9vmJYTDtAlNSzedeCAA=
-github.com/goplus/c2go v0.7.17/go.mod h1:HhlHE5OAwrJyjg3Dz6yj3b1drugJdoUiWeUl+G3NnU4=
-github.com/goplus/gox v1.13.0 h1:QtgYiZs3J1cFqu4yVZPqOnbbXHk6vDqLNkG4DUIg4EY=
-github.com/goplus/gox v1.13.0/go.mod h1:MOJnwqA8/rSUkn0vsayHFK7TD/3XQrdexMTqdzR70X0=
+github.com/goplus/c2go v0.7.18 h1:lymExgcOTTfMSe8eGlZm3LntBmueDpgNEQQsnLlY3qg=
+github.com/goplus/c2go v0.7.18/go.mod h1:XZJUGrDT0uD0a7clRNRJF8gAi1zfHfFbA8UVIlPqz2w=
+github.com/goplus/gox v1.13.1 h1:/X6Ja9R2hQuLVbN+PzLHRtaBcan+YmhASvGO3nMJsZw=
+github.com/goplus/gox v1.13.1/go.mod h1:UNzOPlRyO/uQn+9iHelk6KelAFaaC7OBn5AxtSSsC9c=
 github.com/goplus/mod v0.11.9 h1:XdWvSNi55fQ3KHnk0PVVHsXynG58lTbfXps/C9HjTVQ=
 github.com/goplus/mod v0.11.9/go.mod h1:YxrBMhvWGcvLU14j8e7qyKSVnj5Loba7GgH1rNXJtDg=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@@ -27,11 +27,10 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
+golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -40,13 +39,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
-golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
+golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -55,25 +54,26 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
-golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
+golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/tools v0.14.0 h1:jvNa2pY0M4r62jkRQ6RwEZZyPcymeL9XZMLBbV7U2nc=
-golang.org/x/tools v0.14.0/go.mod h1:uYBEerGOWcJyEORxN+Ek8+TT266gXkNlHdJBwexUsBg=
+golang.org/x/tools v0.15.0 h1:zdAyfUGbYmuVokhzVmghFl2ZJh5QhcfebBgmVPFYA+8=
+golang.org/x/tools v0.15.0/go.mod h1:hpksKq4dtpQWS1uQ61JkdqWM3LscIS6Slf+VVkm+wQk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/load.go b/load.go
index a26bad87..3e8ae230 100644
--- a/load.go
+++ b/load.go
@@ -187,11 +187,12 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 
 	var pkgTest *ast.Package
 	var clConf = &cl.Config{
-		WorkingDir:  dir,
-		Fset:        fset,
-		Importer:    imp,
-		LookupClass: mod.LookupClass,
-		LookupPub:   c2go.LookupPub(mod),
+		WorkingDir:   dir,
+		Fset:         fset,
+		Importer:     imp,
+		LookupClass:  mod.LookupClass,
+		LookupPub:    c2go.LookupPub(mod),
+		FileLineRoot: mod.Root(),
 	}
 	for name, pkg := range pkgs {
 		if strings.HasSuffix(name, "_test") {
diff --git a/x/gocmd/gocmd.go b/x/gocmd/gocmd.go
index ffb89466..30a8b14e 100644
--- a/x/gocmd/gocmd.go
+++ b/x/gocmd/gocmd.go
@@ -29,6 +29,7 @@ type GopEnv = env.Gop
 
 type Config struct {
 	Gop   *GopEnv
+	GoCmd string
 	Flags []string
 	Run   func(cmd *exec.Cmd) error
 }
@@ -39,12 +40,16 @@ func doWithArgs(op string, conf *Config, args ...string) (err error) {
 	if conf == nil {
 		conf = new(Config)
 	}
+	goCmd := conf.GoCmd
+	if goCmd == "" {
+		goCmd = Name()
+	}
 	exargs := make([]string, 1, 16)
 	exargs[0] = op
 	exargs = appendLdflags(exargs, conf.Gop)
 	exargs = append(exargs, conf.Flags...)
 	exargs = append(exargs, args...)
-	cmd := exec.Command("go", exargs...)
+	cmd := exec.Command(goCmd, exargs...)
 	run := conf.Run
 	if run == nil {
 		run = runCmd
@@ -83,3 +88,16 @@ func appendLdflags(exargs []string, env *GopEnv) []string {
 }
 
 // -----------------------------------------------------------------------------
+
+// Name returns name of the go command.
+// It returns value of environment variable `GOP_GOCMD` if not empty.
+// If not found, it returns `go`.
+func Name() string {
+	goCmd := os.Getenv("GOP_GOCMD")
+	if goCmd == "" {
+		goCmd = "go"
+	}
+	return goCmd
+}
+
+// -----------------------------------------------------------------------------


Test patch:
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 9d387a78..cbb010e7 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -731,3 +731,64 @@ func (this *Kai) onMsg(msg string) {
 }
 `, "Game.tgmx", "Kai.tspx")
 }
+
+func TestSpxSelection(t *testing.T) {
+	gopSpxTestEx(t, `
+println "hi"
+`, `
+import "fmt"
+func onMsg(msg string) {
+	fmt.println msg
+	this.position.add 100,200
+	position.add 100,200
+	position.X += 100
+	println position.X
+	this.vector.add 100,200
+	vector.add 100,200
+	vector.X += 100
+	vector.self.X += 100
+	vector.self.Y += 200
+	vector.self.add position.X,position.Y
+	println vector.X
+	println vector.self.self
+}
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
+)
+
+type Game struct {
+	*spx.MyGame
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("hi")
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(Game))
+}
+
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Kai) onMsg(msg string) {
+	fmt.Println(msg)
+	this.Position().Add__0(100, 200)
+	this.Position().Add__0(100, 200)
+	this.Position().X += 100
+	fmt.Println(this.Position().X)
+	this.Vector().Add__0(100, 200)
+	this.Vector().Add__0(100, 200)
+	this.Vector().X += 100
+	this.Vector().Self().X += 100
+	this.Vector().Self().Y += 200
+	this.Vector().Self().Add__0(this.Position().X, this.Position().Y)
+	fmt.Println(this.Vector().X)
+	fmt.Println(this.Vector().Self().Self())
+}
+`, "Game.tgmx", "Kai.tspx")
+}
diff --git a/cl/compile_test.go b/cl/compile_test.go
index e058472a..52022aeb 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -19,6 +19,7 @@ package cl_test
 import (
 	"bytes"
 	"os"
+	"runtime"
 	"strings"
 	"sync"
 	"testing"
@@ -4652,3 +4653,64 @@ func main() {
 }
 `)
 }
+
+func TestCommentLineRoot(t *testing.T) {
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.FileLineRoot = "/foo/root"
+	conf.WorkingDir = "/foo"
+	conf.TargetDir = "/foo"
+	var src = `
+type Point struct {
+	x int
+	y int
+}
+
+func (pt *Point) Test() {
+	println(pt.x, pt.y)
+}
+
+// testPoint is test point
+func testPoint() {
+	var pt Point
+	pt.Test()
+}
+
+println "hello"
+testPoint()
+`
+	var expected = `package main
+
+import "fmt"
+
+type Point struct {
+	x int
+	y int
+}
+//line ../bar.gop:7:1
+func (pt *Point) Test() {
+//line ../bar.gop:8:1
+	fmt.Println(pt.x, pt.y)
+}
+// testPoint is test point
+//
+//line ../bar.gop:12:1
+func testPoint() {
+//line ../bar.gop:13:1
+	var pt Point
+//line ../bar.gop:14:1
+	pt.Test()
+}
+//line ../bar.gop:17
+func main() {
+//line ../bar.gop:17:1
+	fmt.Println("hello")
+//line ../bar.gop:18:1
+	testPoint()
+}
+`
+	if runtime.GOOS == "windows" {
+		expected = strings.Replace(expected, "../", `..\foo\`, -1)
+	}
+	gopClTestEx(t, &conf, "main", src, expected)
+}
diff --git a/cl/internal/typesutil/api_test.go b/cl/internal/typesutil/api_test.go
index 684f2d86..ec3a9f76 100644
--- a/cl/internal/typesutil/api_test.go
+++ b/cl/internal/typesutil/api_test.go
@@ -25,13 +25,55 @@ import (
 )
 
 func TestTypeAndValue(t *testing.T) {
-	ty := gox.NewTypeType(types.Typ[types.Int])
-	ret := NewTypeAndValue(ty, nil)
+	tyInt := types.Typ[types.Int]
+	ty := gox.NewTypeType(tyInt)
+	ret := NewTypeAndValueForType(ty)
 	if !ret.IsType() {
-		t.Fatal("NewTypeAndValue: not type?")
+		t.Fatal("NewTypeAndValueForType: not type?")
 	}
-	ret = NewTypeAndValue(types.Typ[types.Int], constant.MakeInt64(1))
+	ret = NewTypeAndValueForValue(tyInt, constant.MakeInt64(1))
 	if ret.Value == nil {
-		t.Fatal("NewTypeAndValue: not const?")
+		t.Fatal("NewTypeAndValueForValue: not const?")
+	}
+	ret = NewTypeAndValueForVariable(tyInt)
+	if !ret.Addressable() {
+		t.Fatal("NewTypeAndValueForVariable: not variable?")
+	}
+	ret = NewTypeAndValueForCallResult(tyInt)
+	if !ret.IsValue() {
+		t.Fatal("NewTypeAndValueForCall: not value?")
+	}
+	ret = NewTypeAndValueForCallResult(nil)
+	if !ret.IsVoid() {
+		t.Fatal("NewTypeAndValueForCall: not void?")
+	}
+	pkg := types.NewPackage("main", "main")
+	ret = NewTypeAndValueForObject(types.NewConst(0, pkg, "v", tyInt, constant.MakeInt64(100)))
+	if ret.Value == nil {
+		t.Fatal("NewTypeAndValueForObject: not const?")
+	}
+	ret = NewTypeAndValueForObject(types.NewTypeName(0, pkg, "MyInt", tyInt))
+	if !ret.IsType() {
+		t.Fatal("NewTypeAndValueForObject: not type?")
+	}
+	ret = NewTypeAndValueForObject(types.NewVar(0, pkg, "v", tyInt))
+	if !ret.Addressable() {
+		t.Fatal("NewTypeAndValueForObject: not variable?")
+	}
+	ret = NewTypeAndValueForObject(types.NewFunc(0, pkg, "fn", types.NewSignature(nil, nil, nil, false)))
+	if !ret.IsValue() {
+		t.Fatal("NewTypeAndValueForObject: not value?")
+	}
+	ret = NewTypeAndValueForValue(types.Typ[types.UntypedNil], nil)
+	if !ret.IsNil() {
+		t.Fatal("NewTypeAndValueForValue: not nil?")
+	}
+	ret = NewTypeAndValueForObject(types.Universe.Lookup("nil"))
+	if !ret.IsNil() {
+		t.Fatal("NewTypeAndValueForObject: not nil?")
+	}
+	ret = NewTypeAndValueForObject(types.Universe.Lookup("len"))
+	if !ret.IsBuiltin() {
+		t.Fatal("NewTypeAndValueForObject: not builtin?")
 	}
 }
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
new file mode 100644
index 00000000..fbbbe0ac
--- /dev/null
+++ b/x/typesutil/check_test.go
@@ -0,0 +1,187 @@
+package typesutil_test
+
+import (
+	goast "go/ast"
+	"go/importer"
+	goparser "go/parser"
+	"go/types"
+	"log"
+	"os"
+	"path/filepath"
+	"testing"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gop/x/typesutil"
+	"github.com/goplus/mod/gopmod"
+)
+
+func init() {
+	if os.Getenv("GOPROOT") == "" {
+		dir, _ := os.Getwd()
+		os.Setenv("GOPROOT", filepath.Clean(filepath.Join(dir, "./../..")))
+	}
+	typesutil.SetDebug(typesutil.DbgFlagDefault)
+}
+
+func loadFiles(fset *token.FileSet, file string, src interface{}, goxfile string, goxsrc interface{}, gofile string, gosrc interface{}) ([]*ast.File, []*goast.File, error) {
+	var files []*ast.File
+	var gofiles []*goast.File
+	if file != "" {
+		f, err := parser.ParseFile(fset, file, src, 0)
+		if err != nil {
+			return nil, nil, err
+		}
+		files = append(files, f)
+	}
+	if goxfile != "" {
+		f, err := parser.ParseFile(fset, goxfile, goxsrc, parser.ParseGoPlusClass)
+		if err != nil {
+			return nil, nil, err
+		}
+		files = append(files, f)
+	}
+	if gofile != "" {
+		f, err := goparser.ParseFile(fset, gofile, gosrc, 0)
+		if err != nil {
+			return nil, nil, err
+		}
+		gofiles = append(gofiles, f)
+	}
+	return files, gofiles, nil
+}
+
+func checkFiles(fset *token.FileSet, file string, src interface{}, goxfile string, goxsrc interface{}, gofile string, gosrc interface{}) (*typesutil.Info, *types.Info, error) {
+	files, gofiles, err := loadFiles(fset, file, src, goxfile, goxsrc, gofile, gosrc)
+	if err != nil {
+		return nil, nil, err
+	}
+	return checkInfo(fset, files, gofiles)
+}
+
+func checkInfo(fset *token.FileSet, files []*ast.File, gofiles []*goast.File) (*typesutil.Info, *types.Info, error) {
+	conf := &types.Config{}
+	conf.Importer = importer.Default()
+	conf.Error = func(err error) {
+		log.Println(err)
+	}
+	chkOpts := &typesutil.Config{
+		Types: types.NewPackage("main", "main"),
+		Fset:  fset,
+		Mod:   gopmod.Default,
+	}
+	info := &typesutil.Info{
+		Types:      make(map[ast.Expr]types.TypeAndValue),
+		Defs:       make(map[*ast.Ident]types.Object),
+		Uses:       make(map[*ast.Ident]types.Object),
+		Implicits:  make(map[ast.Node]types.Object),
+		Selections: make(map[*ast.SelectorExpr]*types.Selection),
+		Scopes:     make(map[ast.Node]*types.Scope),
+	}
+	ginfo := &types.Info{
+		Types:      make(map[goast.Expr]types.TypeAndValue),
+		Defs:       make(map[*goast.Ident]types.Object),
+		Uses:       make(map[*goast.Ident]types.Object),
+		Implicits:  make(map[goast.Node]types.Object),
+		Selections: make(map[*goast.SelectorExpr]*types.Selection),
+		Scopes:     make(map[goast.Node]*types.Scope),
+	}
+	check := typesutil.NewChecker(conf, chkOpts, ginfo, info)
+	err := check.Files(gofiles, files)
+	return info, ginfo, err
+}
+
+func TestCheckFiles(t *testing.T) {
+	fset := token.NewFileSet()
+	info, ginfo, err := checkFiles(fset, "main.gop", `
+import "fmt"
+
+type Point struct {
+	x int
+	y int
+}
+pt := &Point{}
+pt.x = 100
+pt.y = 200
+fmt.Println(pt)
+
+gopt := GoPoint{100,200}
+gopt.Test()
+gotest()
+fmt.Println(GoValue)
+fmt.Println(&Rect{100,200})
+`, "Rect.gox", `
+var (
+	x int
+	y int
+)
+`, "util.go", `package main
+var GoValue string
+type GoPoint struct {
+	x int
+	y int
+}
+func (p *GoPoint) Test() {
+}
+func gotest() {
+}
+`)
+	if err != nil || info == nil || ginfo == nil {
+		t.Fatalf("check failed: %v", err)
+	}
+
+	for def, obj := range info.Defs {
+		o := info.ObjectOf(def)
+		if o != obj {
+			t.Fatal("bad obj", o)
+		}
+	}
+	for use, obj := range info.Uses {
+		o := info.ObjectOf(use)
+		if o.String() != obj.String() {
+			t.Fatal("bad obj", o)
+		}
+		typ := info.TypeOf(use)
+		if typ.String() != obj.Type().String() {
+			t.Fatal("bad typ", typ)
+		}
+	}
+
+}
+
+func TestCheckGoFiles(t *testing.T) {
+	fset := token.NewFileSet()
+	info, ginfo, err := checkFiles(fset, "", "", "", "", "main.go", `package main
+type GoPoint struct {
+	x int
+	y int
+}
+func main() {
+}
+`)
+	if err != nil || info == nil || ginfo == nil {
+		t.Fatalf("check failed: %v", err)
+	}
+}
+
+func TestCheckError(t *testing.T) {
+	fset := token.NewFileSet()
+	_, _, err := checkFiles(fset, "main.gop", `
+type Point struct {
+	x int
+	y int
+}
+pt := &Point1{}
+println(pt)
+`, "", "", "", "")
+	if err == nil {
+		t.Fatal("no error")
+	}
+	_, _, err = checkFiles(fset, "main.gop", `
+var i int = "hello"
+`, "", "", "", "")
+	if err == nil {
+		t.Fatal("no error")
+	}
+}
diff --git a/x/typesutil/exprstring_test.go b/x/typesutil/exprstring_test.go
new file mode 100644
index 00000000..e88d925f
--- /dev/null
+++ b/x/typesutil/exprstring_test.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.
+
+package typesutil_test
+
+import (
+	"testing"
+
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/typesutil"
+)
+
+type testEntry struct {
+	src, str string
+}
+
+// dup returns a testEntry where both src and str are the same.
+func dup(s string) testEntry {
+	return testEntry{s, s}
+}
+
+var testExprs = []testEntry{
+	// basic type literals
+	dup("x"),
+	dup("true"),
+	dup("42"),
+	dup("3.1415"),
+	dup("2.71828i"),
+	dup(`'a'`),
+	dup(`"foo"`),
+	dup("`bar`"),
+
+	// func and composite literals
+	{"func(){}", "(func() literal)"},
+	{"func(x int) complex128 {}", "(func(x int) complex128 literal)"},
+	{"[]int{1, 2, 3}", "[]int{…}"},
+
+	// type expressions
+	dup("[1 << 10]byte"),
+	dup("[]int"),
+	dup("*int"),
+	dup("struct{x int}"),
+	dup("func()"),
+	dup("func(int, float32) string"),
+	dup("interface{m()}"),
+	dup("interface{m() string; n(x int)}"),
+
+	dup("map[string]int"),
+	dup("chan E"),
+	dup("<-chan E"),
+	dup("chan<- E"),
+
+	// non-type expressions
+	dup("(x)"),
+	dup("x.f"),
+	dup("a[i]"),
+
+	dup("s[:]"),
+	dup("s[i:]"),
+	dup("s[:j]"),
+	dup("s[i:j]"),
+	dup("s[:j:k]"),
+	dup("s[i:j:k]"),
+
+	dup("x.(T)"),
+
+	dup("x.([10]int)"),
+	dup("x.([...]int)"),
+
+	dup("x.(struct{})"),
+	dup("x.(struct{x int; y, z float32; E})"),
+
+	dup("x.(func())"),
+	dup("x.(func(x int))"),
+	dup("x.(func() int)"),
+	dup("x.(func(x, y int, z float32) (r int))"),
+	dup("x.(func(a, b, c int))"),
+	dup("x.(func(x ...T))"),
+
+	dup("x.(interface{})"),
+	dup("x.(interface{m(); n(x int); E})"),
+	dup("x.(interface{m(); n(x int) T; E; F})"),
+
+	dup("x.(map[K]V)"),
+
+	dup("x.(chan E)"),
+	dup("x.(<-chan E)"),
+	dup("x.(chan<- chan int)"),
+	dup("x.(chan<- <-chan int)"),
+	dup("x.(<-chan chan int)"),
+	dup("x.(chan (<-chan int))"),
+
+	dup("f()"),
+	dup("f(x)"),
+	dup("int(x)"),
+	dup("f(x, x + y)"),
+	dup("f(s...)"),
+	dup("f(a, s...)"),
+
+	dup("*x"),
+	dup("&x"),
+	dup("x + y"),
+	dup("x + y << (2 * s)"),
+}
+
+func TestExprString(t *testing.T) {
+	for _, test := range testExprs {
+		x, err := parser.ParseExpr(test.src)
+		if err != nil {
+			t.Errorf("%s: %s", test.src, err)
+			continue
+		}
+		if got := typesutil.ExprString(x); got != test.str {
+			t.Errorf("%s: got %s, want %s", test.src, got, test.str)
+		}
+	}
+}
diff --git a/x/typesutil/imp_test.go b/x/typesutil/imp_test.go
new file mode 100644
index 00000000..9751a4ed
--- /dev/null
+++ b/x/typesutil/imp_test.go
@@ -0,0 +1,20 @@
+package typesutil
+
+import (
+	"go/token"
+	"testing"
+)
+
+func TestNilImport(t *testing.T) {
+	_, err := (&nilImporter{}).Import("fmt")
+	if err == nil {
+		t.Fatal("no error")
+	}
+	imp := newImporter(nil, nil, nil, token.NewFileSet())
+	if imp.(*importer).imp == nil {
+		t.Fatal("nilImporter")
+	}
+	if imp.(*importer).gop == nil {
+		t.Fatal("gopenv.Get")
+	}
+}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
new file mode 100644
index 00000000..840ab8d2
--- /dev/null
+++ b/x/typesutil/info_test.go
@@ -0,0 +1,388 @@
+package typesutil_test
+
+import (
+	"fmt"
+
+	goast "go/ast"
+	"go/constant"
+	goformat "go/format"
+	goparser "go/parser"
+
+	"go/importer"
+	"go/types"
+	"sort"
+	"strings"
+	"testing"
+	"unsafe"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/format"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gop/x/typesutil"
+	"github.com/goplus/mod/gopmod"
+)
+
+func parserSource(fset *token.FileSet, filename string, src interface{}, mode parser.Mode) (*typesutil.Info, error) {
+	f, err := parser.ParseEntry(fset, filename, src, parser.Config{
+		Mode: mode,
+	})
+	if err != nil {
+		return nil, err
+	}
+
+	conf := &types.Config{}
+	conf.Importer = importer.Default()
+	chkOpts := &typesutil.Config{
+		Types: types.NewPackage("main", "main"),
+		Fset:  fset,
+		Mod:   gopmod.Default,
+	}
+	info := &typesutil.Info{
+		Types:      make(map[ast.Expr]types.TypeAndValue),
+		Defs:       make(map[*ast.Ident]types.Object),
+		Uses:       make(map[*ast.Ident]types.Object),
+		Implicits:  make(map[ast.Node]types.Object),
+		Selections: make(map[*ast.SelectorExpr]*types.Selection),
+		Scopes:     make(map[ast.Node]*types.Scope),
+	}
+	check := typesutil.NewChecker(conf, chkOpts, nil, info)
+	err = check.Files(nil, []*ast.File{f})
+	return info, err
+}
+
+func parserGoSource(fset *token.FileSet, filename string, src interface{}, mode goparser.Mode) (*types.Info, error) {
+	f, err := goparser.ParseFile(fset, filename, src, mode)
+	if err != nil {
+		return nil, err
+	}
+
+	conf := &types.Config{}
+	conf.Importer = importer.Default()
+	info := &types.Info{
+		Types:      make(map[goast.Expr]types.TypeAndValue),
+		Defs:       make(map[*goast.Ident]types.Object),
+		Uses:       make(map[*goast.Ident]types.Object),
+		Implicits:  make(map[goast.Node]types.Object),
+		Selections: make(map[*goast.SelectorExpr]*types.Selection),
+		Scopes:     make(map[goast.Node]*types.Scope),
+	}
+	pkg := types.NewPackage("main", "main")
+	check := types.NewChecker(conf, fset, pkg, info)
+	err = check.Files([]*goast.File{f})
+	return info, err
+}
+
+func testInfo(t *testing.T, src interface{}) {
+	fset := token.NewFileSet()
+	info, err := parserSource(fset, "main.gop", src, parser.ParseComments)
+	if err != nil {
+		t.Fatal("parserSource error", err)
+	}
+	goinfo, err := parserGoSource(fset, "main.go", src, goparser.ParseComments)
+	if err != nil {
+		t.Fatal("parserGoSource error", err)
+	}
+	testItems(t, "types", typesList(fset, info.Types), goTypesList(fset, goinfo.Types))
+	testItems(t, "defs", defsList(fset, info.Defs, true), goDefsList(fset, goinfo.Defs, true))
+	testItems(t, "uses", usesList(fset, info.Uses), goUsesList(fset, goinfo.Uses))
+	// TODO check selections
+	//testItems(t, "selections", selectionList(fset, info.Selections), goSelectionList(fset, goinfo.Selections))
+}
+
+func testItems(t *testing.T, name string, items []string, goitems []string) {
+	text := strings.Join(items, "\n")
+	gotext := strings.Join(goitems, "\n")
+	if len(items) != len(goitems) || text != gotext {
+		t.Errorf(`====== check %v error (Go+ count: %v, Go count %v) ====== 
+------ Go+ ------
+%v
+------ Go ------
+%v
+`,
+			name, len(items), len(goitems),
+			text, gotext)
+	} else {
+		t.Log(fmt.Sprintf(`====== check %v pass (count: %v) ======
+%v
+`, name, len(items), text))
+	}
+}
+
+func sortItems(items []string) []string {
+	sort.Strings(items)
+	for i := 0; i < len(items); i++ {
+		items[i] = fmt.Sprintf("%03v: %v", i, items[i])
+	}
+	return items
+}
+
+func typesList(fset *token.FileSet, types map[ast.Expr]types.TypeAndValue) []string {
+	var items []string
+	for expr, tv := range types {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		tvstr := tv.Type.String()
+		if tv.Value != nil {
+			tvstr += " = " + tv.Value.String()
+		}
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s %-30T | %-7s : %s | %v",
+			posn.Line, posn.Column, exprString(fset, expr), expr,
+			mode(tv), tvstr, (*TypeAndValue)(unsafe.Pointer(&tv)).mode)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func goTypesList(fset *token.FileSet, types map[goast.Expr]types.TypeAndValue) []string {
+	var items []string
+	for expr, tv := range types {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		tvstr := tv.Type.String()
+		if tv.Value != nil {
+			tvstr += " = " + tv.Value.String()
+		}
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s %-30T | %-7s : %s | %v",
+			posn.Line, posn.Column, goexprString(fset, expr), expr,
+			mode(tv), tvstr, (*TypeAndValue)(unsafe.Pointer(&tv)).mode)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func defsList(fset *token.FileSet, uses map[*ast.Ident]types.Object, skipNil bool) []string {
+	var items []string
+	for expr, obj := range uses {
+		if skipNil && obj == nil {
+			continue
+		}
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, expr,
+			obj)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func goDefsList(fset *token.FileSet, uses map[*goast.Ident]types.Object, skipNil bool) []string {
+	var items []string
+	for expr, obj := range uses {
+		if skipNil && obj == nil {
+			continue // skip nil object
+		}
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, expr,
+			obj)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func usesList(fset *token.FileSet, uses map[*ast.Ident]types.Object) []string {
+	var items []string
+	for expr, obj := range uses {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, expr,
+			obj)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func goUsesList(fset *token.FileSet, uses map[*goast.Ident]types.Object) []string {
+	var items []string
+	for expr, obj := range uses {
+		if obj == nil {
+			continue // skip nil object
+		}
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, expr,
+			obj)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func selectionList(fset *token.FileSet, sels map[*ast.SelectorExpr]*types.Selection) []string {
+	var items []string
+	for expr, sel := range sels {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, exprString(fset, expr),
+			sel)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func goSelectionList(fset *token.FileSet, sels map[*goast.SelectorExpr]*types.Selection) []string {
+	var items []string
+	for expr, sel := range sels {
+		var buf strings.Builder
+		posn := fset.Position(expr.Pos())
+		// line:col | expr | mode : type = value
+		fmt.Fprintf(&buf, "%2d:%2d | %-19s | %s",
+			posn.Line, posn.Column, goexprString(fset, expr),
+			sel)
+		items = append(items, buf.String())
+	}
+	return sortItems(items)
+}
+
+func mode(tv types.TypeAndValue) string {
+	switch {
+	case tv.IsVoid():
+		return "void"
+	case tv.IsType():
+		return "type"
+	case tv.IsBuiltin():
+		return "builtin"
+	case tv.IsNil():
+		return "nil"
+	case tv.Assignable():
+		if tv.Addressable() {
+			return "var"
+		}
+		return "mapindex"
+	case tv.IsValue():
+		return "value"
+	default:
+		return "unknown"
+	}
+}
+
+func exprString(fset *token.FileSet, expr ast.Expr) string {
+	var buf strings.Builder
+	format.Node(&buf, fset, expr)
+	return buf.String()
+}
+
+func goexprString(fset *token.FileSet, expr goast.Expr) string {
+	var buf strings.Builder
+	goformat.Node(&buf, fset, expr)
+	return buf.String()
+}
+
+type operandMode byte
+
+const (
+	invalid   operandMode = iota // operand is invalid
+	novalue                      // operand represents no value (result of a function call w/o result)
+	builtin                      // operand is a built-in function
+	typexpr                      // operand is a type
+	constant_                    // operand is a constant; the operand's typ is a Basic type
+	variable                     // operand is an addressable variable
+	mapindex                     // operand is a map index expression (acts like a variable on lhs, commaok on rhs of an assignment)
+	value                        // operand is a computed value
+	commaok                      // like value, but operand may be used in a comma,ok expression
+	commaerr                     // like commaok, but second value is error, not boolean
+	cgofunc                      // operand is a cgo function
+)
+
+func (v operandMode) String() string {
+	return operandModeString[int(v)]
+}
+
+var operandModeString = [...]string{
+	invalid:   "invalid operand",
+	novalue:   "no value",
+	builtin:   "built-in",
+	typexpr:   "type",
+	constant_: "constant",
+	variable:  "variable",
+	mapindex:  "map index expression",
+	value:     "value",
+	commaok:   "comma, ok expression",
+	commaerr:  "comma, error expression",
+	cgofunc:   "cgo function",
+}
+
+type TypeAndValue struct {
+	mode  operandMode
+	Type  types.Type
+	Value constant.Value
+}
+
+func TestVarTypes(t *testing.T) {
+	testInfo(t, `package main
+type T struct {
+	x int
+	y int
+}
+var v *int = nil
+var v1 []int;
+var v2 map[int8]string;
+var v3 struct{};
+var v4 *T = &T{100,200};
+`)
+}
+
+func TestStruct(t *testing.T) {
+	testInfo(t, `package main
+
+type Person struct {
+	name string
+	age  int8
+}
+
+func test() {
+	p := Person{
+		name: "jack",
+	}
+	_ = p.name
+}
+`)
+}
+
+func _TestStruct2(t *testing.T) {
+	testInfo(t, `package main
+
+type Person struct {
+	name string
+	age  int8
+}
+
+func test() {
+	p := Person{
+		name: "jack",
+	}
+	p.name = "name"
+}
+`)
+}
+
+func _TestStruct3(t *testing.T) {
+	testInfo(t, `package main
+
+import "fmt"
+
+type Person struct {
+	name string
+	age  int8
+}
+
+func test() {
+	p := Person{
+		name: "jack",
+	}
+	fmt.Println(p)
+}
+`)
+}