Repository: goplus/gop
Pull request #1508 (2023-11-01 04:30:48)

v1.1.9


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 193276de..8970d496 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -10,7 +10,7 @@ jobs:
   Test:
     strategy:
       matrix:
-        go-version: [1.17.x, 1.18.x, 1.19.x, 1.20.x]
+        go-version: [1.18.x, 1.19.x, 1.20.x, 1.21.x]
         os: [ubuntu-latest, windows-latest, macos-11]
     runs-on: ${{ matrix.os }}
     steps:
diff --git a/README.md b/README.md
index 3912c3aa..70e944ca 100644
--- a/README.md
+++ b/README.md
@@ -51,7 +51,7 @@ For more details, see [Quick Start](doc/docs.md).
 
 For now, we suggest you install Go+ from source code.
 
-Note: Requires go1.17 or later
+Note: Requires go1.18 or later
 
 ```bash
 git clone https://github.com/goplus/gop.git
diff --git a/VERSION b/VERSION
deleted file mode 100644
index be388913..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.1.8
diff --git a/ast/ast.go b/ast/ast.go
index 57f498d7..f0b48953 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -1059,6 +1059,7 @@ type (
 		Type     *FuncType     // function signature: parameters, results, and position of "func" keyword
 		Body     *BlockStmt    // function body; or nil for external (non-Go) function
 		Operator bool          // is operator or not
+		Shadow   bool          // is a shadow entry
 	}
 )
 
@@ -1123,7 +1124,7 @@ type FileType = int16
 // are "free-floating" (see also issues #18593, #20744).
 type File struct {
 	Doc     *CommentGroup // associated documentation; or nil
-	Package token.Pos     // position of "package" keyword
+	Package token.Pos     // position of "package" keyword; or NoPos
 	Name    *Ident        // package name
 	Decls   []Decl        // top-level declarations; or nil
 
@@ -1144,8 +1145,18 @@ func (f *File) NoEntrypoint() bool {
 	return f.ShadowEntry != nil
 }
 
+// HasPkgDecl checks if `package xxx` exists or not.
+func (f *File) HasPkgDecl() bool {
+	return f.Package != token.NoPos
+}
+
 // Pos returns position of first character belonging to the node.
-func (f *File) Pos() token.Pos { return f.Package }
+func (f *File) Pos() token.Pos {
+	if f.Package != token.NoPos {
+		return f.Package
+	}
+	return f.Name.NamePos
+}
 
 // End returns position of first character immediately after the node.
 func (f *File) End() token.Pos {
diff --git a/ast/fromgo/gopast.go b/ast/fromgo/gopast.go
index 02220fb2..9ec50a0d 100644
--- a/ast/fromgo/gopast.go
+++ b/ast/fromgo/gopast.go
@@ -211,9 +211,19 @@ func gopIdent(v *ast.Ident) *gopast.Ident {
 	return &gopast.Ident{
 		NamePos: v.NamePos,
 		Name:    v.Name,
+		Obj:     &gopast.Object{Data: v},
 	}
 }
 
+// CheckIdent checks if a Go+ ast.Ident is converted from a Go ast.Ident or not.
+// If it is, CheckIdent returns the original Go ast.Ident object.
+func CheckIdent(v *gopast.Ident) (id *ast.Ident, ok bool) {
+	if o := v.Obj; o != nil && o.Kind == 0 && o.Data != nil {
+		id, ok = o.Data.(*ast.Ident)
+	}
+	return
+}
+
 func gopIdents(names []*ast.Ident) []*gopast.Ident {
 	ret := make([]*gopast.Ident, len(names))
 	for i, v := range names {
@@ -332,6 +342,7 @@ const (
 	KeepCgo
 )
 
+// ASTFile converts a Go ast.File into a Go+ ast.File object.
 func ASTFile(f *ast.File, mode int) *gopast.File {
 	if (mode & KeepFuncBody) != 0 {
 		log.Panicln("ASTFile: doesn't support keeping func body now")
diff --git a/ast/walk.go b/ast/walk.go
index e77558d1..3cdc64e9 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -16,7 +16,9 @@
 
 package ast
 
-import "fmt"
+import (
+	"fmt"
+)
 
 // Visitor - A Visitor's Visit method is invoked for each node encountered by Walk.
 // If the result visitor w is not nil, Walk visits each of the children
@@ -59,7 +61,6 @@ func walkDeclList(v Visitor, list []Decl) {
 // v.Visit(node) is not nil, Walk is invoked recursively with visitor
 // w for each of the non-nil children of node, followed by a call of
 // w.Visit(nil).
-//
 func Walk(v Visitor, node Node) {
 	if v = v.Visit(node); v == nil {
 		return
@@ -344,14 +345,16 @@ func Walk(v Visitor, node Node) {
 		}
 
 	case *FuncDecl:
-		if n.Doc != nil {
-			Walk(v, n.Doc)
-		}
-		if n.Recv != nil {
-			Walk(v, n.Recv)
+		if !n.Shadow { // not a shadow entry
+			if n.Doc != nil {
+				Walk(v, n.Doc)
+			}
+			if n.Recv != nil {
+				Walk(v, n.Recv)
+			}
+			Walk(v, n.Name)
+			Walk(v, n.Type)
 		}
-		Walk(v, n.Name)
-		Walk(v, n.Type)
 		if n.Body != nil {
 			Walk(v, n.Body)
 		}
@@ -372,6 +375,62 @@ func Walk(v Visitor, node Node) {
 			Walk(v, f)
 		}
 
+	// Go+ expr and stmt
+	case *SliceLit:
+		walkExprList(v, n.Elts)
+
+	case *LambdaExpr:
+		walkIdentList(v, n.Lhs)
+		walkExprList(v, n.Rhs)
+
+	case *LambdaExpr2:
+		walkIdentList(v, n.Lhs)
+		Walk(v, n.Body)
+
+	case *ForPhrase:
+		if n.Key != nil {
+			Walk(v, n.Key)
+		}
+		if n.Value != nil {
+			Walk(v, n.Value)
+		}
+		if n.Init != nil {
+			Walk(v, n.Init)
+		}
+		if n.Cond != nil {
+			Walk(v, n.Cond)
+		}
+		Walk(v, n.X)
+
+	case *ComprehensionExpr:
+		if n.Elt != nil {
+			Walk(v, n.Elt)
+		}
+		for _, x := range n.Fors {
+			Walk(v, x)
+		}
+
+	case *ForPhraseStmt:
+		Walk(v, n.ForPhrase)
+		Walk(v, n.Body)
+
+	case *RangeExpr:
+		if n.First != nil {
+			Walk(v, n.First)
+		}
+		if n.Last != nil {
+			Walk(v, n.Last)
+		}
+		if n.Expr3 != nil {
+			Walk(v, n.Expr3)
+		}
+
+	case *ErrWrapExpr:
+		Walk(v, n.X)
+		if n.Default != nil {
+			Walk(v, n.Default)
+		}
+
 	default:
 		panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n))
 	}
@@ -392,7 +451,6 @@ func (f inspector) Visit(node Node) Visitor {
 // f(node); node must not be nil. If f returns true, Inspect invokes f
 // recursively for each of the non-nil children of node, followed by a
 // call of f(nil).
-//
 func Inspect(node Node, f func(Node) bool) {
 	Walk(inspector(f), node)
 }
diff --git a/build_install_run.go b/build_install_run.go
index 688d2a84..9972ee65 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -86,8 +86,8 @@ func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfi
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
-	old := chdirAndMod(localDir)
-	defer restoreDirAndMod(old)
+	old, mod := chdirAndMod(localDir)
+	defer restoreDirAndMod(old, mod)
 	return gocmd.Build(cwdParam(recursively), build)
 }
 
@@ -99,13 +99,18 @@ func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err err
 	return gocmd.BuildFiles(files, build)
 }
 
-func chdirAndMod(dir string) string {
+func chdirAndMod(dir string) (old string, mod os.FileMode) {
+	mod = 0755
+	if info, err := os.Stat(dir); err == nil {
+		mod = info.Mode().Perm()
+	}
 	os.Chmod(dir, 0777)
-	return chdir(dir)
+	old = chdir(dir)
+	return
 }
 
-func restoreDirAndMod(old string) {
-	os.Chmod(".", 0555)
+func restoreDirAndMod(old string, mod os.FileMode) {
+	os.Chmod(".", mod)
 	os.Chdir(old)
 }
 
@@ -158,8 +163,8 @@ func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig)
 	if err != nil {
 		return errors.NewWith(err, `GenGoPkgPath(workDir, pkgPath, conf, false)`, -2, "gop.GenGoPkgPath", workDir, pkgPath, conf, false)
 	}
-	old := chdirAndMod(localDir)
-	defer restoreDirAndMod(old)
+	old, mod := chdirAndMod(localDir)
+	defer restoreDirAndMod(old, mod)
 	return gocmd.Test(cwdParam(recursively), test)
 }
 
diff --git a/cl/builtin.go b/cl/builtin.go
index a97ecf34..3058e246 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -41,39 +41,51 @@ func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg *gox.PkgRef,
 
 func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, buil *gox.PkgRef) {
 	scope := builtin.Scope()
-	typs := []string{"bigint", "bigrat", "bigfloat"}
-	for _, typ := range typs {
-		name := string(typ[0]-('a'-'A')) + typ[1:]
-		scope.Insert(types.NewTypeName(token.NoPos, builtin, typ, ng.Ref(name).Type()))
+	if ng != nil {
+		typs := []string{"bigint", "bigrat", "bigfloat"}
+		for _, typ := range typs {
+			name := string(typ[0]-('a'-'A')) + typ[1:]
+			scope.Insert(types.NewTypeName(token.NoPos, builtin, typ, ng.Ref(name).Type()))
+		}
+		scope.Insert(types.NewTypeName(token.NoPos, builtin, "uint128", ng.Ref("Uint128").Type()))
+		scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type()))
+	}
+	if fmt != nil {
+		initBuiltinFns(builtin, scope, fmt, []string{
+			"print", "println", "printf", "errorf",
+			"fprint", "fprintln", "fprintf",
+			"sprint", "sprintln", "sprintf",
+		})
+	}
+	if os != nil {
+		initBuiltinFns(builtin, scope, os, []string{
+			"open", "create",
+		})
+	}
+	if iox != nil {
+		initBuiltinFns(builtin, scope, iox, []string{
+			"lines",
+		})
+		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "blines", iox.Ref("BLines")))
+	}
+	if buil != nil {
+		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "newRange", buil.Ref("NewRange__0")))
 	}
-	initBuiltinFns(builtin, scope, fmt, []string{
-		"print", "println", "printf", "errorf",
-		"fprint", "fprintln", "fprintf",
-		"sprint", "sprintln", "sprintf",
-	})
-	initBuiltinFns(builtin, scope, os, []string{
-		"open", "create",
-	})
-	initBuiltinFns(builtin, scope, iox, []string{
-		"lines",
-	})
-	scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "blines", iox.Ref("BLines")))
-	scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "newRange", buil.Ref("NewRange__0")))
-	scope.Insert(types.NewTypeName(token.NoPos, builtin, "uint128", ng.Ref("Uint128").Type()))
-	scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type()))
 	scope.Insert(types.NewTypeName(token.NoPos, builtin, "any", gox.TyEmptyInterface))
 }
 
 func newBuiltinDefault(pkg *gox.Package, conf *gox.Config) *types.Package {
 	builtin := types.NewPackage("", "")
-	fmt := pkg.Import("fmt")
-	os := pkg.Import("os")
-	buil := pkg.Import("github.com/goplus/gop/builtin")
-	ng := pkg.Import("github.com/goplus/gop/builtin/ng")
-	iox := pkg.Import("github.com/goplus/gop/builtin/iox")
-	pkg.Import("strconv")
-	pkg.Import("strings")
-	initMathBig(pkg, conf, ng)
+	fmt := pkg.TryImport("fmt")
+	os := pkg.TryImport("os")
+	buil := pkg.TryImport("github.com/goplus/gop/builtin")
+	ng := pkg.TryImport("github.com/goplus/gop/builtin/ng")
+	iox := pkg.TryImport("github.com/goplus/gop/builtin/iox")
+	pkg.TryImport("strconv")
+	pkg.TryImport("strings")
+	if ng != nil {
+		initMathBig(pkg, conf, ng)
+	}
 	initBuiltin(pkg, builtin, os, fmt, ng, iox, buil)
 	gox.InitBuiltin(pkg, builtin, conf)
 	return builtin
diff --git a/cl/c.go b/cl/c.go
index 39b4c989..b203bbb2 100644
--- a/cl/c.go
+++ b/cl/c.go
@@ -60,8 +60,7 @@ func c2goBase(base string) string {
 func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) *gox.PkgRef {
 	cpkg, err := ctx.cpkgs.Import(realPath)
 	if err != nil {
-		pos := ctx.Position(src.Pos())
-		ctx.handleCodeErrorf(&pos,
+		ctx.handleErrorf(src.Pos(),
 			"%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath)
 		return nil
 	}
diff --git a/cl/compile.go b/cl/compile.go
index edbf9b48..f1ff311d 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -36,8 +36,10 @@ import (
 	"github.com/qiniu/x/errors"
 )
 
+type dbgFlags int
+
 const (
-	DbgFlagLoad = 1 << iota
+	DbgFlagLoad dbgFlags = 1 << iota
 	DbgFlagLookup
 	DbgFlagAll = DbgFlagLoad | DbgFlagLookup
 )
@@ -55,41 +57,149 @@ func SetDisableRecover(disableRecover bool) {
 	enableRecover = !disableRecover
 }
 
-func SetDebug(flags int) {
+func SetDebug(flags dbgFlags) {
 	debugLoad = (flags & DbgFlagLoad) != 0
 	debugLookup = (flags & DbgFlagLookup) != 0
 }
 
 // -----------------------------------------------------------------------------
 
+// Recorder represents a compiling event recorder.
+type Recorder interface {
+	// Type maps expressions to their types, and for constant
+	// expressions, also their values. Invalid expressions are
+	// omitted.
+	//
+	// For (possibly parenthesized) identifiers denoting built-in
+	// functions, the recorded signatures are call-site specific:
+	// if the call result is not a constant, the recorded type is
+	// an argument-specific signature. Otherwise, the recorded type
+	// is invalid.
+	//
+	// The Types map does not record the type of every identifier,
+	// only those that appear where an arbitrary expression is
+	// permitted. For instance, the identifier f in a selector
+	// expression x.f is found only in the Selections map, the
+	// identifier z in a variable declaration 'var z int' is found
+	// only in the Defs map, and identifiers denoting packages in
+	// qualified identifiers are collected in the Uses map.
+	Type(ast.Expr, types.TypeAndValue)
+
+	// Instantiate maps identifiers denoting generic types or functions to their
+	// type arguments and instantiated type.
+	//
+	// For example, Instantiate will map the identifier for 'T' in the type
+	// instantiation T[int, string] to the type arguments [int, string] and
+	// resulting instantiated *Named type. Given a generic function
+	// func F[A any](A), Instances will map the identifier for 'F' in the call
+	// expression F(int(1)) to the inferred type arguments [int], and resulting
+	// instantiated *Signature.
+	//
+	// Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs
+	// results in an equivalent of Instances[id].Type.
+	Instantiate(*ast.Ident, types.Instance)
+
+	// Def maps identifiers to the objects they define (including
+	// package names, dots "." of dot-imports, and blank "_" identifiers).
+	// For identifiers that do not denote objects (e.g., the package name
+	// in package clauses, or symbolic variables t in t := x.(type) of
+	// type switch headers), the corresponding objects are nil.
+	//
+	// For an embedded field, Def maps the field *Var it defines.
+	//
+	// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
+	Def(id *ast.Ident, obj types.Object)
+
+	// Use maps identifiers to the objects they denote.
+	//
+	// For an embedded field, Use maps the *TypeName it denotes.
+	//
+	// Invariant: Uses[id].Pos() != id.Pos()
+	Use(id *ast.Ident, obj types.Object)
+
+	// Implicit maps nodes to their implicitly declared objects, if any.
+	// The following node and object types may appear:
+	//
+	//     node               declared object
+	//
+	//     *ast.ImportSpec    *PkgName for imports without renames
+	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
+	//     *ast.Field         anonymous parameter *Var (incl. unnamed results)
+	//
+	Implicit(node ast.Node, obj types.Object)
+
+	// Select maps selector expressions (excluding qualified identifiers)
+	// to their corresponding selections.
+	Select(*ast.SelectorExpr, *types.Selection)
+
+	// Scope maps ast.Nodes to the scopes they define. Package scopes are not
+	// associated with a specific node but with all files belonging to a package.
+	// Thus, the package scope can be found in the type-checked Package object.
+	// Scopes nest, with the Universe scope being the outermost scope, enclosing
+	// the package scope, which contains (one or more) files scopes, which enclose
+	// function scopes which in turn enclose statement and function literal scopes.
+	// Note that even though package-level functions are declared in the package
+	// scope, the function scopes are embedded in the file scope of the file
+	// containing the function declaration.
+	//
+	// The following node types may appear in Scopes:
+	//
+	//     *ast.File
+	//     *ast.FuncType
+	//     *ast.TypeSpec
+	//     *ast.BlockStmt
+	//     *ast.IfStmt
+	//     *ast.SwitchStmt
+	//     *ast.TypeSwitchStmt
+	//     *ast.CaseClause
+	//     *ast.CommClause
+	//     *ast.ForStmt
+	//     *ast.RangeStmt
+	//
+	Scope(ast.Node, *types.Scope)
+}
+
+// -----------------------------------------------------------------------------
+
 type Project = modfile.Project
 type Class = modfile.Class
 
 // Config of loading Go+ packages.
 type Config struct {
-	// Fset provides source position information for syntax trees and types.
+	// Types provides type information for the package (optional).
+	Types *types.Package
+
+	// Fset provides source position information for syntax trees and types (required).
 	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
 	Fset *token.FileSet
 
-	// WorkingDir is the directory in which to run gop compiler.
+	// WorkingDir is the directory in which to run gop compiler (optional).
+	// If WorkingDir is not set, os.Getwd() is used.
 	WorkingDir string
 
-	// TargetDir is the directory in which to generate Go files.
+	// TargetDir is the directory in which to generate Go files (optional).
+	// If TargetDir is not set, it is same as WorkingDir.
 	TargetDir string
 
-	// C2goBase specifies base of standard c2go packages.
+	// C2goBase specifies base of standard c2go packages (optional).
 	// Default is github.com/goplus/.
 	C2goBase string
 
-	// LookupPub lookups the c2go package pubfile (named c2go.a.pub).
+	// LookupPub lookups the c2go package pubfile named c2go.a.pub (required).
+	// See gop/x/c2go.LookupPub.
 	LookupPub func(pkgPath string) (pubfile string, err error)
 
-	// LookupClass lookups a class by specified file extension.
+	// LookupClass lookups a class by specified file extension (required).
+	// See (*github.com/goplus/mod/gopmod.Module).LookupClass.
 	LookupClass func(ext string) (c *Project, ok bool)
 
-	// An Importer resolves import paths to Packages.
+	// An Importer resolves import paths to Packages (optional).
 	Importer types.Importer
 
+	// A Recorder records existing objects including constants, variables and
+	// types etc (optional).
+	Recorder Recorder
+
 	// NoFileLine = true means not to generate file line comments.
 	NoFileLine bool
 
@@ -106,6 +216,27 @@ type Config struct {
 	Outline bool
 }
 
+type goxRecorder struct {
+	rec Recorder
+}
+
+// Member maps identifiers to the objects they denote.
+func (p *goxRecorder) Member(id ast.Node, obj types.Object) {
+	tv := types.TypeAndValue{Type: obj.Type()}
+	switch v := id.(type) {
+	case *ast.SelectorExpr:
+		sel := v.Sel
+		// TODO: record event for a Go ident
+		if _, ok := fromgo.CheckIdent(sel); !ok {
+			p.rec.Use(sel, obj)
+			p.rec.Type(v, tv)
+		}
+	case *ast.Ident: // it's in a classfile and impossible converted from Go
+		p.rec.Use(v, obj)
+		p.rec.Type(v, tv)
+	}
+}
+
 type nodeInterp struct {
 	fset       *token.FileSet
 	files      map[string]*ast.File
@@ -164,13 +295,9 @@ func initLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name strin
 		return
 	}
 	if old, ok := syms[name]; ok {
-		var pos token.Position
-		if start != token.NoPos {
-			pos = ctx.Position(start)
-		}
 		oldpos := ctx.Position(old.pos())
-		ctx.handleCodeErrorf(
-			&pos, "%s redeclared in this block\n\tprevious declaration at %v", name, oldpos)
+		ctx.handleErrorf(
+			start, "%s redeclared in this block\n\tprevious declaration at %v", name, oldpos)
 		return
 	}
 	syms[name] = &baseLoader{start: start, fn: fn}
@@ -198,9 +325,9 @@ func getTypeLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name st
 			if ld.start == token.NoPos {
 				ld.start = start
 			} else {
-				pos := ctx.Position(start)
-				ctx.handleCodeErrorf(&pos, "%s redeclared in this block\n\tprevious declaration at %v",
-					name, ctx.Position(ld.start))
+				ctx.handleErrorf(
+					start, "%s redeclared in this block\n\tprevious declaration at %v",
+					name, ctx.Position(ld.pos()))
 			}
 			return ld
 		}
@@ -258,25 +385,40 @@ type pkgCtx struct {
 	inInst   int             // toType in generic instance
 }
 
+type pkgImp struct {
+	*gox.PkgRef
+	pkgName *types.PkgName
+}
+
 type blockCtx struct {
 	*pkgCtx
 	pkg          *gox.Package
 	cb           *gox.CodeBuilder
 	fset         *token.FileSet
-	imports      map[string]*gox.PkgRef
+	imports      map[string]pkgImp
 	lookups      []*gox.PkgRef
 	clookups     []*cpackages.PkgRef
 	tlookup      *typeParamLookup
 	c2goBase     string // default is `github.com/goplus/`
 	targetDir    string
 	classRecv    *ast.FieldList // available when gmxSettings != nil
+	fileScope    *types.Scope   // only valid when isGopFile
+	rec          Recorder
 	fileLine     bool
 	relativePath bool
 	isClass      bool
+	isGopFile    bool // is Go+ file or not
 }
 
-func (bc *blockCtx) findImport(name string) (pr *gox.PkgRef, ok bool) {
-	pr, ok = bc.imports[name]
+func (bc *blockCtx) recorder() Recorder {
+	if bc.isGopFile {
+		return bc.rec
+	}
+	return nil
+}
+
+func (bc *blockCtx) findImport(name string) (pi pkgImp, ok bool) {
+	pi, ok = bc.imports[name]
 	return
 }
 
@@ -298,6 +440,11 @@ func (p *pkgCtx) handleCodeErrorf(pos *token.Position, format string, args ...in
 	p.handleErr(newCodeErrorf(pos, format, args...))
 }
 
+func (p *pkgCtx) handleErrorf(start token.Pos, format string, args ...interface{}) {
+	pos := p.Position(start)
+	p.handleErr(newCodeErrorf(&pos, format, args...))
+}
+
 func (p *pkgCtx) handleErr(err error) {
 	p.errs = append(p.errs, err)
 }
@@ -382,6 +529,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool),
 	}
 	confGox := &gox.Config{
+		Types:           conf.Types,
 		Fset:            fset,
 		Importer:        conf.Importer,
 		LoadNamed:       ctx.loadNamed,
@@ -392,6 +540,9 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		NoSkipConstant:  conf.NoSkipConstant,
 		PkgPathIox:      ioxPkgPath,
 	}
+	if conf.Recorder != nil {
+		confGox.Recorder = &goxRecorder{rec: conf.Recorder}
+	}
 	if enableRecover {
 		defer func() {
 			if e := recover(); e != nil {
@@ -420,10 +571,14 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	}
 	for fpath, f := range files {
 		fileLine := !conf.NoFileLine
+		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), fpath)
 		ctx := &blockCtx{
-			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
-			fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass,
-			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]*gox.PkgRef),
+			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir, fileScope: fileScope,
+			fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass, rec: conf.Recorder,
+			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]pkgImp), isGopFile: true,
+		}
+		if rec := ctx.rec; rec != nil {
+			rec.Scope(f, fileScope)
 		}
 		preloadGopFile(p, ctx, fpath, f, conf)
 	}
@@ -434,7 +589,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		gofiles = append(gofiles, f)
 		ctx := &blockCtx{
 			pkg: p, pkgCtx: ctx, cb: p.CB(), fset: p.Fset, targetDir: targetDir,
-			imports: make(map[string]*gox.PkgRef),
+			imports: make(map[string]pkgImp),
 		}
 		preloadFile(p, ctx, fpath, f, false, false)
 	}
@@ -686,7 +841,7 @@ retry:
 		typ = t.X
 		goto retry
 	}
-	return nil
+	panic("TODO: parseTypeEmbedName unexpected")
 }
 
 func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFile, genFnBody bool) {
@@ -752,7 +907,8 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 					if debugLoad {
 						log.Println("==> Preload type", name)
 					}
-					ld := getTypeLoader(parent, syms, t.Name.Pos(), name)
+					pos := t.Name.Pos()
+					ld := getTypeLoader(parent, syms, pos, name)
 					defs := ctx.pkg.NewTypeDefs()
 					if gopFile {
 						ld.typ = func() {
@@ -762,13 +918,13 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 								if debugLoad {
 									log.Println("==> Load > AliasType", name)
 								}
-								ctx.pkg.AliasType(name, toType(ctx, t.Type), t.Pos())
+								defs.AliasType(name, toType(ctx, t.Type), t.Pos())
 								return
 							}
 							if debugLoad {
 								log.Println("==> Load > NewType", name)
 							}
-							decl := defs.NewType(name)
+							decl := defs.NewType(name, pos)
 							if t.Doc != nil {
 								defs.SetComments(t.Doc)
 							} else if d.Doc != nil {
@@ -779,6 +935,9 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 									log.Println("==> Load > InitType", name)
 								}
 								decl.InitType(ctx.pkg, toType(ctx, t.Type))
+								if rec := ctx.recorder(); rec != nil {
+									rec.Def(t.Name, decl.Type().Obj())
+								}
 							}
 						}
 					} else {
@@ -872,6 +1031,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 			log.Printf("==> Load method %v.%s\n", recv.Type(), name)
 		}
 	}
+	pkg := ctx.pkg
 	if d.Operator {
 		if recv != nil { // binary op
 			if v, ok := binaryGopNames[name]; ok {
@@ -880,7 +1040,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 		} else { // unary op
 			if v, ok := unaryGopNames[name]; ok {
 				name = v
-				at := ctx.pkg.Types
+				at := pkg.Types
 				arg1 := d.Type.Params.List[0]
 				typ := toType(ctx, arg1.Type)
 				recv = types.NewParam(arg1.Pos(), at, arg1.Names[0].Name, typ)
@@ -889,7 +1049,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 		}
 	}
 	sig := toFuncType(ctx, d.Type, recv, d)
-	fn, err := ctx.pkg.NewFuncWith(d.Pos(), name, sig, func() token.Pos {
+	fn, err := pkg.NewFuncWith(d.Name.Pos(), name, sig, func() token.Pos {
 		return d.Recv.List[0].Type.Pos()
 	})
 	if err != nil {
@@ -897,7 +1057,13 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 		return
 	}
 	if d.Doc != nil {
-		fn.SetComments(d.Doc)
+		fn.SetComments(pkg, d.Doc)
+	}
+	if rec := ctx.recorder(); rec != nil {
+		rec.Def(d.Name, fn.Func)
+		if recv == nil {
+			ctx.fileScope.Insert(fn.Func)
+		}
 	}
 	if genBody {
 		if body := d.Body; body != nil {
@@ -963,7 +1129,7 @@ var unaryGopNames = map[string]string{
 }
 
 func loadFuncBody(ctx *blockCtx, fn *gox.Func, body *ast.BlockStmt, src ast.Node) {
-	cb := fn.BodyStart(ctx.pkg)
+	cb := fn.BodyStart(ctx.pkg, body)
 	compileStmts(ctx, body.List)
 	cb.End(src)
 }
@@ -995,9 +1161,25 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 	} else {
 		pkg = ctx.pkg.Import(simplifyGopPackage(pkgPath), spec)
 	}
+
+	var pos token.Pos
 	var name string
-	if spec.Name != nil {
-		name = spec.Name.Name
+	var specName = spec.Name
+	if specName != nil {
+		name, pos = specName.Name, specName.Pos()
+	} else {
+		name, pos = pkg.Types.Name(), spec.Path.Pos()
+	}
+	pkgName := types.NewPkgName(pos, ctx.pkg.Types, name, pkg.Types)
+	if rec := ctx.recorder(); rec != nil {
+		if specName != nil {
+			rec.Def(specName, pkgName)
+		} else {
+			rec.Implicit(spec, pkgName)
+		}
+	}
+
+	if specName != nil {
 		if name == "." {
 			ctx.lookups = append(ctx.lookups, pkg)
 			return
@@ -1006,10 +1188,8 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 			pkg.MarkForceUsed()
 			return
 		}
-	} else {
-		name = pkg.Types.Name()
 	}
-	ctx.imports[name] = pkg
+	ctx.imports[name] = pkgImp{pkg, pkgName}
 }
 
 func loadConstSpecs(ctx *blockCtx, cdecl *gox.ConstDefs, specs []ast.Spec) {
@@ -1020,12 +1200,14 @@ func loadConstSpecs(ctx *blockCtx, cdecl *gox.ConstDefs, specs []ast.Spec) {
 }
 
 func loadConsts(ctx *blockCtx, cdecl *gox.ConstDefs, v *ast.ValueSpec, iotav int) {
-	names := makeNames(v.Names)
+	vNames := v.Names
+	names := makeNames(vNames)
 	if v.Values == nil {
 		if debugLoad {
 			log.Println("==> Load const", names)
 		}
 		cdecl.Next(iotav, v.Pos(), names...)
+		defNames(ctx, vNames, nil)
 		return
 	}
 	var typ types.Type
@@ -1042,6 +1224,7 @@ func loadConsts(ctx *blockCtx, cdecl *gox.ConstDefs, v *ast.ValueSpec, iotav int
 		return len(v.Values)
 	}
 	cdecl.New(fn, iotav, v.Pos(), typ, names...)
+	defNames(ctx, v.Names, nil)
 }
 
 func loadVars(ctx *blockCtx, v *ast.ValueSpec, global bool) {
@@ -1093,6 +1276,20 @@ func loadVars(ctx *blockCtx, v *ast.ValueSpec, global bool) {
 		}
 		cb.EndInit(nv)
 	}
+	defNames(ctx, v.Names, scope)
+}
+
+func defNames(ctx *blockCtx, names []*ast.Ident, scope *types.Scope) {
+	if rec := ctx.recorder(); rec != nil {
+		if scope == nil {
+			scope = ctx.cb.Scope()
+		}
+		for _, name := range names {
+			if o := scope.Lookup(name.Name); o != nil {
+				rec.Def(name, o)
+			}
+		}
+	}
 }
 
 func makeNames(vals []*ast.Ident) []string {
diff --git a/cl/expr.go b/cl/expr.go
index cf2f8c1e..cd6328ef 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -74,7 +74,7 @@ const (
 
 const errorPkgPath = "github.com/qiniu/x/errors"
 
-func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef, kind int) {
+func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef, kind int) {
 	fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0
 	name := ident.Name
 	if name == "_" {
@@ -85,6 +85,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef,
 		return
 	}
 
+	var oldo types.Object
 	scope := ctx.pkg.Types.Scope()
 	at, o := ctx.cb.Scope().LookupParent(name, token.NoPos)
 	if o != nil {
@@ -119,8 +120,11 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef,
 		if name == "C" && len(ctx.clookups) > 0 {
 			return nil, objCPkgRef
 		}
-		if pr, ok := ctx.findImport(name); ok {
-			return pr, objPkgRef
+		if pi, ok := ctx.findImport(name); ok {
+			if rec := ctx.recorder(); rec != nil {
+				rec.Use(ident, pi.pkgName)
+			}
+			return pi.PkgRef, objPkgRef
 		}
 	}
 
@@ -134,7 +138,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef,
 		if (flags&clIdentAllowBuiltin) == 0 && isBuiltin(o) && !strings.HasPrefix(o.Name(), "print") {
 			panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", name))
 		}
-		o = obj
+		oldo, o = o, obj
 	} else if o == nil {
 		if (clIdentGoto & flags) != 0 {
 			l := ident.Obj.Data.(*ast.Ident)
@@ -149,6 +153,17 @@ find:
 	} else {
 		ctx.cb.VarRef(o, ident)
 	}
+	if rec := ctx.recorder(); rec != nil {
+		e := ctx.cb.Get(-1)
+		if oldo != nil && gox.IsTypeEx(e.Type) {
+			rec.Use(ident, oldo)
+			return
+		}
+		rec.Use(ident, o)
+		typ, _ := gox.DerefType(e.Type)
+		tv := types.TypeAndValue{Type: typ, Value: e.CVal}
+		rec.Type(ident, tv)
+	}
 	return
 }
 
@@ -343,6 +358,10 @@ 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, types.TypeAndValue{Type: e.Type, Value: e.CVal})
+		}
 	}
 	ctx.cb.MemberRef(v.Sel.Name, v)
 }
@@ -361,6 +380,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)
@@ -424,6 +447,9 @@ func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind i
 				cb.Call(0)
 			}
 		}
+		if rec := ctx.recorder(); rec != nil {
+			rec.Use(x, v)
+		}
 		return true
 	}
 	return false
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index 7fe59d7f..ef4d1bba 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -38,11 +38,22 @@ func toRecv(ctx *blockCtx, recv *ast.FieldList) *types.Var {
 		name = v.Names[0].Name
 	}
 	typ, star := getRecvType(v.Type)
-	t := toType(ctx, typ)
+	id, ok := typ.(*ast.Ident)
+	if !ok {
+		panic("TODO: getRecvType")
+	}
+	t := toIdentType(ctx, id)
 	if star {
 		t = types.NewPointer(t)
 	}
-	return ctx.pkg.NewParam(v.Pos(), name, t)
+	ret := ctx.pkg.NewParam(v.Pos(), name, t)
+	if rec := ctx.recorder(); rec != nil {
+		dRecv := recv.List[0]
+		if names := dRecv.Names; len(names) == 1 {
+			rec.Def(names[0], ret)
+		}
+	}
+	return ret
 }
 
 func getRecvTypeName(ctx *pkgCtx, recv *ast.FieldList, handleErr bool) (string, bool) {
@@ -90,7 +101,11 @@ func toParam(ctx *blockCtx, fld *ast.Field, args []*gox.Param) []*gox.Param {
 		return append(args, pkg.NewParam(fld.Pos(), "", typ))
 	}
 	for _, name := range fld.Names {
-		args = append(args, pkg.NewParam(name.Pos(), name.Name, typ))
+		param := pkg.NewParam(name.Pos(), name.Name, typ)
+		args = append(args, param)
+		if rec := ctx.recorder(); rec != nil {
+			rec.Def(name, param)
+		}
 	}
 	return args
 }
@@ -100,14 +115,12 @@ func toParam(ctx *blockCtx, fld *ast.Field, args []*gox.Param) []*gox.Param {
 func toType(ctx *blockCtx, typ ast.Expr) types.Type {
 	switch v := typ.(type) {
 	case *ast.Ident:
-		if enableTypeParams {
-			ctx.idents = append(ctx.idents, v)
-			defer func() {
-				ctx.idents = ctx.idents[:len(ctx.idents)-1]
-			}()
-		}
+		ctx.idents = append(ctx.idents, v)
+		defer func() {
+			ctx.idents = ctx.idents[:len(ctx.idents)-1]
+		}()
 		typ := toIdentType(ctx, v)
-		if enableTypeParams && ctx.inInst == 0 {
+		if ctx.inInst == 0 {
 			if t, ok := typ.(*types.Named); ok {
 				if namedIsTypeParams(ctx, t) {
 					pos := ctx.idents[0].Pos()
@@ -117,7 +130,8 @@ func toType(ctx *blockCtx, typ ast.Expr) types.Type {
 							break
 						}
 					}
-					panic(ctx.newCodeErrorf(pos, "cannot use generic type %v without instantiation", t.Obj().Type()))
+					ctx.handleErrorf(pos, "cannot use generic type %v without instantiation", t.Obj().Type())
+					return types.Typ[types.Invalid]
 				}
 			}
 		}
@@ -142,7 +156,7 @@ func toType(ctx *blockCtx, typ ast.Expr) types.Type {
 		return toFuncType(ctx, v, nil, nil)
 	case *ast.SelectorExpr:
 		typ := toExternalType(ctx, v)
-		if enableTypeParams && ctx.inInst == 0 {
+		if ctx.inInst == 0 {
 			if t, ok := typ.(*types.Named); ok {
 				if namedIsTypeParams(ctx, t) {
 					panic(ctx.newCodeErrorf(v.Pos(), "cannot use generic type %v without instantiation", t.Obj().Type()))
@@ -178,15 +192,25 @@ func toChanType(ctx *blockCtx, v *ast.ChanType) *types.Chan {
 }
 
 func toExternalType(ctx *blockCtx, v *ast.SelectorExpr) types.Type {
-	name := v.X.(*ast.Ident).Name
-	if pr, ok := ctx.findImport(name); ok {
-		o := pr.TryRef(v.Sel.Name)
+	id := v.X.(*ast.Ident)
+	name := id.Name
+	if pi, ok := ctx.findImport(name); ok {
+		rec := ctx.recorder()
+		if rec != nil {
+			rec.Use(id, pi.pkgName)
+		}
+		o := pi.TryRef(v.Sel.Name)
 		if t, ok := o.(*types.TypeName); ok {
+			if rec != nil {
+				rec.Use(v.Sel, t)
+			}
 			return t.Type()
 		}
-		panic(ctx.newCodeErrorf(v.Pos(), "%s.%s is not a type", name, v.Sel.Name))
+		ctx.handleErrorf(v.Pos(), "%s.%s is not a type", name, v.Sel.Name)
+	} else {
+		ctx.handleErrorf(v.Pos(), "undefined: %s", name)
 	}
-	panic(ctx.newCodeErrorf(v.Pos(), "undefined: %s", name))
+	return types.Typ[types.Invalid]
 }
 
 /*-----------------------------------------------------------------------------
@@ -198,25 +222,38 @@ Name context:
 
 // ---------------------------------------------------------------------------*/
 
-func toIdentType(ctx *blockCtx, ident *ast.Ident) types.Type {
+func toIdentType(ctx *blockCtx, ident *ast.Ident) (ret types.Type) {
+	var obj types.Object
+	if rec := ctx.recorder(); rec != nil {
+		defer func() {
+			if obj != nil {
+				rec.Use(ident, obj)
+			}
+		}()
+	}
 	if ctx.tlookup != nil {
 		if typ := ctx.tlookup.Lookup(ident.Name); typ != nil {
+			obj = typ.Obj()
 			return typ
 		}
 	}
 	v, builtin := lookupType(ctx, ident.Name)
 	if isBuiltin(builtin) {
-		panic(ctx.newCodeErrorf(ident.Pos(), "use of builtin %s not in function call", ident.Name))
+		ctx.handleErrorf(ident.Pos(), "use of builtin %s not in function call", ident.Name)
+		return types.Typ[types.Invalid]
 	}
 	if t, ok := v.(*types.TypeName); ok {
+		obj = t
 		return t.Type()
 	}
 	if v, _ := lookupPkgRef(ctx, nil, ident, objPkgRef); v != nil {
 		if t, ok := v.(*types.TypeName); ok {
+			obj = t
 			return t.Type()
 		}
 	}
-	panic(ctx.newCodeErrorf(ident.Pos(), "%s is not a type", ident.Name))
+	ctx.handleErrorf(ident.Pos(), "%s is not a type", ident.Name)
+	return types.Typ[types.Invalid]
 }
 
 // TODO: optimization
@@ -257,8 +294,8 @@ func (p *checkRedecl) chkRedecl(ctx *blockCtx, name string, pos token.Pos) bool
 		return false
 	}
 	if opos, ok := p.names[name]; ok {
-		npos := ctx.Position(pos)
-		ctx.handleCodeErrorf(&npos, "%v redeclared\n\t%v other declaration of %v",
+		ctx.handleErrorf(
+			pos, "%v redeclared\n\t%v other declaration of %v",
 			name, ctx.Position(opos), name)
 		return true
 	}
@@ -272,6 +309,7 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 	fields := make([]*types.Var, 0, len(fieldList))
 	tags := make([]string, 0, len(fieldList))
 	chk := newCheckRedecl()
+	rec := ctx.recorder()
 	for _, field := range fieldList {
 		typ := toType(ctx, field.Type)
 		if len(field.Names) == 0 { // embedded
@@ -282,18 +320,25 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			if t, ok := typ.(*types.Named); ok { // #1196: embedded type should ensure loaded
 				ctx.loadNamed(ctx.pkg, t)
 			}
-			fld := types.NewField(token.NoPos, pkg, name, typ, true)
+			ident := parseTypeEmbedName(field.Type)
+			fld := types.NewField(ident.NamePos, pkg, name, typ, true)
 			fields = append(fields, fld)
 			tags = append(tags, toFieldTag(field.Tag))
+			if rec != nil {
+				rec.Def(ident, fld)
+			}
 			continue
 		}
 		for _, name := range field.Names {
 			if chk.chkRedecl(ctx, name.Name, name.NamePos) {
 				continue
 			}
-			fld := types.NewField(token.NoPos, pkg, name.Name, typ, false)
+			fld := types.NewField(name.NamePos, pkg, name.Name, typ, false)
 			fields = append(fields, fld)
 			tags = append(tags, toFieldTag(field.Tag))
+			if rec != nil {
+				rec.Def(name, fld)
+			}
 		}
 	}
 	return types.NewStruct(fields, tags)
@@ -363,6 +408,7 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 	if methodsList == nil {
 		return types.NewInterfaceType(nil, nil)
 	}
+	var rec = ctx.recorder()
 	var pkg = ctx.pkg.Types
 	var methods []*types.Func
 	var embeddeds []types.Type
@@ -375,9 +421,13 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 			embeddeds = append(embeddeds, typ)
 			continue
 		}
-		name := m.Names[0].Name
+		name := m.Names[0]
 		sig := toFuncType(ctx, m.Type.(*ast.FuncType), nil, nil)
-		methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig))
+		mthd := types.NewFunc(name.NamePos, pkg, name.Name, sig)
+		methods = append(methods, mthd)
+		if rec != nil {
+			rec.Def(name, mthd)
+		}
 	}
 	intf := types.NewInterfaceType(methods, embeddeds).Complete()
 	return intf
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index a201d089..9367eae7 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -41,6 +41,13 @@ func (p *MyGame) Broadcast__2(msg string, data interface{}, wait bool) {
 func (p *MyGame) Play(media string, wait ...bool) {
 }
 
+func (p *MyGame) sendMessage(data interface{}) {
+}
+
+func (p *MyGame) SendMessage(data interface{}) {
+	p.sendMessage(data)
+}
+
 func Gopt_MyGame_Run(game interface{}, resource string) error {
 	return nil
 }
diff --git a/cl/internal/spx/pkg/pkg.go b/cl/internal/spx/pkg/pkg.go
new file mode 100644
index 00000000..649406b5
--- /dev/null
+++ b/cl/internal/spx/pkg/pkg.go
@@ -0,0 +1,23 @@
+package pkg
+
+var (
+	GopPackage = true
+)
+
+type Vector struct {
+	x int
+	y int
+}
+
+func NewVector(x, y int) *Vector {
+	return &Vector{x, y}
+}
+
+func (v *Vector) Add__0(x int, y int) {
+	v.x += x
+	v.y += y
+}
+
+func (v *Vector) Add__1(o *Vector) {
+	v.Add__0(o.x, o.y)
+}
diff --git a/cl/internal/spx/sprite.go b/cl/internal/spx/sprite.go
index 9bbd27fd..00963bb8 100644
--- a/cl/internal/spx/sprite.go
+++ b/cl/internal/spx/sprite.go
@@ -16,7 +16,12 @@
 
 package spx
 
+import (
+	"github.com/goplus/gop/cl/internal/spx/pkg"
+)
+
 type Sprite struct {
+	pos pkg.Vector
 }
 
 func (p *Sprite) SetCostume(costume interface{}) {
@@ -25,6 +30,10 @@ func (p *Sprite) SetCostume(costume interface{}) {
 func (p *Sprite) Say(msg string, secs ...float64) {
 }
 
+func (p *Sprite) Position() *pkg.Vector {
+	return &p.pos
+}
+
 func Gopt_Sprite_Clone__0(sprite interface{}) {
 }
 
diff --git a/cl/outline/cl/compile.go b/cl/outline/cl/compile.go
index fe5e3634..6fd2a2ce 100644
--- a/cl/outline/cl/compile.go
+++ b/cl/outline/cl/compile.go
@@ -660,7 +660,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 					if debugLoad {
 						log.Println("==> Load > NewType", name)
 					}
-					decl := tdecl.NewType(name, pos).SetComments(t.Doc)
+					decl := tdecl.NewType(name, pos).SetComments(p, t.Doc)
 					fnInitType := getInitType(ctx, decl, t, gopFile)
 					parent.insertObject(name, pos, lazyInitType, fnInitType)
 				}
@@ -750,7 +750,7 @@ func preloadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool)
 		}
 	}
 	sig := toFuncType(ctx, d.Type, recv, d)
-	fn, err := ctx.pkg.NewFuncWith(d.Pos(), name, sig, func() token.Pos {
+	fn, err := ctx.pkg.NewFuncWith(d.Name.Pos(), name, sig, func() token.Pos {
 		return d.Recv.List[0].Type.Pos()
 	})
 	if err != nil {
@@ -758,7 +758,7 @@ func preloadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool)
 		return
 	}
 	if d.Doc != nil {
-		fn.SetComments(d.Doc)
+		fn.SetComments(ctx.pkg, d.Doc)
 	}
 	if genBody {
 		if body := d.Body; body != nil {
diff --git a/cl/outline/cl/func_type_and_var.go b/cl/outline/cl/func_type_and_var.go
index cb52ef9d..9a412d49 100644
--- a/cl/outline/cl/func_type_and_var.go
+++ b/cl/outline/cl/func_type_and_var.go
@@ -285,7 +285,7 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			if chk.chkRedecl(ctx, name, field.Type.Pos()) {
 				continue
 			}
-			fld := types.NewField(token.NoPos, pkg, name, typ, true)
+			fld := types.NewField(field.Type.Pos(), pkg, name, typ, true)
 			fields = append(fields, fld)
 			tags = append(tags, toFieldTag(field.Tag))
 			continue
@@ -294,7 +294,7 @@ func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
 			if chk.chkRedecl(ctx, name.Name, name.NamePos) {
 				continue
 			}
-			fld := types.NewField(token.NoPos, pkg, name.Name, typ, false)
+			fld := types.NewField(name.Pos(), pkg, name.Name, typ, false)
 			fields = append(fields, fld)
 			tags = append(tags, toFieldTag(field.Tag))
 		}
diff --git a/cl/outline/outline.go b/cl/outline/outline.go
index 9a6535f8..3d7e438e 100644
--- a/cl/outline/outline.go
+++ b/cl/outline/outline.go
@@ -55,7 +55,8 @@ type Config struct {
 }
 
 type Package struct {
-	pkg *gox.Package
+	pkg  *types.Package
+	docs gox.ObjectDocs
 }
 
 // NewPackage creates a Go/Go+ outline package.
@@ -75,11 +76,11 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err
 	if err != nil {
 		return
 	}
-	return Package{ret}, nil
+	return Package{ret.Types, ret.Docs}, nil
 }
 
 func (p Package) Pkg() *types.Package {
-	return p.pkg.Types
+	return p.pkg
 }
 
 func (p Package) Valid() bool {
@@ -94,7 +95,7 @@ type All struct {
 	Funcs  []Func
 	Types  []*TypeName
 
-	pkg   *types.Package
+	Package
 	named map[*types.TypeName]*TypeName
 }
 
@@ -225,8 +226,8 @@ func (p *All) lookupNamed(pkg *types.Package, name string) (_ *TypeName, ok bool
 func (p Package) Outline(withUnexported ...bool) (ret *All) {
 	pkg := p.Pkg()
 	ret = &All{
-		pkg:   pkg,
-		named: make(map[*types.TypeName]*TypeName),
+		Package: p,
+		named:   make(map[*types.TypeName]*TypeName),
 	}
 	all := (withUnexported != nil && withUnexported[0])
 	aliasr := &typeutil.Map{}
@@ -248,25 +249,25 @@ func (p Package) Outline(withUnexported ...bool) (ret *All) {
 			continue
 		}
 		switch v := o.(type) {
-		case *gox.Func:
+		case *types.Func:
 			sig := v.Type().(*types.Signature)
 			if !all {
 				ret.checkUsedSig(sig)
 			}
 			if name, ok := checkGoptFunc(o.Name()); ok {
 				if named, ok := ret.lookupNamed(pkg, name); ok {
-					named.GoptFuncs = append(named.GoptFuncs, Func{v})
+					named.GoptFuncs = append(named.GoptFuncs, Func{v, p.docs})
 					continue
 				}
 			}
 			kind, named := ret.sigKind(aliasr, sig)
 			switch kind {
 			case sigNormal:
-				ret.Funcs = append(ret.Funcs, Func{v})
+				ret.Funcs = append(ret.Funcs, Func{v, p.docs})
 			case sigCreator:
-				named.Creators = append(named.Creators, Func{v})
+				named.Creators = append(named.Creators, Func{v, p.docs})
 			case sigHelper:
-				named.Helpers = append(named.Helpers, Func{v})
+				named.Helpers = append(named.Helpers, Func{v, p.docs})
 			}
 		case *types.Const:
 			if name := v.Name(); strings.HasPrefix(name, "Gop") {
@@ -364,15 +365,16 @@ func (p Var) Doc() string {
 }
 
 type Func struct {
-	*gox.Func
+	*types.Func
+	docs gox.ObjectDocs
 }
 
 func (p Func) Obj() types.Object {
-	return &p.Func.Func
+	return p.Func
 }
 
 func (p Func) Doc() string {
-	return p.Comments().Text()
+	return p.docs[p.Func].Text()
 }
 
 func CheckOverload(obj types.Object) (name string, fn *types.Func, ok bool) {
@@ -513,10 +515,10 @@ type Type struct {
 	types.Type
 }
 
-func (p Type) CheckNamed(pkg *types.Package) (_ Named, ok bool) {
+func (p Type) CheckNamed(pkg Package) (_ Named, ok bool) {
 	ret, ok := p.Type.(*types.Named)
-	if ok && ret.Obj().Pkg() == pkg {
-		return Named{ret}, true
+	if ok && ret.Obj().Pkg() == pkg.pkg {
+		return Named{ret, pkg.docs}, true
 	}
 	return
 }
@@ -525,6 +527,7 @@ func (p Type) CheckNamed(pkg *types.Package) (_ Named, ok bool) {
 
 type Named struct {
 	*types.Named
+	docs gox.ObjectDocs
 }
 
 func (p Named) Methods() []Func {
@@ -532,7 +535,7 @@ func (p Named) Methods() []Func {
 	ret := make([]Func, n)
 	for i := 0; i < n; i++ {
 		fn := p.Method(i)
-		ret[i] = Func{gox.MethodFrom(fn)}
+		ret[i] = Func{fn, p.docs}
 	}
 	return ret
 }
diff --git a/cl/stmt.go b/cl/stmt.go
index acef23b7..0bb43cf6 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -210,9 +210,21 @@ func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
 			if v, ok := lhs.(*ast.Ident); ok {
 				names[i] = v.Name
 			} else {
+				compileExprLHS(ctx, lhs) // only for typesutil.Check
 				log.Panicln("TODO: non-name $v on left side of :=")
 			}
 		}
+		if rec := ctx.recorder(); rec != nil {
+			newNames := make([]*ast.Ident, 0, len(names))
+			scope := ctx.cb.Scope()
+			for _, lhs := range expr.Lhs {
+				v := lhs.(*ast.Ident)
+				if scope.Lookup(v.Name) == nil {
+					newNames = append(newNames, v)
+				}
+			}
+			defer defNames(ctx, newNames, scope)
+		}
 		ctx.cb.DefineVarStart(expr.Pos(), names...)
 		if enableRecover {
 			defer func() {
@@ -286,10 +298,10 @@ func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
 		if v.Value != nil {
 			names = append(names, v.Value.(*ast.Ident).Name)
 		}
-		cb.ForRange(names...)
+		cb.ForRangeEx(names, v)
 		compileExpr(ctx, v.X)
 	} else {
-		cb.ForRange()
+		cb.ForRangeEx(nil, v)
 		n := 0
 		if v.Key == nil {
 			if v.Value != nil {
@@ -310,11 +322,11 @@ func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
 	if pos == 0 {
 		pos = v.For
 	}
-	cb.RangeAssignThen(pos)
+	cb.RangeAssignThen(pos) // TODO: need NewScope for body
 	compileStmts(ctx, v.Body.List)
 	cb.SetComments(comments, once)
 	setBodyHandler(ctx)
-	cb.End()
+	cb.End(v)
 }
 
 func compileForPhraseStmt(ctx *blockCtx, v *ast.ForPhraseStmt) {
@@ -448,7 +460,7 @@ func toForStmt(forPos token.Pos, value ast.Expr, body *ast.BlockStmt, re *ast.Ra
 func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
-	cb.For()
+	cb.For(v)
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
@@ -457,7 +469,7 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 	} else {
 		cb.None()
 	}
-	cb.Then()
+	cb.Then(v.Body)
 	compileStmts(ctx, v.Body.List)
 	if v.Post != nil {
 		cb.Post()
@@ -465,7 +477,7 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 	}
 	cb.SetComments(comments, once)
 	setBodyHandler(ctx)
-	cb.End()
+	cb.End(v)
 }
 
 // if init; cond then
@@ -476,15 +488,15 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
-	cb.If()
+	cb.If(v)
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
 	compileExpr(ctx, v.Cond)
-	cb.Then()
+	cb.Then(v.Body)
 	compileStmts(ctx, v.Body.List)
 	if e := v.Else; e != nil {
-		cb.Else()
+		cb.Else(e)
 		if stmts, ok := e.(*ast.BlockStmt); ok {
 			compileStmts(ctx, stmts.List)
 		} else {
@@ -492,7 +504,7 @@ func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 		}
 	}
 	cb.SetComments(comments, once)
-	cb.End()
+	cb.End(v)
 }
 
 // typeSwitch(name) init; expr typeAssertThen()
@@ -525,7 +537,7 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 	if ta.Type != nil {
 		panic("TODO: type switch syntax error, please use x.(type)")
 	}
-	cb.TypeSwitch(name)
+	cb.TypeSwitch(name, v)
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
@@ -548,11 +560,15 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 			for t, other := range seen {
 				if T == nil && t == nil || T != nil && t != nil && types.Identical(T, t) {
 					haserr = true
-					pos := ctx.Position(citem.Pos())
+					pos := citem.Pos()
 					if T == types.Typ[types.UntypedNil] {
-						ctx.handleCodeErrorf(&pos, "multiple nil cases in type switch (first at %v)", ctx.Position(other.Pos()))
+						ctx.handleErrorf(
+							pos, "multiple nil cases in type switch (first at %v)",
+							ctx.Position(other.Pos()))
 					} else {
-						ctx.handleCodeErrorf(&pos, "duplicate case %s in type switch\n\tprevious case at %v", T, ctx.Position(other.Pos()))
+						ctx.handleErrorf(
+							pos, "duplicate case %s in type switch\n\tprevious case at %v",
+							T, ctx.Position(other.Pos()))
 					}
 				}
 			}
@@ -562,19 +578,18 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 		}
 		if c.List == nil {
 			if firstDefault != nil {
-				pos := ctx.Position(c.Pos())
-				ctx.handleCodeErrorf(&pos, "multiple defaults in type switch (first at %v)", ctx.Position(firstDefault.Pos()))
+				ctx.handleErrorf(c.Pos(), "multiple defaults in type switch (first at %v)", ctx.Position(firstDefault.Pos()))
 			} else {
 				firstDefault = c
 			}
 		}
-		cb.TypeCase(len(c.List)) // TypeCase(0) means default case
+		cb.TypeCase(len(c.List), c) // TypeCase(0) means default case
 		compileStmts(ctx, c.Body)
 		commentStmt(ctx, stmt)
-		cb.End()
+		cb.End(c)
 	}
 	cb.SetComments(comments, once)
-	cb.End()
+	cb.End(v)
 }
 
 // switch init; tag then
@@ -592,7 +607,7 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
-	cb.Switch()
+	cb.Switch(v)
 	if v.Init != nil {
 		compileStmt(ctx, v.Init)
 	}
@@ -601,7 +616,7 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 	} else {
 		cb.None() // switch {...}
 	}
-	cb.Then()
+	cb.Then(v.Body)
 	seen := make(valueMap)
 	var firstDefault ast.Stmt
 	for _, stmt := range v.Body.List {
@@ -637,23 +652,22 @@ func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 		}
 		if c.List == nil {
 			if firstDefault != nil {
-				pos := ctx.Position(c.Pos())
-				ctx.handleCodeErrorf(&pos, "multiple defaults in switch (first at %v)", ctx.Position(firstDefault.Pos()))
+				ctx.handleErrorf(c.Pos(), "multiple defaults in switch (first at %v)", ctx.Position(firstDefault.Pos()))
 			} else {
 				firstDefault = c
 			}
 		}
-		cb.Case(len(c.List)) // Case(0) means default case
+		cb.Case(len(c.List), c) // Case(0) means default case
 		body, has := hasFallthrough(c.Body)
 		compileStmts(ctx, body)
 		if has {
 			cb.Fallthrough()
 		}
 		commentStmt(ctx, stmt)
-		cb.End()
+		cb.End(c)
 	}
 	cb.SetComments(comments, once)
-	cb.End()
+	cb.End(v)
 }
 
 func hasFallthrough(body []ast.Stmt) ([]ast.Stmt, bool) {
@@ -686,7 +700,7 @@ func hasFallthrough(body []ast.Stmt) ([]ast.Stmt, bool) {
 func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) {
 	cb := ctx.cb
 	comments, once := cb.BackupComments()
-	cb.Select()
+	cb.Select(v)
 	for _, stmt := range v.Body.List {
 		c, ok := stmt.(*ast.CommClause)
 		if !ok {
@@ -697,13 +711,13 @@ func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) {
 			compileStmt(ctx, c.Comm)
 			n = 1
 		}
-		cb.CommCase(n) // CommCase(0) means default case
+		cb.CommCase(n, c) // CommCase(0) means default case
 		compileStmts(ctx, c.Body)
 		commentStmt(ctx, stmt)
-		cb.End()
+		cb.End(c)
 	}
 	cb.SetComments(comments, once)
-	cb.End()
+	cb.End(v)
 }
 
 func compileBranchStmt(ctx *blockCtx, v *ast.BranchStmt) {
@@ -724,8 +738,7 @@ func compileBranchStmt(ctx *blockCtx, v *ast.BranchStmt) {
 	case token.CONTINUE:
 		ctx.cb.Continue(getLabel(ctx, label))
 	case token.FALLTHROUGH:
-		pos := ctx.Position(v.Pos())
-		ctx.handleCodeErrorf(&pos, "fallthrough statement out of place")
+		ctx.handleErrorf(v.Pos(), "fallthrough statement out of place")
 	default:
 		panic("unknown branch statement")
 	}
@@ -736,8 +749,7 @@ func getLabel(ctx *blockCtx, label *ast.Ident) *gox.Label {
 		if l, ok := ctx.cb.LookupLabel(label.Name); ok {
 			return l
 		}
-		pos := ctx.Position(label.Pos())
-		ctx.handleCodeErrorf(&pos, "label %v is not defined", label.Name)
+		ctx.handleErrorf(label.Pos(), "label %v is not defined", label.Name)
 	}
 	return nil
 }
diff --git a/cl/types_go118.go b/cl/typeparams.go
similarity index 98%
rename from cl/types_go118.go
rename to cl/typeparams.go
index 52b2ea4b..aeb82608 100644
--- a/cl/types_go118.go
+++ b/cl/typeparams.go
@@ -1,6 +1,3 @@
-//go:build go1.18
-// +build go1.18
-
 /*
  * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
  *
@@ -26,8 +23,6 @@ import (
 	"github.com/goplus/gop/token"
 )
 
-const enableTypeParams = true
-
 func toTermList(ctx *blockCtx, expr ast.Expr) []*types.Term {
 retry:
 	switch v := expr.(type) {
@@ -135,7 +130,7 @@ type typeParamLookup struct {
 	typeParams []*types.TypeParam
 }
 
-func (p *typeParamLookup) Lookup(name string) types.Type {
+func (p *typeParamLookup) Lookup(name string) *types.TypeParam {
 	for _, t := range p.typeParams {
 		tname := t.Obj().Name()
 		if tname != "_" && name == tname {
@@ -174,6 +169,9 @@ L:
 		case *ast.ParenExpr:
 			typ = t.X
 		case *ast.StarExpr:
+			if ptr {
+				panic("TODO: getRecvType")
+			}
 			ptr = true
 			typ = t.X
 		default:
diff --git a/cl/types_go117.go b/cl/types_go117.go
deleted file mode 100644
index 2f32f916..00000000
--- a/cl/types_go117.go
+++ /dev/null
@@ -1,81 +0,0 @@
-//go:build !go1.18
-// +build !go1.18
-
-/*
- * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package cl
-
-import (
-	"go/types"
-
-	"github.com/goplus/gop/ast"
-)
-
-const enableTypeParams = false
-
-func toBinaryExprType(ctx *blockCtx, v *ast.BinaryExpr) types.Type {
-	panic("type parameters are unsupported at this go version")
-}
-
-func toUnaryExprType(ctx *blockCtx, v *ast.UnaryExpr) types.Type {
-	panic("type parameters are unsupported at this go version")
-}
-
-type typeParamLookup struct {
-}
-
-func (p *typeParamLookup) Lookup(name string) types.Type {
-	return nil
-}
-
-func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var, d *ast.FuncDecl) *types.Signature {
-	params, variadic := toParams(ctx, typ.Params.List)
-	results := toResults(ctx, typ.Results)
-	return types.NewSignature(recv, params, results, variadic)
-}
-
-func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
-	typ := toType(ctx, spec.Type)
-	if named, ok := typ.(*types.Named); ok {
-		typ = getUnderlying(ctx, named)
-	}
-	named.SetUnderlying(typ)
-}
-
-func getRecvType(typ ast.Expr) (ast.Expr, bool) {
-	var ptr bool
-L:
-	for {
-		switch t := typ.(type) {
-		case *ast.ParenExpr:
-			typ = t.X
-		case *ast.StarExpr:
-			ptr = true
-			typ = t.X
-		default:
-			break L
-		}
-	}
-	return typ, ptr
-}
-
-func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
-	return false
-}
-
-func namedIsTypeParams(ctx *blockCtx, t *types.Named) bool {
-	return false
-}
diff --git a/cmd/gopdecl/gopdecl.go b/cmd/gopdecl/gopdecl.go
deleted file mode 100644
index 7a82918c..00000000
--- a/cmd/gopdecl/gopdecl.go
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package main
-
-import (
-	"flag"
-	"fmt"
-	"go/token"
-	"log"
-	"os"
-	"path/filepath"
-	"strings"
-	"unicode"
-
-	goast "go/ast"
-	goparser "go/parser"
-
-	"github.com/goplus/gop/ast"
-	"github.com/goplus/gop/parser"
-	"github.com/goplus/gop/x/types"
-	"github.com/goplus/gox/packages"
-)
-
-var (
-	verboseFlag = flag.Bool("v", false, "print verbose information")
-	internal    = flag.Bool("i", false, "print internal declarations")
-)
-
-func usage() {
-	fmt.Fprintf(os.Stderr, "Usage: gopdecl [-i] [source.gop ...]\n")
-	flag.PrintDefaults()
-}
-
-func isDir(name string) bool {
-	if fi, err := os.Lstat(name); err == nil {
-		return fi.IsDir()
-	}
-	return false
-}
-
-func isPublic(name string) bool {
-	for _, c := range name {
-		return unicode.IsUpper(c)
-	}
-	return false
-}
-
-func main() {
-	flag.Parse()
-	if flag.NArg() < 1 {
-		usage()
-		return
-	}
-	verbose := *verboseFlag
-	if verbose {
-		log.Println("==> Parsing ...")
-	}
-
-	var fset = token.NewFileSet()
-	var in *ast.Package
-	if infile := flag.Arg(0); isDir(infile) {
-		pkgs, first := parser.ParseDir(fset, infile, nil, 0)
-		check(first)
-		for name, pkg := range pkgs {
-			if !strings.HasSuffix(name, "_test") {
-				in = pkg
-				break
-			}
-		}
-	} else {
-		in = &ast.Package{
-			Files:   make(map[string]*ast.File),
-			GoFiles: make(map[string]*goast.File),
-		}
-		for i, n := 0, flag.NArg(); i < n; i++ {
-			infile = flag.Arg(i)
-			switch filepath.Ext(infile) {
-			case ".gop":
-				f, err := parser.ParseFile(fset, infile, nil, 0)
-				check(err)
-				in.Files[infile] = f
-			case ".go":
-				f, err := goparser.ParseFile(fset, infile, nil, 0)
-				check(err)
-				in.GoFiles[infile] = f
-			default:
-				log.Panicln("Unknown support file:", infile)
-			}
-		}
-	}
-
-	if verbose {
-		log.Println("==> Loading ...")
-	}
-	conf := &types.Config{
-		Importer:         packages.NewImporter(fset),
-		IgnoreFuncBodies: true,
-		Error: func(err error) {
-			log.Println(err)
-		},
-		DisableUnusedImportCheck: true,
-	}
-	pkg, err := types.Load(fset, in, conf)
-	check(err)
-
-	if verbose {
-		log.Println("==> Printing ...")
-	}
-	scope := pkg.Scope()
-	names := scope.Names()
-	for _, name := range names {
-		if *internal || isPublic(name) {
-			fmt.Println(scope.Lookup(name))
-		}
-	}
-}
-
-func check(err error) {
-	if err != nil {
-		fmt.Fprintln(os.Stderr, err)
-		os.Exit(1)
-	}
-}
diff --git a/cmd/internal/doc/doc.go b/cmd/internal/doc/doc.go
index 84b4900f..82ca12cf 100644
--- a/cmd/internal/doc/doc.go
+++ b/cmd/internal/doc/doc.go
@@ -100,7 +100,7 @@ func outlinePkg(proj gopprojs.Proj, conf *gop.Config) {
 	} else if err != nil {
 		fmt.Fprintln(os.Stderr, err)
 	} else {
-		outlineDoc(out.Pkg(), out.Outline(*unexp), *unexp, *withDoc)
+		outlineDoc(out.Outline(*unexp), *unexp, *withDoc)
 	}
 }
 
@@ -109,7 +109,8 @@ const (
 	ln     = "\n"
 )
 
-func outlineDoc(pkg *types.Package, out *outline.All, all, withDoc bool) {
+func outlineDoc(out *outline.All, all, withDoc bool) {
+	pkg := out.Pkg()
 	fmt.Printf("package %s // import %s\n\n", pkg.Name(), strconv.Quote(pkg.Path()))
 	if withDoc && len(out.Consts) > 0 {
 		fmt.Print("CONSTANTS\n\n")
@@ -149,7 +150,7 @@ func outlineDoc(pkg *types.Package, out *outline.All, all, withDoc bool) {
 		printFuncsForType(pkg, t.Helpers, withDoc)
 		if !typName.IsAlias() {
 			typ := t.Type()
-			if named, ok := typ.CheckNamed(pkg); ok {
+			if named, ok := typ.CheckNamed(out.Package); ok {
 				for _, fn := range named.Methods() {
 					if o := fn.Obj(); all || o.Exported() {
 						if withDoc {
diff --git a/cmd/internal/gopfmt/fmt.go b/cmd/internal/gopfmt/fmt.go
index d03045d0..40efc8a5 100644
--- a/cmd/internal/gopfmt/fmt.go
+++ b/cmd/internal/gopfmt/fmt.go
@@ -38,14 +38,15 @@ import (
 	xformat "github.com/goplus/gop/x/format"
 )
 
-// Cmd - gop go
+// Cmd - gop fmt
 var Cmd = &base.Command{
-	UsageLine: "gop fmt [-n --smart --mvgo] path ...",
+	UsageLine: "gop fmt [flags] path ...",
 	Short:     "Format Go+ packages",
 }
 
 var (
 	flag        = &Cmd.Flag
+	flagTest    = flag.Bool("t", false, "test if Go+ files are formatted or not.")
 	flagNotExec = flag.Bool("n", false, "prints commands that would be executed.")
 	flagMoveGo  = flag.Bool("mvgo", false, "move .go files to .gop files (only available in `--smart` mode).")
 	flagSmart   = flag.Bool("smart", false, "convert Go code style into Go+ style.")
@@ -56,6 +57,7 @@ func init() {
 }
 
 var (
+	testErrCnt = 0
 	procCnt    = 0
 	walkSubDir = false
 	rootDir    = ""
@@ -93,6 +95,10 @@ func gopfmt(path string, class, smart, mvgo bool) (err error) {
 		return
 	}
 	fmt.Println(path)
+	if *flagTest {
+		testErrCnt++
+		return nil
+	}
 	if mvgo {
 		newPath := strings.TrimSuffix(path, ".go") + ".gop"
 		if err = os.WriteFile(newPath, target, 0666); err != nil {
@@ -199,6 +205,14 @@ func runCmd(cmd *base.Command, args []string) {
 	if narg < 1 {
 		cmd.Usage(os.Stderr)
 	}
+	if *flagTest {
+		defer func() {
+			if testErrCnt > 0 {
+				fmt.Printf("total %d files are not formatted.\n", testErrCnt)
+				os.Exit(1)
+			}
+		}()
+	}
 	walker := newWalker()
 	for i := 0; i < narg; i++ {
 		path := flag.Arg(i)
diff --git a/doc/docs.md b/doc/docs.md
index 784e128b..f2434cf9 100644
--- a/doc/docs.md
+++ b/doc/docs.md
@@ -11,7 +11,7 @@ The Go+ programming language is designed for engineering, STEM education, and da
 
 For now, we suggest you install Go+ from source code.
 
-Note: Requires go1.17 or later
+Note: Requires go1.18 or later
 
 ```bash
 git clone https://github.com/goplus/gop.git
diff --git a/env/build.go b/env/build.go
index b7325414..1405cabf 100644
--- a/env/build.go
+++ b/env/build.go
@@ -22,6 +22,7 @@ var (
 	buildDate string
 )
 
+// BuildDate returns build date of the `gop` command.
 func BuildDate() string {
 	return buildDate
 }
diff --git a/env/gop_nonwindows.go b/env/gop_nonwindows.go
new file mode 100644
index 00000000..482e829f
--- /dev/null
+++ b/env/gop_nonwindows.go
@@ -0,0 +1,24 @@
+//go:build !windows
+// +build !windows
+
+/*
+ * Copyright (c) 2021 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package env
+
+func isGopCmd(fname string) bool {
+	return fname == "gop"
+}
diff --git a/env/path.go b/env/path.go
index d52cf90a..1f071efa 100644
--- a/env/path.go
+++ b/env/path.go
@@ -28,6 +28,8 @@ var (
 	defaultGopRoot string
 )
 
+// GOPROOT returns the root of the Go+ tree. It uses the GOPROOT environment variable,
+// if set at process start, or else the root used during the Go+ build.
 func GOPROOT() string {
 	gopRoot, err := findGopRoot()
 	if err != nil {
diff --git a/env/sys_windows.go b/env/sys_windows.go
index 40149490..1975f0cd 100644
--- a/env/sys_windows.go
+++ b/env/sys_windows.go
@@ -19,3 +19,7 @@ package env
 const (
 	envHOME = "USERPROFILE"
 )
+
+func isGopCmd(fname string) bool {
+	return fname == "gop.exe" || fname == "gop"
+}
diff --git a/env/version.go b/env/version.go
index 5ce9b61e..e70a940d 100644
--- a/env/version.go
+++ b/env/version.go
@@ -17,6 +17,10 @@
 package env
 
 import (
+	"bytes"
+	"os"
+	"os/exec"
+	"path/filepath"
 	"strings"
 )
 
@@ -31,9 +35,42 @@ var (
 )
 
 func init() {
-	if buildVersion != "" && !strings.HasPrefix(buildVersion, "v"+MainVersion+".") {
-		panic("Invalid buildVersion: " + buildVersion)
+	initEnv()
+}
+
+func initEnv() {
+	if buildVersion == "" {
+		initEnvByGop()
+		return
 	}
+	if !strings.HasPrefix(buildVersion, "v"+MainVersion+".") {
+		panic("gop/env: [FATAL] Invalid buildVersion: " + buildVersion)
+	}
+}
+
+func initEnvByGop() {
+	if fname := filepath.Base(os.Args[0]); !isGopCmd(fname) {
+		if ret, err := gopEnv(); err == nil {
+			parts := strings.SplitN(strings.TrimRight(ret, "\n"), "\n", 3)
+			if len(parts) == 3 {
+				buildVersion, buildDate, defaultGopRoot = parts[0], parts[1], parts[2]
+			}
+		}
+	}
+}
+
+var gopEnv = func() (string, error) {
+	var b bytes.Buffer
+	cmd := exec.Command("gop", "env", "GOPVERSION", "BUILDDATE", "GOPROOT")
+	cmd.Stdout = &b
+	err := cmd.Run()
+	return b.String(), err
+}
+
+// Installed checks is `gop` installed or not.
+// If returns false, it means `gop` is not installed or not in PATH.
+func Installed() bool {
+	return buildVersion != ""
 }
 
 // Version returns the GoPlus tree's version string.
diff --git a/go.mod b/go.mod
index 95c7ec3f..73c1f41a 100644
--- a/go.mod
+++ b/go.mod
@@ -1,12 +1,12 @@
 module github.com/goplus/gop
 
-go 1.17
+go 1.18
 
 require (
-	github.com/fsnotify/fsnotify v1.6.0
+	github.com/fsnotify/fsnotify v1.7.0
 	github.com/goplus/c2go v0.7.16
-	github.com/goplus/gox v1.12.5
-	github.com/goplus/mod v0.11.7
+	github.com/goplus/gox v1.12.8
+	github.com/goplus/mod v0.11.8
 	github.com/qiniu/x v1.13.1
 	golang.org/x/tools v0.14.0
 )
diff --git a/go.sum b/go.sum
index 50d77c9b..dc1bcc5d 100644
--- a/go.sum
+++ b/go.sum
@@ -1,17 +1,17 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
-github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
+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.16 h1:B9jRNNytoq7yDBQOZSm2qENQhRQMfNhgOea5XHWNUnI=
 github.com/goplus/c2go v0.7.16/go.mod h1:XODEFX2PeEEJXNLLdykWeZgMSaKJ51fVm+C9IM3FxNQ=
 github.com/goplus/gox v1.12.1/go.mod h1:wymoQJ7ydd42cTlaXb4wNbvn4LlKjR+j8PZehI7v1zQ=
-github.com/goplus/gox v1.12.5 h1:YkdsZDIOW3DZLJctWYKVzdsqMeuCYNn7J4BsFi6B058=
-github.com/goplus/gox v1.12.5/go.mod h1:ferIrvJSoXae8gABk5Z4olEzfAxZpUy4hD91Vm5E/qs=
+github.com/goplus/gox v1.12.8 h1:xXpsGbNIIDmykOQwI/kDcoA3cOX5ISvLPlU5RiPtyZY=
+github.com/goplus/gox v1.12.8/go.mod h1:MOJnwqA8/rSUkn0vsayHFK7TD/3XQrdexMTqdzR70X0=
 github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs=
-github.com/goplus/mod v0.11.7 h1:OTGFi/Jk0qmTy3ih8u3BQ6CbtcaPlTrJBlTacnge0jE=
-github.com/goplus/mod v0.11.7/go.mod h1:yl2QncBKTdXk+8UaNsdo4u2zSpGEJYA5JKjgD3K2h00=
+github.com/goplus/mod v0.11.8 h1:90Kbg+MJhrdsonNGXYx3HUcW4IbBlRHYpIMssJaMMxM=
+github.com/goplus/mod v0.11.8/go.mod h1:yl2QncBKTdXk+8UaNsdo4u2zSpGEJYA5JKjgD3K2h00=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
 github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
 github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
@@ -30,7 +30,6 @@ 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/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=
@@ -42,19 +41,16 @@ 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/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/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=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 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=
@@ -65,7 +61,6 @@ golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuX
 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/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=
diff --git a/load.go b/load.go
index 52b9cc51..9d2065af 100644
--- a/load.go
+++ b/load.go
@@ -21,13 +21,13 @@ import (
 	"go/token"
 	"go/types"
 	"io/fs"
-	"path/filepath"
 	"strings"
 	"syscall"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gox"
 	"github.com/goplus/mod/env"
@@ -58,11 +58,6 @@ func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err er
 		return
 	}
 	if mod != nil {
-		if mod.Path() == "std" { // a Go std package
-			// TODO: should do this at github.com/goplus/mod
-			mod.Module.Module.Mod.Path = ""
-			return
-		}
 		err = mod.ImportClasses()
 		if err != nil {
 			err = errors.NewWith(err, `mod.RegisterClasses()`, -2, "(*gopmod.Module).RegisterClasses", mod)
@@ -79,19 +74,6 @@ func LoadMod(dir string, gop *env.Gop, conf *Config) (mod *gopmod.Module, err er
 	return new(gopmod.Module), nil
 }
 
-func lookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) {
-	return func(pkgPath string) (pubfile string, err error) {
-		if mod.File == nil { // no go.mod/gop.mod file
-			return "", syscall.ENOENT
-		}
-		pkg, err := mod.Lookup(pkgPath)
-		if err == nil {
-			pubfile = filepath.Join(pkg.Dir, "c2go.a.pub")
-		}
-		return
-	}
-}
-
 // -----------------------------------------------------------------------------
 
 func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (out, test *gox.Package, err error) {
@@ -134,7 +116,7 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 		Fset:        fset,
 		Importer:    imp,
 		LookupClass: mod.LookupClass,
-		LookupPub:   lookupPub(mod),
+		LookupPub:   c2go.LookupPub(mod),
 	}
 	for name, pkg := range pkgs {
 		if strings.HasSuffix(name, "_test") {
@@ -205,7 +187,7 @@ func LoadFiles(files []string, conf *Config) (out *gox.Package, err error) {
 			Fset:        fset,
 			Importer:    imp,
 			LookupClass: mod.LookupClass,
-			LookupPub:   lookupPub(mod),
+			LookupPub:   c2go.LookupPub(mod),
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
diff --git a/outline.go b/outline.go
index e718e4d7..9ddcb354 100644
--- a/outline.go
+++ b/outline.go
@@ -28,6 +28,7 @@ import (
 
 	"github.com/goplus/gop/cl/outline"
 	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/mod/gopmod"
 )
@@ -99,7 +100,7 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 			WorkingDir:  dir,
 			Importer:    imp,
 			LookupClass: mod.LookupClass,
-			LookupPub:   lookupPub(mod),
+			LookupPub:   c2go.LookupPub(mod),
 		})
 		if err != nil {
 			return
diff --git a/parser/_instance/instance5/cmd.gop b/parser/_instance/instance5/cmd.gop
new file mode 100644
index 00000000..b693b560
--- /dev/null
+++ b/parser/_instance/instance5/cmd.gop
@@ -0,0 +1,6 @@
+var a [2]int
+if 0 < a[0] {
+	println a
+	println T[int]{1}
+	println (T[int]){a:1,b:2}
+}
diff --git a/parser/_instance/instance5/parser.expect b/parser/_instance/instance5/parser.expect
new file mode 100644
index 00000000..1dc92068
--- /dev/null
+++ b/parser/_instance/instance5/parser.expect
@@ -0,0 +1,115 @@
+package main
+
+file cmd.gop
+noEntrypoint
+ast.GenDecl:
+  Tok: var
+  Specs:
+    ast.ValueSpec:
+      Names:
+        ast.Ident:
+          Name: a
+      Type:
+        ast.ArrayType:
+          Len:
+            ast.BasicLit:
+              Kind: INT
+              Value: 2
+          Elt:
+            ast.Ident:
+              Name: int
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.IfStmt:
+          Cond:
+            ast.BinaryExpr:
+              X:
+                ast.BasicLit:
+                  Kind: INT
+                  Value: 0
+              Op: <
+              Y:
+                ast.IndexExpr:
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Index:
+                    ast.BasicLit:
+                      Kind: INT
+                      Value: 0
+          Body:
+            ast.BlockStmt:
+              List:
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.Ident:
+                          Name: a
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.CompositeLit:
+                          Type:
+                            ast.IndexExpr:
+                              X:
+                                ast.Ident:
+                                  Name: T
+                              Index:
+                                ast.Ident:
+                                  Name: int
+                          Elts:
+                            ast.BasicLit:
+                              Kind: INT
+                              Value: 1
+                ast.ExprStmt:
+                  X:
+                    ast.CallExpr:
+                      Fun:
+                        ast.Ident:
+                          Name: println
+                      Args:
+                        ast.CompositeLit:
+                          Type:
+                            ast.ParenExpr:
+                              X:
+                                ast.IndexExpr:
+                                  X:
+                                    ast.Ident:
+                                      Name: T
+                                  Index:
+                                    ast.Ident:
+                                      Name: int
+                          Elts:
+                            ast.KeyValueExpr:
+                              Key:
+                                ast.Ident:
+                                  Name: a
+                              Value:
+                                ast.BasicLit:
+                                  Kind: INT
+                                  Value: 1
+                            ast.KeyValueExpr:
+                              Key:
+                                ast.Ident:
+                                  Name: b
+                              Value:
+                                ast.BasicLit:
+                                  Kind: INT
+                                  Value: 2
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 b89ec37a..5914f55b 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)
@@ -2067,23 +2068,9 @@ func (p *parser) checkExpr(x ast.Expr) ast.Expr {
 	return x
 }
 
-// isTypeName reports whether x is a (qualified) TypeName.
-func isTypeName(x ast.Expr) bool {
-	switch t := x.(type) {
-	case *ast.BadExpr:
-	case *ast.Ident:
-	case *ast.SelectorExpr:
-		_, isIdent := t.X.(*ast.Ident)
-		return isIdent
-	default:
-		return false // all other nodes are not type names
-	}
-	return true
-}
-
 // isLiteralType reports whether x is a legal composite literal type.
 func isLiteralType(x ast.Expr) bool {
-	switch t := x.(type) {
+	switch t := unparen(x).(type) {
 	case *ast.BadExpr:
 	case *ast.Ident:
 	case *ast.IndexExpr:
@@ -2187,7 +2174,7 @@ L:
 		case token.LBRACE: // {
 			if allowCmd && p.isCmd(x) { // println {...}
 				x = p.parseCallOrConversion(p.checkExprOrType(x), true)
-			} else if isLiteralType(x) && (p.exprLev >= 0 || !isTypeName(x)) {
+			} else if isLiteralType(x) && p.exprLev >= 0 {
 				if lhs {
 					p.resolve(x)
 				}
@@ -3664,9 +3651,11 @@ func (p *parser) parseGlobalStmts(sync map[token.Token]bool, pos token.Pos, stmt
 		Name: &ast.Ident{NamePos: pos, Name: "main"},
 		Doc:  doc,
 		Type: &ast.FuncType{
+			Func:   pos,
 			Params: &ast.FieldList{},
 		},
-		Body: &ast.BlockStmt{List: list},
+		Body:   &ast.BlockStmt{List: list},
+		Shadow: true,
 	}
 	p.shadowEntry = f
 	return f
@@ -3709,7 +3698,7 @@ func (p *parser) parseFile() *ast.File {
 	} else {
 		noPkgDecl = true
 		pos = token.NoPos
-		ident = ast.NewIdent("main")
+		ident = &ast.Ident{NamePos: p.file.Pos(0), Name: "main"}
 	}
 
 	p.openScope()
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/build/build.go b/x/build/build.go
index 9373c616..980e6064 100644
--- a/x/build/build.go
+++ b/x/build/build.go
@@ -65,14 +65,14 @@ type Package struct {
 
 func (p *Package) ToSource() ([]byte, error) {
 	var buf bytes.Buffer
-	if err := gox.WriteTo(&buf, p.Pkg); err != nil {
+	if err := p.Pkg.WriteTo(&buf); err != nil {
 		return nil, err
 	}
 	return buf.Bytes(), nil
 }
 
 func (p *Package) ToAst() *goast.File {
-	return gox.ASTFile(p.Pkg)
+	return p.Pkg.ASTFile()
 }
 
 func ClassKind(fname string) (isProj, ok bool) {
diff --git a/x/c2go/c2go.go b/x/c2go/c2go.go
new file mode 100644
index 00000000..d2240f11
--- /dev/null
+++ b/x/c2go/c2go.go
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package c2go
+
+import (
+	"path/filepath"
+	"syscall"
+
+	"github.com/goplus/mod/gopmod"
+)
+
+// -----------------------------------------------------------------------------
+
+// LookupPub returns a anonymous function required by cl.NewPackage.
+func LookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) {
+	return func(pkgPath string) (pubfile string, err error) {
+		if mod.File == nil { // no go.mod/gop.mod file
+			return "", syscall.ENOENT
+		}
+		pkg, err := mod.Lookup(pkgPath)
+		if err == nil {
+			pubfile = filepath.Join(pkg.Dir, "c2go.a.pub")
+		}
+		return
+	}
+}
+
+// -----------------------------------------------------------------------------
diff --git a/x/types/load.go b/x/types/load.go
deleted file mode 100644
index b62c9540..00000000
--- a/x/types/load.go
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (c) 2022 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-// This package is experimental. Don't use it in production.
-package types
-
-import (
-	"go/token"
-	"go/types"
-	"strings"
-
-	goast "go/ast"
-
-	"github.com/goplus/gop/ast"
-	"github.com/goplus/gop/ast/togo"
-)
-
-// A Config specifies the configuration for type checking.
-// The zero value for Config is a ready-to-use default configuration.
-type Config = types.Config
-
-// A Package describes a Go/Go+ package.
-type Package = types.Package
-
-// Load loads a package and returns the resulting package object and
-// the first error if any.
-//
-// The package is marked as complete if no errors occurred, otherwise it is
-// incomplete. See Config.Error for controlling behavior in the presence of
-// errors.
-func Load(fset *token.FileSet, in *ast.Package, conf *Config) (pkg *Package, err error) {
-	n := len(in.Files) + len(in.GoFiles)
-	files := make([]*goast.File, 0, n)
-	for filename, f := range in.Files {
-		if !strings.HasSuffix(filename, "_test.gop") {
-			files = append(files, togo.ASTFile(f, 0))
-		}
-	}
-	for filename, f := range in.GoFiles {
-		if !strings.HasSuffix(filename, "_test.go") {
-			files = append(files, f)
-		}
-	}
-	return conf.Check(in.Name, fset, files, nil)
-}
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
new file mode 100644
index 00000000..157e7740
--- /dev/null
+++ b/x/typesutil/check.go
@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package typesutil
+
+import (
+	goast "go/ast"
+	"go/types"
+	"path/filepath"
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gop/x/typesutil/internal/typesutil"
+	"github.com/qiniu/x/log"
+)
+
+// -----------------------------------------------------------------------------
+
+type dbgFlags int
+
+const (
+	DbgFlagVerbose dbgFlags = 1 << iota
+	DbgFlagPrintError
+	DbgFlagDisableRecover
+	DbgFlagDefault = DbgFlagVerbose | DbgFlagPrintError
+	DbgFlagAll     = DbgFlagDefault | DbgFlagDisableRecover
+)
+
+var (
+	debugVerbose  bool
+	debugPrintErr bool
+)
+
+func SetDebug(flags dbgFlags) {
+	debugVerbose = (flags & DbgFlagVerbose) != 0
+	debugPrintErr = (flags & DbgFlagPrintError) != 0
+	if (flags & DbgFlagDisableRecover) != 0 {
+		cl.SetDisableRecover(true)
+	}
+}
+
+// -----------------------------------------------------------------------------
+
+type Project = cl.Project
+
+type Config struct {
+	// Types provides type information for the package (optional).
+	Types *types.Package
+
+	// Fset provides source position information for syntax trees and types (required).
+	// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
+	Fset *token.FileSet
+
+	// WorkingDir is the directory in which to run gop compiler (optional).
+	// If WorkingDir is not set, os.Getwd() is used.
+	WorkingDir string
+
+	// C2goBase specifies base of standard c2go packages (optional).
+	// Default is github.com/goplus/.
+	C2goBase string
+
+	// LookupPub lookups the c2go package pubfile named c2go.a.pub (required).
+	// See gop/x/c2go.LookupPub.
+	LookupPub func(pkgPath string) (pubfile string, err error)
+
+	// LookupClass lookups a class by specified file extension (required).
+	// See (*github.com/goplus/mod/gopmod.Module).LookupClass.
+	LookupClass func(ext string) (c *Project, ok bool)
+}
+
+// A Checker maintains the state of the type checker.
+// It must be created with NewChecker.
+type Checker struct {
+	conf    *types.Config
+	opts    *Config
+	goInfo  *types.Info
+	gopInfo *Info
+}
+
+// NewChecker returns a new Checker instance for a given package.
+// Package files may be added incrementally via checker.Files.
+func NewChecker(conf *types.Config, opts *Config, goInfo *types.Info, gopInfo *Info) *Checker {
+	return &Checker{conf, opts, goInfo, gopInfo}
+}
+
+// Files checks the provided files as part of the checker's package.
+func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error) {
+	opts := p.opts
+	pkgTypes := opts.Types
+	fset := opts.Fset
+	conf := p.conf
+	if len(gopFiles) == 0 {
+		checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo)
+		return checker.Files(goFiles)
+	}
+	files := make([]*goast.File, 0, len(goFiles))
+	gofs := make(map[string]*goast.File)
+	gopfs := make(map[string]*ast.File)
+	for _, goFile := range goFiles {
+		f := fset.File(goFile.Pos())
+		file := f.Name()
+		fname := filepath.Base(file)
+		if strings.HasPrefix(fname, "gop_autogen") {
+			continue
+		}
+		gofs[file] = goFile
+		files = append(files, goFile)
+	}
+	for _, gopFile := range gopFiles {
+		f := fset.File(gopFile.Pos())
+		gopfs[f.Name()] = gopFile
+	}
+	if debugVerbose {
+		log.Println("typesutil.Check:", pkgTypes.Path(), "gopFiles =", len(gopfs), "goFiles =", len(gofs))
+	}
+	pkg := &ast.Package{
+		Name:    pkgTypes.Name(),
+		Files:   gopfs,
+		GoFiles: gofs,
+	}
+	_, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{
+		Types:          pkgTypes,
+		Fset:           fset,
+		WorkingDir:     opts.WorkingDir,
+		C2goBase:       opts.C2goBase,
+		LookupPub:      opts.LookupPub,
+		LookupClass:    opts.LookupClass,
+		Importer:       conf.Importer,
+		Recorder:       gopRecorder{p.gopInfo},
+		NoFileLine:     true,
+		NoAutoGenMain:  true,
+		NoSkipConstant: true,
+	})
+	if err != nil {
+		if debugPrintErr {
+			log.Println("typesutil.Check err:", err)
+			log.SingleStack()
+		}
+		return
+	}
+	if len(files) > 0 {
+		scope := pkgTypes.Scope()
+		objMap := make(map[types.Object]types.Object)
+		// remove all objects defined in Go files
+		for _, f := range files {
+			for _, decl := range f.Decls {
+				switch v := decl.(type) {
+				case *goast.GenDecl:
+					for _, spec := range v.Specs {
+						switch v := spec.(type) {
+						case *goast.ValueSpec:
+							for _, name := range v.Names {
+								scopeDelete(objMap, scope, name.Name)
+							}
+						case *goast.TypeSpec:
+							scopeDelete(objMap, scope, v.Name.Name)
+						}
+					}
+				case *goast.FuncDecl:
+					if v.Recv == nil {
+						scopeDelete(objMap, scope, v.Name.Name)
+					}
+				}
+			}
+		}
+		checker := types.NewChecker(conf, fset, pkgTypes, p.goInfo)
+		err = checker.Files(files)
+		for o := range objMap {
+			objMap[o] = scope.Lookup(o.Name())
+		}
+		// correct Go+ types info to avoid there are two instances for same Go object:
+		uses := p.gopInfo.Uses
+		for id, old := range uses {
+			if new := objMap[old]; new != nil {
+				uses[id] = new
+			}
+		}
+	}
+	return
+}
+
+func scopeDelete(objMap map[types.Object]types.Object, scope *types.Scope, name string) {
+	if o := typesutil.ScopeDelete(scope, name); o != nil {
+		objMap[o] = nil
+	}
+}
diff --git a/x/typesutil/eval.go b/x/typesutil/eval.go
new file mode 100644
index 00000000..779ad5e7
--- /dev/null
+++ b/x/typesutil/eval.go
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package typesutil
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+)
+
+// 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. The expression may be an identifier denoting an uninstantiated generic
+// function or type.
+//
+// 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 type-checked.
+//
+// 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 than the respective context-specific type.
+func CheckExpr(fset *token.FileSet, pkg *types.Package, pos token.Pos, expr ast.Expr, info *Info) (err error) {
+	panic("todo")
+}
diff --git a/x/typesutil/gopinfo.go b/x/typesutil/gopinfo.go
new file mode 100644
index 00000000..e95f8631
--- /dev/null
+++ b/x/typesutil/gopinfo.go
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package typesutil
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/qiniu/x/log"
+)
+
+// -----------------------------------------------------------------------------
+
+// Info holds result type information for a type-checked package.
+// Only the information for which a map is provided is collected.
+// If the package has type errors, the collected information may
+// be incomplete.
+type Info struct {
+	// Types maps expressions to their types, and for constant
+	// expressions, also their values. Invalid expressions are
+	// omitted.
+	//
+	// For (possibly parenthesized) identifiers denoting built-in
+	// functions, the recorded signatures are call-site specific:
+	// if the call result is not a constant, the recorded type is
+	// an argument-specific signature. Otherwise, the recorded type
+	// is invalid.
+	//
+	// The Types map does not record the type of every identifier,
+	// only those that appear where an arbitrary expression is
+	// permitted. For instance, the identifier f in a selector
+	// expression x.f is found only in the Selections map, the
+	// identifier z in a variable declaration 'var z int' is found
+	// only in the Defs map, and identifiers denoting packages in
+	// qualified identifiers are collected in the Uses map.
+	Types map[ast.Expr]types.TypeAndValue
+
+	// Instances maps identifiers denoting generic types or functions to their
+	// type arguments and instantiated type.
+	//
+	// For example, Instances will map the identifier for 'T' in the type
+	// instantiation T[int, string] to the type arguments [int, string] and
+	// resulting instantiated *Named type. Given a generic function
+	// func F[A any](A), Instances will map the identifier for 'F' in the call
+	// expression F(int(1)) to the inferred type arguments [int], and resulting
+	// instantiated *Signature.
+	//
+	// Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs
+	// results in an equivalent of Instances[id].Type.
+	Instances map[*ast.Ident]types.Instance
+
+	// Defs maps identifiers to the objects they define (including
+	// package names, dots "." of dot-imports, and blank "_" identifiers).
+	// For identifiers that do not denote objects (e.g., the package name
+	// in package clauses, or symbolic variables t in t := x.(type) of
+	// type switch headers), the corresponding objects are nil.
+	//
+	// For an embedded field, Defs returns the field *Var it defines.
+	//
+	// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
+	Defs map[*ast.Ident]types.Object
+
+	// Uses maps identifiers to the objects they denote.
+	//
+	// For an embedded field, Uses returns the *TypeName it denotes.
+	//
+	// Invariant: Uses[id].Pos() != id.Pos()
+	Uses map[*ast.Ident]types.Object
+
+	// Implicits maps nodes to their implicitly declared objects, if any.
+	// The following node and object types may appear:
+	//
+	//     node               declared object
+	//
+	//     *ast.ImportSpec    *PkgName for imports without renames
+	//     *ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
+	//     *ast.Field         anonymous parameter *Var (incl. unnamed results)
+	//
+	Implicits map[ast.Node]types.Object
+
+	// Selections maps selector expressions (excluding qualified identifiers)
+	// to their corresponding selections.
+	Selections map[*ast.SelectorExpr]*types.Selection
+
+	// Scopes maps ast.Nodes to the scopes they define. Package scopes are not
+	// associated with a specific node but with all files belonging to a package.
+	// Thus, the package scope can be found in the type-checked Package object.
+	// Scopes nest, with the Universe scope being the outermost scope, enclosing
+	// the package scope, which contains (one or more) files scopes, which enclose
+	// function scopes which in turn enclose statement and function literal scopes.
+	// Note that even though package-level functions are declared in the package
+	// scope, the function scopes are embedded in the file scope of the file
+	// containing the function declaration.
+	//
+	// The following node types may appear in Scopes:
+	//
+	//     *ast.File
+	//     *ast.FuncType
+	//     *ast.TypeSpec
+	//     *ast.BlockStmt
+	//     *ast.IfStmt
+	//     *ast.SwitchStmt
+	//     *ast.TypeSwitchStmt
+	//     *ast.CaseClause
+	//     *ast.CommClause
+	//     *ast.ForStmt
+	//     *ast.RangeStmt
+	//
+	Scopes map[ast.Node]*types.Scope
+
+	// InitOrder is the list of package-level initializers in the order in which
+	// they must be executed. Initializers referring to variables related by an
+	// initialization dependency appear in topological order, the others appear
+	// in source order. Variables without an initialization expression do not
+	// appear in this list.
+	// InitOrder []*Initializer
+}
+
+// ObjectOf returns the object denoted by the specified id,
+// or nil if not found.
+//
+// If id is an embedded struct field, ObjectOf returns the field (*Var)
+// it defines, not the type (*TypeName) it uses.
+//
+// Precondition: the Uses and Defs maps are populated.
+func (info *Info) ObjectOf(id *ast.Ident) types.Object {
+	if obj := info.Defs[id]; obj != nil {
+		return obj
+	}
+	return info.Uses[id]
+}
+
+// TypeOf returns the type of expression e, or nil if not found.
+// Precondition: the Types, Uses and Defs maps are populated.
+func (info *Info) TypeOf(e ast.Expr) types.Type {
+	if t, ok := info.Types[e]; ok {
+		return t.Type
+	}
+	if id, _ := e.(*ast.Ident); id != nil {
+		if obj := info.ObjectOf(id); obj != nil {
+			return obj.Type()
+		}
+	}
+	return nil
+}
+
+// -----------------------------------------------------------------------------
+
+type gopRecorder struct {
+	*Info
+}
+
+// Type maps expressions to their types, and for constant
+// expressions, also their values. Invalid expressions are
+// omitted.
+//
+// For (possibly parenthesized) identifiers denoting built-in
+// functions, the recorded signatures are call-site specific:
+// if the call result is not a constant, the recorded type is
+// an argument-specific signature. Otherwise, the recorded type
+// is invalid.
+//
+// The Types map does not record the type of every identifier,
+// only those that appear where an arbitrary expression is
+// permitted. For instance, the identifier f in a selector
+// expression x.f is found only in the Selections map, the
+// identifier z in a variable declaration 'var z int' is found
+// 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
+}
+
+// Instantiate maps identifiers denoting generic types or functions to their
+// type arguments and instantiated type.
+//
+// For example, Instantiate will map the identifier for 'T' in the type
+// instantiation T[int, string] to the type arguments [int, string] and
+// resulting instantiated *Named type. Given a generic function
+// func F[A any](A), Instances will map the identifier for 'F' in the call
+// expression F(int(1)) to the inferred type arguments [int], and resulting
+// instantiated *Signature.
+//
+// Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs
+// results in an equivalent of Instances[id].Type.
+func (info gopRecorder) Instantiate(id *ast.Ident, inst types.Instance) {
+	info.Instances[id] = inst
+}
+
+// Def maps identifiers to the objects they define (including
+// package names, dots "." of dot-imports, and blank "_" identifiers).
+// For identifiers that do not denote objects (e.g., the package name
+// in package clauses, or symbolic variables t in t := x.(type) of
+// type switch headers), the corresponding objects are nil.
+//
+// For an embedded field, Def maps the field *Var it defines.
+//
+// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
+func (info gopRecorder) Def(id *ast.Ident, obj types.Object) {
+	if debugVerbose {
+		log.Println("==> Def:", id, obj)
+	}
+	info.Defs[id] = obj
+}
+
+// Use maps identifiers to the objects they denote.
+//
+// For an embedded field, Use maps the *TypeName it denotes.
+//
+// Invariant: Uses[id].Pos() != id.Pos()
+func (info gopRecorder) Use(id *ast.Ident, obj types.Object) {
+	if debugVerbose {
+		log.Println("==> Use:", id, obj)
+	}
+	info.Uses[id] = obj
+}
+
+// Implicit maps nodes to their implicitly declared objects, if any.
+// The following node and object types may appear:
+//
+//	node               declared object
+//
+//	*ast.ImportSpec    *PkgName for imports without renames
+//	*ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
+//	*ast.Field         anonymous parameter *Var (incl. unnamed results)
+func (info gopRecorder) Implicit(node ast.Node, obj types.Object) {
+	if debugVerbose {
+		log.Println("==> Implicit:", obj)
+	}
+	info.Implicits[node] = obj
+}
+
+// Select maps selector expressions (excluding qualified identifiers)
+// to their corresponding selections.
+func (info gopRecorder) Select(e *ast.SelectorExpr, sel *types.Selection) {
+	info.Selections[e] = sel
+}
+
+// Scope maps ast.Nodes to the scopes they define. Package scopes are not
+// associated with a specific node but with all files belonging to a package.
+// Thus, the package scope can be found in the type-checked Package object.
+// Scopes nest, with the Universe scope being the outermost scope, enclosing
+// the package scope, which contains (one or more) files scopes, which enclose
+// function scopes which in turn enclose statement and function literal scopes.
+// Note that even though package-level functions are declared in the package
+// scope, the function scopes are embedded in the file scope of the file
+// containing the function declaration.
+//
+// The following node types may appear in Scopes:
+//
+//	*ast.File
+//	*ast.FuncType
+//	*ast.TypeSpec
+//	*ast.BlockStmt
+//	*ast.IfStmt
+//	*ast.SwitchStmt
+//	*ast.TypeSwitchStmt
+//	*ast.CaseClause
+//	*ast.CommClause
+//	*ast.ForStmt
+//	*ast.RangeStmt
+func (info gopRecorder) Scope(n ast.Node, scope *types.Scope) {
+	if debugVerbose {
+		log.Println("==> Scope:", scope)
+	}
+	info.Scopes[n] = scope
+}
+
+// -----------------------------------------------------------------------------
diff --git a/x/typesutil/internal/typesutil/types.go b/x/typesutil/internal/typesutil/types.go
new file mode 100644
index 00000000..2835c371
--- /dev/null
+++ b/x/typesutil/internal/typesutil/types.go
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package typesutil
+
+import (
+	"go/token"
+	"go/types"
+	"unsafe"
+)
+
+// A Scope maintains a set of objects and links to its containing
+// (parent) and contained (children) scopes. Objects may be inserted
+// and looked up by name. The zero value for Scope is a ready-to-use
+// empty scope.
+type Scope struct {
+	parent   *Scope
+	children []*Scope
+	number   int                     // parent.children[number-1] is this scope; 0 if there is no parent
+	elems    map[string]types.Object // lazily allocated
+	pos, end token.Pos               // scope extent; may be invalid
+	comment  string                  // for debugging only
+	isFunc   bool                    // set if this is a function scope (internal use only)
+}
+
+// ScopeDelete deletes an object from specified scope by its name.
+func ScopeDelete(s *types.Scope, name string) types.Object {
+	elems := (*Scope)(unsafe.Pointer(s)).elems
+	if o, ok := elems[name]; ok {
+		delete(elems, name)
+		return o
+	}
+	return nil
+}


Test patch:
diff --git a/ast/fromgo/gopast_test.go b/ast/fromgo/gopast_test.go
index 5f748acf..46d51d70 100644
--- a/ast/fromgo/gopast_test.go
+++ b/ast/fromgo/gopast_test.go
@@ -23,6 +23,7 @@ import (
 	"go/token"
 	"testing"
 
+	gopast "github.com/goplus/gop/ast"
 	"github.com/goplus/gop/format"
 )
 
@@ -125,3 +126,14 @@ type foo int
 func (a foo) Str() (string) {}
 `)
 }
+
+func TestCheckIdent(t *testing.T) {
+	if _, ok := CheckIdent(&gopast.Ident{}); ok {
+		t.Fatal("CheckIdent: found?")
+	}
+	if _, ok := CheckIdent(&gopast.Ident{Obj: &gopast.Object{
+		Data: &ast.Ident{},
+	}}); !ok {
+		t.Fatal("CheckIdent: not found?")
+	}
+}
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index b20381db..3f09e167 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,58 @@ func getGoxConf() *gox.Config {
 	return &gox.Config{Fset: fset, Importer: imp}
 }
 
+func TestErrMultiStarRecv(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestErrMultiStarRecv: no panic?")
+		}
+	}()
+	getRecvType(&ast.StarExpr{
+		X: &ast.StarExpr{},
+	})
+}
+
+func TestErrAssign(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestErrAssign: no panic?")
+		}
+	}()
+	ctx := &blockCtx{}
+	compileAssignStmt(ctx, &ast.AssignStmt{
+		Tok: token.DEFINE,
+		Lhs: []ast.Expr{
+			&ast.SelectorExpr{
+				X:   ast.NewIdent("foo"),
+				Sel: ast.NewIdent("bar"),
+			},
+		},
+	})
+}
+
+func TestErrPanicToRecv(t *testing.T) {
+	ctx := &blockCtx{
+		tlookup: &typeParamLookup{
+			[]*types.TypeParam{
+				types.NewTypeParam(types.NewTypeName(0, nil, "t", nil), nil),
+			},
+		},
+	}
+	recv := &ast.FieldList{
+		List: []*ast.Field{
+			{Type: &ast.SelectorExpr{}},
+		},
+	}
+	func() {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("TestErrPanicToRecv: no panic?")
+			}
+		}()
+		toRecv(ctx, recv)
+	}()
+}
+
 func TestCompileErrWrapExpr(t *testing.T) {
 	defer func() {
 		if e := recover(); e != "TODO: can't use expr? in global" {
diff --git a/cl/compile_c2go_test.go b/cl/compile_c2go_test.go
index c20edcc9..8bf5199c 100644
--- a/cl/compile_c2go_test.go
+++ b/cl/compile_c2go_test.go
@@ -35,8 +35,8 @@ C.printf C"Hello, world!\n"
 `, `package main
 
 import (
-	libc "github.com/goplus/gop/cl/internal/libc"
-	unsafe "unsafe"
+	"github.com/goplus/gop/cl/internal/libc"
+	"unsafe"
 )
 
 func main() {
@@ -53,8 +53,8 @@ C.printf C"Hello, world!\n"
 `, `package main
 
 import (
-	libc "github.com/goplus/gop/cl/internal/libc"
-	unsafe "unsafe"
+	"github.com/goplus/gop/cl/internal/libc"
+	"unsafe"
 )
 
 func main() {
@@ -71,8 +71,8 @@ C.printf C"Hello, world!\n"
 `, `package main
 
 import (
-	libc "github.com/goplus/gop/cl/internal/libc"
-	unsafe "unsafe"
+	"github.com/goplus/gop/cl/internal/libc"
+	"unsafe"
 )
 
 func main() {
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 0451c301..28e0fd71 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -108,6 +108,7 @@ func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string)
 		t.Fatal("ParseFSDir:", err)
 	}
 	conf := *gblConf
+	conf.Recorder = nil
 	conf.NoFileLine = false
 	conf.WorkingDir = "/foo"
 	conf.TargetDir = "/foo"
@@ -163,7 +164,7 @@ func onMsg(msg string) {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -210,8 +211,8 @@ func onInit() {
 `, ``, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 const Foo = 1
@@ -247,8 +248,8 @@ func onInit() {
 `, `package main
 
 import (
-	spx "github.com/goplus/gop/cl/internal/spx"
-	math "math"
+	"github.com/goplus/gop/cl/internal/spx"
+	"math"
 )
 
 type Game struct {
@@ -300,7 +301,7 @@ func onCloned() {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -340,8 +341,8 @@ println "Hi"
 `, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 var x float64 = spx.Rand__1(1.2)
@@ -377,8 +378,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -409,8 +410,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -441,8 +442,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Dog struct {
@@ -471,7 +472,7 @@ func TestSpxMainEntry(t *testing.T) {
 `, `
 `, `package main
 
-import spx2 "github.com/goplus/gop/cl/internal/spx2"
+import "github.com/goplus/gop/cl/internal/spx2"
 
 type Game struct {
 	spx2.Game
@@ -490,7 +491,7 @@ var (
 `, `
 `, `package main
 
-import spx2 "github.com/goplus/gop/cl/internal/spx2"
+import "github.com/goplus/gop/cl/internal/spx2"
 
 type Game struct {
 	spx2.Game
@@ -524,8 +525,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -565,7 +566,7 @@ func onMsg(msg string) {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -623,7 +624,7 @@ func onCloned() {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type info struct {
 	x int
@@ -654,3 +655,78 @@ func (this *Kai) onCloned() {
 }
 `, "Game.tgmx", "Kai.tspx")
 }
+
+func TestSpxErrorSel(t *testing.T) {
+	gopSpxErrorTestEx(t, `./Kai.tspx:2:9: this.pos undefined (type *Kai has no field or method pos)`, `
+println "hi"
+`, `
+println this.pos
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxMethodSel(t *testing.T) {
+	gopSpxTestEx(t, `
+sendMessage "Hi"
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+}
+
+func (this *Game) MainEntry() {
+	this.SendMessage("Hi")
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(Game))
+}
+
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Kai) onMsg(msg string) {
+}
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxPkgOverload(t *testing.T) {
+	gopSpxTestEx(t, `
+println "Hi"
+`, `
+func onMsg(msg string) {
+	this.position.add 100,200
+}
+`, `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) {
+	this.Position().Add__0(100, 200)
+}
+`, "Game.tgmx", "Kai.tspx")
+}
diff --git a/cl/compile_test.go b/cl/compile_test.go
index e870aed3..3a54eeb8 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -50,6 +50,7 @@ func init() {
 	gblConf = &cl.Config{
 		Fset:          gblFset,
 		Importer:      imp,
+		Recorder:      gopRecorder{},
 		LookupClass:   lookupClass,
 		LookupPub:     lookupPub,
 		C2goBase:      "github.com/goplus/gop/cl/internal",
@@ -152,10 +153,10 @@ for line <- open("foo.txt")! {
 `, `package main
 
 import (
-	fmt "fmt"
-	os "os"
-	iox "github.com/goplus/gop/builtin/iox"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"os"
+	"github.com/goplus/gop/builtin/iox"
+	"github.com/qiniu/x/errors"
 )
 
 func main() {
@@ -189,9 +190,9 @@ for line <- os.Stdin {
 `, `package main
 
 import (
-	fmt "fmt"
-	os "os"
-	iox "github.com/goplus/gop/builtin/iox"
+	"fmt"
+	"os"
+	"github.com/goplus/gop/builtin/iox"
 )
 
 func main() {
@@ -219,9 +220,9 @@ for line <- lines(r) {
 `, `package main
 
 import (
-	fmt "fmt"
-	iox "github.com/goplus/gop/builtin/iox"
-	io "io"
+	"fmt"
+	"github.com/goplus/gop/builtin/iox"
+	"io"
 )
 
 var r io.Reader
@@ -293,7 +294,7 @@ type T struct{}
 println(&T{},&Point{10,20})
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -310,7 +311,7 @@ for i <- :10, i%3 == 0 {
 	println i
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 1 {
@@ -333,7 +334,7 @@ d := fvec([])
 println a, b, c, d
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fvec []float64
 type foo float64
@@ -537,8 +538,8 @@ println "The Go+ Language for", fields.join(", ")
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func main() {
@@ -554,7 +555,7 @@ p := println
 p "Hello world"
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	p := fmt.Println
@@ -920,8 +921,8 @@ func main() {
 }`, `package main
 
 import (
-	fmt "fmt"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"github.com/qiniu/x/errors"
 )
 
 func t() (int, int, error) {
@@ -954,7 +955,7 @@ func main() {
 	t()!
 }`, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func t() error {
 	return nil
@@ -997,7 +998,7 @@ func (a *A) String() string {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type AA interface {
 	String() string
@@ -1042,7 +1043,7 @@ func (a *A) String() string {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type AA interface {
 	String() string
@@ -1083,7 +1084,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	{
@@ -1135,7 +1136,7 @@ func main() {
 var i int
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(i)
@@ -1178,7 +1179,7 @@ func main() {
 var i = 100
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(i)
@@ -1198,7 +1199,7 @@ func main() {
 	println(n)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	v := []uint64{2, 3, 5}
@@ -1223,7 +1224,7 @@ func consume(xchg chan int) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func consume(xchg chan int) {
 	select {
@@ -1396,7 +1397,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Shape interface {
 	Area() float64
@@ -1441,7 +1442,7 @@ type BigInt struct {
 	*big.Int
 }`, `package main
 
-import big "math/big"
+import "math/big"
 
 type BigInt struct {
 	*big.Int
@@ -1461,10 +1462,10 @@ func foo(script string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	goptest "github.com/goplus/gop/ast/goptest"
-	errors "github.com/qiniu/x/errors"
-	gopq "github.com/goplus/gop/ast/gopq"
+	"fmt"
+	"github.com/goplus/gop/ast/goptest"
+	"github.com/qiniu/x/errors"
+	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1495,10 +1496,10 @@ func foo(script string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	goptest "github.com/goplus/gop/ast/goptest"
-	errors "github.com/qiniu/x/errors"
-	gopq "github.com/goplus/gop/ast/gopq"
+	"fmt"
+	"github.com/goplus/gop/ast/goptest"
+	"github.com/qiniu/x/errors"
+	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1527,8 +1528,8 @@ func add(x, y string) (int, error) {
 `, `package main
 
 import (
-	strconv "strconv"
-	errors "github.com/qiniu/x/errors"
+	"strconv"
+	"github.com/qiniu/x/errors"
 )
 
 func add(x string, y string) (int, error) {
@@ -1568,7 +1569,7 @@ func addSafe(x, y string) int {
 }
 `, `package main
 
-import strconv "strconv"
+import "strconv"
 
 func addSafe(x string, y string) int {
 	return func() (_gop_ret int) {
@@ -1596,8 +1597,8 @@ var ret int = println("Hi")!
 `, `package main
 
 import (
-	fmt "fmt"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"github.com/qiniu/x/errors"
 )
 
 var ret int = func() (_gop_ret int) {
@@ -1621,7 +1622,7 @@ func mkdir(name string) error {
 mkdir! "foo"
 `, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func mkdir(name string) error {
 	return nil
@@ -1649,7 +1650,7 @@ func foo() (func(), error) {
 foo()!()
 `, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func foo() (func(), error) {
 	return nil, nil
@@ -1698,7 +1699,7 @@ var x, y uint128
 var z uint128 = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Uint128
 var z ng.Uint128 = x.Gop_Add__1(y)
@@ -1711,7 +1712,7 @@ var x, y int128
 var z int128 = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Int128
 var z ng.Int128 = x.Gop_Add__1(y)
@@ -1724,7 +1725,7 @@ var x, y bigint
 var z bigint = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Bigint
 var z ng.Bigint = x.Gop_Add(y)
@@ -1737,8 +1738,8 @@ var x = 1r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigint_Init__1(big.NewInt(1))
@@ -1750,7 +1751,7 @@ func TestUint128Lit(t *testing.T) {
 var x uint128 = 1
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Uint128 = ng.Uint128_Init__0(1)
 `)
@@ -1761,7 +1762,7 @@ func TestInt128Lit(t *testing.T) {
 var x int128 = 1
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Int128 = ng.Int128_Init__0(1)
 `)
@@ -1773,8 +1774,8 @@ var x = 1/2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(1, 2))
@@ -1787,8 +1788,8 @@ var x = 3 + 1/2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
@@ -1804,8 +1805,8 @@ var z = 100 + y
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
@@ -1918,7 +1919,7 @@ var x bigint
 x += 3
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Bigint
 
@@ -1935,8 +1936,8 @@ x *= 2
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 func main() {
@@ -1953,8 +1954,8 @@ x *= 2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 func main() {
@@ -2205,12 +2206,12 @@ func main() {
 }
 `, `package main
 
+type bar = foo
 type foo struct {
 	p *foo
 	A int
 	B string `+"`tag1:123`"+`
 }
-type bar = foo
 
 func main() {
 	type a struct {
@@ -2227,7 +2228,7 @@ go println("Hi")
 defer println("Go+")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	go fmt.Println("Hi")
@@ -2247,7 +2248,7 @@ for {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -2284,7 +2285,7 @@ for range a {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -2322,7 +2323,7 @@ for k, v := range new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2350,7 +2351,7 @@ for v <- new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2385,7 +2386,7 @@ for k, v <- new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fooIter struct {
 }
@@ -2421,7 +2422,7 @@ func (p *foo) Gop_Enum(c func(val string)) {
 println([v for v <- new(foo)])
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2472,7 +2473,7 @@ for k, v <- newFoo() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fooIter struct {
 	data *foo
@@ -2521,7 +2522,7 @@ for i, x <- [1, 3, 5, 7, 11, 13, 17] {
 println("sum(5,7,11,13,17):", sum)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	sum := 0
@@ -2692,7 +2693,7 @@ x := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	arr := []float64{1, 2, 3, 4.1, 5, 6}
@@ -2798,8 +2799,8 @@ println("x:", x)
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func main() {
@@ -2822,7 +2823,7 @@ func bar(p pfoo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 	req int
@@ -2851,7 +2852,7 @@ a.B = "Hi"
 flag.Usage = nil
 `, `package main
 
-import flag "flag"
+import "flag"
 
 func main() {
 	a := &struct {
@@ -2911,7 +2912,7 @@ func (M) Bar() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type M int
 
@@ -2958,7 +2959,7 @@ var d = -a       // TODO: -a have no return value!
 var e = a!=b
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2992,7 +2993,7 @@ var e = a.Gop_NE(b)
 func TestCmdlineNoEOL(t *testing.T) {
 	gopClTest(t, `println "Hi"`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hi")
@@ -3007,7 +3008,7 @@ func main() {
 	fmt.println "Hi"
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hi")
@@ -3021,7 +3022,7 @@ func TestDotImport(t *testing.T) {
 var a = round(1.2)
 `, `package main
 
-import math "math"
+import "math"
 
 var a = math.Round(1.2)
 `)
@@ -3033,7 +3034,7 @@ func TestLocalImport(t *testing.T) {
 var a = spx.TestIntValue
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 var a = spx.TestIntValue
 `)
@@ -3068,7 +3069,7 @@ func TestAnonymousImport(t *testing.T) {
 printf("Hello Go+\n")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hello")
@@ -3107,7 +3108,7 @@ func main() {
 	fmt.Println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	const (
@@ -3132,7 +3133,7 @@ if t := false; t {
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	x := 0
@@ -3172,7 +3173,7 @@ default:
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	x := 0
@@ -3213,7 +3214,7 @@ default:
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	v := "Hello"
@@ -3245,7 +3246,7 @@ label:
 	}
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -3272,7 +3273,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func foo(format string, args ...interface {
 }) (int, error) {
@@ -3310,7 +3311,7 @@ func(v string) {
 }("Hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	func(v string) {
@@ -3451,7 +3452,7 @@ Do => {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func Do(func()) {
 }
@@ -3620,7 +3621,7 @@ func bar(foo func(string, ...interface{}) (int, error)) {
 bar(fmt.Printf)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func bar(foo func(string, ...interface {
 }) (int, error)) {
@@ -3657,8 +3658,8 @@ fmt.Println(printf("Hello, %v\n", "Go+"))
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func foo(x string) string {
@@ -3686,7 +3687,7 @@ func TestFuncCall(t *testing.T) {
 
 fmt.Println("Hello")`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hello")
@@ -3704,7 +3705,7 @@ func foo(args ...interface{}) {
 func main() {
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func foo(args ...interface {
 }) {
@@ -3727,7 +3728,7 @@ func foo(args ...interface{}) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	foo("Hello", 123)
@@ -3763,7 +3764,7 @@ func TestAssignUnderscore(t *testing.T) {
 _, err := log.Println("Hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	_, err := fmt.Println("Hello")
@@ -3810,8 +3811,8 @@ println(rmap)
 `, `package main
 
 import (
-	fmt "fmt"
-	foo "github.com/goplus/gop/cl/internal/gop-in-go/foo"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/gop-in-go/foo"
 )
 
 func main() {
@@ -3856,8 +3857,8 @@ func New() Result {
 `, `package main
 
 import (
-	reflect "reflect"
-	testing "testing"
+	"reflect"
+	"testing"
 )
 
 type Repo struct {
@@ -3901,7 +3902,7 @@ for i $ :10 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 1 {
@@ -3914,7 +3915,7 @@ for i $ 1:10:3 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 3 {
@@ -3930,7 +3931,7 @@ for i $ 1:10:2 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 2 {
@@ -3946,7 +3947,7 @@ for i $ 1:10 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 1 {
@@ -3962,7 +3963,7 @@ for i $ :10:2 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 2 {
@@ -3978,7 +3979,7 @@ for range :10 {
 	println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
@@ -3994,7 +3995,7 @@ for _ <- :10 {
 	println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
@@ -4010,8 +4011,8 @@ println [x for x <- 0:3:1]
 `, `package main
 
 import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
+	"fmt"
+	"github.com/goplus/gop/builtin"
 )
 
 func main() {
@@ -4057,7 +4058,7 @@ func TestRangeExpr8(t *testing.T) {
 	}
 	`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -4101,7 +4102,7 @@ func TestRangeExpr9(t *testing.T) {
 	}
 	`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -4157,7 +4158,7 @@ func TestNoEntrypoint(t *testing.T) {
 	gopClTest(t, `println("init")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("init")
@@ -4167,7 +4168,7 @@ func main() {
 println("init")
 `, `package bar
 
-import fmt "fmt"
+import "fmt"
 
 func init() {
 	fmt.Println("init")
@@ -4191,7 +4192,7 @@ println []
 println {}
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println([]interface {
@@ -4215,7 +4216,7 @@ type Point struct {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Point struct {
 	X int
@@ -4238,7 +4239,7 @@ println "hello"
 println("hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Print()
@@ -4257,7 +4258,7 @@ var a any = 100
 println(a)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 var a interface {
 } = 100
@@ -4284,7 +4285,7 @@ func test() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func test() {
 	fmt.Println("hello")
@@ -4299,7 +4300,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("hello")
@@ -4312,7 +4313,7 @@ func TestCommandNotExpr(t *testing.T) {
 println !true
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(false)
@@ -4323,7 +4324,7 @@ a := true
 println !a
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := true
@@ -4334,7 +4335,7 @@ func main() {
 println !func() bool { return true }()
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(!func() bool {
@@ -4388,7 +4389,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	bytes.Buffer
@@ -4405,7 +4406,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	*bytes.Buffer
@@ -4424,7 +4425,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	*bytes.Buffer `+"`spec:\"buffer\"`"+`
@@ -4492,7 +4493,7 @@ n.onKey ["a"],nil,key => {
 }
 `, `package main
 
-import foo "github.com/goplus/gop/cl/internal/overload/foo"
+import "github.com/goplus/gop/cl/internal/overload/foo"
 
 type Mesh struct {
 }
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index 1bd50aff..6ac17953 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -431,7 +431,7 @@ for _, a = range b {
 
 func TestErrInitFunc(t *testing.T) {
 	codeErrorTest(t,
-		`./bar.gop:2:1: func init must have no arguments and no return values`, `
+		`./bar.gop:2:6: func init must have no arguments and no return values`, `
 func init(v byte) {
 }
 `)
@@ -916,7 +916,7 @@ println "hello"
 }
 
 func TestErrVarInFunc(t *testing.T) {
-	codeErrorTest(t, `./bar.gop:6:10: too few arguments in call to set("box")
+	codeErrorTest(t, `./bar.gop:6:10: not enough arguments in call to set
 	have (untyped string)
 	want (name string, v int)
 ./bar.gop:7:10: undefined: a`, `
diff --git a/cl/outline/cl/compile_spx_test.go b/cl/outline/cl/compile_spx_test.go
index e730234f..a4d381b1 100644
--- a/cl/outline/cl/compile_spx_test.go
+++ b/cl/outline/cl/compile_spx_test.go
@@ -163,7 +163,7 @@ func onMsg(msg string) {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -208,8 +208,8 @@ func onInit() {
 `, ``, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 type bar struct {
@@ -249,8 +249,8 @@ func onInit() {
 `, `package main
 
 import (
-	spx "github.com/goplus/gop/cl/internal/spx"
-	math "math"
+	"github.com/goplus/gop/cl/internal/spx"
+	"math"
 )
 
 type Game struct {
@@ -300,7 +300,7 @@ func onCloned() {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -340,8 +340,8 @@ println "Hi"
 `, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 type Kai struct {
@@ -377,8 +377,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -407,8 +407,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -437,8 +437,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Dog struct {
@@ -465,7 +465,7 @@ func TestSpxMainEntry(t *testing.T) {
 `, `
 `, `package main
 
-import spx2 "github.com/goplus/gop/cl/internal/spx2"
+import "github.com/goplus/gop/cl/internal/spx2"
 
 type Game struct {
 	spx2.Game
@@ -488,7 +488,7 @@ var (
 `, `
 `, `package main
 
-import spx2 "github.com/goplus/gop/cl/internal/spx2"
+import "github.com/goplus/gop/cl/internal/spx2"
 
 type Game struct {
 	spx2.Game
@@ -522,8 +522,8 @@ func onMsg(msg string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	spx2 "github.com/goplus/gop/cl/internal/spx2"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx2"
 )
 
 type Game struct {
@@ -563,7 +563,7 @@ func onMsg(msg string) {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -619,7 +619,7 @@ func onCloned() {
 }
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 type Game struct {
 	*spx.MyGame
@@ -650,3 +650,74 @@ func (this *Kai) onCloned() {
 }
 `, "Game.tgmx", "Kai.tspx")
 }
+
+func TestSpxErrorSel(t *testing.T) {
+	gopSpxErrorTestEx(t, `./Kai.tspx:2:9: this.pos undefined (type *Kai has no field or method pos)`, `
+println "hi"
+`, `
+println this.pos
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxMethodSel(t *testing.T) {
+	gopSpxTestEx(t, `
+sendMessage "Hi"
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+}
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+	this.SendMessage("Hi")
+}
+func (this *Kai) onMsg(msg string) {
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(Game))
+}
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxPkgOverload(t *testing.T) {
+	gopSpxTestEx(t, `
+println "Hi"
+`, `
+func onMsg(msg string) {
+	this.position.add 100,200
+}
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
+)
+
+type Game struct {
+	*spx.MyGame
+}
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("Hi")
+}
+func (this *Kai) onMsg(msg string) {
+	this.Position().Add__0(100, 200)
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(Game))
+}
+`, "Game.tgmx", "Kai.tspx")
+}
diff --git a/cl/outline/cl/compile_test.go b/cl/outline/cl/compile_test.go
index 87dd7447..52fd5dc4 100644
--- a/cl/outline/cl/compile_test.go
+++ b/cl/outline/cl/compile_test.go
@@ -157,10 +157,10 @@ for line <- open("foo.txt")! {
 `, `package main
 
 import (
-	fmt "fmt"
-	os "os"
-	iox "github.com/goplus/gop/builtin/iox"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"os"
+	"github.com/goplus/gop/builtin/iox"
+	"github.com/qiniu/x/errors"
 )
 
 func main() {
@@ -194,9 +194,9 @@ for line <- os.Stdin {
 `, `package main
 
 import (
-	fmt "fmt"
-	os "os"
-	iox "github.com/goplus/gop/builtin/iox"
+	"fmt"
+	"os"
+	"github.com/goplus/gop/builtin/iox"
 )
 
 func main() {
@@ -224,9 +224,9 @@ for line <- lines(r) {
 `, `package main
 
 import (
-	fmt "fmt"
-	iox "github.com/goplus/gop/builtin/iox"
-	io "io"
+	"fmt"
+	"github.com/goplus/gop/builtin/iox"
+	"io"
 )
 
 var r io.Reader
@@ -298,7 +298,7 @@ type T struct{}
 println(&T{},&Point{10,20})
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -315,7 +315,7 @@ for i <- :10, i%3 == 0 {
 	println i
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 1 {
@@ -338,7 +338,7 @@ d := fvec([])
 println a, b, c, d
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fvec []float64
 type foo float64
@@ -542,8 +542,8 @@ println "The Go+ Language for", fields.join(", ")
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func main() {
@@ -559,7 +559,7 @@ p := println
 p "Hello world"
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	p := fmt.Println
@@ -927,8 +927,8 @@ func main() {
 }`, `package main
 
 import (
-	fmt "fmt"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"github.com/qiniu/x/errors"
 )
 
 func t() (int, int, error) {
@@ -961,7 +961,7 @@ func main() {
 	t()!
 }`, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func t() error {
 	return nil
@@ -1004,7 +1004,7 @@ func (a *A) String() string {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type AA interface {
 	String() string
@@ -1049,7 +1049,7 @@ func (a *A) String() string {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type AA interface {
 	String() string
@@ -1090,7 +1090,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	{
@@ -1159,7 +1159,7 @@ func main() {
 var i int
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 var i int
 
@@ -1202,7 +1202,7 @@ func main() {
 var i = 100
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 var i = 100
 
@@ -1222,7 +1222,7 @@ func main() {
 	println(n)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	v := []uint64{2, 3, 5}
@@ -1247,7 +1247,7 @@ func consume(xchg chan int) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func consume(xchg chan int) {
 	select {
@@ -1420,7 +1420,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Shape interface {
 	Area() float64
@@ -1465,7 +1465,7 @@ type BigInt struct {
 	*big.Int
 }`, `package main
 
-import big "math/big"
+import "math/big"
 
 type BigInt struct {
 	*big.Int
@@ -1485,10 +1485,10 @@ func foo(script string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	goptest "github.com/goplus/gop/ast/goptest"
-	errors "github.com/qiniu/x/errors"
-	gopq "github.com/goplus/gop/ast/gopq"
+	"fmt"
+	"github.com/goplus/gop/ast/goptest"
+	"github.com/qiniu/x/errors"
+	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1519,10 +1519,10 @@ func foo(script string) {
 `, `package main
 
 import (
-	fmt "fmt"
-	goptest "github.com/goplus/gop/ast/goptest"
-	errors "github.com/qiniu/x/errors"
-	gopq "github.com/goplus/gop/ast/gopq"
+	"fmt"
+	"github.com/goplus/gop/ast/goptest"
+	"github.com/qiniu/x/errors"
+	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1551,8 +1551,8 @@ func add(x, y string) (int, error) {
 `, `package main
 
 import (
-	strconv "strconv"
-	errors "github.com/qiniu/x/errors"
+	"strconv"
+	"github.com/qiniu/x/errors"
 )
 
 func add(x string, y string) (int, error) {
@@ -1592,7 +1592,7 @@ func addSafe(x, y string) int {
 }
 `, `package main
 
-import strconv "strconv"
+import "strconv"
 
 func addSafe(x string, y string) int {
 	return func() (_gop_ret int) {
@@ -1620,8 +1620,8 @@ var ret int = println("Hi")!
 `, `package main
 
 import (
-	fmt "fmt"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"github.com/qiniu/x/errors"
 )
 
 var ret int = func() (_gop_ret int) {
@@ -1645,7 +1645,7 @@ func mkdir(name string) error {
 mkdir! "foo"
 `, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func mkdir(name string) error {
 	return nil
@@ -1673,7 +1673,7 @@ func foo() (func(), error) {
 foo()!()
 `, `package main
 
-import errors "github.com/qiniu/x/errors"
+import "github.com/qiniu/x/errors"
 
 func foo() (func(), error) {
 	return nil, nil
@@ -1722,7 +1722,7 @@ var x, y uint128
 var z uint128 = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Uint128
 var z ng.Uint128 = x.Gop_Add__1(y)
@@ -1735,7 +1735,7 @@ var x, y int128
 var z int128 = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Int128
 var z ng.Int128 = x.Gop_Add__1(y)
@@ -1748,7 +1748,7 @@ var x, y bigint
 var z bigint = x + y
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Bigint
 var z ng.Bigint = x.Gop_Add(y)
@@ -1761,8 +1761,8 @@ var x = 1r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigint_Init__1(big.NewInt(1))
@@ -1774,7 +1774,7 @@ func TestUint128Lit(t *testing.T) {
 var x uint128 = 1
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Uint128 = ng.Uint128_Init__0(1)
 `)
@@ -1785,7 +1785,7 @@ func TestInt128Lit(t *testing.T) {
 var x int128 = 1
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Int128 = ng.Int128_Init__0(1)
 `)
@@ -1797,8 +1797,8 @@ var x = 1/2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(1, 2))
@@ -1811,8 +1811,8 @@ var x = 3 + 1/2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
@@ -1828,8 +1828,8 @@ var z = 100 + y
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
@@ -1942,7 +1942,7 @@ var x bigint
 x += 3
 `, `package main
 
-import ng "github.com/goplus/gop/builtin/ng"
+import "github.com/goplus/gop/builtin/ng"
 
 var x ng.Bigint
 
@@ -1959,8 +1959,8 @@ x *= 2
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 func main() {
@@ -1977,8 +1977,8 @@ x *= 2r
 `, `package main
 
 import (
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 func main() {
@@ -2251,7 +2251,7 @@ go println("Hi")
 defer println("Go+")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	go fmt.Println("Hi")
@@ -2271,7 +2271,7 @@ for {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -2308,7 +2308,7 @@ for range a {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -2346,7 +2346,7 @@ for k, v := range new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2374,7 +2374,7 @@ for v <- new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2409,7 +2409,7 @@ for k, v <- new(foo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fooIter struct {
 }
@@ -2445,7 +2445,7 @@ func (p *foo) Gop_Enum(c func(val string)) {
 println([v for v <- new(foo)])
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -2496,7 +2496,7 @@ for k, v <- newFoo() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type fooIter struct {
 	data *foo
@@ -2545,7 +2545,7 @@ for i, x <- [1, 3, 5, 7, 11, 13, 17] {
 println("sum(5,7,11,13,17):", sum)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	sum := 0
@@ -2716,7 +2716,7 @@ x := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	arr := []float64{1, 2, 3, 4.1, 5, 6}
@@ -2822,8 +2822,8 @@ println("x:", x)
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func main() {
@@ -2846,7 +2846,7 @@ func bar(p pfoo) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 	req int
@@ -2875,7 +2875,7 @@ a.B = "Hi"
 flag.Usage = nil
 `, `package main
 
-import flag "flag"
+import "flag"
 
 func main() {
 	a := &struct {
@@ -2935,7 +2935,7 @@ func (M) Bar() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type M int
 
@@ -2982,7 +2982,7 @@ var d = -a       // TODO: -a have no return value!
 var e = a!=b
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type foo struct {
 }
@@ -3016,7 +3016,7 @@ func (a foo) Gop_NE(b foo) bool {
 func TestCmdlineNoEOL(t *testing.T) {
 	gopClTest(t, `println "Hi"`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hi")
@@ -3031,7 +3031,7 @@ func main() {
 	fmt.println "Hi"
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hi")
@@ -3045,7 +3045,7 @@ func TestDotImport(t *testing.T) {
 var a = round(1.2)
 `, `package main
 
-import math "math"
+import "math"
 
 var a = math.Round(1.2)
 `)
@@ -3057,7 +3057,7 @@ func TestLocalImport(t *testing.T) {
 var a = spx.TestIntValue
 `, `package main
 
-import spx "github.com/goplus/gop/cl/internal/spx"
+import "github.com/goplus/gop/cl/internal/spx"
 
 var a = spx.TestIntValue
 `)
@@ -3092,7 +3092,7 @@ func TestAnonymousImport(t *testing.T) {
 printf("Hello Go+\n")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hello")
@@ -3131,7 +3131,7 @@ func main() {
 	fmt.Println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	const (
@@ -3156,7 +3156,7 @@ if t := false; t {
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	x := 0
@@ -3196,7 +3196,7 @@ default:
 println("x:", x)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	x := 0
@@ -3237,7 +3237,7 @@ default:
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	v := "Hello"
@@ -3269,7 +3269,7 @@ label:
 	}
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := []float64{1, 3.4, 5}
@@ -3296,7 +3296,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func foo(format string, args ...interface {
 }) (int, error) {
@@ -3334,7 +3334,7 @@ func(v string) {
 }("Hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	func(v string) {
@@ -3475,7 +3475,7 @@ Do => {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func Do(func()) {
 }
@@ -3644,7 +3644,7 @@ func bar(foo func(string, ...interface{}) (int, error)) {
 bar(fmt.Printf)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func bar(foo func(string, ...interface {
 }) (int, error)) {
@@ -3681,8 +3681,8 @@ fmt.Println(printf("Hello, %v\n", "Go+"))
 `, `package main
 
 import (
-	fmt "fmt"
-	strings "strings"
+	"fmt"
+	"strings"
 )
 
 func foo(x string) string {
@@ -3710,7 +3710,7 @@ func TestFuncCall(t *testing.T) {
 
 fmt.Println("Hello")`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Hello")
@@ -3728,7 +3728,7 @@ func foo(args ...interface{}) {
 func main() {
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func foo(args ...interface {
 }) {
@@ -3751,7 +3751,7 @@ func foo(args ...interface{}) {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	foo("Hello", 123)
@@ -3787,7 +3787,7 @@ func TestAssignUnderscore(t *testing.T) {
 _, err := log.Println("Hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	_, err := fmt.Println("Hello")
@@ -3838,8 +3838,8 @@ println(rmap)
 `, `package main
 
 import (
-	fmt "fmt"
-	foo "github.com/goplus/gop/cl/internal/gop-in-go/foo"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/gop-in-go/foo"
 )
 
 func main() {
@@ -3884,8 +3884,8 @@ func New() Result {
 `, `package main
 
 import (
-	reflect "reflect"
-	testing "testing"
+	"reflect"
+	"testing"
 )
 
 type Repo struct {
@@ -3929,7 +3929,7 @@ for i $ :10 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 1 {
@@ -3942,7 +3942,7 @@ for i $ 1:10:3 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 3 {
@@ -3958,7 +3958,7 @@ for i $ 1:10:2 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 2 {
@@ -3974,7 +3974,7 @@ for i $ 1:10 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 1; i < 10; i += 1 {
@@ -3990,7 +3990,7 @@ for i $ :10:2 {
 	println(i)
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for i := 0; i < 10; i += 2 {
@@ -4006,7 +4006,7 @@ for range :10 {
 	println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
@@ -4022,7 +4022,7 @@ for _ <- :10 {
 	println("Hi")
 }`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
@@ -4038,8 +4038,8 @@ println [x for x <- 0:3:1]
 `, `package main
 
 import (
-	fmt "fmt"
-	builtin "github.com/goplus/gop/builtin"
+	"fmt"
+	"github.com/goplus/gop/builtin"
 )
 
 func main() {
@@ -4085,7 +4085,7 @@ func TestRangeExpr8(t *testing.T) {
 	}
 	`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -4129,7 +4129,7 @@ func TestRangeExpr9(t *testing.T) {
 	}
 	`, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type T struct {
 }
@@ -4185,7 +4185,7 @@ func TestNoEntrypoint(t *testing.T) {
 	gopClTest(t, `println("init")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("init")
@@ -4195,7 +4195,7 @@ func main() {
 println("init")
 `, `package bar
 
-import fmt "fmt"
+import "fmt"
 
 func init() {
 	fmt.Println("init")
@@ -4219,7 +4219,7 @@ println []
 println {}
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println([]interface {
@@ -4243,7 +4243,7 @@ type Point struct {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Point struct {
 	X int
@@ -4266,7 +4266,7 @@ println "hello"
 println("hello")
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Print()
@@ -4285,7 +4285,7 @@ var a any = 100
 println(a)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 var a interface {
 } = 100
@@ -4312,7 +4312,7 @@ func test() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func test() {
 	fmt.Println("hello")
@@ -4327,7 +4327,7 @@ func main() {
 }
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("hello")
@@ -4340,7 +4340,7 @@ func TestCommandNotExpr(t *testing.T) {
 println !true
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(false)
@@ -4351,7 +4351,7 @@ a := true
 println !a
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	a := true
@@ -4362,7 +4362,7 @@ func main() {
 println !func() bool { return true }()
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println(!func() bool {
@@ -4416,7 +4416,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	bytes.Buffer
@@ -4433,7 +4433,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	*bytes.Buffer
@@ -4452,7 +4452,7 @@ var (
 func test(){}
 `, `package main
 
-import bytes "bytes"
+import "bytes"
 
 type Rect struct {
 	*bytes.Buffer `+"`spec:\"buffer\"`"+`
@@ -4520,7 +4520,7 @@ n.onKey ["a"],nil,key => {
 }
 `, `package main
 
-import foo "github.com/goplus/gop/cl/internal/overload/foo"
+import "github.com/goplus/gop/cl/internal/overload/foo"
 
 type Mesh struct {
 }
diff --git a/cl/outline/cl/error_msg_test.go b/cl/outline/cl/error_msg_test.go
index 3a5f43ee..29919464 100644
--- a/cl/outline/cl/error_msg_test.go
+++ b/cl/outline/cl/error_msg_test.go
@@ -431,7 +431,7 @@ for _, a = range b {
 
 func TestErrInitFunc(t *testing.T) {
 	codeErrorTest(t,
-		`./bar.gop:2:1: func init must have no arguments and no return values`, `
+		`./bar.gop:2:6: func init must have no arguments and no return values`, `
 func init(v byte) {
 }
 `)
@@ -916,7 +916,7 @@ println "hello"
 }
 
 func TestErrVarInFunc(t *testing.T) {
-	codeErrorTest(t, `./bar.gop:6:10: too few arguments in call to set("box")
+	codeErrorTest(t, `./bar.gop:6:10: not enough arguments in call to set
 	have (untyped string)
 	want (name string, v int)
 ./bar.gop:7:10: undefined: a`, `
diff --git a/cl/outline/cl/typeparams_test.go b/cl/outline/cl/typeparams_test.go
index bb0a47f0..644e084f 100644
--- a/cl/outline/cl/typeparams_test.go
+++ b/cl/outline/cl/typeparams_test.go
@@ -161,7 +161,7 @@ v3.Append([1,2,3,4]...)
 v3.Append2([1,2,3,4]...)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type DataString = Data[string]
 type SliceString = Slice[[]string, string]
diff --git a/cl/recorder_test.go b/cl/recorder_test.go
new file mode 100644
index 00000000..1e85a573
--- /dev/null
+++ b/cl/recorder_test.go
@@ -0,0 +1,107 @@
+package cl_test
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+)
+
+type gopRecorder struct {
+}
+
+// Type maps expressions to their types, and for constant
+// expressions, also their values. Invalid expressions are
+// omitted.
+//
+// For (possibly parenthesized) identifiers denoting built-in
+// functions, the recorded signatures are call-site specific:
+// if the call result is not a constant, the recorded type is
+// an argument-specific signature. Otherwise, the recorded type
+// is invalid.
+//
+// The Types map does not record the type of every identifier,
+// only those that appear where an arbitrary expression is
+// permitted. For instance, the identifier f in a selector
+// expression x.f is found only in the Selections map, the
+// identifier z in a variable declaration 'var z int' is found
+// 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) {
+}
+
+// Instantiate maps identifiers denoting generic types or functions to their
+// type arguments and instantiated type.
+//
+// For example, Instantiate will map the identifier for 'T' in the type
+// instantiation T[int, string] to the type arguments [int, string] and
+// resulting instantiated *Named type. Given a generic function
+// func F[A any](A), Instances will map the identifier for 'F' in the call
+// expression F(int(1)) to the inferred type arguments [int], and resulting
+// instantiated *Signature.
+//
+// Invariant: Instantiating Uses[id].Type() with Instances[id].TypeArgs
+// results in an equivalent of Instances[id].Type.
+func (info gopRecorder) Instantiate(id *ast.Ident, inst types.Instance) {
+}
+
+// Def maps identifiers to the objects they define (including
+// package names, dots "." of dot-imports, and blank "_" identifiers).
+// For identifiers that do not denote objects (e.g., the package name
+// in package clauses, or symbolic variables t in t := x.(type) of
+// type switch headers), the corresponding objects are nil.
+//
+// For an embedded field, Def maps the field *Var it defines.
+//
+// Invariant: Defs[id] == nil || Defs[id].Pos() == id.Pos()
+func (info gopRecorder) Def(id *ast.Ident, obj types.Object) {
+}
+
+// Use maps identifiers to the objects they denote.
+//
+// For an embedded field, Use maps the *TypeName it denotes.
+//
+// Invariant: Uses[id].Pos() != id.Pos()
+func (info gopRecorder) Use(id *ast.Ident, obj types.Object) {
+}
+
+// Implicit maps nodes to their implicitly declared objects, if any.
+// The following node and object types may appear:
+//
+//	node               declared object
+//
+//	*ast.ImportSpec    *PkgName for imports without renames
+//	*ast.CaseClause    type-specific *Var for each type switch case clause (incl. default)
+//	*ast.Field         anonymous parameter *Var (incl. unnamed results)
+func (info gopRecorder) Implicit(node ast.Node, obj types.Object) {
+}
+
+// Select maps selector expressions (excluding qualified identifiers)
+// to their corresponding selections.
+func (info gopRecorder) Select(e *ast.SelectorExpr, sel *types.Selection) {
+}
+
+// Scope maps ast.Nodes to the scopes they define. Package scopes are not
+// associated with a specific node but with all files belonging to a package.
+// Thus, the package scope can be found in the type-checked Package object.
+// Scopes nest, with the Universe scope being the outermost scope, enclosing
+// the package scope, which contains (one or more) files scopes, which enclose
+// function scopes which in turn enclose statement and function literal scopes.
+// Note that even though package-level functions are declared in the package
+// scope, the function scopes are embedded in the file scope of the file
+// containing the function declaration.
+//
+// The following node types may appear in Scopes:
+//
+//	*ast.File
+//	*ast.FuncType
+//	*ast.TypeSpec
+//	*ast.BlockStmt
+//	*ast.IfStmt
+//	*ast.SwitchStmt
+//	*ast.TypeSwitchStmt
+//	*ast.CaseClause
+//	*ast.CommClause
+//	*ast.ForStmt
+//	*ast.RangeStmt
+func (info gopRecorder) Scope(n ast.Node, scope *types.Scope) {
+}
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index a35f9369..a3b71457 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -166,7 +166,7 @@ v3.Append([1,2,3,4]...)
 v3.Append2([1,2,3,4]...)
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type DataString = Data[string]
 type SliceString = Slice[[]string, string]
@@ -591,3 +591,38 @@ func test(v1 int, v2 *Data) {
 }
 `)
 }
+
+func TestGenericTypeCompositeLit(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+type A[T any] struct {
+	m T
+}
+
+type B[T any] struct {
+	n A[T]
+}
+
+`, `
+var a [2]int
+if 0 == a[1] {
+	println "world"
+}
+println B[int]{}.n
+if 0 < (B[int]{}).n.m {
+}
+`, `package main
+
+import "fmt"
+
+var a [2]int
+
+func main() {
+	if 0 == a[1] {
+		fmt.Println("world")
+	}
+	fmt.Println(B[int]{}.n)
+	if 0 < (B[int]{}).n.m {
+	}
+}
+`)
+}
diff --git a/env/path_test.go b/env/path_test.go
index a38dc654..834235b3 100644
--- a/env/path_test.go
+++ b/env/path_test.go
@@ -17,7 +17,6 @@
 package env
 
 import (
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"testing"
@@ -39,12 +38,13 @@ func findGoModFile(dir string) (modfile string, noCacheFile bool, err error) {
 
 // Common testing directory structure:
 // testing_root/
-//   src/
-//	   subdir/
-//   valid_goproot/
-//	   go.mod
-//	   go.sum
-//     cmd/gop/
+//
+//	  src/
+//		   subdir/
+//	  valid_goproot/
+//		   go.mod
+//		   go.sum
+//	    cmd/gop/
 func makeTestDir(t *testing.T) (root string, src string, gopRoot string) {
 	root, _ = filepath.EvalSymlinks(t.TempDir())
 	src = filepath.Join(root, "src")
@@ -57,12 +57,12 @@ func makeTestDir(t *testing.T) (root string, src string, gopRoot string) {
 func makeValidGopRoot(root string) {
 	os.Mkdir(root, 0755)
 	os.MkdirAll(filepath.Join(root, "cmd/gop"), 0755)
-	ioutil.WriteFile(filepath.Join(root, "go.mod"), []byte(""), 0644)
-	ioutil.WriteFile(filepath.Join(root, "go.sum"), []byte(""), 0644)
+	os.WriteFile(filepath.Join(root, "go.mod"), []byte(""), 0644)
+	os.WriteFile(filepath.Join(root, "go.sum"), []byte(""), 0644)
 }
 
 func writeDummyFile(path string) {
-	ioutil.WriteFile(path, []byte(""), 0644)
+	os.WriteFile(path, []byte(""), 0644)
 }
 
 func cleanup() {
diff --git a/env/version_test.go b/env/version_test.go
index 5b51422d..7beb6b9f 100644
--- a/env/version_test.go
+++ b/env/version_test.go
@@ -16,21 +16,53 @@
 
 package env
 
-import "testing"
+import (
+	"os"
+	"path/filepath"
+	"testing"
+)
 
-func TestVersion(t *testing.T) {
-	if Version() != "v"+MainVersion+".x" {
-		t.Fatal("TestVersion failed:", Version())
+func TestPanic(t *testing.T) {
+	t.Run("initEnvPanic", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("initEnvPanic: no panic?")
+			}
+		}()
+		buildVersion = "v1.2"
+		initEnv()
+	})
+	t.Run("GOPROOT panic", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("GOPROOT: no panic?")
+			}
+		}()
+		defaultGopRoot = ""
+		os.Setenv(envGOPROOT, "")
+		GOPROOT()
+	})
+}
+
+func TestEnv(t *testing.T) {
+	gopEnv = func() (string, error) {
+		wd, _ := os.Getwd()
+		root := filepath.Dir(wd)
+		return "v1.0.0-beta1\n2023-10-18_17-45-50\n" + root + "\n", nil
+	}
+	buildVersion = ""
+	initEnv()
+	if !Installed() {
+		t.Fatal("not Installed")
 	}
-	buildVersion = "v1.0.0-beta1"
-	if Version() != buildVersion {
+	if Version() != "v1.0.0-beta1" {
 		t.Fatal("TestVersion failed:", Version())
 	}
 	buildVersion = ""
-}
-
-func TestBuild(t *testing.T) {
-	if BuildDate() != "" {
+	if Version() != "v"+MainVersion+".x" {
+		t.Fatal("TestVersion failed:", Version())
+	}
+	if BuildDate() != "2023-10-18_17-45-50" {
 		t.Fatal("BuildInfo failed:", BuildDate())
 	}
 }
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) {
diff --git a/x/build/_testdata/hello/hello.expect b/x/build/_testdata/hello/hello.expect
index e62ecea4..13b98bf6 100644
--- a/x/build/_testdata/hello/hello.expect
+++ b/x/build/_testdata/hello/hello.expect
@@ -1,6 +1,6 @@
 package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Go+")
diff --git a/x/build/_testdata/multi/multi.expect b/x/build/_testdata/multi/multi.expect
index 9eb31ec2..e2243eb6 100644
--- a/x/build/_testdata/multi/multi.expect
+++ b/x/build/_testdata/multi/multi.expect
@@ -1,6 +1,6 @@
 package main
 
-import fmt "fmt"
+import "fmt"
 
 type BaseClass struct {
 	x int
diff --git a/x/build/_testdata/pkg/pkg.expect b/x/build/_testdata/pkg/pkg.expect
index e71e8cb8..54ec8242 100644
--- a/x/build/_testdata/pkg/pkg.expect
+++ b/x/build/_testdata/pkg/pkg.expect
@@ -1,6 +1,6 @@
 package pkg
 
-import fmt "fmt"
+import "fmt"
 
 func Hello() {
 	fmt.Println("Go+")
diff --git a/x/build/build_test.go b/x/build/build_test.go
index 72e46f14..cb867bd2 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -84,7 +84,7 @@ println "Go+"
 `
 	var expect = `package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Go+")
@@ -108,7 +108,7 @@ func TestGox(t *testing.T) {
 println "Go+"
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Rect struct {
 }
@@ -130,7 +130,7 @@ type Buffer struct {
 println "Go+"
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Buffer struct {
 	buf []byte
@@ -157,7 +157,7 @@ type Buffer struct {
 println "Go+"
 `, `package main
 
-import fmt "fmt"
+import "fmt"
 
 type Buffer struct {
 	buf []byte
@@ -183,8 +183,8 @@ println "Go+"
 `, `package main
 
 import (
-	fmt "fmt"
-	bytes "bytes"
+	"fmt"
+	"bytes"
 )
 
 type Rect struct {
@@ -208,8 +208,8 @@ println "Go+"
 `, `package main
 
 import (
-	fmt "fmt"
-	bytes "bytes"
+	"fmt"
+	"bytes"
 )
 
 type Rect struct {
@@ -232,9 +232,9 @@ println a+1/2r
 `, `package main
 
 import (
-	fmt "fmt"
-	ng "github.com/goplus/gop/builtin/ng"
-	big "math/big"
+	"fmt"
+	"github.com/goplus/gop/builtin/ng"
+	"math/big"
 )
 
 func main() {
@@ -256,9 +256,9 @@ for line <- lines(r) {
 `, `package main
 
 import (
-	fmt "fmt"
-	iox "github.com/goplus/gop/builtin/iox"
-	io "io"
+	"fmt"
+	"github.com/goplus/gop/builtin/iox"
+	"io"
 )
 
 var r io.Reader
@@ -299,9 +299,9 @@ println addSafe("10", "abc")
 `, `package main
 
 import (
-	fmt "fmt"
-	strconv "strconv"
-	errors "github.com/qiniu/x/errors"
+	"fmt"
+	"strconv"
+	"github.com/qiniu/x/errors"
 )
 
 func add(x string, y string) (int, error) {
@@ -367,8 +367,8 @@ func TestSpx(t *testing.T) {
 	gopClTestEx(t, "main.tspx", `println "hi"`, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 type MyGame struct {
@@ -385,8 +385,8 @@ func main() {
 	gopClTestEx(t, "Cat.tspx", `println "hi"`, `package main
 
 import (
-	fmt "fmt"
-	spx "github.com/goplus/gop/cl/internal/spx"
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx"
 )
 
 type Cat struct {
@@ -441,7 +441,7 @@ func TestFromTestdata(t *testing.T) {
 func TestFS(t *testing.T) {
 	var expect = []byte(`package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Go+")
@@ -459,7 +459,7 @@ func main() {
 func TestAst(t *testing.T) {
 	var expect = []byte(`package main
 
-import fmt "fmt"
+import "fmt"
 
 func main() {
 	fmt.Println("Go+")
@@ -515,7 +515,7 @@ func (i *emptyImporter) Import(path string) (*types.Package, error) {
 
 func TestContext(t *testing.T) {
 	ctx := build.NewContext(&emptyImporter{}, nil)
-	_, err := ctx.BuildFile("main.gop", `println "Go+"`)
+	_, err := ctx.BuildFile("main.gop", `import "fmt"; fmt.Println "Go+"`)
 	if err == nil {
 		t.Fatal("BuildFile: no error?")
 	}