Repository: goplus/gop
Pull request #1448 (2023-10-05 14:58:08)

outline/cl


Patch:
diff --git a/cl/compile.go b/cl/compile.go
index 81f2ac56..edbf9b48 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -753,6 +753,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 						log.Println("==> Preload type", name)
 					}
 					ld := getTypeLoader(parent, syms, t.Name.Pos(), name)
+					defs := ctx.pkg.NewTypeDefs()
 					if gopFile {
 						ld.typ = func() {
 							old, _ := p.SetCurFile(goFile, true)
@@ -767,11 +768,11 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 							if debugLoad {
 								log.Println("==> Load > NewType", name)
 							}
-							decl := ctx.pkg.NewType(name)
+							decl := defs.NewType(name)
 							if t.Doc != nil {
-								decl.SetComments(t.Doc)
+								defs.SetComments(t.Doc)
 							} else if d.Doc != nil {
-								decl.SetComments(d.Doc)
+								defs.SetComments(d.Doc)
 							}
 							ld.typInit = func() { // decycle
 								if debugLoad {
@@ -1011,14 +1012,14 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 	ctx.imports[name] = pkg
 }
 
-func loadConstSpecs(ctx *blockCtx, cdecl *gox.ConstDecl, specs []ast.Spec) {
+func loadConstSpecs(ctx *blockCtx, cdecl *gox.ConstDefs, specs []ast.Spec) {
 	for iotav, spec := range specs {
 		vSpec := spec.(*ast.ValueSpec)
 		loadConsts(ctx, cdecl, vSpec, iotav)
 	}
 }
 
-func loadConsts(ctx *blockCtx, cdecl *gox.ConstDecl, v *ast.ValueSpec, iotav int) {
+func loadConsts(ctx *blockCtx, cdecl *gox.ConstDefs, v *ast.ValueSpec, iotav int) {
 	names := makeNames(v.Names)
 	if v.Values == nil {
 		if debugLoad {
diff --git a/cl/outline/cl/_walk/walk.go b/cl/outline/cl/_walk/walk.go
new file mode 100644
index 00000000..3357dd90
--- /dev/null
+++ b/cl/outline/cl/_walk/walk.go
@@ -0,0 +1,193 @@
+package walk
+
+import (
+	"go/types"
+	"reflect"
+	"strconv"
+
+	"github.com/goplus/gop/ast"
+)
+
+// -----------------------------------------------------------------------------
+
+type Visitor interface {
+}
+
+// -----------------------------------------------------------------------------
+
+type Context struct {
+	pkg *types.Package
+	ctx *types.Scope
+	vi  Visitor
+}
+
+func New(pkg *types.Package, visitor Visitor) *Context {
+	return &Context{pkg: pkg, vi: visitor}
+}
+
+func (p *Context) File(f *ast.File) error {
+	p.ctx = p.pkg.Scope()
+	for _, decl := range f.Decls {
+		if e := p.decl(f, decl); e != nil {
+			return e
+		}
+	}
+	return nil
+}
+
+func (p *Context) decl(f *ast.File, decl ast.Decl) error {
+	switch d := decl.(type) {
+	case *ast.FuncDecl:
+		return p.funcDecl(f, d)
+	case *ast.GenDecl:
+		return p.genDecl(d)
+	default:
+		panic("Unknown ast.decl: " + reflect.TypeOf(decl).String())
+	}
+}
+
+func (p *Context) genDecl(f *ast.GenDecl) error {
+	return nil
+}
+
+func (p *Context) funcDecl(f *ast.File, d *ast.FuncDecl) error {
+	body := p.block(d.Body.List)
+	if f.ShadowEntry == d {
+		return body
+	}
+	flds := Block{
+		"NAME": d.Name.Name,
+	}
+	p.funcType(flds, d.Type)
+	return Block{
+		"type":   "goplus_func",
+		"fields": flds,
+		"inputs": Block{
+			"BODY": Block{
+				"block": body,
+			},
+		},
+	}
+}
+
+func (p *Context) funcType(ret Block, fnt *ast.FuncType) {
+	p.fieldList(ret, "ARG", fnt.Params)
+	p.fieldList(ret, "RET", fnt.Results)
+	// _, variadic := params[n-1].Type.(*ast.Ellipsis)
+}
+
+func (p *Context) fieldList(ret Block, namePrefix string, flds *ast.FieldList) {
+	if flds == nil {
+		return
+	}
+	i := 0
+	for _, param := range flds.List {
+		last := len(param.Names) - 1
+		for j, name := range param.Names {
+			idx := strconv.Itoa(i)
+			i++
+			arg := namePrefix + idx
+			argt := arg + "_TYPE"
+			typ := ""
+			if j == last {
+				typ = p.toType(param.Type)
+			}
+			ret[arg] = name.Name
+			ret[argt] = typ
+		}
+	}
+}
+
+// -----------------------------------------------------------------------------
+
+func (p *Context) block(stmts []ast.Stmt) (ret Block) {
+	var prev Block
+	for _, stmt := range stmts {
+		b := p.stmt(stmt)
+		if prev != nil {
+			prev.setNext(b)
+		} else {
+			ret = b
+		}
+		prev = b
+	}
+	return
+}
+
+func (p *Context) stmt(stmt ast.Stmt) (ret Block) {
+	switch v := stmt.(type) {
+	case *ast.IfStmt:
+		return p.ifStmt(v)
+	case *ast.BlockStmt:
+		return p.block(v.List)
+	}
+	return Block{
+		"type": "goplus_stmt",
+		"inputs": Block{
+			"CODE": p.toCode(stmt),
+		},
+	}
+}
+
+func (p *Context) ifStmt(v *ast.IfStmt) (ret Block) {
+	cond := p.expr(v.Cond)
+	body := p.block(v.Body.List)
+	inputs := Block{
+		"COND": cond,
+		"BODY": body,
+	}
+	if v.Init != nil {
+		inputs["INIT"] = p.stmt(v.Init)
+	}
+	if v.Else != nil {
+		inputs["ELSE"] = p.stmt(v.Else)
+	}
+	return Block{
+		"type":   "goplus_if",
+		"inputs": inputs,
+	}
+}
+
+// -----------------------------------------------------------------------------
+
+func (p *Context) expr(expr ast.Expr) (ret Block) {
+	switch v := expr.(type) {
+	case *ast.BinaryExpr:
+		return p.binaryExpr(v)
+	case *ast.UnaryExpr:
+		return p.unaryExpr(v)
+	}
+	return Block{
+		"type": "goplus_expr",
+		"inputs": Block{
+			"CODE": p.toCode(expr),
+		},
+	}
+}
+
+func (p *Context) unaryExpr(v *ast.UnaryExpr) (ret Block) {
+	return
+}
+
+func (p *Context) binaryExpr(v *ast.BinaryExpr) (ret Block) {
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+func (p *Context) toType(t ast.Expr) string {
+	return p.str(t)
+}
+
+func (p *Context) toCode(v ast.Node) (ret Block) {
+	return Block{
+		"block": Block{
+			"type": "text",
+			"fields": Block{
+				"TEXT": p.str(v),
+			},
+		},
+	}
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/builtin.go b/cl/outline/cl/builtin.go
new file mode 100644
index 00000000..a97ecf34
--- /dev/null
+++ b/cl/outline/cl/builtin.go
@@ -0,0 +1,82 @@
+/*
+ * 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/token"
+	"go/types"
+
+	"github.com/goplus/gox"
+)
+
+// -----------------------------------------------------------------------------
+
+func initMathBig(pkg *gox.Package, conf *gox.Config, big *gox.PkgRef) {
+	big.EnsureImported()
+	conf.UntypedBigInt = big.Ref("UntypedBigint").Type().(*types.Named)
+	conf.UntypedBigRat = big.Ref("UntypedBigrat").Type().(*types.Named)
+	conf.UntypedBigFloat = big.Ref("UntypedBigfloat").Type().(*types.Named)
+}
+
+func initBuiltinFns(builtin *types.Package, scope *types.Scope, pkg *gox.PkgRef, fns []string) {
+	for _, fn := range fns {
+		fnTitle := string(fn[0]-'a'+'A') + fn[1:]
+		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, fn, pkg.Ref(fnTitle)))
+	}
+}
+
+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()))
+	}
+	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)
+	initBuiltin(pkg, builtin, os, fmt, ng, iox, buil)
+	gox.InitBuiltin(pkg, builtin, conf)
+	return builtin
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/c.go b/cl/outline/cl/c.go
new file mode 100644
index 00000000..39b4c989
--- /dev/null
+++ b/cl/outline/cl/c.go
@@ -0,0 +1,72 @@
+/*
+ * 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 cl
+
+import (
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gox"
+)
+
+const (
+	c2goInvalid = iota
+	c2goStandard
+	c2goUserDef
+)
+
+func checkC2go(pkgPath string) (realPkgPath string, kind int) {
+	if strings.HasPrefix(pkgPath, "C") {
+		if len(pkgPath) == 1 {
+			return "libc", c2goStandard
+		}
+		if pkgPath[1] == '/' {
+			realPkgPath = pkgPath[2:]
+			if strings.IndexByte(realPkgPath, '/') < 0 {
+				kind = c2goStandard
+			} else {
+				kind = c2goUserDef
+			}
+		}
+	}
+	return
+}
+
+func c2goBase(base string) string {
+	if base == "" {
+		base = "github.com/goplus/"
+	} else if !strings.HasSuffix(base, "/") {
+		base += "/"
+	}
+	return base
+}
+
+// -----------------------------------------------------------------------------
+
+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,
+			"%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath)
+		return nil
+	}
+	ctx.clookups = append(ctx.clookups, cpkg)
+	return cpkg.Pkg()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/classfile.go b/cl/outline/cl/classfile.go
new file mode 100644
index 00000000..da47da77
--- /dev/null
+++ b/cl/outline/cl/classfile.go
@@ -0,0 +1,193 @@
+/*
+ * 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 (
+	goast "go/ast"
+	"go/constant"
+	"go/types"
+	"path/filepath"
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+)
+
+// -----------------------------------------------------------------------------
+
+type gmxSettings struct {
+	gameClass  string
+	game       gox.Ref
+	sprite     map[string]gox.Ref
+	scheds     []string
+	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
+	pkgImps    []*gox.PkgRef
+	pkgPaths   []string
+	hasScheds  bool
+	gameIsPtr  bool
+}
+
+func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
+	if !p.hasScheds {
+		return nil
+	}
+	if p.schedStmts == nil {
+		p.schedStmts = make([]goast.Stmt, 2)
+		for i, v := range p.scheds {
+			fn := cb.Val(spxLookup(p.pkgImps, v)).Call(0).InternalStack().Pop().Val
+			p.schedStmts[i] = &goast.ExprStmt{X: fn}
+		}
+		if len(p.scheds) < 2 {
+			p.schedStmts[1] = p.schedStmts[0]
+		}
+	}
+	return p.schedStmts
+}
+
+func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings {
+	ext := parser.ClassFileExt(file)
+	gt, ok := conf.LookupClass(ext)
+	if !ok {
+		panic("TODO: class not found")
+	}
+	var name string
+	if f.IsProj {
+		_, name = filepath.Split(file)
+		if idx := strings.Index(name, "."); idx > 0 {
+			name = name[:idx]
+			if name == "main" {
+				name = gt.Class
+			}
+		}
+	}
+	pkgPaths := gt.PkgPaths
+	p := &gmxSettings{gameClass: name, pkgPaths: pkgPaths}
+	p.pkgImps = make([]*gox.PkgRef, len(pkgPaths))
+	for i, pkgPath := range pkgPaths {
+		p.pkgImps[i] = pkg.Import(pkgPath)
+	}
+	spx := p.pkgImps[0]
+	if gt.Class != "" {
+		p.game, p.gameIsPtr = spxRef(spx, gt.Class)
+	}
+	p.sprite = make(map[string]types.Object)
+	for _, v := range gt.Works {
+		obj, _ := spxRef(spx, v.Class)
+		p.sprite[v.Ext] = obj
+	}
+	if x := getStringConst(spx, "Gop_sched"); x != "" {
+		p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
+	}
+	return p
+}
+
+func spxLookup(pkgImps []*gox.PkgRef, name string) gox.Ref {
+	for _, pkg := range pkgImps {
+		if o := pkg.TryRef(name); o != nil {
+			return o
+		}
+	}
+	panic("spxLookup: symbol not found - " + name)
+}
+
+func getDefaultClass(file string) string {
+	_, name := filepath.Split(file)
+	if idx := strings.Index(name, "."); idx > 0 {
+		name = name[:idx]
+	}
+	return name
+}
+
+func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) {
+	if strings.HasPrefix(typ, "*") {
+		typ, isPtr = typ[1:], true
+	}
+	obj = spx.Ref(typ)
+	return
+}
+
+func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) {
+	obj, isPtr = spxTryRef(spx, typ)
+	if obj == nil {
+		panic(spx.Path() + "." + typ + " not found")
+	}
+	return
+}
+
+func getStringConst(spx *gox.PkgRef, name string) string {
+	if o := spx.TryRef(name); o != nil {
+		if c, ok := o.(*types.Const); ok {
+			return constant.StringVal(c.Val())
+		}
+	}
+	return ""
+}
+
+func getFields(f *ast.File) (specs []ast.Spec) {
+	for _, decl := range f.Decls {
+		if g, ok := decl.(*ast.GenDecl); ok {
+			if g.Tok == token.VAR {
+				specs, g.Specs = g.Specs, nil
+				return
+			}
+			continue
+		}
+		break
+	}
+	return
+}
+
+func setBodyHandler(ctx *blockCtx) {
+	if ctx.isClass { // in a Go+ class file
+		if scheds := ctx.getScheds(ctx.cb); scheds != nil {
+			ctx.cb.SetBodyHandler(func(body *goast.BlockStmt, kind int) {
+				idx := 0
+				if len(body.List) == 0 {
+					idx = 1
+				}
+				gox.InsertStmtFront(body, scheds[idx])
+			})
+		}
+	}
+}
+
+func gmxMainFunc(p *gox.Package, ctx *pkgCtx) {
+	if o := p.Types.Scope().Lookup(ctx.gameClass); o != nil && hasMethod(o, "MainEntry") {
+		// new(Game).Main()
+		p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).
+			Val(p.Builtin().Ref("new")).Val(o).Call(1).
+			MemberVal("Main").Call(0).EndStmt().
+			End()
+	}
+}
+
+func hasMethod(o types.Object, name string) bool {
+	if obj, ok := o.(*types.TypeName); ok {
+		if t, ok := obj.Type().(*types.Named); ok {
+			for i, n := 0, t.NumMethods(); i < n; i++ {
+				if t.Method(i).Name() == name {
+					return true
+				}
+			}
+		}
+	}
+	return false
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/compile.go b/cl/outline/cl/compile.go
new file mode 100644
index 00000000..fe5e3634
--- /dev/null
+++ b/cl/outline/cl/compile.go
@@ -0,0 +1,969 @@
+/*
+ * 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 cl
+
+import (
+	"fmt"
+	"go/types"
+	"log"
+	"os"
+	"reflect"
+	"sort"
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/ast/fromgo"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
+	"github.com/goplus/mod/modfile"
+	"github.com/qiniu/x/errors"
+)
+
+type dbgFlags int
+
+const (
+	DbgFlagLoad dbgFlags = 1 << iota
+	DbgFlagLookup
+	DbgFlagAll = DbgFlagLoad | DbgFlagLookup
+)
+
+var (
+	enableRecover = true
+)
+
+var (
+	debugLoad   bool
+	debugLookup bool
+)
+
+func SetDisableRecover(disableRecover bool) {
+	enableRecover = !disableRecover
+}
+
+func SetDebug(flags dbgFlags) {
+	debugLoad = (flags & DbgFlagLoad) != 0
+	debugLookup = (flags & DbgFlagLookup) != 0
+}
+
+// -----------------------------------------------------------------------------
+
+type Project = modfile.Project
+
+// Config of loading Go+ packages.
+type Config struct {
+	// Fset provides source position information for syntax trees and types.
+	// 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 string
+
+	// TargetDir is the directory in which to generate Go files.
+	TargetDir string
+
+	// C2goBase specifies base of standard c2go packages.
+	// Default is github.com/goplus/.
+	C2goBase string
+
+	// LookupPub lookups the c2go package pubfile (named c2go.a.pub).
+	LookupPub func(pkgPath string) (pubfile string, err error)
+
+	// LookupClass lookups a class by specified file extension.
+	LookupClass func(ext string) (c *Project, ok bool)
+
+	// An Importer resolves import paths to Packages.
+	Importer types.Importer
+
+	// NoFileLine = true means not to generate file line comments.
+	NoFileLine bool
+
+	// RelativePath = true means to generate file line comments with relative file path.
+	RelativePath bool
+
+	// NoAutoGenMain = true means not to auto generate main func is no entry.
+	NoAutoGenMain bool
+
+	// NoSkipConstant = true means to disable optimization of skipping constants.
+	NoSkipConstant bool
+}
+
+// -----------------------------------------------------------------------------
+
+type nodeInterp struct {
+	fset       *token.FileSet
+	files      map[string]*ast.File
+	workingDir string
+}
+
+func (p *nodeInterp) Position(start token.Pos) token.Position {
+	pos := p.fset.Position(start)
+	pos.Filename = relFile(p.workingDir, pos.Filename)
+	return pos
+}
+
+func (p *nodeInterp) Caller(node ast.Node) string {
+	if expr, ok := node.(*ast.CallExpr); ok {
+		node = expr.Fun
+		start := node.Pos()
+		pos := p.fset.Position(start)
+		f := p.files[pos.Filename]
+		n := int(node.End() - start)
+		return string(f.Code[pos.Offset : pos.Offset+n])
+	}
+	return "the function call"
+}
+
+func (p *nodeInterp) LoadExpr(node ast.Node) (src string, pos token.Position) {
+	start := node.Pos()
+	pos = p.fset.Position(start)
+	f := p.files[pos.Filename]
+	if f == nil { // not found
+		return
+	}
+	n := int(node.End() - start)
+	pos.Filename = relFile(p.workingDir, pos.Filename)
+	if pos.Offset+n < 0 {
+		log.Println("LoadExpr:", node, pos.Filename, pos.Line, pos.Offset, node.Pos(), node.End(), n)
+	}
+	src = string(f.Code[pos.Offset : pos.Offset+n])
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+type lazyKind int
+
+const (
+	lazyAll lazyKind = iota
+	lazyAliasType
+	lazyInitType
+	lazyVarOrConst
+)
+
+type lazy struct {
+	pos  token.Pos
+	load func()
+	kind lazyKind
+}
+
+func (f *lazy) resolve(kind lazyKind) bool {
+	if load := f.load; load != nil && (kind == lazyAll || f.kind == kind) {
+		f.load = nil
+		load()
+		return true
+	}
+	return false
+}
+
+type lazyobjs map[string]*lazy
+
+func (p lazyobjs) insert(ctx *pkgCtx, name string, o *lazy) {
+	if old, ok := p[name]; ok {
+		npos := ctx.Position(o.pos)
+		ctx.handleCodeErrorf(&npos,
+			"%v redeclared in this block\n\t%v other declaration of %v", name, ctx.Position(old.pos), name)
+		return
+	}
+	p[name] = o
+}
+
+func (p lazyobjs) load(ctx *pkgCtx, name string, kind lazyKind) bool {
+	if enableRecover {
+		defer func() {
+			if e := recover(); e != nil {
+				ctx.handleRecover(e)
+			}
+		}()
+	}
+	if f, ok := p[name]; ok {
+		return f.resolve(kind)
+	}
+	return false
+}
+
+// -----------------------------------------------------------------------------
+
+type pkgCtx struct {
+	*nodeInterp
+	*gmxSettings
+	cpkgs *cpackages.Importer
+	lazys lazyobjs // delay: (1) alias types; (2) init named types; (4) global constants/variables
+	funcs []func() // delay: (3) func prototypes
+	bodys []func() // delay: (5) generate function body
+	errs  errors.List
+
+	// idents []*ast.Ident // toType ident recored
+	inInst int // toType in generic instance
+}
+
+func newCodeErrorf(pos *token.Position, format string, args ...interface{}) *gox.CodeError {
+	return &gox.CodeError{Pos: pos, Msg: fmt.Sprintf(format, args...)}
+}
+
+func (p *pkgCtx) newCodeError(start token.Pos, msg string) error {
+	pos := p.Position(start)
+	return &gox.CodeError{Pos: &pos, Msg: msg}
+}
+
+func (p *pkgCtx) newCodeErrorf(start token.Pos, format string, args ...interface{}) error {
+	pos := p.Position(start)
+	return newCodeErrorf(&pos, format, args...)
+}
+
+func (p *pkgCtx) handleCodeErrorf(pos *token.Position, format string, args ...interface{}) {
+	p.handleErr(newCodeErrorf(pos, format, args...))
+}
+
+func (p *pkgCtx) handleErr(err error) {
+	p.errs = append(p.errs, err)
+}
+
+func (p *pkgCtx) handleRecover(e interface{}) {
+	err, ok := e.(error)
+	if !ok {
+		if msg, ok := e.(string); ok {
+			err = errors.New(msg)
+		} else {
+			panic(e)
+		}
+	}
+	p.handleErr(err)
+}
+
+func (p *pkgCtx) loadNamed(at *gox.Package, t *types.Named) {
+	o := t.Obj()
+	if o.Pkg() == at.Types {
+		p.loadObject(o.Name())
+	}
+}
+
+func (p *pkgCtx) insertObject(name string, pos token.Pos, kind lazyKind, load func()) {
+	if name == "_" {
+		p.bodys = append(p.bodys, load)
+	} else {
+		p.lazys.insert(p, name, &lazy{pos, load, kind})
+	}
+}
+
+func (p *pkgCtx) insertNames(pos token.Pos, names []*ast.Ident, load func()) {
+	lazy := &lazy{pos, load, lazyVarOrConst}
+	for _, ident := range names {
+		name := ident.Name
+		if name == "_" {
+			p.bodys = append(p.bodys, lazy.load)
+		} else {
+			p.lazys.insert(p, name, lazy)
+		}
+	}
+}
+
+func (p *pkgCtx) loadObject(name string) bool {
+	return p.lazys.load(p, name, lazyAll)
+}
+
+type blockCtx struct {
+	*pkgCtx
+	pkg          *gox.Package
+	cb           *gox.CodeBuilder
+	imports      map[string]*gox.PkgRef
+	lookups      []*gox.PkgRef
+	clookups     []*cpackages.PkgRef
+	tlookup      *typeParamLookup
+	c2goBase     string // default is `github.com/goplus/`
+	targetDir    string
+	classRecv    *ast.FieldList // available when gmxSettings != nil
+	isClass      bool
+	fileLine     bool
+	relativePath bool
+}
+
+func (p *blockCtx) ensureLoaded(t *types.Named) {
+	if t.Underlying() == nil {
+		p.loadNamed(p.pkg, t)
+	}
+}
+
+func getUnderlying(ctx *blockCtx, typ types.Type) types.Type {
+	u := typ.Underlying()
+	if u == nil {
+		if t, ok := typ.(*types.Named); ok {
+			ctx.loadNamed(ctx.pkg, t)
+			u = t.Underlying()
+		}
+	}
+	return u
+}
+
+func (p *blockCtx) findImport(name string) (pr *gox.PkgRef, ok bool) {
+	pr, ok = p.imports[name]
+	return
+}
+
+// -----------------------------------------------------------------------------
+
+// Context saves package level states during compiling.
+type Context = pkgCtx
+
+func (p *Context) Complete(pkg *gox.Package, autoGenMain bool) error {
+	for _, genBody := range p.bodys {
+		genBody()
+	}
+	pkgTypes := pkg.Types
+	if autoGenMain && pkgTypes.Name() == "main" {
+		if obj := pkgTypes.Scope().Lookup("main"); obj == nil {
+			old, _ := pkg.SetCurFile(defaultGoFile, false)
+			pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg).End()
+			pkg.RestoreCurFile(old)
+		}
+	}
+	return p.errs.ToError()
+}
+
+func (p *Context) genOutline() error {
+	for _, f := range p.lazys {
+		f.resolve(lazyAliasType)
+	}
+	for _, f := range p.lazys {
+		f.resolve(lazyInitType)
+	}
+	for _, fnProto := range p.funcs {
+		fnProto()
+	}
+	for _, f := range p.lazys {
+		f.resolve(lazyVarOrConst)
+	}
+	return p.errs.ToError()
+}
+
+// -----------------------------------------------------------------------------
+
+const (
+	defaultGoFile  = ""
+	skippingGoFile = "_skip"
+	testingGoFile  = "_test"
+)
+
+const (
+	ioxPkgPath = "github.com/goplus/gop/builtin/iox"
+)
+
+// NewOutline creates a Go+ package outline.
+func NewOutline(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, ctx *Context, err error) {
+	workingDir := conf.WorkingDir
+	if workingDir == "" {
+		workingDir, _ = os.Getwd()
+	}
+	targetDir := conf.TargetDir
+	if targetDir == "" {
+		targetDir = workingDir
+	}
+	fset := conf.Fset
+	files := pkg.Files
+	interp := &nodeInterp{
+		fset: fset, files: files, workingDir: workingDir,
+	}
+	ctx = &pkgCtx{
+		nodeInterp: interp,
+		lazys:      make(lazyobjs),
+	}
+	confGox := &gox.Config{
+		Fset:            fset,
+		Importer:        conf.Importer,
+		LoadNamed:       ctx.loadNamed,
+		HandleErr:       ctx.handleErr,
+		NodeInterpreter: interp,
+		NewBuiltin:      newBuiltinDefault,
+		DefaultGoFile:   defaultGoFile,
+		NoSkipConstant:  conf.NoSkipConstant,
+		PkgPathIox:      ioxPkgPath,
+	}
+	if enableRecover {
+		defer func() {
+			if e := recover(); e != nil {
+				ctx.handleRecover(e)
+				err = ctx.errs.ToError()
+			}
+		}()
+	}
+	p = gox.NewPackage(pkgPath, pkg.Name, confGox)
+	for file, gmx := range files {
+		if gmx.IsProj {
+			ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
+			break
+		}
+	}
+	if ctx.gmxSettings == nil {
+		for file, gmx := range files {
+			if gmx.IsClass && !gmx.IsNormalGox {
+				ctx.gmxSettings = newGmx(ctx, p, file, gmx, conf)
+				break
+			}
+		}
+	}
+
+	// sort files
+	type File struct {
+		*ast.File
+		path string
+	}
+	sfiles := make([]*File, 0, len(files))
+	for fpath, f := range files {
+		sfiles = append(sfiles, &File{f, fpath})
+	}
+	sort.Slice(sfiles, func(i, j int) bool {
+		return sfiles[i].path < sfiles[j].path
+	})
+
+	// fset = p.Fset // conf.Fset maybe is nil
+	for _, f := range sfiles {
+		fileLine := !conf.NoFileLine
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), targetDir: targetDir,
+			fileLine: fileLine, relativePath: conf.RelativePath, isClass: f.IsClass,
+			c2goBase: c2goBase(conf.C2goBase), imports: make(map[string]*gox.PkgRef),
+		}
+		preloadGopFile(p, ctx, f.path, f.File, conf)
+	}
+
+	for fpath, gof := range pkg.GoFiles {
+		f := fromgo.ASTFile(gof, 0)
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), targetDir: targetDir,
+			imports: make(map[string]*gox.PkgRef),
+		}
+		preloadFile(p, ctx, fpath, f, false)
+	}
+	err = ctx.genOutline()
+	return
+}
+
+// NewPackage creates a Go+ package instance.
+func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package, err error) {
+	p, ctx, err := NewOutline(pkgPath, pkg, conf)
+	if err != nil {
+		return
+	}
+	err = ctx.Complete(p, !conf.NoAutoGenMain)
+	return
+}
+
+func getEntrypoint(f *ast.File) string {
+	switch {
+	case f.IsProj:
+		return "MainEntry"
+	case f.IsClass:
+		return "Main"
+	case f.Name.Name != "main":
+		return "init"
+	default:
+		return "main"
+	}
+}
+
+func genGoFile(file string, gopFile bool) string {
+	if gopFile {
+		if strings.HasSuffix(file, "_test.gop") {
+			return testingGoFile
+		}
+		return defaultGoFile
+	}
+	return skippingGoFile
+}
+
+func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) {
+	var parent = ctx.pkgCtx
+	var classType string
+	var baseTypeName string
+	var baseType types.Type
+	var spxClass bool
+	switch {
+	case f.IsProj:
+		classType = parent.gameClass
+		o := parent.game
+		baseTypeName, baseType = o.Name(), o.Type()
+		if parent.gameIsPtr {
+			baseType = types.NewPointer(baseType)
+		}
+	case f.IsClass:
+		classType = getDefaultClass(file)
+		if parent.gmxSettings != nil {
+			ext := parser.ClassFileExt(file)
+			o, ok := parent.sprite[ext]
+			if ok {
+				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
+			}
+		}
+	}
+	if classType != "" {
+		ctx.classRecv = &ast.FieldList{List: []*ast.Field{{
+			Names: []*ast.Ident{
+				{Name: "this"},
+			},
+			Type: &ast.StarExpr{
+				X: &ast.Ident{Name: classType},
+			},
+		}}}
+		if parent.gmxSettings != nil {
+			ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
+			for i, pkgPath := range parent.pkgPaths {
+				ctx.lookups[i] = p.Import(pkgPath)
+			}
+		}
+		if debugLoad {
+			log.Println("==> Load > NewType", classType)
+		}
+		pos := f.Pos()
+		specs := getFields(f)
+		decl := p.NewTypeDefs().NewType(classType, pos)
+		parent.insertObject(classType, pos, lazyInitType, func() {
+			if debugLoad {
+				log.Println("==> Load > InitType", classType)
+			}
+			pkg := p.Types
+			var flds []*types.Var
+			var tags []string
+			chk := newCheckRedecl()
+			if len(baseTypeName) != 0 {
+				flds = append(flds, types.NewField(pos, pkg, baseTypeName, baseType, true))
+				tags = append(tags, "")
+				chk.chkRedecl(ctx, baseTypeName, pos)
+			}
+			if spxClass && parent.gmxSettings != nil && parent.gameClass != "" {
+				typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: parent.gameClass}})
+				name := getTypeName(typ)
+				if !chk.chkRedecl(ctx, name, pos) {
+					fld := types.NewField(pos, pkg, name, typ, true)
+					flds = append(flds, fld)
+					tags = append(tags, "")
+				}
+			}
+			for _, v := range specs {
+				spec := v.(*ast.ValueSpec)
+				var embed bool
+				if spec.Names == nil {
+					embed = true
+					v := parseTypeEmbedName(spec.Type)
+					spec.Names = []*ast.Ident{v}
+				}
+				typ := toType(ctx, spec.Type)
+				for _, name := range spec.Names {
+					if chk.chkRedecl(ctx, name.Name, name.Pos()) {
+						continue
+					}
+					flds = append(flds, types.NewField(name.Pos(), pkg, name.Name, typ, embed))
+					tags = append(tags, toFieldTag(spec.Tag))
+				}
+			}
+			decl.InitType(p, types.NewStruct(flds, tags))
+		})
+	}
+	// check class project no MainEntry and auto added
+	if f.IsProj && !conf.NoAutoGenMain && !f.NoEntrypoint() && f.Name.Name == "main" {
+		entry := getEntrypoint(f)
+		var hasEntry bool
+		for _, decl := range f.Decls {
+			switch d := decl.(type) {
+			case *ast.FuncDecl:
+				if d.Name.Name == entry {
+					hasEntry = true
+				}
+			}
+		}
+		if !hasEntry {
+			f.Decls = append(f.Decls, &ast.FuncDecl{
+				Name: ast.NewIdent(entry),
+				Type: &ast.FuncType{Params: &ast.FieldList{}},
+				Body: &ast.BlockStmt{},
+			})
+		}
+	}
+	if d := f.ShadowEntry; d != nil {
+		d.Name.Name = getEntrypoint(f)
+	}
+	preloadFile(p, ctx, file, f, true)
+}
+
+func parseTypeEmbedName(typ ast.Expr) *ast.Ident {
+retry:
+	switch t := typ.(type) {
+	case *ast.Ident:
+		return t
+	case *ast.SelectorExpr:
+		return t.Sel
+	case *ast.StarExpr:
+		typ = t.X
+		goto retry
+	}
+	return nil
+}
+
+func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFile bool) {
+	parent := ctx.pkgCtx
+	goFile := genGoFile(file, gopFile)
+	old, _ := p.SetCurFile(goFile, true)
+	defer p.RestoreCurFile(old)
+	for _, decl := range f.Decls {
+		switch d := decl.(type) {
+		case *ast.FuncDecl:
+			dRecv := d.Recv
+			if dRecv == nil {
+				dRecv = ctx.classRecv // maybe in class file (.spx/.gmx)
+			}
+			parent.funcs = append(parent.funcs, func() {
+				old, _ := p.SetCurFile(goFile, true)
+				defer p.RestoreCurFile(old)
+				var recv *types.Var
+				var ok bool
+				if dRecv != nil {
+					if recv, ok = toRecv(ctx, dRecv); !ok {
+						return
+					}
+				}
+				preloadFunc(ctx, recv, d, gopFile)
+			})
+		case *ast.GenDecl:
+			switch d.Tok {
+			case token.IMPORT:
+				for _, item := range d.Specs {
+					loadImport(ctx, item.(*ast.ImportSpec))
+				}
+			case token.TYPE:
+				tdecl := ctx.pkg.NewTypeDefs().SetComments(d.Doc)
+				for _, spec := range d.Specs {
+					t := spec.(*ast.TypeSpec)
+					name, pos := t.Name.Name, t.Pos()
+					if t.Assign != token.NoPos { // alias type
+						parent.insertObject(name, pos, lazyAliasType, func() {
+							if debugLoad {
+								log.Println("==> Load > AliasType", name)
+							}
+							tdecl.AliasType(name, toType(ctx, t.Type), pos)
+						})
+						continue
+					}
+					if debugLoad {
+						log.Println("==> Load > NewType", name)
+					}
+					decl := tdecl.NewType(name, pos).SetComments(t.Doc)
+					fnInitType := getInitType(ctx, decl, t, gopFile)
+					parent.insertObject(name, pos, lazyInitType, fnInitType)
+				}
+			case token.CONST:
+				pkg := ctx.pkg
+				cdecl := pkg.NewConstDefs(pkg.Types.Scope())
+				for iotav, spec := range d.Specs {
+					vSpec := spec.(*ast.ValueSpec)
+					if debugLoad {
+						log.Println("==> Preload const", vSpec.Names)
+					}
+					at := cdecl.NewPos()
+					fn := makeF(ctx, cdecl, vSpec)
+					ctx.insertNames(vSpec.Pos(), vSpec.Names, func() {
+						loadConsts(ctx, cdecl, at, fn, vSpec, iotav)
+					})
+				}
+			case token.VAR:
+				pkg := ctx.pkg
+				vdecl := pkg.NewVarDefs(pkg.Types.Scope())
+				for _, spec := range d.Specs {
+					vSpec := spec.(*ast.ValueSpec)
+					if debugLoad {
+						log.Println("==> Preload var", vSpec.Names)
+					}
+					at := vdecl.NewPos()
+					ctx.insertNames(vSpec.Pos(), vSpec.Names, func() {
+						loadVars(ctx, vdecl, at, vSpec)
+					})
+				}
+			default:
+				log.Panicln("TODO - tok:", d.Tok, "spec:", reflect.TypeOf(d.Specs).Elem())
+			}
+		default:
+			log.Panicln("TODO - gopkg.Package.load: unknown decl -", reflect.TypeOf(decl))
+		}
+	}
+	if f.IsProj {
+		parent.bodys = append(parent.bodys, func() {
+			old, _ := p.SetCurFile(goFile, true)
+			defer p.RestoreCurFile(old)
+			gmxMainFunc(p, parent)
+		})
+	}
+}
+
+func getInitType(ctx *blockCtx, decl *gox.TypeDecl, t *ast.TypeSpec, gopFile bool) func() {
+	if gopFile {
+		return func() { // decycle
+			if debugLoad {
+				log.Println("==> Load > InitType", t.Name)
+			}
+			decl.InitType(ctx.pkg, toTypeInited(ctx, t.Type))
+		}
+	}
+	return func() {
+		if debugLoad {
+			log.Println("==> Load > InitType", t.Name)
+		}
+		initType(ctx, decl.Type(), t)
+	}
+}
+
+func preloadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
+	name := d.Name.Name
+	if debugLoad {
+		if recv == nil {
+			log.Println("==> New func", name)
+		} else {
+			log.Printf("==> New method %v.%s\n", recv.Type(), name)
+		}
+	}
+	if d.Operator {
+		if recv != nil { // binary op
+			if v, ok := binaryGopNames[name]; ok {
+				name = v
+			}
+		} else { // unary op
+			if v, ok := unaryGopNames[name]; ok {
+				name = v
+				at := ctx.pkg.Types
+				arg1 := d.Type.Params.List[0]
+				typ := toType(ctx, arg1.Type)
+				recv = types.NewParam(arg1.Pos(), at, arg1.Names[0].Name, typ)
+				d.Type.Params.List = nil
+			}
+		}
+	}
+	sig := toFuncType(ctx, d.Type, recv, d)
+	fn, err := ctx.pkg.NewFuncWith(d.Pos(), name, sig, func() token.Pos {
+		return d.Recv.List[0].Type.Pos()
+	})
+	if err != nil {
+		ctx.handleErr(err)
+		return
+	}
+	if d.Doc != nil {
+		fn.SetComments(d.Doc)
+	}
+	if genBody {
+		if body := d.Body; body != nil {
+			ctx.bodys = append(ctx.bodys, func() { // interface issue: #795
+				loadFuncBody(ctx, fn, body, d)
+			})
+		}
+	}
+}
+
+func loadFuncBody(ctx *blockCtx, fn *gox.Func, body *ast.BlockStmt, src ast.Node) {
+	cb := fn.BodyStart(ctx.pkg)
+	compileStmts(ctx, body.List)
+	cb.End(src)
+}
+
+var binaryGopNames = map[string]string{
+	"+": "Gop_Add",
+	"-": "Gop_Sub",
+	"*": "Gop_Mul",
+	"/": "Gop_Quo",
+	"%": "Gop_Rem",
+
+	"&":  "Gop_And",
+	"|":  "Gop_Or",
+	"^":  "Gop_Xor",
+	"<<": "Gop_Lsh",
+	">>": "Gop_Rsh",
+	"&^": "Gop_AndNot",
+
+	"+=": "Gop_AddAssign",
+	"-=": "Gop_SubAssign",
+	"*=": "Gop_MulAssign",
+	"/=": "Gop_QuoAssign",
+	"%=": "Gop_RemAssign",
+
+	"&=":  "Gop_AndAssign",
+	"|=":  "Gop_OrAssign",
+	"^=":  "Gop_XorAssign",
+	"<<=": "Gop_LshAssign",
+	">>=": "Gop_RshAssign",
+	"&^=": "Gop_AndNotAssign",
+
+	"==": "Gop_EQ",
+	"!=": "Gop_NE",
+	"<=": "Gop_LE",
+	"<":  "Gop_LT",
+	">=": "Gop_GE",
+	">":  "Gop_GT",
+
+	"&&": "Gop_LAnd",
+	"||": "Gop_LOr",
+
+	"<-": "Gop_Send",
+}
+
+var unaryGopNames = map[string]string{
+	"++": "Gop_Inc",
+	"--": "Gop_Dec",
+	"-":  "Gop_Neg",
+	"+":  "Gop_Dup",
+	"^":  "Gop_Not",
+	"!":  "Gop_LNot",
+	"<-": "Gop_Recv",
+}
+
+func simplifyGopPackage(pkgPath string) string {
+	if strings.HasPrefix(pkgPath, "gop/") {
+		return "github.com/goplus/" + pkgPath
+	}
+	return pkgPath
+}
+
+func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
+	if enableRecover {
+		defer func() {
+			if e := recover(); e != nil {
+				ctx.handleRecover(e)
+			}
+		}()
+	}
+	var pkg *gox.PkgRef
+	var pkgPath = toString(spec.Path)
+	if realPath, kind := checkC2go(pkgPath); kind != c2goInvalid {
+		if kind == c2goStandard {
+			realPath = ctx.c2goBase + realPath
+		}
+		if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg == nil {
+			return
+		}
+	} else {
+		pkg = ctx.pkg.Import(simplifyGopPackage(pkgPath), spec)
+	}
+	var name string
+	if spec.Name != nil {
+		name = spec.Name.Name
+		if name == "." {
+			ctx.lookups = append(ctx.lookups, pkg)
+			return
+		}
+		if name == "_" {
+			pkg.MarkForceUsed()
+			return
+		}
+	} else {
+		name = pkg.Types.Name()
+	}
+	ctx.imports[name] = pkg
+}
+
+func makeF(ctx *blockCtx, decl *gox.ConstDefs, v *ast.ValueSpec) gox.F {
+	if v.Values != nil {
+		decl.F = func(cb *gox.CodeBuilder) int {
+			for _, val := range v.Values {
+				compileExpr(ctx, val)
+			}
+			return len(v.Values)
+		}
+	}
+	return decl.F
+}
+
+func loadConstSpecs(ctx *blockCtx, decl *gox.ConstDefs, specs []ast.Spec) {
+	for iotav, spec := range specs {
+		vSpec := spec.(*ast.ValueSpec)
+		loadConsts(ctx, decl, decl.NewPos(), makeF(ctx, decl, vSpec), vSpec, iotav)
+	}
+}
+
+func loadConsts(ctx *blockCtx, decl *gox.ConstDefs, at gox.ValueAt, fn gox.F, v *ast.ValueSpec, iotav int) {
+	names := makeNames(v.Names)
+	if v.Values == nil {
+		if debugLoad {
+			log.Println("==> Load const", names)
+		}
+		decl.NextAt(at, fn, iotav, v.Pos(), names...)
+		return
+	}
+	var typ types.Type
+	if v.Type != nil {
+		typ = toType(ctx, v.Type)
+	}
+	if debugLoad {
+		log.Println("==> Load const", names, typ)
+	}
+	decl.NewAt(at, fn, iotav, v.Pos(), typ, names...)
+}
+
+func loadVarSpecs(ctx *blockCtx, decl *gox.VarDefs, specs []ast.Spec) {
+	for _, spec := range specs {
+		loadVars(ctx, decl, decl.NewPos(), spec.(*ast.ValueSpec))
+	}
+}
+
+func loadVars(ctx *blockCtx, decl *gox.VarDefs, at gox.ValueAt, v *ast.ValueSpec) {
+	var typ types.Type
+	if v.Type != nil {
+		typ = toType(ctx, v.Type)
+	}
+	names := makeNames(v.Names)
+	if debugLoad {
+		log.Println("==> Load var", typ, names)
+	}
+	varDecl := decl.NewAt(at, v.Names[0].Pos(), typ, names...)
+	if nv := len(v.Values); nv > 0 {
+		cb := varDecl.InitStart(ctx.pkg)
+		if enableRecover {
+			defer func() {
+				if e := recover(); e != nil {
+					cb.ResetInit()
+					panic(e)
+				}
+			}()
+		}
+		if nv == 1 && len(names) == 2 {
+			compileExpr(ctx, v.Values[0], clCallWithTwoValue)
+		} else {
+			for _, val := range v.Values {
+				switch e := val.(type) {
+				case *ast.LambdaExpr, *ast.LambdaExpr2:
+					if len(v.Values) == 1 {
+						sig := checkLambdaFuncType(ctx, e, typ, clLambaAssign, v.Names[0])
+						compileLambda(ctx, e, sig)
+					} else {
+						panic(ctx.newCodeErrorf(e.Pos(), "lambda unsupport multiple assignment"))
+					}
+				case *ast.SliceLit:
+					compileSliceLit(ctx, e, typ)
+				case *ast.CompositeLit:
+					compileCompositeLit(ctx, e, typ, false)
+				default:
+					compileExpr(ctx, val)
+				}
+			}
+		}
+		cb.EndInit(nv)
+	}
+}
+
+func makeNames(vals []*ast.Ident) []string {
+	names := make([]string, len(vals))
+	for i, v := range vals {
+		names[i] = v.Name
+	}
+	return names
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/expr.go b/cl/outline/cl/expr.go
new file mode 100644
index 00000000..de5d7fd9
--- /dev/null
+++ b/cl/outline/cl/expr.go
@@ -0,0 +1,1107 @@
+/*
+ * 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 (
+	"bytes"
+	goast "go/ast"
+	gotoken "go/token"
+	"go/types"
+	"log"
+	"math/big"
+	"reflect"
+	"strconv"
+	"strings"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/printer"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
+)
+
+/*-----------------------------------------------------------------------------
+
+Name context:
+- varVal               (ident)
+- varRef = expr        (identLHS)
+- pkgRef.member        (selectorExpr)
+- pkgRef.member = expr (selectorExprLHS)
+- pkgRef.fn(args)      (callExpr)
+- fn(args)             (callExpr)
+- spx.fn(args)         (callExpr)
+- this.member          (classMember)
+- this.method(args)    (classMember)
+
+Name lookup:
+- local variables
+- $recv members (only in class files)
+- package globals (variables, constants, types, imported packages etc.)
+- $spx package exports (only in class files)
+- $universe package exports (including builtins)
+
+// ---------------------------------------------------------------------------*/
+
+const (
+	clIdentCanAutoCall = 1 << iota
+	clIdentAllowBuiltin
+	clIdentLHS
+	clIdentSelectorExpr // this ident is X of ast.SelectorExpr
+	clIdentGoto
+	clCallWithTwoValue
+	clCommandWithoutArgs
+)
+
+const (
+	objNormal = iota
+	objPkgRef
+	objCPkgRef
+)
+
+const errorPkgPath = "github.com/qiniu/x/errors"
+
+func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (obj *gox.PkgRef, kind int) {
+	fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0
+	name := ident.Name
+	if name == "_" {
+		if fvalue {
+			panic(ctx.newCodeError(ident.Pos(), "cannot use _ as value"))
+		}
+		ctx.cb.VarRef(nil)
+		return
+	}
+
+	scope := ctx.pkg.Types.Scope()
+	at, o := ctx.cb.Scope().LookupParent(name, token.NoPos)
+	if o != nil {
+		if at != scope && at != types.Universe { // local object
+			goto find
+		}
+	}
+
+	if ctx.isClass { // in a Go+ class file
+		if fn := ctx.cb.Func(); fn != nil {
+			sig := fn.Ancestor().Type().(*types.Signature)
+			if recv := sig.Recv(); recv != nil {
+				ctx.cb.Val(recv)
+				if compileMember(ctx, ident, name, flags&^clCommandWithoutArgs) == nil { // class member object
+					return
+				}
+				ctx.cb.InternalStack().PopN(1)
+			}
+		}
+	}
+
+	// global object
+	if ctx.loadObject(name) {
+		o, at = scope.Lookup(name), scope
+	}
+	if o != nil && at != types.Universe {
+		goto find
+	}
+
+	// pkgRef object
+	if (flags & clIdentSelectorExpr) != 0 {
+		if name == "C" && len(ctx.clookups) > 0 {
+			return nil, objCPkgRef
+		}
+		if pr, ok := ctx.findImport(name); ok {
+			return pr, objPkgRef
+		}
+	}
+
+	// object from import . "xxx"
+	if compilePkgRef(ctx, nil, ident, flags, objPkgRef) {
+		return
+	}
+
+	// universe object
+	if obj := ctx.pkg.Builtin().TryRef(name); obj != nil {
+		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
+	} else if o == nil {
+		if (clIdentGoto & flags) != 0 {
+			l := ident.Obj.Data.(*ast.Ident)
+			panic(ctx.newCodeErrorf(l.Pos(), "label %v is not defined", l.Name))
+		}
+		panic(ctx.newCodeErrorf(ident.Pos(), "undefined: %s", name))
+	}
+
+find:
+	if fvalue {
+		ctx.cb.Val(o, ident)
+	} else {
+		ctx.cb.VarRef(o, ident)
+	}
+	return
+}
+
+func isBuiltin(o types.Object) bool {
+	if _, ok := o.(*types.Builtin); ok {
+		return ok
+	}
+	return false
+}
+
+func compileMember(ctx *blockCtx, v ast.Node, name string, flags int) error {
+	var mflag gox.MemberFlag
+	switch {
+	case (flags & clIdentLHS) != 0:
+		mflag = gox.MemberFlagRef
+	case (flags & clCommandWithoutArgs) != 0:
+		mflag = gox.MemberFlagMethodAlias
+	case (flags & clIdentCanAutoCall) != 0:
+		mflag = gox.MemberFlagAutoProperty
+	default:
+		mflag = gox.MemberFlagMethodAlias
+	}
+	_, err := ctx.cb.Member(name, mflag, v)
+	return err
+}
+
+func compileExprLHS(ctx *blockCtx, expr ast.Expr) {
+	switch v := expr.(type) {
+	case *ast.Ident:
+		compileIdent(ctx, v, clIdentLHS)
+	case *ast.IndexExpr:
+		compileIndexExprLHS(ctx, v)
+	case *ast.SelectorExpr:
+		compileSelectorExprLHS(ctx, v)
+	case *ast.StarExpr:
+		compileStarExprLHS(ctx, v)
+	default:
+		log.Panicln("compileExpr failed: unknown -", reflect.TypeOf(v))
+	}
+}
+
+func twoValue(inFlags []int) bool {
+	return inFlags != nil && (inFlags[0]&clCallWithTwoValue) != 0
+}
+
+func compileExpr(ctx *blockCtx, expr ast.Expr, inFlags ...int) {
+	switch v := expr.(type) {
+	case *ast.Ident:
+		flags := clIdentCanAutoCall
+		if inFlags != nil {
+			flags |= inFlags[0]
+		}
+		compileIdent(ctx, v, flags)
+	case *ast.BasicLit:
+		compileBasicLit(ctx, v)
+	case *ast.CallExpr:
+		flags := 0
+		if inFlags != nil {
+			flags = inFlags[0]
+		}
+		compileCallExpr(ctx, v, flags)
+	case *ast.SelectorExpr:
+		flags := clIdentCanAutoCall
+		if inFlags != nil {
+			flags |= inFlags[0]
+		}
+		compileSelectorExpr(ctx, v, flags)
+	case *ast.BinaryExpr:
+		compileBinaryExpr(ctx, v)
+	case *ast.UnaryExpr:
+		compileUnaryExpr(ctx, v, twoValue(inFlags))
+	case *ast.FuncLit:
+		compileFuncLit(ctx, v)
+	case *ast.CompositeLit:
+		compileCompositeLit(ctx, v, nil, false)
+	case *ast.SliceLit:
+		compileSliceLit(ctx, v, nil)
+	case *ast.RangeExpr:
+		compileRangeExpr(ctx, v)
+	case *ast.IndexExpr:
+		compileIndexExpr(ctx, v, twoValue(inFlags))
+	case *ast.IndexListExpr:
+		compileIndexListExpr(ctx, v, twoValue(inFlags))
+	case *ast.SliceExpr:
+		compileSliceExpr(ctx, v)
+	case *ast.StarExpr:
+		compileStarExpr(ctx, v)
+	case *ast.ArrayType:
+		ctx.cb.Typ(toArrayType(ctx, v), v)
+	case *ast.MapType:
+		ctx.cb.Typ(toMapType(ctx, v), v)
+	case *ast.StructType:
+		ctx.cb.Typ(toStructType(ctx, v), v)
+	case *ast.ChanType:
+		ctx.cb.Typ(toChanType(ctx, v), v)
+	case *ast.InterfaceType:
+		ctx.cb.Typ(toInterfaceType(ctx, v), v)
+	case *ast.ComprehensionExpr:
+		compileComprehensionExpr(ctx, v, twoValue(inFlags))
+	case *ast.TypeAssertExpr:
+		compileTypeAssertExpr(ctx, v, twoValue(inFlags))
+	case *ast.ParenExpr:
+		compileExpr(ctx, v.X, inFlags...)
+	case *ast.ErrWrapExpr:
+		compileErrWrapExpr(ctx, v, 0)
+	case *ast.FuncType:
+		ctx.cb.Typ(toFuncType(ctx, v, nil, nil), v)
+	case *ast.Ellipsis:
+		panic("compileEllipsis: ast.Ellipsis unexpected")
+	case *ast.KeyValueExpr:
+		panic("compileExpr: ast.KeyValueExpr unexpected")
+	default:
+		log.Panicln("compileExpr failed: unknown -", reflect.TypeOf(v))
+	}
+}
+
+func compileExprOrNone(ctx *blockCtx, expr ast.Expr) {
+	if expr != nil {
+		compileExpr(ctx, expr)
+	} else {
+		ctx.cb.None()
+	}
+}
+
+func compileUnaryExpr(ctx *blockCtx, v *ast.UnaryExpr, twoValue bool) {
+	compileExpr(ctx, v.X)
+	ctx.cb.UnaryOp(gotoken.Token(v.Op), twoValue, v)
+}
+
+func compileBinaryExpr(ctx *blockCtx, v *ast.BinaryExpr) {
+	compileExpr(ctx, v.X)
+	compileExpr(ctx, v.Y)
+	ctx.cb.BinaryOp(gotoken.Token(v.Op), v)
+}
+
+func compileIndexExprLHS(ctx *blockCtx, v *ast.IndexExpr) {
+	compileExpr(ctx, v.X)
+	compileExpr(ctx, v.Index)
+	ctx.cb.IndexRef(1, v)
+}
+
+func compileStarExprLHS(ctx *blockCtx, v *ast.StarExpr) { // *x = ...
+	compileExpr(ctx, v.X)
+	ctx.cb.ElemRef()
+}
+
+func compileStarExpr(ctx *blockCtx, v *ast.StarExpr) { // ... = *x
+	compileExpr(ctx, v.X)
+	ctx.cb.Star()
+}
+
+func compileTypeAssertExpr(ctx *blockCtx, v *ast.TypeAssertExpr, twoValue bool) {
+	compileExpr(ctx, v.X)
+	if v.Type == nil {
+		panic("TODO: x.(type) is only used in type switch")
+	}
+	typ := toType(ctx, v.Type)
+	ctx.cb.TypeAssert(typ, twoValue, v)
+}
+
+func compileIndexExpr(ctx *blockCtx, v *ast.IndexExpr, twoValue bool) { // x[i]
+	compileExpr(ctx, v.X)
+	compileExpr(ctx, v.Index)
+	ctx.cb.Index(1, twoValue, v)
+}
+
+func compileIndexListExpr(ctx *blockCtx, v *ast.IndexListExpr, twoValue bool) { // fn[t1,t2]
+	compileExpr(ctx, v.X)
+	n := len(v.Indices)
+	for i := 0; i < n; i++ {
+		compileExpr(ctx, v.Indices[i])
+	}
+	ctx.cb.Index(n, twoValue, v)
+}
+
+func compileSliceExpr(ctx *blockCtx, v *ast.SliceExpr) { // x[i:j:k]
+	compileExpr(ctx, v.X)
+	compileExprOrNone(ctx, v.Low)
+	compileExprOrNone(ctx, v.High)
+	if v.Slice3 {
+		compileExprOrNone(ctx, v.Max)
+	}
+	ctx.cb.Slice(v.Slice3, v)
+}
+
+func compileSelectorExprLHS(ctx *blockCtx, v *ast.SelectorExpr) {
+	switch x := v.X.(type) {
+	case *ast.Ident:
+		if at, kind := compileIdent(ctx, x, clIdentLHS|clIdentSelectorExpr); kind != objNormal {
+			ctx.cb.VarRef(at.Ref(v.Sel.Name))
+			return
+		}
+	default:
+		compileExpr(ctx, v.X)
+	}
+	ctx.cb.MemberRef(v.Sel.Name, v)
+}
+
+func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
+	switch x := v.X.(type) {
+	case *ast.Ident:
+		if at, kind := compileIdent(ctx, x, flags|clIdentCanAutoCall|clIdentSelectorExpr); kind != objNormal {
+			if compilePkgRef(ctx, at, v.Sel, flags, kind) {
+				return
+			}
+			if token.IsExported(v.Sel.Name) {
+				panic(ctx.newCodeErrorf(x.Pos(), "undefined: %s.%s", x.Name, v.Sel.Name))
+			}
+			panic(ctx.newCodeErrorf(x.Pos(), "cannot refer to unexported name %s.%s", x.Name, v.Sel.Name))
+		}
+	default:
+		compileExpr(ctx, v.X)
+	}
+	if err := compileMember(ctx, v, v.Sel.Name, flags); err != nil {
+		panic(err)
+	}
+}
+
+func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
+	if c := name[0]; c >= 'a' && c <= 'z' {
+		name = string(rune(c)+('A'-'a')) + name[1:]
+		if v := at.TryRef(name); v != nil && gox.IsFunc(v.Type()) {
+			return v, true
+		}
+		return
+	}
+	return at.TryRef(name), false
+}
+
+func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) {
+	if pkg != nil {
+		return pkgRef(pkg, x.Name)
+	}
+	if pkgKind == objPkgRef {
+		for _, at := range ctx.lookups {
+			if o2, alias2 := pkgRef(at, x.Name); o2 != nil {
+				if o != nil {
+					panic(ctx.newCodeErrorf(
+						x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
+						x.Name, at.Types.Path(), pkg.Types.Path()))
+				}
+				pkg, o, alias = at, o2, alias2
+			}
+		}
+	} else {
+		var cpkg *cpackages.PkgRef
+		for _, at := range ctx.clookups {
+			if o2 := at.Lookup(x.Name); o2 != nil {
+				if o != nil {
+					panic(ctx.newCodeErrorf(
+						x.Pos(), "confliction: %s declared both in \"%s\" and \"%s\"",
+						x.Name, at.Pkg().Types.Path(), cpkg.Pkg().Types.Path()))
+				}
+				cpkg, o = at, o2
+			}
+		}
+	}
+	return
+}
+
+func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind int) bool {
+	if v, alias := lookupPkgRef(ctx, at, x, pkgKind); v != nil {
+		cb := ctx.cb
+		if (flags & clIdentLHS) != 0 {
+			cb.VarRef(v, x)
+		} else {
+			autoprop := alias && (flags&clIdentCanAutoCall) != 0
+			if autoprop && !gox.HasAutoProperty(v.Type()) {
+				return false
+			}
+			cb.Val(v, x)
+			if autoprop {
+				cb.Call(0)
+			}
+		}
+		return true
+	}
+	return false
+}
+
+type fnType struct {
+	next     *fnType
+	params   *types.Tuple
+	n1       int
+	variadic bool
+	typetype bool
+	inited   bool
+}
+
+func (p *fnType) arg(i int, ellipsis bool) types.Type {
+	if i < p.n1 {
+		return p.params.At(i).Type()
+	}
+	if p.variadic {
+		t := p.params.At(p.n1).Type()
+		if ellipsis {
+			return t
+		}
+		return t.(*types.Slice).Elem()
+	}
+	return nil
+}
+
+func (p *fnType) init(t *types.Signature) {
+	p.params, p.variadic = t.Params(), t.Variadic()
+	p.n1 = p.params.Len()
+	if p.variadic {
+		p.n1--
+	}
+}
+
+func (p *fnType) initTypeType(t *gox.TypeType) {
+	param := types.NewParam(0, nil, "", t.Type())
+	p.params, p.typetype = types.NewTuple(param), true
+	p.n1 = 1
+}
+
+func (p *fnType) initWith(fnt types.Type, idx, nin int) {
+	if p.inited {
+		return
+	}
+	p.inited = true
+	if t, ok := fnt.(*gox.TypeType); ok {
+		p.initTypeType(t)
+	} else if t := gox.CheckSignatures(fnt, idx, nin); t != nil {
+		p.init(t[0])
+		for i := 1; i < len(t); i++ {
+			fn := &fnType{}
+			fn.inited = true
+			fn.init(t[i])
+			p.next = fn
+			p = p.next
+		}
+	}
+}
+
+func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
+	switch fn := v.Fun.(type) {
+	case *ast.Ident:
+		compileIdent(ctx, fn, clIdentAllowBuiltin|inFlags)
+	case *ast.SelectorExpr:
+		compileSelectorExpr(ctx, fn, 0)
+	case *ast.ErrWrapExpr:
+		if v.IsCommand() {
+			callExpr := *v
+			callExpr.Fun = fn.X
+			ewExpr := *fn
+			ewExpr.X = &callExpr
+			compileErrWrapExpr(ctx, &ewExpr, inFlags)
+			return
+		}
+		compileErrWrapExpr(ctx, fn, 0)
+	default:
+		compileExpr(ctx, fn)
+	}
+	var fnt = ctx.cb.Get(-1).Type
+	var flags gox.InstrFlags
+	var ellipsis = v.Ellipsis != gotoken.NoPos
+	if ellipsis {
+		flags = gox.InstrFlagEllipsis
+	}
+	if (inFlags & clCallWithTwoValue) != 0 {
+		flags |= gox.InstrFlagTwoValue
+	}
+	var fn *fnType = &fnType{}
+	for fn != nil {
+		err := compileCallArgs(fn, fnt, ctx, v, ellipsis, flags)
+		if err == nil {
+			break
+		}
+		if fn.next == nil {
+			panic(err)
+		}
+		fn = fn.next
+	}
+}
+
+func compileCallArgs(fn *fnType, fnt types.Type, ctx *blockCtx, v *ast.CallExpr, ellipsis bool, flags gox.InstrFlags) (err error) {
+	n := ctx.cb.InternalStack().Len()
+	defer func() {
+		if v := recover(); v != nil {
+			err = v.(error)
+			ctx.cb.InternalStack().SetLen(n)
+		}
+	}()
+	for i, arg := range v.Args {
+		switch expr := arg.(type) {
+		case *ast.LambdaExpr:
+			fn.initWith(fnt, i, len(expr.Lhs))
+			sig := checkLambdaFuncType(ctx, expr, fn.arg(i, true), clLambaArgument, v.Fun)
+			compileLambdaExpr(ctx, expr, sig)
+		case *ast.LambdaExpr2:
+			fn.initWith(fnt, i, len(expr.Lhs))
+			sig := checkLambdaFuncType(ctx, expr, fn.arg(i, true), clLambaArgument, v.Fun)
+			compileLambdaExpr2(ctx, expr, sig)
+		case *ast.CompositeLit:
+			fn.initWith(fnt, i, -1)
+			compileCompositeLit(ctx, expr, fn.arg(i, ellipsis), true)
+		case *ast.SliceLit:
+			fn.initWith(fnt, i, -2)
+			t := fn.arg(i, ellipsis)
+			switch t.(type) {
+			case *types.Slice:
+			case *types.Named:
+				if _, ok := getUnderlying(ctx, t).(*types.Slice); !ok {
+					t = nil
+				}
+			default:
+				t = nil
+			}
+			typetype := fn.typetype && t != nil
+			if typetype {
+				ctx.cb.InternalStack().Pop()
+			}
+			compileSliceLit(ctx, expr, t)
+			if typetype {
+				return
+			}
+		default:
+			compileExpr(ctx, arg)
+		}
+	}
+	ctx.cb.CallWith(len(v.Args), flags, v)
+	return
+}
+
+type clLambaFlag string
+
+const (
+	clLambaAssign   clLambaFlag = "assignment"
+	clLambaField    clLambaFlag = "field value"
+	clLambaArgument clLambaFlag = "argument"
+)
+
+// check lambda func type
+func checkLambdaFuncType(ctx *blockCtx, lambda ast.Expr, ftyp types.Type, flag clLambaFlag, toNode ast.Node) *types.Signature {
+	typ := ftyp
+retry:
+	switch t := typ.(type) {
+	case *types.Signature:
+		if l, ok := lambda.(*ast.LambdaExpr); ok {
+			if len(l.Rhs) != t.Results().Len() {
+				break
+			}
+		}
+		return t
+	case *types.Named:
+		typ = t.Underlying()
+		goto retry
+	}
+	src, _ := ctx.LoadExpr(toNode)
+	err := ctx.newCodeErrorf(lambda.Pos(), "cannot use lambda literal as type %v in %v to %v", ftyp, flag, src)
+	panic(err)
+}
+
+func compileLambda(ctx *blockCtx, lambda ast.Expr, sig *types.Signature) {
+	switch expr := lambda.(type) {
+	case *ast.LambdaExpr:
+		compileLambdaExpr(ctx, expr, sig)
+	case *ast.LambdaExpr2:
+		compileLambdaExpr2(ctx, expr, sig)
+	}
+}
+
+func makeLambdaParams(ctx *blockCtx, pos token.Pos, lhs []*ast.Ident, in *types.Tuple) *types.Tuple {
+	pkg := ctx.pkg
+	n := len(lhs)
+	if nin := in.Len(); n != nin {
+		fewOrMany := "few"
+		if n > nin {
+			fewOrMany = "many"
+		}
+		has := make([]string, n)
+		for i, v := range lhs {
+			has[i] = v.Name
+		}
+		panic(ctx.newCodeErrorf(
+			pos, "too %s arguments in lambda expression\n\thave (%s)\n\twant %v", fewOrMany, strings.Join(has, ", "), in))
+	}
+	if n == 0 {
+		return nil
+	}
+	params := make([]*types.Var, n)
+	for i, name := range lhs {
+		params[i] = pkg.NewParam(name.Pos(), name.Name, in.At(i).Type())
+	}
+	return types.NewTuple(params...)
+}
+
+func makeLambdaResults(pkg *gox.Package, out *types.Tuple) *types.Tuple {
+	nout := out.Len()
+	if nout == 0 {
+		return nil
+	}
+	results := make([]*types.Var, nout)
+	for i := 0; i < nout; i++ {
+		results[i] = pkg.NewParam(token.NoPos, "", out.At(i).Type())
+	}
+	return types.NewTuple(results...)
+}
+
+func compileLambdaExpr(ctx *blockCtx, v *ast.LambdaExpr, sig *types.Signature) {
+	pkg := ctx.pkg
+	params := makeLambdaParams(ctx, v.Pos(), v.Lhs, sig.Params())
+	results := makeLambdaResults(pkg, sig.Results())
+	ctx.cb.NewClosure(params, results, false).BodyStart(pkg)
+	for _, v := range v.Rhs {
+		compileExpr(ctx, v)
+	}
+	ctx.cb.Return(len(v.Rhs)).End(v)
+}
+
+func compileLambdaExpr2(ctx *blockCtx, v *ast.LambdaExpr2, sig *types.Signature) {
+	pkg := ctx.pkg
+	params := makeLambdaParams(ctx, v.Pos(), v.Lhs, sig.Params())
+	results := makeLambdaResults(pkg, sig.Results())
+	fn := ctx.cb.NewClosure(params, results, false)
+
+	comments, once := ctx.cb.BackupComments()
+	loadFuncBody(ctx, fn, v.Body, v)
+	ctx.cb.SetComments(comments, once)
+}
+
+func compileFuncLit(ctx *blockCtx, v *ast.FuncLit) {
+	cb := ctx.cb
+	sig := toFuncType(ctx, v.Type, nil, nil)
+	fn := cb.NewClosureWith(sig)
+	if body := v.Body; body != nil {
+		// TODO: delay load closure body
+		// ctx.bodys = append(ctx.bodys, func() {
+		comments, once := cb.BackupComments()
+		loadFuncBody(ctx, fn, body, v)
+		cb.SetComments(comments, once)
+		// })
+	}
+}
+
+func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
+	cb := ctx.cb
+	switch v.Kind {
+	case token.RAT:
+		val := v.Value
+		bi, _ := new(big.Int).SetString(val[:len(val)-1], 10) // remove r suffix
+		cb.UntypedBigInt(bi, v)
+	case token.CSTRING:
+		s, err := strconv.Unquote(v.Value)
+		if err != nil {
+			log.Panicln("compileBasicLit:", err)
+		}
+		n := len(s)
+		tyInt8 := types.Typ[types.Int8]
+		typ := types.NewArray(tyInt8, int64(n+1))
+		cb.Typ(types.NewPointer(tyInt8)).Typ(types.Typ[types.UnsafePointer])
+		for i := 0; i < n; i++ {
+			cb.Val(rune(s[i]))
+		}
+		cb.Val(rune(0)).ArrayLit(typ, n+1).UnaryOp(gotoken.AND).Call(1).Call(1)
+	default:
+		cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
+	}
+}
+
+const (
+	compositeLitVal    = 0
+	compositeLitKeyVal = 1
+)
+
+func checkCompositeLitElts(ctx *blockCtx, elts []ast.Expr, onlyStruct bool) (kind int) {
+	for _, elt := range elts {
+		if _, ok := elt.(*ast.KeyValueExpr); ok {
+			return compositeLitKeyVal
+		}
+	}
+	if len(elts) == 0 && onlyStruct {
+		return compositeLitKeyVal
+	}
+	return compositeLitVal
+}
+
+func compileCompositeLitElts(ctx *blockCtx, elts []ast.Expr, kind int, expected *kvType) {
+	for _, elt := range elts {
+		if kv, ok := elt.(*ast.KeyValueExpr); ok {
+			if key, ok := kv.Key.(*ast.CompositeLit); ok && key.Type == nil {
+				compileCompositeLit(ctx, key, expected.Key(), false)
+			} else {
+				compileExpr(ctx, kv.Key)
+			}
+			if val, ok := kv.Value.(*ast.CompositeLit); ok && val.Type == nil {
+				compileCompositeLit(ctx, val, expected.Elem(), false)
+			} else {
+				compileExpr(ctx, kv.Value)
+			}
+		} else {
+			if kind == compositeLitKeyVal {
+				ctx.cb.None()
+			}
+			if val, ok := elt.(*ast.CompositeLit); ok && val.Type == nil {
+				compileCompositeLit(ctx, val, expected.Elem(), false)
+			} else {
+				compileExpr(ctx, elt)
+			}
+		}
+	}
+}
+
+func compileStructLitInKeyVal(ctx *blockCtx, elts []ast.Expr, t *types.Struct, typ types.Type, src *ast.CompositeLit) {
+	for _, elt := range elts {
+		kv := elt.(*ast.KeyValueExpr)
+		name := kv.Key.(*ast.Ident)
+		idx := lookupField(t, name.Name)
+		if idx >= 0 {
+			ctx.cb.Val(idx)
+		} else {
+			src, pos := ctx.LoadExpr(name)
+			err := newCodeErrorf(&pos, "%s undefined (type %v has no field or method %s)", src, typ, name.Name)
+			panic(err)
+		}
+		switch expr := kv.Value.(type) {
+		case *ast.LambdaExpr, *ast.LambdaExpr2:
+			sig := checkLambdaFuncType(ctx, expr, t.Field(idx).Type(), clLambaField, kv.Key)
+			compileLambda(ctx, expr, sig)
+		default:
+			compileExpr(ctx, kv.Value)
+		}
+	}
+	ctx.cb.StructLit(typ, len(elts)<<1, true, src)
+}
+
+func lookupField(t *types.Struct, name string) int {
+	for i, n := 0, t.NumFields(); i < n; i++ {
+		if fld := t.Field(i); fld.Name() == name {
+			return i
+		}
+	}
+	return -1
+}
+
+type kvType struct {
+	underlying types.Type
+	key, val   types.Type
+	cached     bool
+}
+
+func (p *kvType) required() *kvType {
+	if !p.cached {
+		p.cached = true
+		switch t := p.underlying.(type) {
+		case *types.Slice:
+			p.key, p.val = types.Typ[types.Int], t.Elem()
+		case *types.Array:
+			p.key, p.val = types.Typ[types.Int], t.Elem()
+		case *types.Map:
+			p.key, p.val = t.Key(), t.Elem()
+		}
+	}
+	return p
+}
+
+func (p *kvType) Key() types.Type {
+	return p.required().key
+}
+
+func (p *kvType) Elem() types.Type {
+	return p.required().val
+}
+
+func compileCompositeLit(ctx *blockCtx, v *ast.CompositeLit, expected types.Type, onlyStruct bool) {
+	var hasPtr bool
+	var typ, underlying types.Type
+	var kind = checkCompositeLitElts(ctx, v.Elts, onlyStruct)
+	if v.Type != nil {
+		typ = toType(ctx, v.Type)
+		underlying = getUnderlying(ctx, typ)
+	} else if expected != nil {
+		if t, ok := expected.(*types.Pointer); ok {
+			expected, hasPtr = t.Elem(), true
+		}
+		if onlyStruct {
+			if kind == compositeLitKeyVal {
+				t := getUnderlying(ctx, expected)
+				if _, ok := t.(*types.Struct); ok { // can't omit non-struct type
+					typ, underlying = expected, t
+				}
+			}
+		} else {
+			typ, underlying = expected, getUnderlying(ctx, expected)
+		}
+	}
+	if t, ok := underlying.(*types.Struct); ok && kind == compositeLitKeyVal {
+		compileStructLitInKeyVal(ctx, v.Elts, t, typ, v)
+		if hasPtr {
+			ctx.cb.UnaryOp(gotoken.AND)
+		}
+		return
+	}
+	compileCompositeLitElts(ctx, v.Elts, kind, &kvType{underlying: underlying})
+	n := len(v.Elts)
+	if typ == nil {
+		if kind == compositeLitVal && n > 0 {
+			panic("TODO: mapLit should be in {key: val, ...} form")
+		}
+		ctx.cb.MapLit(nil, n<<1)
+		return
+	}
+	switch underlying.(type) {
+	case *types.Slice:
+		ctx.cb.SliceLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
+	case *types.Array:
+		ctx.cb.ArrayLitEx(typ, n<<kind, kind == compositeLitKeyVal, v)
+	case *types.Map:
+		ctx.cb.MapLit(typ, n<<1, v)
+	case *types.Struct:
+		ctx.cb.StructLit(typ, n, false, v)
+	default:
+		log.Panicln("compileCompositeLit: unknown type -", reflect.TypeOf(underlying))
+	}
+	if hasPtr {
+		ctx.cb.UnaryOp(gotoken.AND)
+	}
+}
+
+func compileSliceLit(ctx *blockCtx, v *ast.SliceLit, typ types.Type) {
+	n := len(v.Elts)
+	for _, elt := range v.Elts {
+		compileExpr(ctx, elt)
+	}
+	if sliceHasTypeParam(ctx, typ) {
+		ctx.cb.SliceLitEx(nil, n, false, v)
+	} else {
+		ctx.cb.SliceLitEx(typ, n, false, v)
+	}
+}
+
+func compileRangeExpr(ctx *blockCtx, v *ast.RangeExpr) {
+	pkg, cb := ctx.pkg, ctx.cb
+	cb.Val(pkg.Builtin().Ref("newRange"))
+	if v.First == nil {
+		ctx.cb.Val(0, v)
+	} else {
+		compileExpr(ctx, v.First)
+	}
+	compileExpr(ctx, v.Last)
+	if v.Expr3 == nil {
+		ctx.cb.Val(1, v)
+	} else {
+		compileExpr(ctx, v.Expr3)
+	}
+	cb.Call(3)
+}
+
+const (
+	comprehensionInvalid = iota
+	comprehensionList
+	comprehensionMap
+	comprehensionSelect
+)
+
+func comprehensionKind(v *ast.ComprehensionExpr) int {
+	switch v.Tok {
+	case token.LBRACK: // [
+		return comprehensionList
+	case token.LBRACE: // {
+		if _, ok := v.Elt.(*ast.KeyValueExpr); ok {
+			return comprehensionMap
+		}
+		return comprehensionSelect
+	}
+	panic("TODO: invalid comprehensionExpr")
+}
+
+// [expr for k, v <- container, cond]
+// {for k, v <- container, cond}
+// {expr for k, v <- container, cond}
+// {kexpr: vexpr for k, v <- container, cond}
+func compileComprehensionExpr(ctx *blockCtx, v *ast.ComprehensionExpr, twoValue bool) {
+	kind := comprehensionKind(v)
+	pkg, cb := ctx.pkg, ctx.cb
+	var results *types.Tuple
+	var ret *gox.Param
+	if v.Elt == nil {
+		boolean := pkg.NewParam(token.NoPos, "_gop_ok", types.Typ[types.Bool])
+		results = types.NewTuple(boolean)
+	} else {
+		ret = pkg.NewAutoParam("_gop_ret")
+		if kind == comprehensionSelect && twoValue {
+			boolean := pkg.NewParam(token.NoPos, "_gop_ok", types.Typ[types.Bool])
+			results = types.NewTuple(ret, boolean)
+		} else {
+			results = types.NewTuple(ret)
+		}
+	}
+	cb.NewClosure(nil, results, false).BodyStart(pkg)
+	if kind == comprehensionMap {
+		cb.VarRef(ret).ZeroLit(ret.Type()).Assign(1)
+	}
+	end := 0
+	for i := len(v.Fors) - 1; i >= 0; i-- {
+		names := make([]string, 0, 2)
+		forStmt := v.Fors[i]
+		if forStmt.Key != nil {
+			names = append(names, forStmt.Key.Name)
+		} else {
+			names = append(names, "_")
+		}
+		names = append(names, forStmt.Value.Name)
+		cb.ForRange(names...)
+		compileExpr(ctx, forStmt.X)
+		cb.RangeAssignThen(forStmt.TokPos)
+		if forStmt.Cond != nil {
+			cb.If()
+			if forStmt.Init != nil {
+				compileStmt(ctx, forStmt.Init)
+			}
+			compileExpr(ctx, forStmt.Cond)
+			cb.Then()
+			end++
+		}
+		end++
+	}
+	switch kind {
+	case comprehensionList:
+		// _gop_ret = append(_gop_ret, elt)
+		cb.VarRef(ret)
+		cb.Val(pkg.Builtin().Ref("append"))
+		cb.Val(ret)
+		compileExpr(ctx, v.Elt)
+		cb.Call(2).Assign(1)
+	case comprehensionMap:
+		// _gop_ret[key] = val
+		cb.Val(ret)
+		kv := v.Elt.(*ast.KeyValueExpr)
+		compileExpr(ctx, kv.Key)
+		cb.IndexRef(1)
+		compileExpr(ctx, kv.Value)
+		cb.Assign(1)
+	default:
+		if v.Elt == nil {
+			// return true
+			cb.Val(true)
+			cb.Return(1)
+		} else {
+			// return elt, true
+			compileExpr(ctx, v.Elt)
+			n := 1
+			if twoValue {
+				cb.Val(true)
+				n++
+			}
+			cb.Return(n)
+		}
+	}
+	for i := 0; i < end; i++ {
+		cb.End()
+	}
+	cb.Return(0).End().Call(0)
+}
+
+var (
+	tyError = types.Universe.Lookup("error").Type()
+)
+
+func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) {
+	pkg, cb := ctx.pkg, ctx.cb
+	useClosure := v.Tok == token.NOT || v.Default != nil
+	if !useClosure && (cb.Scope().Parent() == types.Universe) {
+		panic("TODO: can't use expr? in global")
+	}
+
+	compileExpr(ctx, v.X, inFlags)
+	x := cb.InternalStack().Pop()
+	n := 0
+	results, ok := x.Type.(*types.Tuple)
+	if ok {
+		n = results.Len() - 1
+	}
+
+	var ret []*types.Var
+	if n > 0 {
+		i, retName := 0, "_gop_ret"
+		ret = make([]*gox.Param, n)
+		for {
+			ret[i] = pkg.NewAutoParam(retName)
+			i++
+			if i >= n {
+				break
+			}
+			retName = "_gop_ret" + strconv.Itoa(i+1)
+		}
+	}
+	sig := types.NewSignature(nil, nil, types.NewTuple(ret...), false)
+	if useClosure {
+		cb.NewClosureWith(sig).BodyStart(pkg)
+	} else {
+		cb.CallInlineClosureStart(sig, 0, false)
+	}
+
+	cb.NewVar(tyError, "_gop_err")
+	err := cb.Scope().Lookup("_gop_err")
+
+	for _, retVar := range ret {
+		cb.VarRef(retVar)
+	}
+	cb.VarRef(err)
+	cb.InternalStack().Push(x)
+	cb.Assign(n+1, 1)
+
+	cb.If().Val(err).CompareNil(gotoken.NEQ).Then()
+	if v.Default == nil {
+		pos := pkg.Fset.Position(v.Pos())
+		currentFunc := ctx.cb.Func().Ancestor()
+		const newFrameArgs = 5
+
+		currentFuncName := currentFunc.Name()
+		if currentFuncName == "" {
+			currentFuncName = "main"
+		}
+
+		currentFuncName = strings.Join([]string{currentFunc.Pkg().Name(), currentFuncName}, ".")
+
+		cb.
+			VarRef(err).
+			Val(pkg.Import(errorPkgPath).Ref("NewFrame")).
+			Val(err).
+			Val(sprintAst(pkg.Fset, v.X)).
+			Val(pos.Filename).
+			Val(pos.Line).
+			Val(currentFuncName).
+			Call(newFrameArgs).
+			Assign(1)
+	}
+
+	if v.Tok == token.NOT { // expr!
+		cb.Val(pkg.Builtin().Ref("panic")).Val(err).Call(1).EndStmt()
+	} else if v.Default == nil { // expr?
+		cb.Val(err).ReturnErr(true)
+	} else { // expr?:val
+		compileExpr(ctx, v.Default)
+		cb.Return(1)
+	}
+	cb.End().Return(0).End()
+	if useClosure {
+		cb.Call(0)
+	}
+}
+
+func sprintAst(fset *token.FileSet, x ast.Node) string {
+	var buf bytes.Buffer
+	err := printer.Fprint(&buf, fset, x)
+	if err != nil {
+		panic("Unexpected error: " + err.Error())
+	}
+
+	return buf.String()
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/func_type_and_var.go b/cl/outline/cl/func_type_and_var.go
new file mode 100644
index 00000000..cb52ef9d
--- /dev/null
+++ b/cl/outline/cl/func_type_and_var.go
@@ -0,0 +1,422 @@
+/*
+ * 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/constant"
+	"go/types"
+	"log"
+	"math/big"
+	"reflect"
+	"strconv"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+)
+
+// -----------------------------------------------------------------------------
+
+func toRecv(ctx *blockCtx, recv *ast.FieldList) (ret *types.Var, ok bool) {
+	v := recv.List[0]
+	var name string
+	if len(v.Names) > 0 {
+		name = v.Names[0].Name
+	}
+	t := toType(ctx, v.Type)
+	if ok = checkRecvType(ctx, t, v.Type); ok {
+		ret = ctx.pkg.NewParam(v.Pos(), name, t)
+	}
+	return
+}
+
+func checkRecvType(ctx *blockCtx, typ types.Type, recv ast.Expr) (ok bool) {
+	t := indirect(typ)
+	if _, ok = t.(*types.Named); !ok {
+		pos := ctx.Position(recv.Pos())
+		ctx.handleCodeErrorf(&pos, "invalid receiver type %v", typ)
+	}
+	return
+}
+
+func indirect(typ types.Type) types.Type {
+	if t, ok := typ.(*types.Pointer); ok {
+		return t.Elem()
+	}
+	return typ
+}
+
+func toResults(ctx *blockCtx, in *ast.FieldList) *types.Tuple {
+	if in == nil {
+		return nil
+	}
+	flds := in.List
+	n := len(flds)
+	args := make([]*types.Var, 0, n)
+	for _, fld := range flds {
+		args = toParam(ctx, fld, args)
+	}
+	return types.NewTuple(args...)
+}
+
+func toParams(ctx *blockCtx, flds []*ast.Field) (typ *types.Tuple, variadic bool) {
+	n := len(flds)
+	if n == 0 {
+		return nil, false
+	}
+	args := make([]*types.Var, 0, n)
+	for _, fld := range flds {
+		args = toParam(ctx, fld, args)
+	}
+	_, ok := flds[n-1].Type.(*ast.Ellipsis)
+	return types.NewTuple(args...), ok
+}
+
+func toParam(ctx *blockCtx, fld *ast.Field, args []*gox.Param) []*gox.Param {
+	typ := toType(ctx, fld.Type)
+	pkg := ctx.pkg
+	if len(fld.Names) == 0 {
+		return append(args, pkg.NewParam(fld.Pos(), "", typ))
+	}
+	for _, name := range fld.Names {
+		args = append(args, pkg.NewParam(name.Pos(), name.Name, typ))
+	}
+	return args
+}
+
+// -----------------------------------------------------------------------------
+
+func toTypeInited(ctx *blockCtx, typ ast.Expr) types.Type {
+	t := toType(ctx, typ)
+	if named, ok := t.(*types.Named); ok {
+		ctx.ensureLoaded(named)
+	}
+	return t
+}
+
+func toType(ctx *blockCtx, typ ast.Expr) types.Type {
+	switch v := typ.(type) {
+	case *ast.Ident:
+		/*
+			ctx.idents = append(ctx.idents, v)
+			defer func() {
+				ctx.idents = ctx.idents[:len(ctx.idents)-1]
+			}()
+		*/
+		typ := toIdentType(ctx, v)
+		/*
+			if ctx.inInst == 0 {
+				if t, ok := typ.(*types.Named); ok {
+					if withTypeParams(ctx, t) {
+						pos := ctx.idents[0].Pos()
+						for _, i := range ctx.idents {
+							if i.Name == v.Name {
+								pos = i.Pos()
+								break
+							}
+						}
+						panic(ctx.newCodeErrorf(pos, "cannot use generic type %v without instantiation", t.Obj().Type()))
+					}
+				}
+			}
+		*/
+		return typ
+	case *ast.StarExpr:
+		elem := toType(ctx, v.X)
+		return types.NewPointer(elem)
+	case *ast.ArrayType:
+		return toArrayType(ctx, v)
+	case *ast.InterfaceType:
+		return toInterfaceType(ctx, v)
+	case *ast.Ellipsis:
+		elem := toType(ctx, v.Elt)
+		return types.NewSlice(elem)
+	case *ast.MapType:
+		return toMapType(ctx, v)
+	case *ast.StructType:
+		return toStructType(ctx, v)
+	case *ast.ChanType:
+		return toChanType(ctx, v)
+	case *ast.FuncType:
+		return toFuncType(ctx, v, nil, nil)
+	case *ast.SelectorExpr:
+		typ := toExternalType(ctx, v)
+		if ctx.inInst == 0 {
+			if t, ok := typ.(*types.Named); ok {
+				if withTypeParams(ctx, t) {
+					panic(ctx.newCodeErrorf(v.Pos(), "cannot use generic type %v without instantiation", t.Obj().Type()))
+				}
+			}
+		}
+		return typ
+	case *ast.ParenExpr:
+		return toType(ctx, v.X)
+	case *ast.BinaryExpr:
+		return toBinaryExprType(ctx, v)
+	case *ast.UnaryExpr:
+		return toUnaryExprType(ctx, v)
+	case *ast.IndexExpr:
+		return toIndexType(ctx, v)
+	case *ast.IndexListExpr:
+		return toIndexListType(ctx, v)
+	}
+	log.Panicln("toType: unknown -", reflect.TypeOf(typ))
+	return nil
+}
+
+var (
+	typesChanDirs = [...]types.ChanDir{
+		ast.RECV:            types.RecvOnly,
+		ast.SEND:            types.SendOnly,
+		ast.SEND | ast.RECV: types.SendRecv,
+	}
+)
+
+func toChanType(ctx *blockCtx, v *ast.ChanType) *types.Chan {
+	return types.NewChan(typesChanDirs[v.Dir], toType(ctx, v.Value))
+}
+
+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)
+		if t, ok := o.(*types.TypeName); ok {
+			return t.Type()
+		}
+		panic(ctx.newCodeErrorf(v.Pos(), "%s.%s is not a type", name, v.Sel.Name))
+	}
+	panic(ctx.newCodeErrorf(v.Pos(), "undefined: %s", name))
+}
+
+/*-----------------------------------------------------------------------------
+
+Name context:
+- type
+- pkgRef.type
+- spx.type
+
+// ---------------------------------------------------------------------------*/
+
+func toIdentType(ctx *blockCtx, ident *ast.Ident) types.Type {
+	if ctx.tlookup != nil {
+		if typ := ctx.tlookup.Lookup(ident.Name); typ != nil {
+			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))
+	}
+	if t, ok := v.(*types.TypeName); ok {
+		return t.Type()
+	}
+	if v, _ := lookupPkgRef(ctx, nil, ident, objPkgRef); v != nil {
+		if t, ok := v.(*types.TypeName); ok {
+			return t.Type()
+		}
+	}
+	panic(ctx.newCodeErrorf(ident.Pos(), "%s is not a type", ident.Name))
+}
+
+func lookupType(ctx *blockCtx, name string) (obj types.Object, builtin types.Object) {
+	at, o := ctx.cb.Scope().LookupParent(name, token.NoPos)
+	if o != nil {
+		if at != types.Universe {
+			if debugLookup {
+				log.Println("==> LookupParent", name, "=>", o)
+			}
+			return o, nil
+		}
+	}
+	if obj := ctx.pkg.Builtin().TryRef(name); obj != nil {
+		return obj, o
+	}
+	return o, o
+}
+
+type checkRedecl struct {
+	// ctx *blockCtx
+	names map[string]token.Pos
+}
+
+func newCheckRedecl() *checkRedecl {
+	p := &checkRedecl{names: make(map[string]token.Pos)}
+	return p
+}
+
+func (p *checkRedecl) chkRedecl(ctx *blockCtx, name string, pos token.Pos) bool {
+	if name == "_" {
+		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",
+			name, ctx.Position(opos), name)
+		return true
+	}
+	p.names[name] = pos
+	return false
+}
+
+func toStructType(ctx *blockCtx, v *ast.StructType) *types.Struct {
+	pkg := ctx.pkg.Types
+	fieldList := v.Fields.List
+	fields := make([]*types.Var, 0, len(fieldList))
+	tags := make([]string, 0, len(fieldList))
+	chk := newCheckRedecl()
+	for _, field := range fieldList {
+		typ := toTypeInited(ctx, field.Type)
+		if len(field.Names) == 0 { // embedded
+			name := getTypeName(typ)
+			if chk.chkRedecl(ctx, name, field.Type.Pos()) {
+				continue
+			}
+			fld := types.NewField(token.NoPos, pkg, name, typ, true)
+			fields = append(fields, fld)
+			tags = append(tags, toFieldTag(field.Tag))
+			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)
+			fields = append(fields, fld)
+			tags = append(tags, toFieldTag(field.Tag))
+		}
+	}
+	return types.NewStruct(fields, tags)
+}
+
+func toFieldTag(v *ast.BasicLit) string {
+	if v != nil {
+		tag, err := strconv.Unquote(v.Value)
+		if err != nil {
+			log.Panicln("TODO: toFieldTag -", err)
+		}
+		return tag
+	}
+	return ""
+}
+
+func getTypeName(typ types.Type) string {
+	if t, ok := typ.(*types.Pointer); ok {
+		typ = t.Elem()
+	}
+	switch t := typ.(type) {
+	case *types.Named:
+		return t.Obj().Name()
+	case *types.Basic:
+		return t.Name()
+	default:
+		panic("TODO: getTypeName")
+	}
+}
+
+func toMapType(ctx *blockCtx, v *ast.MapType) *types.Map {
+	key := toType(ctx, v.Key)
+	val := toType(ctx, v.Value)
+	return types.NewMap(key, val)
+}
+
+func toArrayType(ctx *blockCtx, v *ast.ArrayType) types.Type {
+	elem := toType(ctx, v.Elt)
+	if v.Len == nil {
+		return types.NewSlice(elem)
+	}
+	if _, ok := v.Len.(*ast.Ellipsis); ok {
+		return types.NewArray(elem, -1) // A negative length indicates an unknown length
+	}
+	return types.NewArray(elem, toInt64(ctx, v.Len, "non-constant array bound %s"))
+}
+
+func toInt64(ctx *blockCtx, e ast.Expr, emsg string) int64 {
+	cb := ctx.pkg.ConstStart()
+	compileExpr(ctx, e)
+	tv := cb.EndConst()
+	if val := tv.CVal; val != nil {
+		if val.Kind() == constant.Float {
+			if v, ok := constant.Val(val).(*big.Rat); ok && v.IsInt() {
+				return v.Num().Int64()
+			}
+		} else if v, ok := constant.Int64Val(val); ok {
+			return v
+		}
+	}
+	src, pos := ctx.LoadExpr(e)
+	panic(newCodeErrorf(&pos, emsg, src))
+}
+
+func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) *types.Interface {
+	methodsList := v.Methods.List
+	if methodsList == nil {
+		return gox.TyEmptyInterface
+	}
+	var pkg = ctx.pkg.Types
+	var methods []*types.Func
+	var embeddeds []types.Type
+	for _, m := range methodsList {
+		if len(m.Names) == 0 { // embedded
+			typ := toTypeInited(ctx, m.Type)
+			embeddeds = append(embeddeds, typ)
+			continue
+		}
+		name := m.Names[0].Name
+		sig := toFuncType(ctx, m.Type.(*ast.FuncType), nil, nil)
+		methods = append(methods, types.NewFunc(token.NoPos, pkg, name, sig))
+	}
+	intf := types.NewInterfaceType(methods, embeddeds).Complete()
+	return intf
+}
+
+func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
+	ctx.inInst++
+	defer func() {
+		ctx.inInst--
+	}()
+	ctx.cb.Typ(toTypeInited(ctx, v.X), v.X)
+	ctx.cb.Typ(toTypeInited(ctx, v.Index), v.Index)
+	ctx.cb.Index(1, false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+}
+
+func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type {
+	ctx.inInst++
+	defer func() {
+		ctx.inInst--
+	}()
+	ctx.cb.Typ(toTypeInited(ctx, v.X), v.X)
+	for _, index := range v.Indices {
+		ctx.cb.Typ(toTypeInited(ctx, index), index)
+	}
+	ctx.cb.Index(len(v.Indices), false, v)
+	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+}
+
+// -----------------------------------------------------------------------------
+
+func toString(l *ast.BasicLit) string {
+	if l.Kind == token.STRING {
+		s, err := strconv.Unquote(l.Value)
+		if err == nil {
+			return s
+		}
+	}
+	panic("TODO: toString - convert ast.BasicLit to string failed")
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/stmt.go b/cl/outline/cl/stmt.go
new file mode 100644
index 00000000..c59eee93
--- /dev/null
+++ b/cl/outline/cl/stmt.go
@@ -0,0 +1,829 @@
+/*
+ * 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 (
+	"fmt"
+	"go/constant"
+	"log"
+	"path/filepath"
+	"reflect"
+
+	goast "go/ast"
+	gotoken "go/token"
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+)
+
+func relFile(dir string, file string) string {
+	if rel, err := filepath.Rel(dir, file); err == nil {
+		if rel[0] == '.' {
+			return rel
+		}
+		return "./" + rel
+	}
+	return file
+}
+
+func commentStmt(ctx *blockCtx, stmt ast.Stmt) {
+	if ctx.fileLine {
+		start := stmt.Pos()
+		pos := ctx.fset.Position(start)
+		if ctx.relativePath {
+			pos.Filename = relFile(ctx.targetDir, pos.Filename)
+		}
+		line := fmt.Sprintf("\n//line %s:%d", pos.Filename, pos.Line)
+		comments := &goast.CommentGroup{
+			List: []*goast.Comment{{Text: line}},
+		}
+		ctx.cb.SetComments(comments, false)
+	}
+}
+
+func compileStmts(ctx *blockCtx, body []ast.Stmt) {
+	for _, stmt := range body {
+		if v, ok := stmt.(*ast.LabeledStmt); ok {
+			expr := v.Label
+			ctx.cb.NewLabel(expr.Pos(), expr.Name)
+		}
+	}
+	for _, stmt := range body {
+		compileStmt(ctx, stmt)
+	}
+}
+
+func compileStmt(ctx *blockCtx, stmt ast.Stmt) {
+	if enableRecover {
+		defer func() {
+			if e := recover(); e != nil {
+				ctx.handleRecover(e)
+				ctx.cb.ResetStmt()
+			}
+		}()
+	}
+	commentStmt(ctx, stmt)
+	switch v := stmt.(type) {
+	case *ast.ExprStmt:
+		inFlags := 0
+		if isCommandWithoutArgs(v.X) {
+			inFlags = clCommandWithoutArgs
+		}
+		compileExpr(ctx, v.X, inFlags)
+		if inFlags != 0 && gox.IsFunc(ctx.cb.InternalStack().Get(-1).Type) {
+			ctx.cb.Call(0)
+		}
+	case *ast.AssignStmt:
+		compileAssignStmt(ctx, v)
+	case *ast.ReturnStmt:
+		compileReturnStmt(ctx, v)
+	case *ast.IfStmt:
+		compileIfStmt(ctx, v)
+	case *ast.SwitchStmt:
+		compileSwitchStmt(ctx, v)
+	case *ast.RangeStmt:
+		compileRangeStmt(ctx, v)
+	case *ast.ForStmt:
+		compileForStmt(ctx, v)
+	case *ast.ForPhraseStmt:
+		compileForPhraseStmt(ctx, v)
+	case *ast.IncDecStmt:
+		compileIncDecStmt(ctx, v)
+	case *ast.DeferStmt:
+		compileDeferStmt(ctx, v)
+	case *ast.GoStmt:
+		compileGoStmt(ctx, v)
+	case *ast.DeclStmt:
+		compileDeclStmt(ctx, v)
+	case *ast.TypeSwitchStmt:
+		compileTypeSwitchStmt(ctx, v)
+	case *ast.SendStmt:
+		compileSendStmt(ctx, v)
+	case *ast.BranchStmt:
+		compileBranchStmt(ctx, v)
+	case *ast.LabeledStmt:
+		compileLabeledStmt(ctx, v)
+	case *ast.BlockStmt:
+		ctx.cb.Block()
+		compileStmts(ctx, v.List)
+		ctx.cb.End()
+		return
+	case *ast.SelectStmt:
+		compileSelectStmt(ctx, v)
+	case *ast.EmptyStmt:
+		// do nothing
+	default:
+		log.Panicln("TODO - compileStmt failed: unknown -", reflect.TypeOf(v))
+	}
+	ctx.cb.EndStmt()
+}
+
+func isCommandWithoutArgs(x ast.Expr) bool {
+retry:
+	switch t := x.(type) {
+	case *ast.Ident:
+		return true
+	case *ast.SelectorExpr:
+		x = t.X
+		goto retry
+	}
+	return false
+}
+
+func compileReturnStmt(ctx *blockCtx, expr *ast.ReturnStmt) {
+	var n = -1
+	var results *types.Tuple
+	for i, ret := range expr.Results {
+		if c, ok := ret.(*ast.CompositeLit); ok && c.Type == nil {
+			if n < 0 {
+				results = ctx.cb.Func().Type().(*types.Signature).Results()
+				n = results.Len()
+			}
+			var typ types.Type
+			if i < n {
+				typ = results.At(i).Type()
+			}
+			compileCompositeLit(ctx, c, typ, true)
+		} else {
+			inFlags := 0
+			if len(expr.Results) == 1 {
+				if _, ok := ret.(*ast.ComprehensionExpr); ok {
+					results = ctx.cb.Func().Type().(*types.Signature).Results()
+					if results.Len() == 2 {
+						inFlags = clCallWithTwoValue
+					}
+				}
+			}
+			switch v := ret.(type) {
+			case *ast.LambdaExpr, *ast.LambdaExpr2:
+				rtyp := ctx.cb.Func().Type().(*types.Signature).Results().At(i).Type()
+				sig, ok := rtyp.(*types.Signature)
+				if !ok {
+					panic(ctx.newCodeErrorf(
+						ret.Pos(), "cannot use lambda expression as type %v in return statement", rtyp))
+				}
+				compileLambda(ctx, v, sig)
+			default:
+				compileExpr(ctx, ret, inFlags)
+			}
+		}
+	}
+	ctx.cb.Return(len(expr.Results), expr)
+}
+
+func compileIncDecStmt(ctx *blockCtx, expr *ast.IncDecStmt) {
+	compileExprLHS(ctx, expr.X)
+	ctx.cb.IncDec(gotoken.Token(expr.Tok))
+}
+
+func compileSendStmt(ctx *blockCtx, expr *ast.SendStmt) {
+	compileExpr(ctx, expr.Chan)
+	compileExpr(ctx, expr.Value)
+	ctx.cb.Send()
+}
+
+func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
+	tok := expr.Tok
+	inFlags := 0
+	if len(expr.Lhs) == 2 && len(expr.Rhs) == 1 {
+		inFlags = clCallWithTwoValue
+	}
+	if tok == token.DEFINE {
+		names := make([]string, len(expr.Lhs))
+		for i, lhs := range expr.Lhs {
+			if v, ok := lhs.(*ast.Ident); ok {
+				names[i] = v.Name
+			} else {
+				log.Panicln("TODO: non-name $v on left side of :=")
+			}
+		}
+		ctx.cb.DefineVarStart(expr.Pos(), names...)
+		if enableRecover {
+			defer func() {
+				if e := recover(); e != nil {
+					ctx.cb.ResetInit()
+					panic(e)
+				}
+			}()
+		}
+		for _, rhs := range expr.Rhs {
+			compileExpr(ctx, rhs, inFlags)
+		}
+		ctx.cb.EndInit(len(expr.Rhs))
+		return
+	}
+	for _, lhs := range expr.Lhs {
+		compileExprLHS(ctx, lhs)
+	}
+	for _, rhs := range expr.Rhs {
+		switch e := rhs.(type) {
+		case *ast.LambdaExpr, *ast.LambdaExpr2:
+			if len(expr.Lhs) == 1 && len(expr.Rhs) == 1 {
+				typ := ctx.cb.Get(-1).Type.(interface{ Elem() types.Type }).Elem()
+				sig := checkLambdaFuncType(ctx, e, typ, clLambaAssign, expr.Lhs[0])
+				compileLambda(ctx, e, sig)
+			} else {
+				panic(ctx.newCodeErrorf(e.Pos(), "lambda unsupport multiple assignment"))
+			}
+		default:
+			compileExpr(ctx, rhs, inFlags)
+		}
+	}
+	if tok == token.ASSIGN {
+		ctx.cb.AssignWith(len(expr.Lhs), len(expr.Rhs), expr)
+		return
+	}
+	if len(expr.Lhs) != 1 || len(expr.Rhs) != 1 {
+		panic("TODO: invalid syntax of assign by operator")
+	}
+	ctx.cb.AssignOp(gotoken.Token(tok), expr)
+}
+
+// forRange(names...) x rangeAssignThen
+//
+//	body
+//
+// end
+// forRange k v x rangeAssignThen
+//
+//	body
+//
+// end
+func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
+	if re, ok := v.X.(*ast.RangeExpr); ok {
+		tok := token.DEFINE
+		if v.Tok == token.ASSIGN {
+			tok = v.Tok
+		}
+		compileForStmt(ctx, toForStmt(v.For, v.Key, v.Body, re, tok, nil))
+		return
+	}
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	if v.Tok == token.DEFINE {
+		names := make([]string, 1, 2)
+		if v.Key == nil {
+			names[0] = "_"
+		} else {
+			names[0] = v.Key.(*ast.Ident).Name
+		}
+		if v.Value != nil {
+			names = append(names, v.Value.(*ast.Ident).Name)
+		}
+		cb.ForRange(names...)
+		compileExpr(ctx, v.X)
+	} else {
+		cb.ForRange()
+		n := 0
+		if v.Key == nil {
+			if v.Value != nil {
+				ctx.cb.VarRef(nil) // underscore
+				n++
+			}
+		} else {
+			compileExprLHS(ctx, v.Key)
+			n++
+		}
+		if v.Value != nil {
+			compileExprLHS(ctx, v.Value)
+			n++
+		}
+		compileExpr(ctx, v.X)
+	}
+	pos := v.TokPos
+	if pos == 0 {
+		pos = v.For
+	}
+	cb.RangeAssignThen(pos)
+	compileStmts(ctx, v.Body.List)
+	cb.SetComments(comments, once)
+	setBodyHandler(ctx)
+	cb.End()
+}
+
+func compileForPhraseStmt(ctx *blockCtx, v *ast.ForPhraseStmt) {
+	if re, ok := v.X.(*ast.RangeExpr); ok {
+		compileForStmt(ctx, toForStmt(v.For, v.Value, v.Body, re, token.DEFINE, v.ForPhrase))
+		return
+	}
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	names := make([]string, 1, 2)
+	if v.Key == nil {
+		names[0] = "_"
+	} else {
+		names[0] = v.Key.Name
+	}
+	if v.Value != nil {
+		names = append(names, v.Value.Name)
+	}
+	cb.ForRange(names...)
+	compileExpr(ctx, v.X)
+	cb.RangeAssignThen(v.TokPos)
+	if v.Cond != nil {
+		cb.If()
+		compileExpr(ctx, v.Cond)
+		cb.Then()
+		compileStmts(ctx, v.Body.List)
+		cb.SetComments(comments, once)
+		cb.End()
+	} else {
+		compileStmts(ctx, v.Body.List)
+		cb.SetComments(comments, once)
+	}
+	setBodyHandler(ctx)
+	cb.End()
+}
+
+func toForStmt(forPos token.Pos, value ast.Expr, body *ast.BlockStmt, re *ast.RangeExpr, tok token.Token, fp *ast.ForPhrase) *ast.ForStmt {
+	nilIdent := value == nil
+	if !nilIdent {
+		if v, ok := value.(*ast.Ident); ok && v.Name == "_" {
+			nilIdent = true
+		}
+	}
+	if nilIdent {
+		value = &ast.Ident{NamePos: forPos, Name: "_gop_k"}
+	}
+	first := re.First
+	if first == nil {
+		first = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "0"}
+	}
+	initLhs := []ast.Expr{value}
+	initRhs := []ast.Expr{first}
+	replaceValue := false
+	var cond ast.Expr
+	var post ast.Expr
+	switch re.Last.(type) {
+	case *ast.Ident, *ast.BasicLit:
+		cond = re.Last
+	default:
+		replaceValue = true
+		cond = &ast.Ident{NamePos: forPos, Name: "_gop_end"}
+		initLhs = append(initLhs, cond)
+		initRhs = append(initRhs, re.Last)
+	}
+	if re.Expr3 == nil {
+		post = &ast.BasicLit{ValuePos: forPos, Kind: token.INT, Value: "1"}
+	} else {
+		switch re.Expr3.(type) {
+		case *ast.Ident, *ast.BasicLit:
+			post = re.Expr3
+		default:
+			replaceValue = true
+			post = &ast.Ident{NamePos: forPos, Name: "_gop_step"}
+			initLhs = append(initLhs, post)
+			initRhs = append(initRhs, re.Expr3)
+		}
+	}
+	if tok == token.ASSIGN && replaceValue {
+		oldValue := value
+		value = &ast.Ident{NamePos: forPos, Name: "_gop_k"}
+		initLhs[0] = value
+		body.List = append([]ast.Stmt{&ast.AssignStmt{
+			Lhs:    []ast.Expr{oldValue},
+			TokPos: forPos,
+			Tok:    token.ASSIGN,
+			Rhs:    []ast.Expr{value},
+		}}, body.List...)
+		tok = token.DEFINE
+	}
+	if fp != nil && fp.Cond != nil {
+		condStmt := &ast.IfStmt{
+			If:   fp.IfPos,
+			Init: fp.Init,
+			Cond: fp.Cond,
+			Body: body,
+		}
+		body = &ast.BlockStmt{
+			List: []ast.Stmt{condStmt},
+		}
+	}
+	return &ast.ForStmt{
+		For: forPos,
+		Init: &ast.AssignStmt{
+			Lhs:    initLhs,
+			TokPos: re.To,
+			Tok:    tok,
+			Rhs:    initRhs,
+		},
+		Cond: &ast.BinaryExpr{
+			X:     value,
+			OpPos: re.To,
+			Op:    token.LSS,
+			Y:     cond,
+		},
+		Post: &ast.AssignStmt{
+			Lhs:    []ast.Expr{value},
+			TokPos: re.Colon2,
+			Tok:    token.ADD_ASSIGN,
+			Rhs:    []ast.Expr{post},
+		},
+		Body: body,
+	}
+}
+
+// for init; cond then
+//
+//	body
+//	post
+//
+// end
+func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	cb.For()
+	if v.Init != nil {
+		compileStmt(ctx, v.Init)
+	}
+	if v.Cond != nil {
+		compileExpr(ctx, v.Cond)
+	} else {
+		cb.None()
+	}
+	cb.Then()
+	compileStmts(ctx, v.Body.List)
+	if v.Post != nil {
+		cb.Post()
+		compileStmt(ctx, v.Post)
+	}
+	cb.SetComments(comments, once)
+	setBodyHandler(ctx)
+	cb.End()
+}
+
+// if init; cond then
+//
+//	body
+//
+// end
+func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	cb.If()
+	if v.Init != nil {
+		compileStmt(ctx, v.Init)
+	}
+	compileExpr(ctx, v.Cond)
+	cb.Then()
+	compileStmts(ctx, v.Body.List)
+	if e := v.Else; e != nil {
+		cb.Else()
+		if stmts, ok := e.(*ast.BlockStmt); ok {
+			compileStmts(ctx, stmts.List)
+		} else {
+			compileStmt(ctx, e)
+		}
+	}
+	cb.SetComments(comments, once)
+	cb.End()
+}
+
+// typeSwitch(name) init; expr typeAssertThen()
+// type1 type2 ... typeN typeCase(N)
+//
+//	...
+//	end
+//
+// type1 type2 ... typeM typeCase(M)
+//
+//	...
+//	end
+//
+// end
+func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
+	var cb = ctx.cb
+	comments, once := cb.BackupComments()
+	var name string
+	var ta *ast.TypeAssertExpr
+	switch stmt := v.Assign.(type) {
+	case *ast.AssignStmt:
+		if stmt.Tok != token.DEFINE || len(stmt.Lhs) != 1 || len(stmt.Rhs) != 1 {
+			panic("TODO: type switch syntax error")
+		}
+		name = stmt.Lhs[0].(*ast.Ident).Name
+		ta = stmt.Rhs[0].(*ast.TypeAssertExpr)
+	case *ast.ExprStmt:
+		ta = stmt.X.(*ast.TypeAssertExpr)
+	}
+	if ta.Type != nil {
+		panic("TODO: type switch syntax error, please use x.(type)")
+	}
+	cb.TypeSwitch(name)
+	if v.Init != nil {
+		compileStmt(ctx, v.Init)
+	}
+	compileExpr(ctx, ta.X)
+	cb.TypeAssertThen()
+	seen := make(map[types.Type]ast.Expr)
+	var firstDefault ast.Stmt
+	for _, stmt := range v.Body.List {
+		c, ok := stmt.(*ast.CaseClause)
+		if !ok {
+			log.Panicln("TODO: compile TypeSwitchStmt failed - case clause expected.")
+		}
+		for _, citem := range c.List {
+			compileExpr(ctx, citem)
+			T := cb.Get(-1).Type
+			if tt, ok := T.(*gox.TypeType); ok {
+				T = tt.Type()
+			}
+			var haserr bool
+			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())
+					if T == types.Typ[types.UntypedNil] {
+						ctx.handleCodeErrorf(&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()))
+					}
+				}
+			}
+			if !haserr {
+				seen[T] = citem
+			}
+		}
+		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()))
+			} else {
+				firstDefault = c
+			}
+		}
+		cb.TypeCase(len(c.List)) // TypeCase(0) means default case
+		compileStmts(ctx, c.Body)
+		commentStmt(ctx, stmt)
+		cb.End()
+	}
+	cb.SetComments(comments, once)
+	cb.End()
+}
+
+// switch init; tag then
+// expr1 expr2 ... exprN case(N)
+//
+//	...
+//	end
+//
+// expr1 expr2 ... exprM case(M)
+//
+//	...
+//	end
+//
+// end
+func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	cb.Switch()
+	if v.Init != nil {
+		compileStmt(ctx, v.Init)
+	}
+	if v.Tag != nil { // switch tag {....}
+		compileExpr(ctx, v.Tag)
+	} else {
+		cb.None() // switch {...}
+	}
+	cb.Then()
+	seen := make(valueMap)
+	var firstDefault ast.Stmt
+	for _, stmt := range v.Body.List {
+		c, ok := stmt.(*ast.CaseClause)
+		if !ok {
+			log.Panicln("TODO: compile SwitchStmt failed - case clause expected.")
+		}
+		for _, citem := range c.List {
+			compileExpr(ctx, citem)
+			v := cb.Get(-1)
+			if val := goVal(v.CVal); val != nil {
+				// look for duplicate types for a given value
+				// (quadratic algorithm, but these lists tend to be very short)
+				typ := types.Default(v.Type)
+				var haserr bool
+				for _, vt := range seen[val] {
+					if types.Identical(typ, vt.typ) {
+						haserr = true
+						src, pos := ctx.LoadExpr(v.Src)
+						if _, ok := v.Src.(*ast.BasicLit); ok {
+							ctx.handleCodeErrorf(&pos, "duplicate case %s in switch\n\tprevious case at %v",
+								src, ctx.Position(vt.pos))
+						} else {
+							ctx.handleCodeErrorf(&pos, "duplicate case %s (value %#v) in switch\n\tprevious case at %v",
+								src, val, ctx.Position(vt.pos))
+						}
+					}
+				}
+				if !haserr {
+					seen[val] = append(seen[val], valueType{v.Src.Pos(), typ})
+				}
+			}
+		}
+		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()))
+			} else {
+				firstDefault = c
+			}
+		}
+		cb.Case(len(c.List)) // Case(0) means default case
+		body, has := hasFallthrough(c.Body)
+		compileStmts(ctx, body)
+		if has {
+			cb.Fallthrough()
+		}
+		commentStmt(ctx, stmt)
+		cb.End()
+	}
+	cb.SetComments(comments, once)
+	cb.End()
+}
+
+func hasFallthrough(body []ast.Stmt) ([]ast.Stmt, bool) {
+	if n := len(body); n > 0 {
+		if bs, ok := body[n-1].(*ast.BranchStmt); ok && bs.Tok == token.FALLTHROUGH {
+			return body[:n-1], true
+		}
+	}
+	return body, false
+}
+
+// select
+// stmt1 commCase(1)
+//
+//	...
+//	end
+//
+// stmt2 commCase(1)
+//
+//	...
+//	end
+//
+// ...
+// commCase(0)
+//
+//	...
+//	end
+//
+// end
+func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) {
+	cb := ctx.cb
+	comments, once := cb.BackupComments()
+	cb.Select()
+	for _, stmt := range v.Body.List {
+		c, ok := stmt.(*ast.CommClause)
+		if !ok {
+			log.Panicln("TODO: compile SelectStmt failed - comm clause expected.")
+		}
+		var n int
+		if c.Comm != nil {
+			compileStmt(ctx, c.Comm)
+			n = 1
+		}
+		cb.CommCase(n) // CommCase(0) means default case
+		compileStmts(ctx, c.Body)
+		commentStmt(ctx, stmt)
+		cb.End()
+	}
+	cb.SetComments(comments, once)
+	cb.End()
+}
+
+func compileBranchStmt(ctx *blockCtx, v *ast.BranchStmt) {
+	label := v.Label
+	switch v.Tok {
+	case token.GOTO:
+		cb := ctx.cb
+		if l, ok := cb.LookupLabel(label.Name); ok {
+			cb.Goto(l)
+			return
+		}
+		compileCallExpr(ctx, &ast.CallExpr{
+			Fun:  &ast.Ident{NamePos: v.TokPos, Name: "goto", Obj: &ast.Object{Data: label}},
+			Args: []ast.Expr{label},
+		}, clIdentGoto)
+	case token.BREAK:
+		ctx.cb.Break(getLabel(ctx, label))
+	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")
+	default:
+		panic("unknown branch statement")
+	}
+}
+
+func getLabel(ctx *blockCtx, label *ast.Ident) *gox.Label {
+	if label != nil {
+		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)
+	}
+	return nil
+}
+
+func compileLabeledStmt(ctx *blockCtx, v *ast.LabeledStmt) {
+	l, _ := ctx.cb.LookupLabel(v.Label.Name)
+	ctx.cb.Label(l)
+	compileStmt(ctx, v.Stmt)
+}
+
+func compileGoStmt(ctx *blockCtx, v *ast.GoStmt) {
+	compileCallExpr(ctx, v.Call, 0)
+	ctx.cb.Go()
+}
+
+func compileDeferStmt(ctx *blockCtx, v *ast.DeferStmt) {
+	compileCallExpr(ctx, v.Call, 0)
+	ctx.cb.Defer()
+}
+
+func compileDeclStmt(ctx *blockCtx, expr *ast.DeclStmt) {
+	switch d := expr.Decl.(type) {
+	case *ast.GenDecl:
+		switch d.Tok {
+		case token.TYPE:
+			for _, spec := range d.Specs {
+				compileType(ctx, spec.(*ast.TypeSpec))
+			}
+		case token.CONST:
+			cdecl := ctx.pkg.NewConstDefs(ctx.cb.Scope())
+			loadConstSpecs(ctx, cdecl, d.Specs)
+		case token.VAR:
+			vdecl := ctx.pkg.NewVarDefs(ctx.cb.Scope())
+			loadVarSpecs(ctx, vdecl, d.Specs)
+		default:
+			log.Panicln("TODO: compileDeclStmt - unknown")
+		}
+	}
+}
+
+func compileType(ctx *blockCtx, t *ast.TypeSpec) {
+	name := t.Name.Name
+	if name == "_" {
+		return
+	}
+	if t.Assign != token.NoPos { // alias type
+		ctx.cb.AliasType(name, toType(ctx, t.Type))
+	} else {
+		ctx.cb.NewType(name).InitType(ctx.pkg, toType(ctx, t.Type))
+	}
+}
+
+type (
+	valueMap  map[interface{}][]valueType // underlying Go value -> valueType
+	valueType struct {
+		pos token.Pos
+		typ types.Type
+	}
+)
+
+// goVal returns the Go value for val, or nil.
+func goVal(val constant.Value) interface{} {
+	// val should exist, but be conservative and check
+	if val == nil {
+		return nil
+	}
+	// Match implementation restriction of other compilers.
+	// gc only checks duplicates for integer, floating-point
+	// and string values, so only create Go values for these
+	// types.
+	switch val.Kind() {
+	case constant.Int:
+		if x, ok := constant.Int64Val(val); ok {
+			return x
+		}
+		if x, ok := constant.Uint64Val(val); ok {
+			return x
+		}
+	case constant.Float:
+		if x, ok := constant.Float64Val(val); ok {
+			return x
+		}
+	case constant.String:
+		return constant.StringVal(val)
+	}
+	return nil
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/types_go117.go b/cl/outline/cl/types_go117.go
new file mode 100644
index 00000000..e07999d4
--- /dev/null
+++ b/cl/outline/cl/types_go117.go
@@ -0,0 +1,65 @@
+//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"
+)
+
+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) {
+	if spec.TypeParams != nil {
+		panic("Go/Go+ source with generic but go version is too low")
+	}
+	typ := toType(ctx, spec.Type)
+	if named, ok := typ.(*types.Named); ok {
+		typ = getUnderlying(ctx, named)
+	}
+	named.SetUnderlying(typ)
+}
+
+func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
+	return false
+}
+
+func withTypeParams(ctx *blockCtx, t *types.Named) bool {
+	return false
+}
diff --git a/cl/outline/cl/types_go118.go b/cl/outline/cl/types_go118.go
new file mode 100644
index 00000000..fe00872f
--- /dev/null
+++ b/cl/outline/cl/types_go118.go
@@ -0,0 +1,266 @@
+//go:build go1.18
+// +build go1.18
+
+/*
+ * 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 cl
+
+import (
+	"go/types"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+)
+
+func toTermList(ctx *blockCtx, expr ast.Expr) []*types.Term {
+retry:
+	switch v := expr.(type) {
+	case *ast.UnaryExpr:
+		if v.Op != token.TILDE {
+			panic(ctx.newCodeErrorf(v.Pos(), "invalid op %v must ~", v.Op))
+		}
+		return []*types.Term{types.NewTerm(true, toType(ctx, v.X))}
+	case *ast.BinaryExpr:
+		if v.Op != token.OR {
+			panic(ctx.newCodeErrorf(v.Pos(), "invalid op %v must |", v.Op))
+		}
+		return append(toTermList(ctx, v.X), toTermList(ctx, v.Y)...)
+	case *ast.ParenExpr:
+		expr = v.X
+		goto retry
+	}
+	return []*types.Term{types.NewTerm(false, toType(ctx, expr))}
+}
+
+func toBinaryExprType(ctx *blockCtx, v *ast.BinaryExpr) types.Type {
+	return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))})
+}
+
+func toUnaryExprType(ctx *blockCtx, v *ast.UnaryExpr) types.Type {
+	return types.NewInterfaceType(nil, []types.Type{types.NewUnion(toTermList(ctx, v))})
+}
+
+func toTypeParams(ctx *blockCtx, params *ast.FieldList) []*types.TypeParam {
+	if params == nil {
+		return nil
+	}
+	return collectTypeParams(ctx, params)
+}
+
+func recvTypeParams(ctx *blockCtx, typ ast.Expr, named *types.Named) (tparams []*types.TypeParam) {
+	orgTypeParams := named.TypeParams()
+	if orgTypeParams == nil {
+		return nil
+	}
+L:
+	for {
+		switch t := typ.(type) {
+		case *ast.ParenExpr:
+			typ = t.X
+		case *ast.StarExpr:
+			typ = t.X
+		default:
+			break L
+		}
+	}
+	switch t := typ.(type) {
+	case *ast.IndexExpr:
+		if orgTypeParams.Len() != 1 {
+			panic(ctx.newCodeErrorf(typ.Pos(), "got 1 type parameter, but receiver base type declares %v", orgTypeParams.Len()))
+		}
+		v := t.Index.(*ast.Ident)
+		tp := orgTypeParams.At(0)
+		obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil)
+		tparams = []*types.TypeParam{types.NewTypeParam(obj, tp.Constraint())}
+	case *ast.IndexListExpr:
+		n := len(t.Indices)
+		if n != orgTypeParams.Len() {
+			panic(ctx.newCodeErrorf(typ.Pos(), "got %v arguments but %v type parameters", n, orgTypeParams.Len()))
+		}
+		tparams = make([]*types.TypeParam, n)
+		for i := 0; i < n; i++ {
+			v := t.Indices[i].(*ast.Ident)
+			tp := orgTypeParams.At(i)
+			obj := types.NewTypeName(v.Pos(), tp.Obj().Pkg(), v.Name, nil)
+			tparams[i] = types.NewTypeParam(obj, tp.Constraint())
+		}
+	default:
+		panic(ctx.newCodeErrorf(typ.Pos(), "cannot use generic type %v without instantiation", named))
+	}
+	return
+}
+
+func toFuncType(ctx *blockCtx, typ *ast.FuncType, recv *types.Var, d *ast.FuncDecl) *types.Signature {
+	var typeParams []*types.TypeParam
+	if recv != nil && d.Recv != nil {
+		typ := recv.Type()
+		if pt, ok := typ.(*types.Pointer); ok {
+			typ = pt.Elem()
+		}
+		typeParams = recvTypeParams(ctx, d.Recv.List[0].Type, typ.(*types.Named))
+	} else {
+		typeParams = toTypeParams(ctx, typ.TypeParams)
+	}
+	if len(typeParams) > 0 {
+		ctx.tlookup = &typeParamLookup{typeParams}
+		defer func() {
+			ctx.tlookup = nil
+		}()
+	}
+	params, variadic := toParams(ctx, typ.Params.List)
+	results := toResults(ctx, typ.Results)
+	if recv != nil {
+		return types.NewSignatureType(recv, typeParams, nil, params, results, variadic)
+	}
+	return types.NewSignatureType(recv, nil, typeParams, params, results, variadic)
+}
+
+type typeParamLookup struct {
+	typeParams []*types.TypeParam
+}
+
+func (p *typeParamLookup) Lookup(name string) types.Type {
+	for _, t := range p.typeParams {
+		tname := t.Obj().Name()
+		if tname != "_" && name == tname {
+			return t
+		}
+	}
+	return nil
+}
+
+func initType(ctx *blockCtx, named *types.Named, spec *ast.TypeSpec) {
+	typeParams := toTypeParams(ctx, spec.TypeParams)
+	if len(typeParams) > 0 {
+		named.SetTypeParams(typeParams)
+		ctx.tlookup = &typeParamLookup{typeParams}
+		defer func() {
+			ctx.tlookup = nil
+		}()
+	}
+	org := ctx.inInst
+	ctx.inInst = 0
+	defer func() {
+		ctx.inInst = org
+	}()
+	typ := toType(ctx, spec.Type)
+	if named, ok := typ.(*types.Named); ok {
+		typ = getUnderlying(ctx, named)
+	}
+	named.SetUnderlying(typ)
+}
+
+func collectTypeParams(ctx *blockCtx, list *ast.FieldList) []*types.TypeParam {
+	var tparams []*types.TypeParam
+	// Declare type parameters up-front, with empty interface as type bound.
+	// The scope of type parameters starts at the beginning of the type parameter
+	// list (so we can have mutually recursive parameterized interfaces).
+	for _, f := range list.List {
+		tparams = declareTypeParams(ctx, tparams, f.Names)
+	}
+
+	ctx.tlookup = &typeParamLookup{tparams}
+	defer func() {
+		ctx.tlookup = nil
+	}()
+
+	index := 0
+	for _, f := range list.List {
+		var bound types.Type
+		// NOTE: we may be able to assert that f.Type != nil here, but this is not
+		// an invariant of the AST, so we are cautious.
+		if f.Type != nil {
+			bound = boundTypeParam(ctx, f.Type)
+			if isTypeParam(bound) {
+				// We may be able to allow this since it is now well-defined what
+				// the underlying type and thus type set of a type parameter is.
+				// But we may need some additional form of cycle detection within
+				// type parameter lists.
+				//check.error(f.Type, MisplacedTypeParam, "cannot use a type parameter as constraint")
+				bound = types.Typ[types.Invalid]
+			}
+		} else {
+			bound = types.Typ[types.Invalid]
+		}
+		for i := range f.Names {
+			tparams[index+i].SetConstraint(bound)
+		}
+		index += len(f.Names)
+	}
+	return tparams
+}
+
+func declareTypeParams(ctx *blockCtx, tparams []*types.TypeParam, names []*ast.Ident) []*types.TypeParam {
+	// Use Typ[Invalid] for the type constraint to ensure that a type
+	// is present even if the actual constraint has not been assigned
+	// yet.
+	// TODO(gri) Need to systematically review all uses of type parameter
+	//           constraints to make sure we don't rely on them if they
+	//           are not properly set yet.
+	for _, name := range names {
+		tname := types.NewTypeName(name.Pos(), ctx.pkg.Types, name.Name, nil)
+		tpar := types.NewTypeParam(tname, types.Typ[types.Invalid]) // assigns type to tpar as a side-effect
+		// check.declare(check.scope, name, tname, check.scope.pos) // TODO(gri) check scope position
+		tparams = append(tparams, tpar)
+	}
+	return tparams
+}
+
+func isTypeParam(t types.Type) bool {
+	_, ok := t.(*types.TypeParam)
+	return ok
+}
+
+func sliceHasTypeParam(ctx *blockCtx, typ types.Type) bool {
+	if typ == nil {
+		return false
+	}
+	var t *types.Slice
+	switch tt := typ.(type) {
+	case *types.Named:
+		t = getUnderlying(ctx, tt).(*types.Slice)
+	case *types.Slice:
+		t = tt
+	}
+	_, ok := t.Elem().(*types.TypeParam)
+	return ok
+}
+
+func boundTypeParam(ctx *blockCtx, x ast.Expr) types.Type {
+	// A type set literal of the form ~T and A|B may only appear as constraint;
+	// embed it in an implicit interface so that only interface type-checking
+	// needs to take care of such type expressions.
+	wrap := false
+	switch op := x.(type) {
+	case *ast.UnaryExpr:
+		wrap = op.Op == token.TILDE // ~E
+	case *ast.BinaryExpr:
+		wrap = op.Op == token.OR // A|B
+	}
+	if wrap {
+		x := &ast.InterfaceType{Methods: &ast.FieldList{List: []*ast.Field{{Type: x}}}}
+		t := toInterfaceType(ctx, x)
+		t.MarkImplicit() // mark t as implicit interface if all went well
+		return t
+	}
+	return toType(ctx, x)
+}
+
+func withTypeParams(ctx *blockCtx, t *types.Named) bool {
+	ctx.ensureLoaded(t)
+	return t.TypeParams() != nil && t.TypeArgs() == nil
+}
diff --git a/cl/outline/outline.go b/cl/outline/outline.go
index 840c0ab3..9a6535f8 100644
--- a/cl/outline/outline.go
+++ b/cl/outline/outline.go
@@ -55,7 +55,7 @@ type Config struct {
 }
 
 type Package struct {
-	Pkg *types.Package
+	pkg *gox.Package
 }
 
 // NewPackage creates a Go/Go+ outline package.
@@ -75,11 +75,15 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err
 	if err != nil {
 		return
 	}
-	return Package{ret.Types}, nil
+	return Package{ret}, nil
+}
+
+func (p Package) Pkg() *types.Package {
+	return p.pkg.Types
 }
 
 func (p Package) Valid() bool {
-	return p.Pkg != nil
+	return p.pkg != nil
 }
 
 // -----------------------------------------------------------------------------
@@ -219,13 +223,14 @@ 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:   p.Pkg,
+		pkg:   pkg,
 		named: make(map[*types.TypeName]*TypeName),
 	}
 	all := (withUnexported != nil && withUnexported[0])
 	aliasr := &typeutil.Map{}
-	scope := p.Pkg.Scope()
+	scope := pkg.Scope()
 	names := scope.Names()
 	objs := make([]types.Object, len(names))
 	for i, name := range names {
@@ -249,7 +254,7 @@ func (p Package) Outline(withUnexported ...bool) (ret *All) {
 				ret.checkUsedSig(sig)
 			}
 			if name, ok := checkGoptFunc(o.Name()); ok {
-				if named, ok := ret.lookupNamed(p.Pkg, name); ok {
+				if named, ok := ret.lookupNamed(pkg, name); ok {
 					named.GoptFuncs = append(named.GoptFuncs, Func{v})
 					continue
 				}
diff --git a/cl/stmt.go b/cl/stmt.go
index a4871a34..acef23b7 100644
--- a/cl/stmt.go
+++ b/cl/stmt.go
@@ -256,10 +256,14 @@ func compileAssignStmt(ctx *blockCtx, expr *ast.AssignStmt) {
 }
 
 // forRange(names...) x rangeAssignThen
-//    body
+//
+//	body
+//
 // end
 // forRange k v x rangeAssignThen
-//    body
+//
+//	body
+//
 // end
 func compileRangeStmt(ctx *blockCtx, v *ast.RangeStmt) {
 	if re, ok := v.X.(*ast.RangeExpr); ok {
@@ -436,8 +440,10 @@ func toForStmt(forPos token.Pos, value ast.Expr, body *ast.BlockStmt, re *ast.Ra
 }
 
 // for init; cond then
-//    body
-//    post
+//
+//	body
+//	post
+//
 // end
 func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 	cb := ctx.cb
@@ -463,7 +469,9 @@ func compileForStmt(ctx *blockCtx, v *ast.ForStmt) {
 }
 
 // if init; cond then
-//    body
+//
+//	body
+//
 // end
 func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 	cb := ctx.cb
@@ -489,11 +497,15 @@ func compileIfStmt(ctx *blockCtx, v *ast.IfStmt) {
 
 // typeSwitch(name) init; expr typeAssertThen()
 // type1 type2 ... typeN typeCase(N)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // type1 type2 ... typeM typeCase(M)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // end
 func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 	var cb = ctx.cb
@@ -567,11 +579,15 @@ func compileTypeSwitchStmt(ctx *blockCtx, v *ast.TypeSwitchStmt) {
 
 // switch init; tag then
 // expr1 expr2 ... exprN case(N)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // expr1 expr2 ... exprM case(M)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // end
 func compileSwitchStmt(ctx *blockCtx, v *ast.SwitchStmt) {
 	cb := ctx.cb
@@ -651,15 +667,21 @@ func hasFallthrough(body []ast.Stmt) ([]ast.Stmt, bool) {
 
 // select
 // stmt1 commCase(1)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // stmt2 commCase(1)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // ...
 // commCase(0)
-//    ...
-//    end
+//
+//	...
+//	end
+//
 // end
 func compileSelectStmt(ctx *blockCtx, v *ast.SelectStmt) {
 	cb := ctx.cb
@@ -745,7 +767,7 @@ func compileDeclStmt(ctx *blockCtx, expr *ast.DeclStmt) {
 				compileType(ctx, spec.(*ast.TypeSpec))
 			}
 		case token.CONST:
-			cdecl := ctx.pkg.NewConstDecl(ctx.cb.Scope())
+			cdecl := ctx.pkg.NewConstDefs(ctx.cb.Scope())
 			loadConstSpecs(ctx, cdecl, d.Specs)
 		case token.VAR:
 			for _, spec := range d.Specs {
diff --git a/cmd/internal/doc/doc.go b/cmd/internal/doc/doc.go
index 7676a4e6..84b4900f 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.Pkg(), out.Outline(*unexp), *unexp, *withDoc)
 	}
 }
 
diff --git a/go.mod b/go.mod
index 3fa118b9..63f11c88 100644
--- a/go.mod
+++ b/go.mod
@@ -5,8 +5,8 @@ go 1.17
 require (
 	github.com/fsnotify/fsnotify v1.6.0
 	github.com/goplus/c2go v0.7.16
-	github.com/goplus/gox v1.12.1
-	github.com/goplus/mod v0.11.5
+	github.com/goplus/gox v1.12.2
+	github.com/goplus/mod v0.11.6
 	github.com/qiniu/x v1.13.0
 	golang.org/x/tools v0.14.0
 )
diff --git a/go.sum b/go.sum
index a4c93de6..5dd74c9a 100644
--- a/go.sum
+++ b/go.sum
@@ -6,10 +6,12 @@ github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbS
 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 h1:zIrXLAszu1ZDLfq70SnubunOloUq+yxj6r+XYpyCtGI=
 github.com/goplus/gox v1.12.1/go.mod h1:wymoQJ7ydd42cTlaXb4wNbvn4LlKjR+j8PZehI7v1zQ=
-github.com/goplus/mod v0.11.5 h1:3zcWlkLq/adU+y8GynZRMjSE50DfbnILdkRnToDaq24=
+github.com/goplus/gox v1.12.2 h1:2P6ndYbC8p/ChYNmSRuF9GpjZigydYO/DnOTpoe9uJI=
+github.com/goplus/gox v1.12.2/go.mod h1:ferIrvJSoXae8gABk5Z4olEzfAxZpUy4hD91Vm5E/qs=
 github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs=
+github.com/goplus/mod v0.11.6 h1:3H8ddFtdxIbSuBrmYmIZJxgGu4YjyvuFYNTbit+J0Xg=
+github.com/goplus/mod v0.11.6/go.mod h1:gExaEMWgU6pgmJ2Dd6jhDxhw8YGd9AOjCcnsRB3KG5w=
 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=
diff --git a/outline.go b/outline.go
index 89dfff2a..e718e4d7 100644
--- a/outline.go
+++ b/outline.go
@@ -86,7 +86,7 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 
 	for name, pkg := range pkgs {
 		if out.Valid() {
-			err = fmt.Errorf("%w: %s, %s", ErrMultiPackges, name, out.Pkg.Name())
+			err = fmt.Errorf("%w: %s, %s", ErrMultiPackges, name, out.Pkg().Name())
 			return
 		}
 		if len(pkg.Files)+len(pkg.GoFiles) == 0 { // no Go/Go+ source files


Test patch:
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 7ceb8a59..e0722723 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -306,18 +306,16 @@ type Game struct {
 	*spx.MyGame
 	Kai Kai
 }
-
-func (this *Game) onInit() {
-	this.Kai.Clone()
-	this.Broadcast__0("msg1")
-}
-
 type Kai struct {
 	spx.Sprite
 	*Game
 	a int
 }
 
+func (this *Game) onInit() {
+	this.Kai.Clone()
+	this.Broadcast__0("msg1")
+}
 func (this *Kai) onInit() {
 	this.a = 1
 }
@@ -353,6 +351,10 @@ type index struct {
 	Kai Kai
 	t   spx.Sound
 }
+type Kai struct {
+	spx.Sprite
+	*index
+}
 
 func (this *index) MainEntry() {
 	spx.Gopt_MyGame_Run(this, "hzip://open.qiniu.us/weather/res.zip")
@@ -360,12 +362,6 @@ func (this *index) MainEntry() {
 func main() {
 	spx.Gopt_MyGame_Main(new(index))
 }
-
-type Kai struct {
-	spx.Sprite
-	*index
-}
-
 func (this *Kai) Main() {
 	fmt.Println("Hi")
 }
@@ -500,17 +496,16 @@ type Game struct {
 	spx2.Game
 	Kai Kai
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 }
 func main() {
 	new(Game).Main()
 }
-
-type Kai struct {
-	spx2.Sprite
-	*Game
-}
 `, "Game.t2gmx", "Kai.t2spx")
 
 	gopSpxTestExConf(t, "KaiAndGmx", &conf, `
@@ -537,6 +532,10 @@ type Game struct {
 	spx2.Game
 	Kai Kai
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
@@ -544,12 +543,6 @@ func (this *Game) MainEntry() {
 func main() {
 	new(Game).Main()
 }
-
-type Kai struct {
-	spx2.Sprite
-	*Game
-}
-
 func (this *Kai) Main() {
 	fmt.Println("Hello")
 }
diff --git a/cl/compile_test.go b/cl/compile_test.go
index b2ab4068..e870aed3 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -384,14 +384,14 @@ func main() {
 }
 `, `package main
 
-type Inner interface {
-	DoStuff() error
-}
 type Outer interface {
 	Inner
 }
 type impl struct {
 }
+type Inner interface {
+	DoStuff() error
+}
 
 func (a *impl) DoStuff() error {
 	return nil
@@ -427,6 +427,17 @@ type A struct{ T }
 type B struct{ T }
 `, `package main
 
+type I interface {
+	M() int
+}
+type T int
+type A struct {
+	T
+}
+type B struct {
+	T
+}
+
 func main() {
 	i := I(A{})
 	b := make(chan I, 1)
@@ -434,22 +445,9 @@ func main() {
 	var ok bool
 	i, ok = <-b
 }
-
-type T int
-
 func (T) M() int {
 	return 0
 }
-
-type A struct {
-	T
-}
-type I interface {
-	M() int
-}
-type B struct {
-	T
-}
 `)
 }
 
@@ -1001,22 +999,20 @@ func (a *A) String() string {
 
 import fmt "fmt"
 
-func main() {
-	var a AA = &A{str: "hello"}
-	fmt.Println(a.(*A))
+type AA interface {
+	String() string
 }
-
 type A struct {
 	str string
 }
 
+func main() {
+	var a AA = &A{str: "hello"}
+	fmt.Println(a.(*A))
+}
 func (a *A) String() string {
 	return a.str
 }
-
-type AA interface {
-	String() string
-}
 `)
 	gopClNamedTest(t, "getInterface", `
 package main
@@ -1028,15 +1024,15 @@ func main() {
 	fmt.Println(a.(*A))
 }
 
+type AA interface {
+	String() string
+}
+
 func get() AA {
 	var a AA
 	return a
 }
 
-type AA interface {
-	String() string
-}
-
 type A struct {
 	str string
 }
@@ -1048,6 +1044,13 @@ func (a *A) String() string {
 
 import fmt "fmt"
 
+type AA interface {
+	String() string
+}
+type A struct {
+	str string
+}
+
 func main() {
 	a := get()
 	fmt.Println(a.(*A))
@@ -1056,14 +1059,6 @@ func get() AA {
 	var a AA
 	return a
 }
-
-type AA interface {
-	String() string
-}
-type A struct {
-	str string
-}
-
 func (a *A) String() string {
 	return a.str
 }
@@ -1412,17 +1407,15 @@ type Rect struct {
 	w float64
 	h float64
 }
-
-func (p *Rect) Area() float64 {
-	return p.w * p.h
-}
-
 type Circle struct {
 	x float64
 	y float64
 	r float64
 }
 
+func (p *Rect) Area() float64 {
+	return p.w * p.h
+}
 func (p *Circle) Area() float64 {
 	return 3.14 * p.r * p.r
 }
@@ -2212,12 +2205,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 {
@@ -2396,14 +2389,12 @@ import fmt "fmt"
 
 type fooIter struct {
 }
+type foo struct {
+}
 
 func (p fooIter) Next() (key string, val int, ok bool) {
 	return
 }
-
-type foo struct {
-}
-
 func (p *foo) Gop_Enum() fooIter {
 }
 func main() {
@@ -2487,6 +2478,10 @@ type fooIter struct {
 	data *foo
 	idx  int
 }
+type foo struct {
+	key []int
+	val []string
+}
 
 func (p *fooIter) Next() (key int, val string, ok bool) {
 	if p.idx < len(p.data.key) {
@@ -2495,12 +2490,6 @@ func (p *fooIter) Next() (key int, val string, ok bool) {
 	}
 	return
 }
-
-type foo struct {
-	key []int
-	val []string
-}
-
 func (p *foo) Gop_Enum() *fooIter {
 	return &fooIter{data: p}
 }
@@ -3871,6 +3860,13 @@ import (
 	testing "testing"
 )
 
+type Repo struct {
+	Title string
+}
+type Result struct {
+	Repo Repo
+}
+
 func TestNew(t *testing.T) {
 	ret := New()
 	expected := Result{}
@@ -3885,13 +3881,6 @@ func New() Result {
 func newRepo() Repo {
 	return Repo{Title: "Hi"}
 }
-
-type Repo struct {
-	Title string
-}
-type Result struct {
-	Repo Repo
-}
 `)
 	}
 }
diff --git a/cl/outline/cl/builtin_test.go b/cl/outline/cl/builtin_test.go
new file mode 100644
index 00000000..56b07473
--- /dev/null
+++ b/cl/outline/cl/builtin_test.go
@@ -0,0 +1,256 @@
+/*
+ * 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 (
+	"errors"
+	"go/types"
+	"testing"
+
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/gox/cpackages"
+	"github.com/goplus/gox/packages"
+	"github.com/goplus/mod/modfile"
+)
+
+var (
+	goxConf = getGoxConf()
+)
+
+func getGoxConf() *gox.Config {
+	fset := token.NewFileSet()
+	imp := packages.NewImporter(fset)
+	return &gox.Config{Fset: fset, Importer: imp}
+}
+
+func TestCompileErrWrapExpr(t *testing.T) {
+	defer func() {
+		if e := recover(); e != "TODO: can't use expr? in global" {
+			t.Fatal("TestCompileErrWrapExpr failed")
+		}
+	}()
+	pkg := gox.NewPackage("", "foo", goxConf)
+	ctx := &blockCtx{pkg: pkg, cb: pkg.CB()}
+	compileErrWrapExpr(ctx, &ast.ErrWrapExpr{Tok: token.QUESTION}, 0)
+}
+
+func TestToString(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("toString: no error?")
+		}
+	}()
+	toString(&ast.BasicLit{Kind: token.INT, Value: "1"})
+}
+
+func TestGetTypeName(t *testing.T) {
+	if getTypeName(types.Typ[types.Int]) != "int" {
+		t.Fatal("getTypeName int failed")
+	}
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("getTypeName: no error?")
+		}
+	}()
+	getTypeName(types.NewSlice(types.Typ[types.Int]))
+}
+
+func TestHandleRecover(t *testing.T) {
+	var ctx pkgCtx
+	ctx.handleRecover("hello")
+	if !(len(ctx.errs) == 1 && ctx.errs[0].Error() == "hello") {
+		t.Fatal("TestHandleRecover failed:", ctx.errs)
+	}
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestHandleRecover failed: no error?")
+		}
+	}()
+	ctx.handleRecover(100)
+}
+
+func TestCanAutoCall(t *testing.T) {
+	if !isCommandWithoutArgs(
+		&ast.SelectorExpr{
+			X:   &ast.SelectorExpr{X: ast.NewIdent("foo"), Sel: ast.NewIdent("bar")},
+			Sel: ast.NewIdent("val"),
+		}) {
+		t.Fatal("TestCanAutoCall failed")
+	}
+}
+
+func TestClRangeStmt(t *testing.T) {
+	ctx := &blockCtx{
+		cb: &gox.CodeBuilder{},
+	}
+	stmt := &ast.RangeStmt{
+		Tok:  token.DEFINE,
+		X:    &ast.SliceLit{},
+		Body: &ast.BlockStmt{},
+	}
+	compileRangeStmt(ctx, stmt)
+	stmt.Tok = token.ASSIGN
+	stmt.Value = &ast.Ident{Name: "_"}
+	compileRangeStmt(ctx, stmt)
+}
+
+// -----------------------------------------------------------------------------
+
+func TestGetStringConst(t *testing.T) {
+	spx := &gox.PkgRef{Types: types.NewPackage("", "foo")}
+	if v := getStringConst(spx, "unknown"); v != "" {
+		t.Fatal("getStringConst:", v)
+	}
+}
+
+func TestSpxRef(t *testing.T) {
+	defer func() {
+		if e := recover(); !isError(e, "foo.bar not found") {
+			t.Fatal("TestSpxRef:", e)
+		}
+	}()
+	pkg := &gox.PkgRef{
+		Types: types.NewPackage("foo", "foo"),
+	}
+	spxRef(pkg, "bar")
+}
+
+func isError(e interface{}, msg string) bool {
+	if e != nil {
+		if err, ok := e.(error); ok {
+			return err.Error() == msg
+		}
+		if err, ok := e.(string); ok {
+			return err == msg
+		}
+	}
+	return false
+}
+
+func TestGmxSettings(t *testing.T) {
+	pkg := gox.NewPackage("", "foo", goxConf)
+	gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
+		LookupClass: lookupClass,
+	})
+	scheds := gmx.getScheds(pkg.CB())
+	if len(scheds) != 2 || scheds[0] == nil || scheds[0] != scheds[1] {
+		t.Fatal("TestGmxSettings failed")
+	}
+	gmx.hasScheds = false
+	if gmx.getScheds(nil) != nil {
+		t.Fatal("TestGmxSettings failed: hasScheds?")
+	}
+	_, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{
+		"main.t2gmx": {
+			IsProj: true,
+		},
+	}}, &Config{
+		LookupClass: lookupClassErr,
+	})
+	if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Game not found` {
+		t.Fatal("newGmx:", e)
+	}
+}
+
+func TestSpxLookup(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestSpxLookup failed: no error?")
+		}
+	}()
+	spxLookup(nil, "foo")
+}
+
+func lookupClass(ext string) (c *modfile.Project, ok bool) {
+	switch ext {
+	case ".t2gmx", ".t2spx":
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works:    []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	}
+	return
+}
+
+func lookupClassErr(ext string) (c *modfile.Project, ok bool) {
+	switch ext {
+	case ".t2gmx", ".t2spx":
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works:    []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/libc"}}, true
+	}
+	return
+}
+
+func TestGetGoFile(t *testing.T) {
+	if f := genGoFile("a_test.gop", true); f != testingGoFile {
+		t.Fatal("TestGetGoFile:", f)
+	}
+	if f := genGoFile("a_test.gop", false); f != skippingGoFile {
+		t.Fatal("TestGetGoFile:", f)
+	}
+}
+
+func TestC2goBase(t *testing.T) {
+	if c2goBase("") != "github.com/goplus/" {
+		t.Fatal("c2goBase failed")
+	}
+}
+
+func TestErrLoadImport(t *testing.T) {
+	testPanic(t, ".: unknownpkg not found or not a valid C package (c2go.a.pub file not found).\n", func() {
+		pkg := &pkgCtx{
+			nodeInterp: &nodeInterp{
+				fset: token.NewFileSet(),
+			},
+			cpkgs: cpackages.NewImporter(
+				&cpackages.Config{LookupPub: func(pkgPath string) (pubfile string, err error) {
+					return "", errors.New("not found")
+				}})}
+		ctx := &blockCtx{pkgCtx: pkg}
+		spec := &ast.ImportSpec{
+			Path: &ast.BasicLit{Kind: token.STRING, Value: `"C/unknownpkg"`},
+		}
+		loadImport(ctx, spec)
+		panic(ctx.errs[0].Error())
+	})
+}
+
+func TestErrCompileBasicLit(t *testing.T) {
+	testPanic(t, "compileBasicLit: invalid syntax\n", func() {
+		ctx := &blockCtx{cb: new(gox.CodeBuilder)}
+		compileBasicLit(ctx, &ast.BasicLit{Kind: token.CSTRING, Value: `\\x`})
+	})
+}
+
+func testPanic(t *testing.T, panicMsg string, doPanic func()) {
+	t.Run(panicMsg, func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil {
+				t.Fatal("testPanic: no error?")
+			} else if msg := e.(string); msg != panicMsg {
+				t.Fatalf("\nResult:\n%s\nExpected Panic:\n%s\n", msg, panicMsg)
+			}
+		}()
+		doPanic()
+	})
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/outline/cl/compile_c2go_test.go b/cl/outline/cl/compile_c2go_test.go
new file mode 100644
index 00000000..c02af114
--- /dev/null
+++ b/cl/outline/cl/compile_c2go_test.go
@@ -0,0 +1,94 @@
+/*
+ * 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 cl_test
+
+import (
+	"path/filepath"
+	"strings"
+)
+
+func lookupPub(pkgPath string) (pubfile string, err error) {
+	relPath := strings.TrimPrefix(pkgPath, "github.com/goplus/gop/")
+	return filepath.Join(gopRootDir, relPath, "c2go.a.pub"), nil
+}
+
+/*
+func TestHelloC2go(t *testing.T) {
+	gopClTest(t, `
+import "C"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestHelloC2go2(t *testing.T) {
+	gopClTest(t, `
+import "C/github.com/goplus/gop/cl/internal/libc"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestHelloC2go3(t *testing.T) {
+	gopClTest(t, `
+import "C/libc"
+
+C.printf C"Hello, world!\n"
+`, `package main
+
+import (
+	libc "github.com/goplus/gop/cl/internal/libc"
+	unsafe "unsafe"
+)
+
+func main() {
+	libc.Printf((*int8)(unsafe.Pointer(&[15]int8{'H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!', '\n', '\x00'})))
+}
+`)
+}
+
+func TestErrHelloC2go(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:7:3: confliction: printf declared both in "github.com/goplus/gop/cl/internal/libc" and "github.com/goplus/gop/cl/internal/libc"`, `
+import (
+	"C"
+	"C/libc"
+)
+
+C.printf C"Hello, world!\n"
+`)
+}
+*/
diff --git a/cl/outline/cl/compile_spx_test.go b/cl/outline/cl/compile_spx_test.go
new file mode 100644
index 00000000..cb7925f4
--- /dev/null
+++ b/cl/outline/cl/compile_spx_test.go
@@ -0,0 +1,588 @@
+/*
+ * 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_test
+
+import (
+	"bytes"
+	"os"
+	"path"
+	"testing"
+
+	"github.com/goplus/gop/cl/outline/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/mod/modfile"
+)
+
+func lookupClass(ext string) (c *modfile.Project, ok bool) {
+	switch ext {
+	case ".tgmx", ".tspx":
+		return &modfile.Project{
+			Ext: ".tgmx", Class: "*MyGame",
+			Works:    []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx", "math"}}, true
+	case ".t2gmx", ".t2spx", ".t2spx2":
+		return &modfile.Project{
+			Ext: ".t2gmx", Class: "Game",
+			Works: []*modfile.Class{{Ext: ".t2spx", Class: "Sprite"},
+				{Ext: ".t2spx2", Class: "Sprite2"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	case ".t3spx", ".t3spx2":
+		return &modfile.Project{
+			Works: []*modfile.Class{{Ext: ".t3spx", Class: "Sprite"},
+				{Ext: ".t3spx2", Class: "Sprite2"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	}
+	return
+}
+
+func spxParserConf() parser.Config {
+	return parser.Config{
+		ClassKind: func(fname string) (isProj bool, ok bool) {
+			ext := path.Ext(fname)
+			c, ok := lookupClass(ext)
+			if ok {
+				isProj = (c.Ext == ext)
+			}
+			return
+		},
+	}
+}
+
+func gopSpxTest(t *testing.T, gmx, spxcode, expected string) {
+	gopSpxTestEx(t, gmx, spxcode, expected, "index.tgmx", "bar.tspx")
+}
+
+func gopSpxTestEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) {
+	gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile)
+}
+
+func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile string) {
+	t.Run(name, func(t *testing.T) {
+		cl.SetDisableRecover(true)
+		defer cl.SetDisableRecover(false)
+
+		fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
+		pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
+		if err != nil {
+			scanner.PrintError(os.Stderr, err)
+			t.Fatal("ParseFSDir:", err)
+		}
+		bar := pkgs["main"]
+		pkg, err := cl.NewPackage("", bar, conf)
+		if err != nil {
+			t.Fatal("NewPackage:", err)
+		}
+		var b bytes.Buffer
+		err = pkg.WriteTo(&b)
+		if err != nil {
+			t.Fatal("gox.WriteTo failed:", err)
+		}
+		result := b.String()
+		if result != expected {
+			t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+		}
+	})
+}
+
+func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string) {
+	fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("ParseFSDir:", err)
+	}
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.WorkingDir = "/foo"
+	conf.TargetDir = "/foo"
+	bar := pkgs["main"]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
+
+func TestSpxError(t *testing.T) {
+	gopSpxErrorTestEx(t, `./Game.tgmx:6:2: userScore redeclared
+	./Game.tgmx:5:2 other declaration of userScore`, `
+import "bytes"
+var (
+	Kai Kai
+	userScore int
+	userScore string
+)
+`, `
+println "hi"
+`, "Game.tgmx", "Kai.tspx")
+
+	gopSpxErrorTestEx(t, `./Kai.tspx:4:2: id redeclared
+	./Kai.tspx:3:2 other declaration of id`, `
+var (
+	Kai Kai
+	userScore int
+)
+`, `
+var (
+	id int
+	id string
+)
+println "hi"
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxBasic(t *testing.T) {
+	gopSpxTestEx(t, `
+func onInit() {
+	for {
+	}
+}
+`, `
+func onMsg(msg string) {
+	for {
+		say "Hi"
+	}
+}
+`, `package main
+
+import spx "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+}
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Game) onInit() {
+	for {
+		spx.SchedNow()
+	}
+}
+func (this *Kai) onMsg(msg string) {
+	for {
+		spx.Sched()
+		this.Say("Hi")
+	}
+}
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxBasic2(t *testing.T) {
+	gopSpxTest(t, `
+import (
+	"fmt"
+)
+
+const (
+	Foo = 1
+)
+
+func bar() {
+}
+
+func onInit() {
+	Foo
+	bar
+	fmt.Println("Hi")
+}
+`, ``, `package main
+
+import (
+	fmt "fmt"
+	spx "github.com/goplus/gop/cl/internal/spx"
+)
+
+type bar struct {
+	spx.Sprite
+	*index
+}
+type index struct {
+	*spx.MyGame
+}
+
+const Foo = 1
+
+func (this *index) bar() {
+}
+func (this *index) onInit() {
+	this.bar()
+	fmt.Println("Hi")
+}
+`)
+}
+
+func TestSpxMethod(t *testing.T) {
+	gopSpxTestEx(t, `
+func onInit() {
+	sched
+	broadcast "msg1"
+	TestIntValue = 1
+	x := round(1.2)
+}
+`, `
+func onInit() {
+	setCostume "kai-a"
+	play "recordingWhere"
+	say "Where do you come from?", 2
+	broadcast "msg2"
+}
+`, `package main
+
+import (
+	spx "github.com/goplus/gop/cl/internal/spx"
+	math "math"
+)
+
+type Game struct {
+	*spx.MyGame
+}
+type bar struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Game) onInit() {
+	spx.Sched()
+	this.Broadcast__0("msg1")
+	spx.TestIntValue = 1
+	x := math.Round(1.2)
+}
+func (this *bar) onInit() {
+	this.SetCostume("kai-a")
+	this.Play("recordingWhere")
+	this.Say("Where do you come from?", 2)
+	this.Broadcast__0("msg2")
+}
+`, "Game.tgmx", "bar.tspx")
+}
+
+func TestSpxVar(t *testing.T) {
+	gopSpxTestEx(t, `
+var (
+	Kai Kai
+)
+
+func onInit() {
+	Kai.clone()
+	broadcast("msg1")
+}
+`, `
+var (
+	a int
+)
+
+func onInit() {
+	a = 1
+}
+
+func onCloned() {
+	say("Hi")
+}
+`, `package main
+
+import spx "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+	Kai Kai
+}
+type Kai struct {
+	spx.Sprite
+	*Game
+	a int
+}
+
+func (this *Game) onInit() {
+	this.Kai.Clone()
+	this.Broadcast__0("msg1")
+}
+func (this *Kai) onInit() {
+	this.a = 1
+}
+func (this *Kai) onCloned() {
+	this.Say("Hi")
+}
+`, "Game.tgmx", "Kai.tspx")
+}
+
+func TestSpxRun(t *testing.T) {
+	gopSpxTestEx(t, `
+var (
+	Kai Kai
+	t   Sound
+)
+
+var x float64 = rand(1.2)
+
+run "hzip://open.qiniu.us/weather/res.zip"
+`, `
+println "Hi"
+`, `package main
+
+import (
+	fmt "fmt"
+	spx "github.com/goplus/gop/cl/internal/spx"
+)
+
+type Kai struct {
+	spx.Sprite
+	*index
+}
+type index struct {
+	*spx.MyGame
+	Kai Kai
+	t   spx.Sound
+}
+
+var x float64 = spx.Rand__1(1.2)
+
+func (this *Kai) Main() {
+	fmt.Println("Hi")
+}
+func (this *index) MainEntry() {
+	spx.Gopt_MyGame_Run(this, "hzip://open.qiniu.us/weather/res.zip")
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(index))
+}
+`, "index.tgmx", "Kai.tspx")
+}
+
+func TestSpx2(t *testing.T) {
+	gopSpxTestEx(t, `
+println("Hi")
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Game struct {
+	spx2.Game
+}
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("Hi")
+}
+func (this *Kai) onMsg(msg string) {
+}
+func main() {
+	new(Game).Main()
+}
+`, "Game.t2gmx", "Kai.t2spx")
+
+	gopSpxTestEx(t, `
+println("Hi, Sprite2")
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Game struct {
+	spx2.Game
+}
+type Kai struct {
+	spx2.Sprite2
+	*Game
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("Hi, Sprite2")
+}
+func (this *Kai) onMsg(msg string) {
+}
+func main() {
+	new(Game).Main()
+}
+`, "Game.t2gmx", "Kai.t2spx2")
+
+	gopSpxTestEx(t, `
+println("Hi, Sprite")
+`, `
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Dog struct {
+	spx2.Sprite
+}
+type Kai struct {
+	spx2.Sprite2
+}
+
+func (this *Dog) Main() {
+	fmt.Println("Hi, Sprite")
+}
+func (this *Kai) onMsg(msg string) {
+}
+`, "Dog.t3spx", "Kai.t3spx2")
+}
+
+func TestSpxMainEntry(t *testing.T) {
+	conf := *gblConf
+	conf.Importer = nil
+	conf.NoAutoGenMain = false
+
+	gopSpxTestExConf(t, "Nocode", &conf, `
+`, `
+`, `package main
+
+import spx2 "github.com/goplus/gop/cl/internal/spx2"
+
+type Game struct {
+	spx2.Game
+}
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+}
+func main() {
+	new(Game).Main()
+}
+`, "Game.t2gmx", "Kai.t2spx")
+	gopSpxTestExConf(t, "OnlyGmx", &conf, `
+var (
+	Kai Kai
+)
+`, `
+`, `package main
+
+import spx2 "github.com/goplus/gop/cl/internal/spx2"
+
+type Game struct {
+	spx2.Game
+	Kai Kai
+}
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+}
+func main() {
+	new(Game).Main()
+}
+`, "Game.t2gmx", "Kai.t2spx")
+
+	gopSpxTestExConf(t, "KaiAndGmx", &conf, `
+var (
+	Kai Kai
+)
+func MainEntry() {
+	println "Hi"
+}
+`, `
+func Main() {
+	println "Hello"
+}
+func onMsg(msg string) {
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	spx2 "github.com/goplus/gop/cl/internal/spx2"
+)
+
+type Game struct {
+	spx2.Game
+	Kai Kai
+}
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
+
+func (this *Game) MainEntry() {
+	fmt.Println("Hi")
+}
+func (this *Kai) Main() {
+	fmt.Println("Hello")
+}
+func (this *Kai) onMsg(msg string) {
+}
+func main() {
+	new(Game).Main()
+}
+`, "Game.t2gmx", "Kai.t2spx")
+}
+
+func TestSpxGoxBasic(t *testing.T) {
+	gopSpxTestEx(t, `
+func onInit() {
+	for {
+	}
+}
+`, `
+func onMsg(msg string) {
+	for {
+		say "Hi"
+	}
+}
+`, `package main
+
+import spx "github.com/goplus/gop/cl/internal/spx"
+
+type Game struct {
+	*spx.MyGame
+}
+type Kai struct {
+	spx.Sprite
+	*Game
+}
+
+func (this *Game) onInit() {
+	for {
+		spx.SchedNow()
+	}
+}
+func (this *Kai) onMsg(msg string) {
+	for {
+		spx.Sched()
+		this.Say("Hi")
+	}
+}
+`, "Game.tgmx.gox", "Kai.tspx.gox")
+}
diff --git a/cl/outline/cl/compile_test.go b/cl/outline/cl/compile_test.go
new file mode 100644
index 00000000..87dd7447
--- /dev/null
+++ b/cl/outline/cl/compile_test.go
@@ -0,0 +1,4616 @@
+/*
+ * 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_test
+
+import (
+	"bytes"
+	"os"
+	"strings"
+	"sync"
+	"testing"
+
+	"github.com/goplus/gop"
+	"github.com/goplus/gop/ast"
+	"github.com/goplus/gop/cl/outline/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+	"github.com/goplus/gop/token"
+	"github.com/goplus/gox"
+	"github.com/goplus/mod/env"
+)
+
+const (
+	gopRootDir = ".."
+)
+
+var (
+	gblFset *token.FileSet
+	gblConf *cl.Config
+)
+
+func init() {
+	gox.SetDebug(gox.DbgFlagAll)
+	cl.SetDebug(cl.DbgFlagAll)
+	gblFset = token.NewFileSet()
+	imp := gop.NewImporter(nil, &env.Gop{Root: gopRootDir, Version: "1.0"}, gblFset)
+	gblConf = &cl.Config{
+		Fset:          gblFset,
+		Importer:      imp,
+		LookupClass:   lookupClass,
+		LookupPub:     lookupPub,
+		C2goBase:      "github.com/goplus/gop/cl/internal",
+		NoFileLine:    true,
+		NoAutoGenMain: true,
+	}
+}
+
+func gopClNamedTest(t *testing.T, name string, gopcode, expected string) {
+	t.Run(name, func(t *testing.T) {
+		gopClTest(t, gopcode, expected)
+	})
+}
+
+func gopClTest(t *testing.T, gopcode, expected string) {
+	gopClTestEx(t, gblConf, "main", gopcode, expected)
+}
+
+func gopClTestFile(t *testing.T, gopcode, expected string, fname string) {
+	fs := memfs.SingleFile("/foo", fname, gopcode)
+	gopClTestFS(t, gblConf, fs, "main", expected)
+}
+
+func gopClTestEx(t *testing.T, conf *cl.Config, pkgname, gopcode, expected string) {
+	fs := memfs.SingleFile("/foo", "bar.gop", gopcode)
+	gopClTestFS(t, conf, fs, pkgname, expected)
+}
+
+func gopMixedClTest(t *testing.T, pkgname, gocode, gopcode, expected string, outline ...bool) {
+	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
+	gopClTestFS(t, gblConf, fs, pkgname, expected, outline...)
+}
+
+func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, expected string, outline ...bool) {
+	cl.SetDisableRecover(true)
+	defer cl.SetDisableRecover(false)
+
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{Mode: parser.ParseComments})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("ParseFSDir:", err)
+	}
+	bar := pkgs[pkgname]
+	newPackage := cl.NewPackage
+	if outline != nil && outline[0] {
+		newPackage = func(pkgPath string, pkg *ast.Package, conf *cl.Config) (p *gox.Package, err error) {
+			p, _, err = cl.NewOutline(pkgPath, pkg, conf)
+			return
+		}
+	}
+	pkg, err := newPackage("github.com/goplus/gop/cl", bar, conf)
+	if err != nil {
+		t.Fatal("NewPackage:", err)
+	}
+	var b bytes.Buffer
+	err = pkg.WriteTo(&b)
+	if err != nil {
+		t.Fatal("gox.WriteTo failed:", err)
+	}
+	result := b.String()
+	if result != expected {
+		t.Fatalf("\nResult:\n%s\nExpected:\n%s\n", result, expected)
+	}
+}
+
+func TestVargCommand(t *testing.T) {
+	gopClTest(t, `
+type foo int
+
+func (f foo) Ls(args ...string) {
+}
+
+var f foo
+f.ls
+`, `package main
+
+type foo int
+
+var f foo
+
+func (f foo) Ls(args ...string) {
+}
+func main() {
+	f.Ls()
+}
+`)
+}
+
+func _TestCommandInPkg(t *testing.T) {
+	gopClTest(t, `
+func Ls(args ...string) {
+}
+
+ls
+`, `
+`)
+}
+
+func TestFileOpen(t *testing.T) {
+	gopClTest(t, `
+for line <- open("foo.txt")! {
+	println line
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	os "os"
+	iox "github.com/goplus/gop/builtin/iox"
+	errors "github.com/qiniu/x/errors"
+)
+
+func main() {
+	for _gop_it := iox.EnumLines(func() (_gop_ret *os.File) {
+		var _gop_err error
+		_gop_ret, _gop_err = os.Open("foo.txt")
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "open(\"foo.txt\")", "/foo/bar.gop", 2, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(line)
+	}
+}
+`)
+}
+
+func TestFileEnumLines(t *testing.T) {
+	gopClTest(t, `
+import "os"
+
+for line <- os.Stdin {
+	println line
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	os "os"
+	iox "github.com/goplus/gop/builtin/iox"
+)
+
+func main() {
+	for _gop_it := iox.EnumLines(os.Stdin); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(line)
+	}
+}
+`)
+}
+
+func TestIoxLines(t *testing.T) {
+	gopClTest(t, `
+import "io"
+
+var r io.Reader
+
+for line <- lines(r) {
+	println line
+}
+`, `package main
+
+import (
+	fmt "fmt"
+	iox "github.com/goplus/gop/builtin/iox"
+	io "io"
+)
+
+var r io.Reader
+
+func main() {
+	for _gop_it := iox.Lines(r).Gop_Enum(); ; {
+		var _gop_ok bool
+		line, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(line)
+	}
+}
+`)
+}
+
+func TestMixedGo(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+import "strconv"
+
+const n = 10
+
+func f(v int) string {
+	return strconv.Itoa(v)
+}
+
+type foo struct {
+	v int
+}
+
+func (a foo) _() {
+}
+
+func (a foo) Str() string {
+	return f(a.v)
+}
+
+func (a *foo) Bar() int {
+	return 0
+}
+
+type foo2 = foo
+type foo3 foo2
+`, `
+var a [n]int
+var b string = f(n)
+var c foo2
+var d int = c.v
+var e = foo3{}
+var x string = c.str
+`, `package main
+
+var a [10]int
+var b string = f(n)
+var c foo
+var d int = c.v
+var e = foo3{}
+var x string = c.Str()
+`, true)
+	gopMixedClTest(t, "main", `package main
+type Point struct {
+	X int
+	Y int
+}
+`, `
+type T struct{}
+println(&T{},&Point{10,20})
+`, `package main
+
+import fmt "fmt"
+
+type T struct {
+}
+
+func main() {
+	fmt.Println(&T{}, &Point{10, 20})
+}
+`, false)
+}
+
+func Test_RangeExpressionIf_Issue1243(t *testing.T) {
+	gopClTest(t, `
+for i <- :10, i%3 == 0 {
+	println i
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 0; i < 10; i += 1 {
+		if i%3 == 0 {
+			fmt.Println(i)
+		}
+	}
+}
+`)
+}
+
+func Test_CastSlice_Issue1240(t *testing.T) {
+	gopClTest(t, `
+type fvec []float64
+type foo float64
+a := []float64([1, 2])
+b := fvec([1, 2])
+c := foo([1, 2])
+d := fvec([])
+println a, b, c, d
+`, `package main
+
+import fmt "fmt"
+
+type fvec []float64
+type foo float64
+
+func main() {
+	a := []float64{1, 2}
+	b := fvec{1, 2}
+	c := foo([]int{1, 2})
+	d := fvec{}
+	fmt.Println(a, b, c, d)
+}
+`)
+}
+
+func TestUnderscoreRedeclared_Issue1197(t *testing.T) {
+	gopClTest(t, `
+func() (_ [2]int) { type _ int; return }()
+`, `package main
+
+func main() {
+	func() (_ [2]int) {
+		return
+	}()
+}
+`)
+}
+
+func TestInterfaceBugNilUnderlying_Issue1198(t *testing.T) {
+	gopClTest(t, `
+import "runtime"
+
+type Outer interface{ Inner }
+
+type impl struct{}
+
+func New() Outer { return &impl{} }
+
+type Inner interface {
+	DoStuff() error
+}
+
+func (a *impl) DoStuff() error {
+	return nil
+}
+
+func main() {
+	var outer Outer = New()
+}
+`, `package main
+
+type Outer interface {
+	Inner
+}
+type impl struct {
+}
+type Inner interface {
+	DoStuff() error
+}
+
+func New() Outer {
+	return &impl{}
+}
+func (a *impl) DoStuff() error {
+	return nil
+}
+func main() {
+	var outer Outer = New()
+}
+`)
+}
+
+func TestInterfaceBugNilUnderlying_Issue1196(t *testing.T) {
+	gopClTest(t, `
+func main() {
+	i := I(A{})
+
+	b := make(chan I, 1)
+	b <- B{}
+
+	var ok bool
+	i, ok = <-b
+}
+
+type I interface{ M() int }
+
+type T int
+
+func (T) M() int { return 0 }
+
+type A struct{ T }
+type B struct{ T }
+`, `package main
+
+type I interface {
+	M() int
+}
+type T int
+type A struct {
+	T
+}
+type B struct {
+	T
+}
+
+func main() {
+	i := I(A{})
+	b := make(chan I, 1)
+	b <- B{}
+	var ok bool
+	i, ok = <-b
+}
+func (T) M() int {
+	return 0
+}
+`)
+}
+
+func TestMyIntInc_Issue1195(t *testing.T) {
+	gopClTest(t, `
+type MyInt int
+var c MyInt
+c++
+`, `package main
+
+type MyInt int
+
+var c MyInt
+
+func main() {
+	c++
+}
+`)
+}
+
+func TestAutoPropMixedName_Issue1194(t *testing.T) {
+	gopClTest(t, `
+type Point struct {
+	Min, Max int
+}
+
+type Obj struct {
+	bbox Point
+}
+
+func (o *Obj) Bbox() Point {
+	return o.bbox
+}
+
+func (o *Obj) Points() [2]int{
+	return [2]int{o.bbox.Min, o.bbox.Max}
+}
+`, `package main
+
+type Point struct {
+	Min int
+	Max int
+}
+type Obj struct {
+	bbox Point
+}
+
+func (o *Obj) Bbox() Point {
+	return o.bbox
+}
+func (o *Obj) Points() [2]int {
+	return [2]int{o.bbox.Min, o.bbox.Max}
+}
+`)
+}
+
+func TestShiftUntypedInt_Issue1193(t *testing.T) {
+	gopClTest(t, `
+func GetValue(shift uint) uint {
+	return 1 << shift
+}`, `package main
+
+func GetValue(shift uint) uint {
+	return 1 << shift
+}
+`)
+}
+
+func TestInitFunc(t *testing.T) {
+	gopClTest(t, `
+
+func init() {}
+func init() {}
+`, `package main
+
+func init() {
+}
+func init() {
+}
+`)
+}
+
+func TestSlogan(t *testing.T) {
+	gopClTest(t, `
+fields := ["engineering", "STEM education", "data science"]
+println "The Go+ Language for", fields.join(", ")
+`, `package main
+
+import (
+	fmt "fmt"
+	strings "strings"
+)
+
+func main() {
+	fields := []string{"engineering", "STEM education", "data science"}
+	fmt.Println("The Go+ Language for", strings.Join(fields, ", "))
+}
+`)
+}
+
+func TestAssignPrintln(t *testing.T) {
+	gopClTest(t, `
+p := println
+p "Hello world"
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	p := fmt.Println
+	p("Hello world")
+}
+`)
+}
+
+func TestRedefineBuiltin(t *testing.T) {
+	gopClTest(t, `
+func main() {
+	const a = append + len
+}
+
+const (
+	append = iota
+	len
+)
+`, `package main
+
+const (
+	append = iota
+	len
+)
+
+func main() {
+	const a = append + len
+}
+`)
+}
+
+func TestTypeConvIssue804(t *testing.T) {
+	gopClTest(t, `
+c := make(chan int)
+d := (chan<- int)(c)
+e := (<-chan int)(c)
+f := (*int)(nil)
+a := c == d
+b := c == e
+`, `package main
+
+func main() {
+	c := make(chan int)
+	d := (chan<- int)(c)
+	e := (<-chan int)(c)
+	f := (*int)(nil)
+	a := c == d
+	b := c == e
+}
+`)
+}
+
+func TestUntypedFloatIssue798(t *testing.T) {
+	gopClTest(t, `
+func isPow10(x uint64) bool {
+	switch x {
+	case 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9,
+		1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19:
+		return true
+	}
+	return false
+}
+`, `package main
+
+func isPow10(x uint64) bool {
+	switch x {
+	case 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19:
+		return true
+	}
+	return false
+}
+`)
+}
+
+func TestInterfaceIssue795(t *testing.T) {
+	gopClTest(t, `
+type I interface {
+	a(s string) I
+	b(s string) string
+}
+
+type T1 int
+
+func (t T1) a(s string) I {
+	return t
+}
+
+func (T1) b(s string) string {
+	return s
+}
+`, `package main
+
+type I interface {
+	a(s string) I
+	b(s string) string
+}
+type T1 int
+
+func (t T1) a(s string) I {
+	return t
+}
+func (T1) b(s string) string {
+	return s
+}
+`)
+}
+
+func TestChanRecvIssue789(t *testing.T) {
+	gopClTest(t, `
+func foo(ch chan int) (int, bool) {
+	x, ok := (<-ch)
+	return x, ok
+}
+`, `package main
+
+func foo(ch chan int) (int, bool) {
+	x, ok := <-ch
+	return x, ok
+}
+`)
+}
+
+func TestNamedChanCloseIssue790(t *testing.T) {
+	gopClTest(t, `
+type XChan chan int
+
+func foo(ch XChan) {
+	close(ch)
+}
+`, `package main
+
+type XChan chan int
+
+func foo(ch XChan) {
+	close(ch)
+}
+`)
+}
+
+func TestUntypedFloatIssue793(t *testing.T) {
+	gopClTest(t, `
+var a [1e1]int
+`, `package main
+
+var a [10]int
+`)
+}
+
+func TestUntypedFloatIssue788(t *testing.T) {
+	gopClTest(t, `
+func foo(v int) bool {
+    return v > 1.1e5
+}
+`, `package main
+
+func foo(v int) bool {
+	return v > 1.1e5
+}
+`)
+}
+
+func TestSwitchCompositeLitIssue801(t *testing.T) {
+	gopClTest(t, `
+type T struct {
+	X int
+}
+
+switch (T{}) {
+case T{1}:
+	panic("bad")
+}
+`, `package main
+
+type T struct {
+	X int
+}
+
+func main() {
+	switch (T{}) {
+	case T{1}:
+		panic("bad")
+	}
+}
+`)
+}
+
+func TestConstIssue800(t *testing.T) {
+	gopClTest(t, `
+const (
+	h0_0, h0_1 = 1.0 / (iota + 1), 1.0 / (iota + 2)
+	h1_0, h1_1
+)
+`, `package main
+
+const (
+	h0_0, h0_1 = 1.0 / (iota + 1), 1.0 / (iota + 2)
+	h1_0, h1_1
+)
+`)
+}
+
+func TestConstIssue805(t *testing.T) {
+	gopClTest(t, `
+const (
+	n1 = +5
+	d1 = +3
+
+	q1 = +1
+	r1 = +2
+)
+
+const (
+	ret1 = n1/d1 != q1
+	ret2 = n1%d1 != r1
+	ret3 = n1/d1 != q1 || n1%d1 != r1
+)
+`, `package main
+
+const (
+	n1 = +5
+	d1 = +3
+	q1 = +1
+	r1 = +2
+)
+const (
+	ret1 = n1/d1 != q1
+	ret2 = n1%d1 != r1
+	ret3 = false
+)
+`)
+}
+
+func TestUntypedNilIssue806(t *testing.T) {
+	gopClTest(t, `
+switch f := func() {}; f {
+case nil:
+}
+`, `package main
+
+func main() {
+	switch f := func() {
+	}; f {
+	case nil:
+	}
+}
+`)
+}
+
+func TestSwitchIssue807(t *testing.T) {
+	gopClTest(t, `
+switch {
+case interface{}(true):
+}
+`, `package main
+
+func main() {
+	switch {
+	case interface {
+	}(true):
+	}
+}
+`)
+}
+
+func TestUntypedComplexIssue799(t *testing.T) {
+	gopClTest(t, `
+const ulp1 = imag(1i + 2i / 3 - 5i / 3)
+const ulp2 = imag(1i + complex(0, 2) / 3 - 5i / 3)
+
+func main() {
+	const a = (ulp1 == ulp2)
+}
+`, `package main
+
+const ulp1 = imag(1i + 2i/3 - 5i/3)
+const ulp2 = imag(1i + complex(0, 2)/3 - 5i/3)
+
+func main() {
+	const a = ulp1 == ulp2
+}
+`)
+}
+
+func TestUnderscoreConstAndVar(t *testing.T) {
+	gopClTest(t, `
+const (
+	c0 = 1 << iota
+	_
+	_
+	_
+	c4
+)
+
+func i() int {
+	return 23
+}
+
+var (
+	_ = i()
+	_ = i()
+)
+`, `package main
+
+const (
+	c0 = 1 << iota
+	_
+	_
+	_
+	c4
+)
+
+var (
+	_ = i()
+	_ = i()
+)
+
+func i() int {
+	return 23
+}
+`)
+}
+
+func TestUnderscoreFuncAndMethod(t *testing.T) {
+	gopClTest(t, `
+func _() {
+}
+
+type T struct {
+	_, _, _ int
+}
+
+func (T) _() {
+}
+
+func (T) _() {
+}
+`, `package main
+
+type T struct {
+	_ int
+	_ int
+	_ int
+}
+
+func _() {
+}
+func (T) _() {
+}
+func (T) _() {
+}
+`)
+}
+
+func TestErrWrapIssue772(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func t() (int,int,error){
+	return 0, 0, nil
+}
+
+func main() {
+	a, b := t()!
+	println(a, b)
+}`, `package main
+
+import (
+	fmt "fmt"
+	errors "github.com/qiniu/x/errors"
+)
+
+func t() (int, int, error) {
+	return 0, 0, nil
+}
+func main() {
+	a, b := func() (_gop_ret int, _gop_ret2 int) {
+		var _gop_err error
+		_gop_ret, _gop_ret2, _gop_err = t()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "t()", "/foo/bar.gop", 9, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()
+	fmt.Println(a, b)
+}
+`)
+}
+
+func TestErrWrapIssue778(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func t() error {
+	return nil
+}
+
+func main() {
+	t()!
+}`, `package main
+
+import errors "github.com/qiniu/x/errors"
+
+func t() error {
+	return nil
+}
+func main() {
+	func() {
+		var _gop_err error
+		_gop_err = t()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "t()", "/foo/bar.gop", 9, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestIssue774(t *testing.T) {
+	gopClNamedTest(t, "InterfaceTypeAssert", `
+package main
+
+import "fmt"
+
+func main() {
+	var a AA = &A{str: "hello"}
+	fmt.Println(a.(*A))
+}
+
+type AA interface {
+	String() string
+}
+
+type A struct {
+	str string
+}
+
+func (a *A) String() string {
+	return a.str
+}
+`, `package main
+
+import fmt "fmt"
+
+type AA interface {
+	String() string
+}
+type A struct {
+	str string
+}
+
+func main() {
+	var a AA = &A{str: "hello"}
+	fmt.Println(a.(*A))
+}
+func (a *A) String() string {
+	return a.str
+}
+`)
+	gopClNamedTest(t, "getInterface", `
+package main
+
+import "fmt"
+
+func main() {
+	a := get()
+	fmt.Println(a.(*A))
+}
+
+func get() AA {
+	var a AA
+	return a
+}
+
+type AA interface {
+	String() string
+}
+
+type A struct {
+	str string
+}
+
+func (a *A) String() string {
+	return a.str
+}
+`, `package main
+
+import fmt "fmt"
+
+type AA interface {
+	String() string
+}
+type A struct {
+	str string
+}
+
+func main() {
+	a := get()
+	fmt.Println(a.(*A))
+}
+func get() AA {
+	var a AA
+	return a
+}
+func (a *A) String() string {
+	return a.str
+}
+`)
+}
+
+func TestBlockStmt(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func main() {
+	{
+		type T int
+		t := T(100)
+		println(t)
+	}
+	{
+		type T string
+		t := "hello"
+		println(t)
+	}
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	{
+		type T int
+		t := T(100)
+		fmt.Println(t)
+	}
+	{
+		type T string
+		t := "hello"
+		fmt.Println(t)
+	}
+}
+`)
+}
+
+func TestConstTypeConvIssue792(t *testing.T) {
+	gopClTest(t, `
+const dots = ". . . " + ". . . . . "
+const n = uint(len(dots))
+`, `package main
+
+const dots = ". . . " + ". . . . . "
+const n = uint(len(dots))
+`)
+}
+
+func TestConstDeps(t *testing.T) {
+	gopClTest(t, `
+const (
+	i = a
+	a = 1
+)
+`, `package main
+
+const (
+	i = a
+	a = 1
+)
+`)
+}
+
+func TestVarInitTwoValueIssue791(t *testing.T) {
+	gopClTest(t, `
+var (
+	m      = map[string]string{"a": "A"}
+	a, ok  = m["a"]
+)
+`, `package main
+
+var (
+	m     = map[string]string{"a": "A"}
+	a, ok = m["a"]
+)
+`)
+}
+
+func TestVarAfterMain(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func main() {
+	println(i)
+}
+
+var i int
+`, `package main
+
+import fmt "fmt"
+
+var i int
+
+func main() {
+	fmt.Println(i)
+}
+`)
+	gopClTest(t, `
+package main
+
+func f(v float64) float64 {
+	return v
+}
+func main() {
+	sink = f(100)
+}
+
+var sink float64
+`, `package main
+
+var sink float64
+
+func f(v float64) float64 {
+	return v
+}
+func main() {
+	sink = f(100)
+}
+`)
+}
+
+func TestVarAfterMain2(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func main() {
+	println(i)
+}
+
+var i = 100
+`, `package main
+
+import fmt "fmt"
+
+var i = 100
+
+func main() {
+	fmt.Println(i)
+}
+`)
+}
+
+func TestVarInMain(t *testing.T) {
+	gopClTest(t, `
+package main
+
+func main() {
+	v := []uint64{2, 3, 5}
+	var n = len(v)
+	println(n)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	v := []uint64{2, 3, 5}
+	var n = len(v)
+	fmt.Println(n)
+}
+`)
+}
+
+func TestSelect(t *testing.T) {
+	gopClTest(t, `
+
+func consume(xchg chan int) {
+	select {
+	case c := <-xchg:
+		println(c)
+	case xchg <- 1:
+		println("send ok")
+	default:
+		println(0)
+	}
+}
+`, `package main
+
+import fmt "fmt"
+
+func consume(xchg chan int) {
+	select {
+	case c := <-xchg:
+		fmt.Println(c)
+	case xchg <- 1:
+		fmt.Println("send ok")
+	default:
+		fmt.Println(0)
+	}
+}
+`)
+}
+
+func TestTypeSwitch(t *testing.T) {
+	gopClTest(t, `
+
+func bar(p *interface{}) {
+}
+
+func foo(v interface{}) {
+	switch t := v.(type) {
+	case int, string:
+		bar(&v)
+	case bool:
+		var x bool = t
+	default:
+		bar(nil)
+	}
+}
+`, `package main
+
+func bar(p *interface {
+}) {
+}
+func foo(v interface {
+}) {
+	switch t := v.(type) {
+	case int, string:
+		bar(&v)
+	case bool:
+		var x bool = t
+	default:
+		bar(nil)
+	}
+}
+`)
+}
+
+func TestTypeSwitch2(t *testing.T) {
+	gopClTest(t, `
+
+func bar(p *interface{}) {
+}
+
+func foo(v interface{}) {
+	switch bar(nil); v.(type) {
+	case int, string:
+		bar(&v)
+	}
+}
+`, `package main
+
+func bar(p *interface {
+}) {
+}
+func foo(v interface {
+}) {
+	switch bar(nil); v.(type) {
+	case int, string:
+		bar(&v)
+	}
+}
+`)
+}
+
+func TestTypeAssert(t *testing.T) {
+	gopClTest(t, `
+
+func foo(v interface{}) {
+	x := v.(int)
+	y, ok := v.(string)
+}
+`, `package main
+
+func foo(v interface {
+}) {
+	x := v.(int)
+	y, ok := v.(string)
+}
+`)
+}
+
+func TestInterface(t *testing.T) {
+	gopClTest(t, `
+
+type Shape interface {
+	Area() float64
+}
+
+func foo(shape Shape) {
+	shape.Area()
+}
+`, `package main
+
+type Shape interface {
+	Area() float64
+}
+
+func foo(shape Shape) {
+	shape.Area()
+}
+`)
+}
+
+func TestInterfaceEmbedded(t *testing.T) {
+	gopClTest(t, `
+type Shape interface {
+	Area() float64
+}
+
+type Bar interface {
+	Shape
+}
+`, `package main
+
+type Shape interface {
+	Area() float64
+}
+type Bar interface {
+	Shape
+}
+`)
+}
+
+func TestInterfaceExample(t *testing.T) {
+	gopClTest(t, `
+type Shape interface {
+	Area() float64
+}
+
+type Rect struct {
+	x, y, w, h float64
+}
+
+func (p *Rect) Area() float64 {
+	return p.w * p.h
+}
+
+type Circle struct {
+	x, y, r float64
+}
+
+func (p *Circle) Area() float64 {
+	return 3.14 * p.r * p.r
+}
+
+func Area(shapes ...Shape) float64 {
+	s := 0.0
+	for shape <- shapes {
+		s += shape.Area()
+	}
+	return s
+}
+
+func main() {
+	rect := &Rect{0, 0, 2, 5}
+	circle := &Circle{0, 0, 3}
+	println(Area(circle, rect))
+}
+`, `package main
+
+import fmt "fmt"
+
+type Shape interface {
+	Area() float64
+}
+type Rect struct {
+	x float64
+	y float64
+	w float64
+	h float64
+}
+type Circle struct {
+	x float64
+	y float64
+	r float64
+}
+
+func (p *Rect) Area() float64 {
+	return p.w * p.h
+}
+func (p *Circle) Area() float64 {
+	return 3.14 * p.r * p.r
+}
+func Area(shapes ...Shape) float64 {
+	s := 0.0
+	for _, shape := range shapes {
+		s += shape.Area()
+	}
+	return s
+}
+func main() {
+	rect := &Rect{0, 0, 2, 5}
+	circle := &Circle{0, 0, 3}
+	fmt.Println(Area(circle, rect))
+}
+`)
+}
+
+func TestEmbeddField(t *testing.T) {
+	gopClTest(t, `import "math/big"
+
+type BigInt struct {
+	*big.Int
+}`, `package main
+
+import big "math/big"
+
+type BigInt struct {
+	*big.Int
+}
+`)
+}
+
+func TestAutoProperty(t *testing.T) {
+	gopClTest(t, `import "github.com/goplus/gop/ast/goptest"
+
+func foo(script string) {
+	doc := goptest.New(script)!
+
+	println(doc.any.funcDecl.name)
+	println(doc.any.importSpec.name)
+}
+`, `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"
+)
+
+func foo(script string) {
+	doc := func() (_gop_ret gopq.NodeSet) {
+		var _gop_err error
+		_gop_ret, _gop_err = goptest.New(script)
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "goptest.New(script)", "/foo/bar.gop", 4, "main.foo")
+			panic(_gop_err)
+		}
+		return
+	}()
+	fmt.Println(doc.Any().FuncDecl().Name())
+	fmt.Println(doc.Any().ImportSpec().Name())
+}
+`)
+}
+
+func TestSimplifyAutoProperty(t *testing.T) {
+	gopClTest(t, `import "gop/ast/goptest"
+
+func foo(script string) {
+	doc := goptest.New(script)!
+
+	println(doc.any.funcDecl.name)
+	println(doc.any.importSpec.name)
+}
+`, `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"
+)
+
+func foo(script string) {
+	doc := func() (_gop_ret gopq.NodeSet) {
+		var _gop_err error
+		_gop_ret, _gop_err = goptest.New(script)
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "goptest.New(script)", "/foo/bar.gop", 4, "main.foo")
+			panic(_gop_err)
+		}
+		return
+	}()
+	fmt.Println(doc.Any().FuncDecl().Name())
+	fmt.Println(doc.Any().ImportSpec().Name())
+}
+`)
+}
+
+func TestErrWrapBasic(t *testing.T) {
+	gopClTest(t, `
+import "strconv"
+
+func add(x, y string) (int, error) {
+	return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
+}
+`, `package main
+
+import (
+	strconv "strconv"
+	errors "github.com/qiniu/x/errors"
+)
+
+func add(x string, y string) (int, error) {
+	var _autoGo_1 int
+	{
+		var _gop_err error
+		_autoGo_1, _gop_err = strconv.Atoi(x)
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(x)", "/foo/bar.gop", 5, "main.add")
+			return 0, _gop_err
+		}
+		goto _autoGo_2
+	_autoGo_2:
+	}
+	var _autoGo_3 int
+	{
+		var _gop_err error
+		_autoGo_3, _gop_err = strconv.Atoi(y)
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "strconv.Atoi(y)", "/foo/bar.gop", 5, "main.add")
+			return 0, _gop_err
+		}
+		goto _autoGo_4
+	_autoGo_4:
+	}
+	return _autoGo_1 + _autoGo_3, nil
+}
+`)
+}
+
+func TestErrWrapDefVal(t *testing.T) {
+	gopClTest(t, `
+import "strconv"
+
+func addSafe(x, y string) int {
+	return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
+}
+`, `package main
+
+import strconv "strconv"
+
+func addSafe(x string, y string) int {
+	return func() (_gop_ret int) {
+		var _gop_err error
+		_gop_ret, _gop_err = strconv.Atoi(x)
+		if _gop_err != nil {
+			return 0
+		}
+		return
+	}() + func() (_gop_ret int) {
+		var _gop_err error
+		_gop_ret, _gop_err = strconv.Atoi(y)
+		if _gop_err != nil {
+			return 0
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestErrWrapPanic(t *testing.T) {
+	gopClTest(t, `
+var ret int = println("Hi")!
+`, `package main
+
+import (
+	fmt "fmt"
+	errors "github.com/qiniu/x/errors"
+)
+
+var ret int = func() (_gop_ret int) {
+	var _gop_err error
+	_gop_ret, _gop_err = fmt.Println("Hi")
+	if _gop_err != nil {
+		_gop_err = errors.NewFrame(_gop_err, "println(\"Hi\")", "/foo/bar.gop", 2, "main.main")
+		panic(_gop_err)
+	}
+	return
+}()
+`)
+}
+
+func TestErrWrapCommand(t *testing.T) {
+	gopClTest(t, `
+func mkdir(name string) error {
+	return nil
+}
+
+mkdir! "foo"
+`, `package main
+
+import errors "github.com/qiniu/x/errors"
+
+func mkdir(name string) error {
+	return nil
+}
+func main() {
+	func() {
+		var _gop_err error
+		_gop_err = mkdir("foo")
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "mkdir \"foo\"", "/foo/bar.gop", 6, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestErrWrapCall(t *testing.T) {
+	gopClTest(t, `
+func foo() (func(), error) {
+	return nil, nil
+}
+
+foo()!()
+`, `package main
+
+import errors "github.com/qiniu/x/errors"
+
+func foo() (func(), error) {
+	return nil, nil
+}
+func main() {
+	func() (_gop_ret func()) {
+		var _gop_err error
+		_gop_ret, _gop_err = foo()
+		if _gop_err != nil {
+			_gop_err = errors.NewFrame(_gop_err, "foo()", "/foo/bar.gop", 6, "main.main")
+			panic(_gop_err)
+		}
+		return
+	}()()
+}
+`)
+}
+
+func TestMakeAndNew(t *testing.T) {
+	gopClTest(t, `
+var a *int = new(int)
+var b map[string]int = make(map[string]int)
+var c []byte = make([]byte, 0, 2)
+`, `package main
+
+var a *int = new(int)
+var b map[string]int = make(map[string]int)
+var c []byte = make([]byte, 0, 2)
+`)
+}
+
+func TestVarDecl(t *testing.T) {
+	gopClTest(t, `
+var a int
+var x, y = 1, "Hi"
+`, `package main
+
+var a int
+var x, y = 1, "Hi"
+`)
+}
+
+func TestUint128Add(t *testing.T) {
+	gopClTest(t, `
+var x, y uint128
+var z uint128 = x + y
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x, y ng.Uint128
+var z ng.Uint128 = x.Gop_Add__1(y)
+`)
+}
+
+func TestInt128Add(t *testing.T) {
+	gopClTest(t, `
+var x, y int128
+var z int128 = x + y
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x, y ng.Int128
+var z ng.Int128 = x.Gop_Add__1(y)
+`)
+}
+
+func TestBigIntAdd(t *testing.T) {
+	gopClTest(t, `
+var x, y bigint
+var z bigint = x + y
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x, y ng.Bigint
+var z ng.Bigint = x.Gop_Add(y)
+`)
+}
+
+func TestBigIntLit(t *testing.T) {
+	gopClTest(t, `
+var x = 1r
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+var x = ng.Bigint_Init__1(big.NewInt(1))
+`)
+}
+
+func TestUint128Lit(t *testing.T) {
+	gopClTest(t, `
+var x uint128 = 1
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x ng.Uint128 = ng.Uint128_Init__0(1)
+`)
+}
+
+func TestInt128Lit(t *testing.T) {
+	gopClTest(t, `
+var x int128 = 1
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x ng.Int128 = ng.Int128_Init__0(1)
+`)
+}
+
+func TestBigRatLit(t *testing.T) {
+	gopClTest(t, `
+var x = 1/2r
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+var x = ng.Bigrat_Init__2(big.NewRat(1, 2))
+`)
+}
+
+func TestBigRatLitAdd(t *testing.T) {
+	gopClTest(t, `
+var x = 3 + 1/2r
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
+`)
+}
+
+func TestBigRatAdd(t *testing.T) {
+	gox.SetDebug(gox.DbgFlagAll)
+	gopClTest(t, `
+var x = 3 + 1/2r
+var y = x + 100
+var z = 100 + y
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+var x = ng.Bigrat_Init__2(big.NewRat(7, 2))
+var y = x.Gop_Add(ng.Bigrat_Init__0(100))
+var z = ng.Bigrat_Init__0(100) + y
+`)
+}
+
+func TestTypeConv(t *testing.T) {
+	gopClTest(t, `
+var a = (*struct{})(nil)
+var b = interface{}(nil)
+var c = (func())(nil)
+var x uint32 = uint32(0)
+var y *uint32 = (*uint32)(nil)
+`, `package main
+
+var a = (*struct {
+})(nil)
+var b = interface {
+}(nil)
+var c = (func())(nil)
+var x uint32 = uint32(0)
+var y *uint32 = (*uint32)(nil)
+`)
+}
+
+func TestStar(t *testing.T) {
+	gopClTest(t, `
+var x *uint32 = (*uint32)(nil)
+var y uint32 = *x
+`, `package main
+
+var x *uint32 = (*uint32)(nil)
+var y uint32 = *x
+`)
+}
+
+func TestLHS(t *testing.T) {
+	gopClTest(t, `
+type T struct {
+	a int
+}
+
+func foo() *T {
+	return nil
+}
+
+foo().a = 123
+`, `package main
+
+type T struct {
+	a int
+}
+
+func foo() *T {
+	return nil
+}
+func main() {
+	foo().a = 123
+}
+`)
+}
+
+func TestSend(t *testing.T) {
+	gopClTest(t, `
+var x chan bool
+x <- true
+`, `package main
+
+var x chan bool
+
+func main() {
+	x <- true
+}
+`)
+}
+
+func TestIncDec(t *testing.T) {
+	gopClTest(t, `
+var x uint32
+x++
+`, `package main
+
+var x uint32
+
+func main() {
+	x++
+}
+`)
+}
+
+func TestAssignOp(t *testing.T) {
+	gopClTest(t, `
+var x uint32
+x += 3
+`, `package main
+
+var x uint32
+
+func main() {
+	x += 3
+}
+`)
+}
+
+func TestBigIntAssignOp(t *testing.T) {
+	gopClTest(t, `
+var x bigint
+x += 3
+`, `package main
+
+import ng "github.com/goplus/gop/builtin/ng"
+
+var x ng.Bigint
+
+func main() {
+	x.Gop_AddAssign(ng.Bigint_Init__0(3))
+}
+`)
+}
+
+func TestBigIntAssignOp2(t *testing.T) {
+	gopClTest(t, `
+x := 3r
+x *= 2
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+func main() {
+	x := ng.Bigint_Init__1(big.NewInt(3))
+	x.Gop_MulAssign(ng.Bigint_Init__0(2))
+}
+`)
+}
+
+func TestBigIntAssignOp3(t *testing.T) {
+	gopClTest(t, `
+x := 3r
+x *= 2r
+`, `package main
+
+import (
+	ng "github.com/goplus/gop/builtin/ng"
+	big "math/big"
+)
+
+func main() {
+	x := ng.Bigint_Init__1(big.NewInt(3))
+	x.Gop_MulAssign(ng.Bigint_Init__1(big.NewInt(2)))
+}
+`)
+}
+
+func TestCompositeLit(t *testing.T) {
+	gopClTest(t, `
+x := []float64{1, 3.4, 5}
+y := map[string]int{"Hello": 1, "Go+": 5}
+z := [...]int{1, 3, 5}
+a := {"Hello": 1, "Go+": 5.1}
+`, `package main
+
+func main() {
+	x := []float64{1, 3.4, 5}
+	y := map[string]int{"Hello": 1, "Go+": 5}
+	z := [...]int{1, 3, 5}
+	a := map[string]float64{"Hello": 1, "Go+": 5.1}
+}
+`)
+}
+
+func TestCompositeLit2(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+	A int
+}
+
+x := []*struct{a int}{
+	{1}, {3}, {5},
+}
+y := map[foo]struct{a string}{
+	{1}: {"Hi"},
+}
+z := [...]foo{
+	{1}, {3}, {5},
+}
+`, `package main
+
+type foo struct {
+	A int
+}
+
+func main() {
+	x := []*struct {
+		a int
+	}{&struct {
+		a int
+	}{1}, &struct {
+		a int
+	}{3}, &struct {
+		a int
+	}{5}}
+	y := map[foo]struct {
+		a string
+	}{foo{1}: struct {
+		a string
+	}{"Hi"}}
+	z := [...]foo{foo{1}, foo{3}, foo{5}}
+}
+`)
+}
+
+// deduce struct type as parameters of a function call
+func TestCompositeLit3(t *testing.T) {
+	gopClTest(t, `
+type Config struct {
+	A int
+}
+
+func foo(conf *Config) {
+}
+
+func bar(conf ...Config) {
+}
+
+foo({A: 1})
+bar({A: 2})
+foo({})
+bar({})
+`, `package main
+
+type Config struct {
+	A int
+}
+
+func foo(conf *Config) {
+}
+func bar(conf ...Config) {
+}
+func main() {
+	foo(&Config{A: 1})
+	bar(Config{A: 2})
+	foo(&Config{})
+	bar(Config{})
+}
+`)
+}
+
+// deduce struct type as results of a function call
+func TestCompositeLit4(t *testing.T) {
+	gopClTest(t, `
+type Result struct {
+	A int
+}
+
+func foo() *Result {
+	return {A: 1}
+}
+`, `package main
+
+type Result struct {
+	A int
+}
+
+func foo() *Result {
+	return &Result{A: 1}
+}
+`)
+}
+
+func TestCompositeLit5(t *testing.T) {
+	gopClTest(t, `
+type mymap map[float64]string
+var x = {1:"hello", 2:"world"}
+var y map[float64]string = {1:"hello", 2:"world"}
+var z mymap = {1:"hello", 2:"world"}
+`, `package main
+
+type mymap map[float64]string
+
+var x = map[int]string{1: "hello", 2: "world"}
+var y map[float64]string = map[float64]string{1: "hello", 2: "world"}
+var z mymap = mymap{1: "hello", 2: "world"}
+`)
+}
+
+func TestSliceLit(t *testing.T) {
+	gopClTest(t, `
+x := [1, 3.4, 5]
+y := [1]
+z := []
+`, `package main
+
+func main() {
+	x := []float64{1, 3.4, 5}
+	y := []int{1}
+	z := []interface {
+	}{}
+}
+`)
+	gopClTest(t, `
+type vector []float64
+var x = [1, 2, 3]
+var y []float64 = [1, 2, 3]
+var z vector = [1, 2, 3]
+`, `package main
+
+type vector []float64
+
+var x = []int{1, 2, 3}
+var y []float64 = []float64{1, 2, 3}
+var z vector = vector{1, 2, 3}
+`)
+}
+
+func TestChan(t *testing.T) {
+	gopClTest(t, `
+a := make(chan int, 10)
+a <- 3
+var b int = <-a
+x, ok := <-a
+`, `package main
+
+func main() {
+	a := make(chan int, 10)
+	a <- 3
+	var b int = <-a
+	x, ok := <-a
+}
+`)
+}
+
+func TestKeyValModeLit(t *testing.T) {
+	gopClTest(t, `
+a := [...]float64{1, 3: 3.4, 5}
+b := []float64{2: 1.2, 3, 6: 4.5}
+`, `package main
+
+func main() {
+	a := [...]float64{1, 3: 3.4, 5}
+	b := []float64{2: 1.2, 3, 6: 4.5}
+}
+`)
+}
+
+func TestStructLit(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+	A int
+	B string "tag1:123"
+}
+
+a := struct {
+	A int
+	B string "tag1:123"
+}{1, "Hello"}
+
+b := foo{1, "Hello"}
+c := foo{B: "Hi"}
+`, `package main
+
+type foo struct {
+	A int
+	B string `+"`tag1:123`"+`
+}
+
+func main() {
+	a := struct {
+		A int
+		B string `+"`tag1:123`"+`
+	}{1, "Hello"}
+	b := foo{1, "Hello"}
+	c := foo{B: "Hi"}
+}
+`)
+}
+
+func TestStructType(t *testing.T) {
+	gopClTest(t, `
+type bar = foo
+
+type foo struct {
+	p *bar
+	A int
+	B string "tag1:123"
+}
+
+func main() {
+	type a struct {
+		p *a
+	}
+	type b = a
+}
+`, `package main
+
+type bar = foo
+type foo struct {
+	p *foo
+	A int
+	B string `+"`tag1:123`"+`
+}
+
+func main() {
+	type a struct {
+		p *a
+	}
+	type b = a
+}
+`)
+}
+
+func TestDeferGo(t *testing.T) {
+	gopClTest(t, `
+go println("Hi")
+defer println("Go+")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	go fmt.Println("Hi")
+	defer fmt.Println("Go+")
+}
+`)
+}
+
+func TestFor(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3.4, 5]
+for i := 0; i < 3; i=i+1 {
+	println(i)
+}
+for {
+	println("loop")
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	a := []float64{1, 3.4, 5}
+	for i := 0; i < 3; i = i + 1 {
+		fmt.Println(i)
+	}
+	for {
+		fmt.Println("loop")
+	}
+}
+`)
+}
+
+func TestRangeStmt(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3.4, 5]
+for _, x := range a {
+	println(x)
+}
+for i, x := range a {
+	println(i, x)
+}
+
+var i int
+var x float64
+for _, x = range a {
+	println(i, x)
+}
+for i, x = range a {
+	println(i, x)
+}
+for range a {
+	println("Hi")
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	a := []float64{1, 3.4, 5}
+	for _, x := range a {
+		fmt.Println(x)
+	}
+	for i, x := range a {
+		fmt.Println(i, x)
+	}
+	var i int
+	var x float64
+	for _, x = range a {
+		fmt.Println(i, x)
+	}
+	for i, x = range a {
+		fmt.Println(i, x)
+	}
+	for range a {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeStmtUDT(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(key int, val string)) {
+}
+
+for k, v := range new(foo) {
+	println(k, v)
+}
+`, `package main
+
+import fmt "fmt"
+
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(key int, val string)) {
+}
+func main() {
+	new(foo).Gop_Enum(func(k int, v string) {
+		fmt.Println(k, v)
+	})
+}
+`)
+}
+
+func TestForPhraseUDT(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(val string)) {
+}
+
+for v <- new(foo) {
+	println(v)
+}
+`, `package main
+
+import fmt "fmt"
+
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(val string)) {
+}
+func main() {
+	new(foo).Gop_Enum(func(v string) {
+		fmt.Println(v)
+	})
+}
+`)
+}
+
+func TestForPhraseUDT2(t *testing.T) {
+	gopClTest(t, `
+type fooIter struct {
+}
+
+func (p fooIter) Next() (key string, val int, ok bool) {
+	return
+}
+
+type foo struct {
+}
+
+func (p *foo) Gop_Enum() fooIter {
+}
+
+for k, v <- new(foo) {
+	println(k, v)
+}
+`, `package main
+
+import fmt "fmt"
+
+type fooIter struct {
+}
+type foo struct {
+}
+
+func (p fooIter) Next() (key string, val int, ok bool) {
+	return
+}
+func (p *foo) Gop_Enum() fooIter {
+}
+func main() {
+	for _gop_it := new(foo).Gop_Enum(); ; {
+		var _gop_ok bool
+		k, v, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(k, v)
+	}
+}
+`)
+}
+
+func TestForPhraseUDT3(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(val string)) {
+}
+
+println([v for v <- new(foo)])
+`, `package main
+
+import fmt "fmt"
+
+type foo struct {
+}
+
+func (p *foo) Gop_Enum(c func(val string)) {
+}
+func main() {
+	fmt.Println(func() (_gop_ret []string) {
+		new(foo).Gop_Enum(func(v string) {
+			_gop_ret = append(_gop_ret, v)
+		})
+		return
+	}())
+}
+`)
+}
+
+func TestForPhraseUDT4(t *testing.T) {
+	gopClTest(t, `
+type fooIter struct {
+	data *foo
+	idx  int
+}
+
+func (p *fooIter) Next() (key int, val string, ok bool) {
+	if p.idx < len(p.data.key) {
+		key, val, ok = p.data.key[p.idx], p.data.val[p.idx], true
+		p.idx++
+	}
+	return
+}
+
+type foo struct {
+	key []int
+	val []string
+}
+
+func newFoo() *foo {
+	return &foo{key: [3, 7], val: ["Hi", "Go+"]}
+}
+
+func (p *foo) Gop_Enum() *fooIter {
+	return &fooIter{data: p}
+}
+
+for k, v <- newFoo() {
+	println(k, v)
+}
+`, `package main
+
+import fmt "fmt"
+
+type fooIter struct {
+	data *foo
+	idx  int
+}
+type foo struct {
+	key []int
+	val []string
+}
+
+func (p *fooIter) Next() (key int, val string, ok bool) {
+	if p.idx < len(p.data.key) {
+		key, val, ok = p.data.key[p.idx], p.data.val[p.idx], true
+		p.idx++
+	}
+	return
+}
+func newFoo() *foo {
+	return &foo{key: []int{3, 7}, val: []string{"Hi", "Go+"}}
+}
+func (p *foo) Gop_Enum() *fooIter {
+	return &fooIter{data: p}
+}
+func main() {
+	for _gop_it := newFoo().Gop_Enum(); ; {
+		var _gop_ok bool
+		k, v, _gop_ok := _gop_it.Next()
+		if !_gop_ok {
+			break
+		}
+		fmt.Println(k, v)
+	}
+}
+`)
+}
+
+func TestForPhrase(t *testing.T) {
+	gopClTest(t, `
+sum := 0
+for x <- [1, 3, 5, 7, 11, 13, 17], x > 3 {
+	sum = sum + x
+}
+for i, x <- [1, 3, 5, 7, 11, 13, 17] {
+	sum = sum + i*x
+}
+println("sum(5,7,11,13,17):", sum)
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	sum := 0
+	for _, x := range []int{1, 3, 5, 7, 11, 13, 17} {
+		if x > 3 {
+			sum = sum + x
+		}
+	}
+	for i, x := range []int{1, 3, 5, 7, 11, 13, 17} {
+		sum = sum + i*x
+	}
+	fmt.Println("sum(5,7,11,13,17):", sum)
+}
+`)
+}
+
+func TestMapComprehension(t *testing.T) {
+	gopClTest(t, `
+y := {x: i for i, x <- ["1", "3", "5", "7", "11"]}
+`, `package main
+
+func main() {
+	y := func() (_gop_ret map[string]int) {
+		_gop_ret = map[string]int{}
+		for i, x := range []string{"1", "3", "5", "7", "11"} {
+			_gop_ret[x] = i
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestMapComprehensionCond(t *testing.T) {
+	gopClTest(t, `
+z := {v: k for k, v <- {"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7}, v > 3}
+`, `package main
+
+func main() {
+	z := func() (_gop_ret map[int]string) {
+		_gop_ret = map[int]string{}
+		for k, v := range map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7} {
+			if v > 3 {
+				_gop_ret[v] = k
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestMapComprehensionCond2(t *testing.T) {
+	gopClTest(t, `
+z := {t: k for k, v <- {"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7}, t := v; t > 3}
+`, `package main
+
+func main() {
+	z := func() (_gop_ret map[int]string) {
+		_gop_ret = map[int]string{}
+		for k, v := range map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7} {
+			if t := v; t > 3 {
+				_gop_ret[t] = k
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestExistsComprehension(t *testing.T) {
+	gopClTest(t, `
+hasFive := {for x <- ["1", "3", "5", "7", "11"], x == "5"}
+`, `package main
+
+func main() {
+	hasFive := func() (_gop_ok bool) {
+		for _, x := range []string{"1", "3", "5", "7", "11"} {
+			if x == "5" {
+				return true
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestSelectComprehension(t *testing.T) {
+	gopClTest(t, `
+y := {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"}
+`, `package main
+
+func main() {
+	y := func() (_gop_ret int) {
+		for i, x := range []string{"1", "3", "5", "7", "11"} {
+			if x == "5" {
+				return i
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestSelectComprehensionTwoValue(t *testing.T) {
+	gopClTest(t, `
+y, ok := {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"}
+`, `package main
+
+func main() {
+	y, ok := func() (_gop_ret int, _gop_ok bool) {
+		for i, x := range []string{"1", "3", "5", "7", "11"} {
+			if x == "5" {
+				return i, true
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestSelectComprehensionRetTwoValue(t *testing.T) {
+	gopClTest(t, `
+func foo() (int, bool) {
+	return {i for i, x <- ["1", "3", "5", "7", "11"], x == "5"}
+}
+`, `package main
+
+func foo() (int, bool) {
+	return func() (_gop_ret int, _gop_ok bool) {
+		for i, x := range []string{"1", "3", "5", "7", "11"} {
+			if x == "5" {
+				return i, true
+			}
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestListComprehension(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3.4, 5]
+b := [x*x for x <- a]
+`, `package main
+
+func main() {
+	a := []float64{1, 3.4, 5}
+	b := func() (_gop_ret []float64) {
+		for _, x := range a {
+			_gop_ret = append(_gop_ret, x*x)
+		}
+		return
+	}()
+}
+`)
+}
+
+func TestListComprehensionMultiLevel(t *testing.T) {
+	gopClTest(t, `
+arr := [1, 2, 3, 4.1, 5, 6]
+x := [[a, b] for a <- arr, a < b for b <- arr, b > 2]
+println("x:", x)
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	arr := []float64{1, 2, 3, 4.1, 5, 6}
+	x := func() (_gop_ret [][]float64) {
+		for _, b := range arr {
+			if b > 2 {
+				for _, a := range arr {
+					if a < b {
+						_gop_ret = append(_gop_ret, []float64{a, b})
+					}
+				}
+			}
+		}
+		return
+	}()
+	fmt.Println("x:", x)
+}
+`)
+}
+
+func TestSliceGet(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3, 5, 7, 9]
+b := a[:3]
+c := a[1:]
+d := a[1:2:3]
+e := "Hello, Go+"[7:]
+`, `package main
+
+func main() {
+	a := []int{1, 3, 5, 7, 9}
+	b := a[:3]
+	c := a[1:]
+	d := a[1:2:3]
+	e := "Hello, Go+"[7:]
+}
+`)
+}
+
+func TestIndexGetTwoValue(t *testing.T) {
+	gopClTest(t, `
+a := {"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7}
+x, ok := a["Hi"]
+y := a["Go+"]
+`, `package main
+
+func main() {
+	a := map[string]int{"Hello": 1, "Hi": 3, "xsw": 5, "Go+": 7}
+	x, ok := a["Hi"]
+	y := a["Go+"]
+}
+`)
+}
+
+func TestIndexGet(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3.4, 5]
+b := a[1]
+`, `package main
+
+func main() {
+	a := []float64{1, 3.4, 5}
+	b := a[1]
+}
+`)
+}
+
+func TestIndexRef(t *testing.T) {
+	gopClTest(t, `
+a := [1, 3.4, 5]
+a[1] = 2.1
+`, `package main
+
+func main() {
+	a := []float64{1, 3.4, 5}
+	a[1] = 2.1
+}
+`)
+}
+
+func TestIndexArrayPtrIssue784(t *testing.T) {
+	gopClTest(t, `
+type intArr [2]int
+
+func foo(a *intArr) {
+	a[1] = 10
+}
+`, `package main
+
+type intArr [2]int
+
+func foo(a *intArr) {
+	a[1] = 10
+}
+`)
+}
+
+func TestMemberVal(t *testing.T) {
+	gopClTest(t, `import "strings"
+
+x := strings.NewReplacer("?", "!").Replace("hello, world???")
+println("x:", x)
+`, `package main
+
+import (
+	fmt "fmt"
+	strings "strings"
+)
+
+func main() {
+	x := strings.NewReplacer("?", "!").Replace("hello, world???")
+	fmt.Println("x:", x)
+}
+`)
+}
+
+func TestNamedPtrMemberIssue786(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+	req int
+}
+
+type pfoo *foo
+
+func bar(p pfoo) {
+	println(p.req)
+}
+`, `package main
+
+import fmt "fmt"
+
+type foo struct {
+	req int
+}
+type pfoo *foo
+
+func bar(p pfoo) {
+	fmt.Println(p.req)
+}
+`)
+}
+
+func TestMember(t *testing.T) {
+	gopClTest(t, `
+
+import "flag"
+
+a := &struct {
+	A int
+	B string
+}{1, "Hello"}
+
+x := a.A
+a.B = "Hi"
+
+flag.Usage = nil
+`, `package main
+
+import flag "flag"
+
+func main() {
+	a := &struct {
+		A int
+		B string
+	}{1, "Hello"}
+	x := a.A
+	a.B = "Hi"
+	flag.Usage = nil
+}
+`)
+}
+
+func TestElem(t *testing.T) {
+	gopClTest(t, `
+
+func foo(a *int, b int) {
+	b = *a
+	*a = b
+}
+`, `package main
+
+func foo(a *int, b int) {
+	b = *a
+	*a = b
+}
+`)
+}
+
+func TestNamedPtrIssue797(t *testing.T) {
+	gopClTest(t, `
+type Bar *int
+
+func foo(a Bar) {
+	var b int = *a
+}
+`, `package main
+
+type Bar *int
+
+func foo(a Bar) {
+	var b int = *a
+}
+`)
+}
+
+func TestMethod(t *testing.T) {
+	gopClTest(t, `
+type M int
+
+func (m M) Foo() {
+	println("foo", m)
+}
+
+func (M) Bar() {
+	println("bar")
+}
+`, `package main
+
+import fmt "fmt"
+
+type M int
+
+func (m M) Foo() {
+	fmt.Println("foo", m)
+}
+func (M) Bar() {
+	fmt.Println("bar")
+}
+`)
+}
+
+func TestOverloadOp(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (a *foo) + (b *foo) *foo {
+	println("a + b")
+	return &foo{}
+}
+
+func (a foo) - (b foo) foo {
+	println("a - b")
+	return foo{}
+}
+
+func -(a foo) {
+	println("-a")
+}
+
+func ++(a foo) {
+	println("a++")
+}
+
+func (a foo) != (b foo) bool{
+	println("a!=b")	
+	return true
+}
+
+var a, b foo
+var c = a - b
+var d = -a       // TODO: -a have no return value!
+var e = a!=b
+`, `package main
+
+import fmt "fmt"
+
+type foo struct {
+}
+
+var a, b foo
+var c = a.Gop_Sub(b)
+var d = a.Gop_Neg()
+var e = a.Gop_NE(b)
+
+func (a *foo) Gop_Add(b *foo) *foo {
+	fmt.Println("a + b")
+	return &foo{}
+}
+func (a foo) Gop_Sub(b foo) foo {
+	fmt.Println("a - b")
+	return foo{}
+}
+func (a foo) Gop_Neg() {
+	fmt.Println("-a")
+}
+func (a foo) Gop_Inc() {
+	fmt.Println("a++")
+}
+func (a foo) Gop_NE(b foo) bool {
+	fmt.Println("a!=b")
+	return true
+}
+`)
+}
+
+func TestCmdlineNoEOL(t *testing.T) {
+	gopClTest(t, `println "Hi"`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("Hi")
+}
+`)
+}
+
+func TestImport(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func main() {
+	fmt.println "Hi"
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("Hi")
+}
+`)
+}
+
+func TestDotImport(t *testing.T) {
+	gopClTest(t, `import . "math"
+
+var a = round(1.2)
+`, `package main
+
+import math "math"
+
+var a = math.Round(1.2)
+`)
+}
+
+func TestLocalImport(t *testing.T) {
+	gopClTest(t, `import "./internal/spx"
+
+var a = spx.TestIntValue
+`, `package main
+
+import spx "github.com/goplus/gop/cl/internal/spx"
+
+var a = spx.TestIntValue
+`)
+}
+
+func TestImportUnused(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func main() {
+}`, `package main
+
+func main() {
+}
+`)
+}
+
+func TestImportForceUsed(t *testing.T) {
+	gopClTest(t, `import _ "fmt"
+
+func main() {
+}`, `package main
+
+import _ "fmt"
+
+func main() {
+}
+`)
+}
+
+func TestAnonymousImport(t *testing.T) {
+	gopClTest(t, `println("Hello")
+printf("Hello Go+\n")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("Hello")
+	fmt.Printf("Hello Go+\n")
+}
+`)
+}
+
+func TestVarAndConst(t *testing.T) {
+	gopClTest(t, `
+const (
+	i = 1
+	x float64 = 1
+)
+var j int = i
+`, `package main
+
+const (
+	i         = 1
+	x float64 = 1
+)
+
+var j int = i
+`)
+}
+
+func TestDeclStmt(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func main() {
+	const (
+		i = 1
+		x float64 = 1
+	)
+	var j int = i
+	fmt.Println("Hi")
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	const (
+		i         = 1
+		x float64 = 1
+	)
+	var j int = i
+	fmt.Println("Hi")
+}
+`)
+}
+
+func TestIf(t *testing.T) {
+	gopClTest(t, `x := 0
+if t := false; t {
+	x = 3
+} else if !t {
+	x = 5
+} else {
+	x = 7
+}
+println("x:", x)
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	x := 0
+	if t := false; t {
+		x = 3
+	} else if !t {
+		x = 5
+	} else {
+		x = 7
+	}
+	fmt.Println("x:", x)
+}
+`)
+}
+
+func TestSwitch(t *testing.T) {
+	gopClTest(t, `x := 0
+switch s := "Hello"; s {
+default:
+	x = 7
+case "world", "hi":
+	x = 5
+case "xsw":
+	x = 3
+}
+println("x:", x)
+
+v := "Hello"
+switch {
+case v == "xsw":
+	x = 3
+case v == "hi", v == "world":
+	x = 9
+default:
+	x = 11
+}
+println("x:", x)
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	x := 0
+	switch s := "Hello"; s {
+	default:
+		x = 7
+	case "world", "hi":
+		x = 5
+	case "xsw":
+		x = 3
+	}
+	fmt.Println("x:", x)
+	v := "Hello"
+	switch {
+	case v == "xsw":
+		x = 3
+	case v == "hi", v == "world":
+		x = 9
+	default:
+		x = 11
+	}
+	fmt.Println("x:", x)
+}
+`)
+}
+
+func TestSwitchFallthrough(t *testing.T) {
+	gopClTest(t, `v := "Hello"
+switch v {
+case "Hello":
+	println(v)
+	fallthrough
+case "hi":
+	println(v)
+	fallthrough
+default:
+	println(v)
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	v := "Hello"
+	switch v {
+	case "Hello":
+		fmt.Println(v)
+		fallthrough
+	case "hi":
+		fmt.Println(v)
+		fallthrough
+	default:
+		fmt.Println(v)
+	}
+}
+`)
+}
+
+func TestBranchStmt(t *testing.T) {
+	gopClTest(t, `
+	a := [1, 3.4, 5]
+label:
+	for i := 0; i < 3; i=i+1 {
+		println(i)
+		break
+		break label
+		continue
+		continue label
+		goto label
+	}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	a := []float64{1, 3.4, 5}
+label:
+	for i := 0; i < 3; i = i + 1 {
+		fmt.Println(i)
+		break
+		break label
+		continue
+		continue label
+		goto label
+	}
+}
+`)
+}
+
+func TestReturn(t *testing.T) {
+	gopClTest(t, `
+func foo(format string, args ...interface{}) (int, error) {
+	return printf(format, args...)
+}
+
+func main() {
+}
+`, `package main
+
+import fmt "fmt"
+
+func foo(format string, args ...interface {
+}) (int, error) {
+	return fmt.Printf(format, args...)
+}
+func main() {
+}
+`)
+}
+
+func TestReturnExpr(t *testing.T) {
+	gopClTest(t, `
+func foo(format string, args ...interface{}) (int, error) {
+	return 0, nil
+}
+
+func main() {
+}
+`, `package main
+
+func foo(format string, args ...interface {
+}) (int, error) {
+	return 0, nil
+}
+func main() {
+}
+`)
+}
+
+func TestClosure(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func(v string) {
+	fmt.Println(v)
+}("Hello")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	func(v string) {
+		fmt.Println(v)
+	}("Hello")
+}
+`)
+}
+
+func TestFunc(t *testing.T) {
+	gopClTest(t, `func foo(format string, a [10]int, args ...interface{}) {
+}
+
+func main() {
+}`, `package main
+
+func foo(format string, a [10]int, args ...interface {
+}) {
+}
+func main() {
+}
+`)
+}
+
+func TestLambdaExpr(t *testing.T) {
+	gopClTest(t, `
+func Map(c []float64, t func(float64) float64) {
+	// ...
+}
+
+func Map2(c []float64, t func(float64) (float64, float64)) {
+	// ...
+}
+
+Map([1.2, 3.5, 6], x => x * x)
+Map2([1.2, 3.5, 6], x => (x * x, x + x))
+`, `package main
+
+func Map(c []float64, t func(float64) float64) {
+}
+func Map2(c []float64, t func(float64) (float64, float64)) {
+}
+func main() {
+	Map([]float64{1.2, 3.5, 6}, func(x float64) float64 {
+		return x * x
+	})
+	Map2([]float64{1.2, 3.5, 6}, func(x float64) (float64, float64) {
+		return x * x, x + x
+	})
+}
+`)
+	gopClTest(t, `type Foo struct {
+	Plot func(x float64) (float64, float64)
+}
+foo := &Foo{
+	Plot: x => (x * 2, x * x),
+}`, `package main
+
+type Foo struct {
+	Plot func(x float64) (float64, float64)
+}
+
+func main() {
+	foo := &Foo{Plot: func(x float64) (float64, float64) {
+		return x * 2, x * x
+	}}
+}
+`)
+	gopClTest(t, `
+type Fn func(x float64) (float64, float64)
+type Foo struct {
+	Plot Fn
+}
+foo := &Foo{
+	Plot: x => (x * 2, x * x),
+}`, `package main
+
+type Fn func(x float64) (float64, float64)
+type Foo struct {
+	Plot Fn
+}
+
+func main() {
+	foo := &Foo{Plot: func(x float64) (float64, float64) {
+		return x * 2, x * x
+	}}
+}
+`)
+	gopClTest(t, `
+type Fn func() (int, error)
+func Do(fn Fn) {
+}
+
+Do => (100, nil)
+`, `package main
+
+type Fn func() (int, error)
+
+func Do(fn Fn) {
+}
+func main() {
+	Do(func() (int, error) {
+		return 100, nil
+	})
+}
+`)
+	gopClTest(t, `
+var fn func(int) (int,error) = x => (x*x, nil)
+`, `package main
+
+var fn func(int) (int, error) = func(x int) (int, error) {
+	return x * x, nil
+}
+`)
+	gopClTest(t, `
+var fn func(int) (int,error)
+fn = x => (x*x, nil)
+`, `package main
+
+var fn func(int) (int, error)
+
+func main() {
+	fn = func(x int) (int, error) {
+		return x * x, nil
+	}
+}
+`)
+}
+
+func TestLambdaExpr2(t *testing.T) {
+	gopClTest(t, `
+func Do(func()) {
+	// ...
+}
+
+Do => {
+	println "Hi"
+}
+`, `package main
+
+import fmt "fmt"
+
+func Do(func()) {
+}
+func main() {
+	Do(func() {
+		fmt.Println("Hi")
+	})
+}
+`)
+	gopClTest(t, `
+func Do(fn func() (int, error)) {
+	// ...
+}
+
+Do => {
+	return 100, nil
+}
+`, `package main
+
+func Do(fn func() (int, error)) {
+}
+func main() {
+	Do(func() (int, error) {
+		return 100, nil
+	})
+}
+`)
+	gopClTest(t, `type Foo struct {
+	Plot func(x float64) (float64, float64)
+}
+foo := &Foo{
+	Plot: x => {
+		return x * 2, x * x
+	},
+}`, `package main
+
+type Foo struct {
+	Plot func(x float64) (float64, float64)
+}
+
+func main() {
+	foo := &Foo{Plot: func(x float64) (float64, float64) {
+		return x * 2, x * x
+	}}
+}
+`)
+	gopClTest(t, `
+type Fn func(x float64) (float64, float64)
+type Foo struct {
+	Plot Fn
+}
+foo := &Foo{
+	Plot: x => {
+		return x * 2, x * x
+	},
+}`, `package main
+
+type Fn func(x float64) (float64, float64)
+type Foo struct {
+	Plot Fn
+}
+
+func main() {
+	foo := &Foo{Plot: func(x float64) (float64, float64) {
+		return x * 2, x * x
+	}}
+}
+`)
+
+	gopClTest(t, `
+type Fn func() (int, error)
+func Do(fn Fn) {
+}
+
+Do => {
+	return 100, nil
+}
+`, `package main
+
+type Fn func() (int, error)
+
+func Do(fn Fn) {
+}
+func main() {
+	Do(func() (int, error) {
+		return 100, nil
+	})
+}
+`)
+	gopClTest(t, `
+var fn func(int) (int,error) = x => {
+	return x * x, nil
+}
+`, `package main
+
+var fn func(int) (int, error) = func(x int) (int, error) {
+	return x * x, nil
+}
+`)
+	gopClTest(t, `
+var fn func(int) (int,error)
+fn = x => {
+	return x * x, nil
+}
+`, `package main
+
+var fn func(int) (int, error)
+
+func main() {
+	fn = func(x int) (int, error) {
+		return x * x, nil
+	}
+}
+`)
+}
+
+func TestLambdaExpr3(t *testing.T) {
+	gopClTest(t, `
+func intSeq() func() int {
+	i := 0
+	return => {
+		i++
+		return i
+	}
+}
+`, `package main
+
+func intSeq() func() int {
+	i := 0
+	return func() int {
+		i++
+		return i
+	}
+}
+`)
+	gopClTest(t, `
+func intDouble() func(int) int {
+	return i => i*2
+}
+`, `package main
+
+func intDouble() func(int) int {
+	return func(i int) int {
+		return i * 2
+	}
+}
+`)
+}
+
+func TestUnnamedMainFunc(t *testing.T) {
+	gopClTest(t, `i := 1`, `package main
+
+func main() {
+	i := 1
+}
+`)
+}
+
+func TestFuncAsParam(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func bar(foo func(string, ...interface{}) (int, error)) {
+	foo("Hello, %v!\n", "Go+")
+}
+
+bar(fmt.Printf)
+`, `package main
+
+import fmt "fmt"
+
+func bar(foo func(string, ...interface {
+}) (int, error)) {
+	foo("Hello, %v!\n", "Go+")
+}
+func main() {
+	bar(fmt.Printf)
+}
+`)
+}
+
+func TestFuncAsParam2(t *testing.T) {
+	gopClTest(t, `import (
+	"fmt"
+	"strings"
+)
+
+func foo(x string) string {
+	return strings.NewReplacer("?", "!").Replace(x)
+}
+
+func printf(format string, args ...interface{}) (n int, err error) {
+	n, err = fmt.Printf(format, args...)
+	return
+}
+
+func bar(foo func(string, ...interface{}) (int, error)) {
+	foo("Hello, %v!\n", "Go+")
+}
+
+bar(printf)
+fmt.Println(foo("Hello, world???"))
+fmt.Println(printf("Hello, %v\n", "Go+"))
+`, `package main
+
+import (
+	fmt "fmt"
+	strings "strings"
+)
+
+func foo(x string) string {
+	return strings.NewReplacer("?", "!").Replace(x)
+}
+func printf(format string, args ...interface {
+}) (n int, err error) {
+	n, err = fmt.Printf(format, args...)
+	return
+}
+func bar(foo func(string, ...interface {
+}) (int, error)) {
+	foo("Hello, %v!\n", "Go+")
+}
+func main() {
+	bar(printf)
+	fmt.Println(foo("Hello, world???"))
+	fmt.Println(printf("Hello, %v\n", "Go+"))
+}
+`)
+}
+
+func TestFuncCall(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+fmt.Println("Hello")`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("Hello")
+}
+`)
+}
+
+func TestFuncCallEllipsis(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func foo(args ...interface{}) {
+	fmt.Println(args...)
+}
+
+func main() {
+}`, `package main
+
+import fmt "fmt"
+
+func foo(args ...interface {
+}) {
+	fmt.Println(args...)
+}
+func main() {
+}
+`)
+}
+
+func TestFuncCallCodeOrder(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func main() {
+	foo("Hello", 123)
+}
+
+func foo(args ...interface{}) {
+	fmt.Println(args...)
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	foo("Hello", 123)
+}
+func foo(args ...interface {
+}) {
+	fmt.Println(args...)
+}
+`)
+}
+
+func TestInterfaceMethods(t *testing.T) {
+	gopClTest(t, `package main
+
+func foo(v ...interface { Bar() }) {
+}
+
+func main() {
+}`, `package main
+
+func foo(v ...interface {
+	Bar()
+}) {
+}
+func main() {
+}
+`)
+}
+
+func TestAssignUnderscore(t *testing.T) {
+	gopClTest(t, `import log "fmt"
+
+_, err := log.Println("Hello")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	_, err := fmt.Println("Hello")
+}
+`)
+}
+
+func TestOperator(t *testing.T) {
+	gopClTest(t, `
+a := "Hi"
+b := a + "!"
+c := 13
+d := -c
+`, `package main
+
+func main() {
+	a := "Hi"
+	b := a + "!"
+	c := 13
+	d := -c
+}
+`)
+}
+
+var (
+	autogen sync.Mutex
+)
+
+func removeAutogenFiles() {
+	os.Remove("./internal/gop-in-go/foo/gop_autogen.go")
+	os.Remove("./internal/gop-in-go/foo/gop_autogen_test.go")
+	os.Remove("./internal/gop-in-go/foo/gop_autogen2_test.go")
+}
+
+func _TestImportGopPkg(t *testing.T) {
+	autogen.Lock()
+	defer autogen.Unlock()
+
+	dir, _ := os.Getwd()
+	os.Chdir("../..")
+	defer os.Chdir(dir)
+
+	removeAutogenFiles()
+	gopClTest(t, `import "github.com/goplus/gop/cl/internal/gop-in-go/foo"
+
+rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2})
+println(rmap)
+`, `package main
+
+import (
+	fmt "fmt"
+	foo "github.com/goplus/gop/cl/internal/gop-in-go/foo"
+)
+
+func main() {
+	rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2})
+	fmt.Println(rmap)
+}
+`)
+}
+
+func TestCallDep(t *testing.T) {
+	for i := 0; i < 2; i++ {
+		gopClTest(t, `
+import (
+	"reflect"
+	"testing"
+)
+
+func TestNew(t *testing.T) {
+	ret := New()
+	expected := Result{}
+	if reflect.DeepEqual(ret, expected) {
+		t.Fatal("Test failed:", ret, expected)
+	}
+}
+
+type Repo struct {
+	Title string
+}
+
+func newRepo() Repo {
+	return {Title: "Hi"}
+}
+
+type Result struct {
+	Repo Repo
+}
+
+func New() Result {
+	repo := newRepo()
+	return {Repo: repo}
+}
+`, `package main
+
+import (
+	reflect "reflect"
+	testing "testing"
+)
+
+type Repo struct {
+	Title string
+}
+type Result struct {
+	Repo Repo
+}
+
+func TestNew(t *testing.T) {
+	ret := New()
+	expected := Result{}
+	if reflect.DeepEqual(ret, expected) {
+		t.Fatal("Test failed:", ret, expected)
+	}
+}
+func newRepo() Repo {
+	return Repo{Title: "Hi"}
+}
+func New() Result {
+	repo := newRepo()
+	return Result{Repo: repo}
+}
+`)
+	}
+}
+
+func testRangeExpr(t *testing.T, codeTpl, expect string) {
+	for k, s := range []string{" <- ", " := range ", " = range "} {
+		if k == 2 {
+			codeTpl = "i:=0\n" + codeTpl
+			expect = strings.Replace(expect, "for i := ", "i := 0\n\tfor i = ", -1)
+		}
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
+func TestRangeExpr(t *testing.T) {
+	testRangeExpr(t, `
+for i $ :10 {
+	println(i)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 0; i < 10; i += 1 {
+		fmt.Println(i)
+	}
+}
+`)
+	testRangeExpr(t, `
+for i $ 1:10:3 {
+	println(i)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 3 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr2(t *testing.T) {
+	testRangeExpr(t, `
+for i $ 1:10:2 {
+	println(i)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 2 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr3(t *testing.T) {
+	testRangeExpr(t, `
+for i $ 1:10 {
+	println(i)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 1; i < 10; i += 1 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr4(t *testing.T) {
+	testRangeExpr(t, `
+for i $ :10:2 {
+	println(i)
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for i := 0; i < 10; i += 2 {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr5(t *testing.T) {
+	gopClTest(t, `
+for range :10 {
+	println("Hi")
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr6(t *testing.T) {
+	gopClTest(t, `
+for _ <- :10 {
+	println("Hi")
+}`, `package main
+
+import fmt "fmt"
+
+func main() {
+	for _gop_k := 0; _gop_k < 10; _gop_k += 1 {
+		fmt.Println("Hi")
+	}
+}
+`)
+}
+
+func TestRangeExpr7(t *testing.T) {
+	gopClTest(t, `
+println [x for x <- 0:3:1]
+`, `package main
+
+import (
+	fmt "fmt"
+	builtin "github.com/goplus/gop/builtin"
+)
+
+func main() {
+	fmt.Println(func() (_gop_ret []int) {
+		for _gop_it := builtin.NewRange__0(0, 3, 1).Gop_Enum(); ; {
+			var _gop_ok bool
+			x, _gop_ok := _gop_it.Next()
+			if !_gop_ok {
+				break
+			}
+			_gop_ret = append(_gop_ret, x)
+		}
+		return
+	}())
+}
+`)
+}
+
+func testRangeExpr8(t *testing.T, codeTpl, expect string) {
+	for _, s := range []string{" <- ", " := range "} {
+		gopClTest(t, strings.Replace(codeTpl, "$", s, -1), expect)
+	}
+}
+
+func TestRangeExpr8(t *testing.T) {
+	testRangeExpr8(t, `
+	type T struct{}
+
+	func (t T)start()int{
+		return 0
+	}
+	func (t T)end()int{
+		return 3 
+	}
+	func (t T)step()int{
+		return 1 
+	}
+	
+	t:=T{}
+	
+	for i <- t.start():t.end():t.step(){
+		println i
+	}
+	`, `package main
+
+import fmt "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	for i, _gop_end, _gop_step := t.start(), t.end(), t.step(); i < _gop_end; i += _gop_step {
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestRangeExpr9(t *testing.T) {
+	testRangeExpr8(t, `
+	type T struct{}
+
+	func (t T)start()int{
+		return 0
+	}
+	func (t T)end()int{
+		return 3 
+	}
+	func (t T)step()int{
+		return 1 
+	}
+	
+	t:=T{}
+	i:=0
+	for i =range t.start():t.end():t.step(){
+		println i
+	}
+	`, `package main
+
+import fmt "fmt"
+
+type T struct {
+}
+
+func (t T) start() int {
+	return 0
+}
+func (t T) end() int {
+	return 3
+}
+func (t T) step() int {
+	return 1
+}
+func main() {
+	t := T{}
+	i := 0
+	for _gop_k, _gop_end, _gop_step := t.start(), t.end(), t.step(); _gop_k < _gop_end; _gop_k += _gop_step {
+		i = _gop_k
+		fmt.Println(i)
+	}
+}
+`)
+}
+
+func TestGoFuncInstr(t *testing.T) {
+	gopClTest(t, `package main
+
+//go:noinline
+//go:uintptrescapes
+func test(s string, p, q uintptr, rest ...uintptr) int {
+}`, `package main
+//go:noinline
+//go:uintptrescapes
+func test(s string, p uintptr, q uintptr, rest ...uintptr) int {
+}
+`)
+}
+
+func TestGoTypeInstr(t *testing.T) {
+	gopClTest(t, `package main
+
+//go:notinheap
+type S struct{ x int }
+`, `package main
+//go:notinheap
+type S struct {
+	x int
+}
+`)
+}
+
+func TestNoEntrypoint(t *testing.T) {
+	gopClTest(t, `println("init")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("init")
+}
+`)
+	gopClTestEx(t, gblConf, "bar", `package bar
+println("init")
+`, `package bar
+
+import fmt "fmt"
+
+func init() {
+	fmt.Println("init")
+}
+`)
+}
+
+func TestParentExpr(t *testing.T) {
+	gopClTest(t, `var t1 *(int)
+var t2 chan (int)
+`, `package main
+
+var t1 *int
+var t2 chan int
+`)
+}
+
+func TestCommandStyle(t *testing.T) {
+	gopClTest(t, `
+println []
+println {}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println([]interface {
+	}{})
+	fmt.Println(map[string]interface {
+	}{})
+}
+`)
+}
+
+func TestTypeLoader(t *testing.T) {
+	gopClTest(t, `import "fmt"
+
+func (p *Point) String() string {
+	return fmt.Sprintf("%v-%v",p.X,p.Y)
+}
+
+type Point struct {
+	X int
+	Y int
+}
+`, `package main
+
+import fmt "fmt"
+
+type Point struct {
+	X int
+	Y int
+}
+
+func (p *Point) String() string {
+	return fmt.Sprintf("%v-%v", p.X, p.Y)
+}
+`)
+}
+
+func TestCallPrintln(t *testing.T) {
+	gopClTest(t, `
+print
+print "hello"
+print("hello")
+println
+println "hello"
+println("hello")
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Print()
+	fmt.Print("hello")
+	fmt.Print("hello")
+	fmt.Println()
+	fmt.Println("hello")
+	fmt.Println("hello")
+}
+`)
+}
+
+func TestAnyAlias(t *testing.T) {
+	gopClTest(t, `
+var a any = 100
+println(a)
+`, `package main
+
+import fmt "fmt"
+
+var a interface {
+} = 100
+
+func main() {
+	fmt.Println(a)
+}
+`)
+}
+
+func TestMainEntry(t *testing.T) {
+	conf := *gblConf
+	conf.NoAutoGenMain = false
+
+	gopClTestEx(t, &conf, "main", `
+`, `package main
+
+func main() {
+}
+`)
+	gopClTestEx(t, &conf, "main", `
+func test() {
+	println "hello"
+}
+`, `package main
+
+import fmt "fmt"
+
+func test() {
+	fmt.Println("hello")
+}
+func main() {
+}
+`)
+
+	gopClTestEx(t, &conf, "main", `
+func main() {
+	println "hello"
+}
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println("hello")
+}
+`)
+}
+
+func TestCommandNotExpr(t *testing.T) {
+	gopClTest(t, `
+println !true
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println(false)
+}
+`)
+	gopClTest(t, `
+a := true
+println !a
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	a := true
+	fmt.Println(!a)
+}
+`)
+	gopClTest(t, `
+println !func() bool { return true }()
+`, `package main
+
+import fmt "fmt"
+
+func main() {
+	fmt.Println(!func() bool {
+		return true
+	}())
+}
+`)
+}
+
+func TestClassFileGopx(t *testing.T) {
+	gopClTestFile(t, `
+var (
+	BaseClass
+	Width, Height float64
+	*AggClass
+)
+
+type BaseClass struct{
+	x int
+	y int
+}
+type AggClass struct{}
+
+func Area() float64 {
+	return Width * Height
+}
+`, `package main
+
+type Rect struct {
+	BaseClass
+	Width  float64
+	Height float64
+	*AggClass
+}
+type BaseClass struct {
+	x int
+	y int
+}
+type AggClass struct {
+}
+
+func (this *Rect) Area() float64 {
+	return this.Width * this.Height
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	*bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer "spec:\"buffer\""
+	a int "json:\"a\""
+	b int
+)
+func test(){}
+`, `package main
+
+import bytes "bytes"
+
+type Rect struct {
+	*bytes.Buffer `+"`spec:\"buffer\"`"+`
+	a             int `+"`json:\"a\"`"+`
+	b             int
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+}
+
+func TestOverload(t *testing.T) {
+	gopClTest(t, `
+import "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var (
+	m1 = &Mesh{}
+	m2 = &Mesh{}
+)
+
+foo.onKey "hello", => {
+}
+foo.onKey "hello", key => {
+}
+foo.onKey ["1"], => {
+}
+foo.onKey ["2"], key => {
+}
+foo.onKey [m1,m2], => {
+}
+foo.onKey [m1,m2], key => {
+}
+foo.onKey ["a"], ["b"], key => {
+}
+foo.onKey ["a"], [m1,m2], key => {
+}
+foo.onKey ["a"],nil,key => {
+}
+n := &foo.N{}
+n.onKey "hello", => {
+}
+n.onKey "hello", key => {
+}
+n.onKey ["1"], => {
+}
+n.onKey ["2"], key => {
+}
+n.onKey [m1,m2], => {
+}
+n.onKey [m1,m2], key => {
+}
+n.onKey ["a"], ["b"], key => {
+}
+n.onKey ["a"], [m1,m2], key => {
+}
+n.onKey ["a"],nil,key => {
+}
+`, `package main
+
+import foo "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+var (
+	m1 = &Mesh{}
+	m2 = &Mesh{}
+)
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+func main() {
+	foo.OnKey__0("hello", func() {
+	})
+	foo.OnKey__1("hello", func(key string) {
+	})
+	foo.OnKey__2([]string{"1"}, func() {
+	})
+	foo.OnKey__3([]string{"2"}, func(key string) {
+	})
+	foo.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	foo.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	foo.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	foo.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	foo.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	n := &foo.N{}
+	n.OnKey__0("hello", func() {
+	})
+	n.OnKey__1("hello", func(key string) {
+	})
+	n.OnKey__2([]string{"1"}, func() {
+	})
+	n.OnKey__3([]string{"2"}, func(key string) {
+	})
+	n.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	n.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	n.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	n.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+}
+`)
+}
+
+func TestClassFileMember(t *testing.T) {
+	gopClTestFile(t, `type Engine struct {
+}
+
+func (e *Engine) EnterPointerLock() {
+}
+
+func (e *Engine) SetEnable(b bool) {
+}
+
+
+func Engine() *Engine {
+	return &Engine{}
+}
+
+func Test() {
+	engine.setEnable true
+	engine.enterPointerLock
+}
+`, `package main
+
+type Rect struct {
+}
+type Engine struct {
+}
+
+func (e *Engine) EnterPointerLock() {
+}
+func (e *Engine) SetEnable(b bool) {
+}
+func (this *Rect) Engine() *Engine {
+	return &Engine{}
+}
+func (this *Rect) Test() {
+	this.Engine().SetEnable(true)
+	this.Engine().EnterPointerLock()
+}
+`, "Rect.gox")
+}
diff --git a/cl/outline/cl/error_msg_test.go b/cl/outline/cl/error_msg_test.go
new file mode 100644
index 00000000..3a5f43ee
--- /dev/null
+++ b/cl/outline/cl/error_msg_test.go
@@ -0,0 +1,966 @@
+/*
+ * 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_test
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"testing"
+
+	"github.com/goplus/gop/cl/outline/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+)
+
+func codeErrorTest(t *testing.T, msg, src string) {
+	codeErrorTestEx(t, "main", "bar.gop", msg, src)
+}
+
+func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
+	fs := memfs.SingleFile("/foo", filename, src)
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("parser.ParseFSDir failed")
+	}
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.WorkingDir = "/foo"
+	conf.TargetDir = "/foo"
+	bar := pkgs[pkgname]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
+
+func TestErrLambdaExpr(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:7:6: too few arguments in lambda expression\n\thave ()\n\twant (int, int)", `
+
+func foo(func(int, int)) {
+}
+
+func main() {
+	foo(=> {})
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:7:6: too many arguments in lambda expression\n\thave (x, y, z)\n\twant (int, int)", `
+
+func foo(func(int, int)) {
+}
+
+func main() {
+	foo((x, y, z) => {})
+}
+`)
+	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value to Plot", `
+type Foo struct {
+	Plot int
+}
+foo := &Foo{
+	Plot: x => (x * 2, x * x),
+}
+`)
+	codeErrorTest(t, "./bar.gop:6:8: cannot use lambda literal as type int in field value to Plot", `
+type Foo struct {
+	Plot int
+}
+foo := &Foo{
+	Plot: x => {
+		return x * 2, x * x
+	},
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:5: cannot use lambda literal as type int in argument to foo", `
+func foo(int) {
+}
+foo(=> {})
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:5: cannot use lambda literal as type func() in argument to foo", `
+func foo(func()) {
+}
+foo => (100)
+`)
+	codeErrorTest(t,
+		"./bar.gop:6:8: cannot use lambda literal as type func() int in field value to Plot", `
+type Foo struct {
+	Plot func() int
+}
+foo := &Foo{
+	Plot: x => (x * 2, x * x),
+}
+`)
+
+	codeErrorTest(t,
+		"./bar.gop:2:18: cannot use lambda literal as type func() in assignment to foo", `
+var foo func() = => (100)
+`)
+	codeErrorTest(t,
+		"./bar.gop:3:7: cannot use lambda literal as type func() in assignment to foo", `
+var foo func()
+foo = => (100)
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:29: lambda unsupport multiple assignment", `
+var foo, foo1 func() = nil, => {}
+`)
+	codeErrorTest(t,
+		"./bar.gop:3:15: lambda unsupport multiple assignment", `
+var foo func()
+_, foo = nil, => {}
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:9: cannot use lambda expression as type int in return statement", `
+func intSeq() int {
+	i := 0
+	return => {
+		i++
+		return i
+	}
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:6:10: cannot use i (type int) as type string in return argument", `
+func intSeq() func() string {
+	i := 0
+	return => {
+		i++
+		return i
+	}
+}
+`)
+}
+
+func TestErrErrWrap(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: a", `func main() {
+	a!
+}
+`)
+}
+
+func TestErrVar(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:6:5: assignment mismatch: 1 variables but fmt.Println returns 2 values", `import "fmt"
+
+func main() {
+}
+
+var a = fmt.Println(1)
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:5: assignment mismatch: 1 variables but 2 values", `func main() {
+}
+
+var a = 1, 2
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: foo", `func main() {
+	foo.x = 1
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:2: use of builtin len not in function call", `func main() {
+	len.x = 1
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:10: undefined: foo", `func main() {
+	println(foo.x)
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:10: use of builtin len not in function call", `func main() {
+	println(len.x)
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:10: undefined: foo", `func main() {
+	println(foo)
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:3:20: use of builtin len not in function call", `package main
+
+func foo(v map[int]len) {
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:5:20: bar is not a type", `package main
+
+var bar = 1
+
+func foo(v map[int]bar) {
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:6: use of builtin len not in function call", `func main() {
+	new(len)
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: foo", `func main() {
+	foo = 1
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:9: cannot use _ as value", `func main() {
+	foo := _
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:9: use of builtin len not in function call", `func main() {
+	foo := len
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:2: println is not a variable", `func main() {
+	println = "hello"
+}
+`)
+}
+
+func TestErrImport(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:8:2: confliction: NewEncoding declared both in "encoding/base64" and "encoding/base32"`, `
+import (
+	. "encoding/base32"
+	. "encoding/base64"
+)
+
+func foo() {
+	NewEncoding("Hi")
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:2: cannot refer to unexported name os.undefined", `
+import "os"
+
+func foo() {
+	os.undefined
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:2: undefined: os.UndefinedObject", `
+import "os"
+
+func foo() {
+	os.UndefinedObject
+}`)
+	codeErrorTest(t,
+		"./bar.gop:2:13: undefined: testing", `
+func foo(t *testing.T) {
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:12: testing.Verbose is not a type", `
+import "testing"
+
+func foo(t testing.Verbose) {
+}`)
+}
+
+func TestErrConst(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:3:7: a redeclared in this block\n\t./bar.gop:2:5 other declaration of a", `
+var a int
+const a = 1
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:2: missing value in const declaration", `
+const (
+	a = iota
+	b, c
+)
+`)
+}
+
+func TestErrNewVar(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:3:5: a redeclared in this block\n\t./bar.gop:2:5 other declaration of a", `
+var a int
+var a string
+`)
+}
+
+func TestErrDefineVar(t *testing.T) {
+	codeErrorTest(t, "./bar.gop:3:1: no new variables on left side of :=\n"+
+		"./bar.gop:3:6: cannot use \"Hi\" (type untyped string) as type int in assignment", `
+a := 1
+a := "Hi"
+`)
+}
+
+func TestErrAssignMismatchT(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:2:16: cannot use []string{} (type []string) as type string in assignment`, `
+var a string = []string{}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:16: cannot use [2]string{} (type [2]string) as type string in assignment`, `
+var a string = [2]string{}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:16: cannot use map[int]string{} (type map[int]string) as type string in assignment`, `
+var a string = map[int]string{}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:16: cannot use T{} (type T) as type string in assignment`, `
+type T struct{}
+var a string = T{}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:16: cannot use func(){} (type func()) as type string in assignment`, `
+var a string = func(){}
+`)
+}
+
+func TestErrAssign(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:8:1: assignment mismatch: 1 variables but bar returns 2 values`, `
+
+func bar() (n int, err error) {
+	return
+}
+
+x := 1
+x = bar()
+`)
+	codeErrorTest(t,
+		`./bar.gop:4:1: assignment mismatch: 1 variables but 2 values`, `
+
+x := 1
+x = 1, "Hi"
+`)
+}
+
+func TestErrReturn(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:4:2: too few arguments to return\n\thave (untyped int)\n\twant (int, error)", `
+
+func foo() (int, error) {
+	return 1
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:2: too many arguments to return\n\thave (untyped int, untyped int, untyped string)\n\twant (int, error)", `
+
+func foo() (int, error) {
+	return 1, 2, "Hi"
+}
+`)
+	codeErrorTest(t,
+		`./bar.gop:4:12: cannot use "Hi" (type untyped string) as type error in return argument`, `
+
+func foo() (int, error) {
+	return 1, "Hi"
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:8:2: too few arguments to return\n\thave (byte)\n\twant (int, error)", `
+
+func bar() (v byte) {
+	return
+}
+
+func foo() (int, error) {
+	return bar()
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:8:2: too many arguments to return\n\thave (n int, err error)\n\twant (v byte)", `
+
+func bar() (n int, err error) {
+	return
+}
+
+func foo() (v byte) {
+	return bar()
+}
+`)
+	codeErrorTest(t,
+		`./bar.gop:8:2: cannot use byte value as type error in return argument`, `
+
+func bar() (n int, v byte) {
+	return
+}
+
+func foo() (int, error) {
+	return bar()
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:4:2: not enough arguments to return\n\thave ()\n\twant (byte)", `
+
+func foo() byte {
+	return
+}
+`)
+}
+
+func TestErrForRange(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:4:8: cannot assign type string to a (type int) in range`, `
+a := 1
+var b []string
+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`, `
+func init(v byte) {
+}
+`)
+}
+
+func TestErrRecv(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:5:9: invalid receiver type a (a is a pointer type)`, `
+
+type a *int
+
+func (p a) foo() {
+}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:9: invalid receiver type error (error is an interface type)`, `
+func (p error) foo() {
+}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:9: invalid receiver type []byte`, `
+func (p []byte) foo() {
+}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:9: invalid receiver type *[]byte`, `
+func (p *[]byte) foo() {
+}
+`)
+}
+
+func TestErrStructLit(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:39: too many values in struct{x int; y string}{...}`, `
+x := 1
+a := struct{x int; y string}{1, "Hi", 2}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:30: too few values in struct{x int; y string}{...}`, `
+x := 1
+a := struct{x int; y string}{1}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:33: cannot use x (type int) as type string in value of field y`, `
+x := 1
+a := struct{x int; y string}{1, x}
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:30: z undefined (type struct{x int; y string} has no field or method z)`, `
+a := struct{x int; y string}{z: 1}
+`)
+}
+
+func TestErrArray(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:8: non-constant array bound n`, `
+var n int
+var a [n]int
+`)
+}
+
+func TestErrArrayLit(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:14: cannot use a as index which must be non-negative integer constant`,
+		`
+a := "Hi"
+b := [10]int{a: 1}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:20: array index 10 out of bounds [0:10]`,
+		`
+a := "Hi"
+b := [10]int{9: 1, 3}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:16: array index 1 out of bounds [0:1]`,
+		`
+a := "Hi"
+b := [1]int{1, 2}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:14: array index 12 (value 12) out of bounds [0:10]`,
+		`
+a := "Hi"
+b := [10]int{12: 2}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:14: cannot use a+"!" (type string) as type int in array literal`,
+		`
+a := "Hi"
+b := [10]int{a+"!"}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:17: cannot use a (type string) as type int in array literal`,
+		`
+a := "Hi"
+b := [10]int{2: a}
+`)
+}
+
+func TestErrSliceLit(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:12: cannot use a as index which must be non-negative integer constant`,
+		`
+a := "Hi"
+b := []int{a: 1}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:12: cannot use a (type string) as type int in slice literal`,
+		`
+a := "Hi"
+b := []int{a}
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:15: cannot use a (type string) as type int in slice literal`,
+		`
+a := "Hi"
+b := []int{2: a}
+`)
+}
+
+func TestErrMapLit(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:2:21: cannot use 1+2 (type untyped int) as type string in map key
+./bar.gop:3:27: cannot use "Go" + "+" (type untyped string) as type int in map value`,
+		`
+a := map[string]int{1+2: 2}
+b := map[string]int{"Hi": "Go" + "+"}
+`)
+}
+
+func TestErrSlice(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:4:6: cannot slice a (type *byte)`,
+		`
+var a *byte
+x := 1
+b := a[x:2]
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:6: cannot slice a (type bool)`,
+		`
+a := true
+b := a[1:2]
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:6: invalid operation a[1:2:5] (3-index slice of string)`,
+		`
+a := "Hi"
+b := a[1:2:5]
+`)
+}
+
+func TestErrIndex(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:10: assignment mismatch: 2 variables but 1 values`,
+		`
+a := "Hi"
+b, ok := a[1]
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:6: invalid operation: a[1] (type bool does not support indexing)`,
+		`
+a := true
+b := a[1]
+`)
+}
+
+func TestErrIndexRef(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:1: cannot assign to a[1] (strings are immutable)`,
+		`
+a := "Hi"
+a[1] = 'e'
+`)
+}
+
+func TestErrStar(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:2: invalid indirect of a (type string)`,
+		`
+a := "Hi"
+*a = 'e'
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:7: invalid indirect of a (type string)`,
+		`
+a := "Hi"
+b := *a
+`)
+}
+
+func TestErrMember(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:6: a.x undefined (type string has no field or method x)`,
+		`
+a := "Hello"
+b := a.x
+`)
+}
+
+func TestErrMemberRef(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:3:1: a.x undefined (type string has no field or method x)`,
+		`
+a := "Hello"
+a.x = 1
+`)
+	codeErrorTest(t,
+		`./bar.gop:5:1: a.x undefined (type aaa has no field or method x)`,
+		`
+type aaa byte
+
+a := aaa(0)
+a.x = 1
+`)
+	codeErrorTest(t,
+		`./bar.gop:5:1: a.z undefined (type aaa has no field or method z)`,
+		`
+type aaa struct {x int; y string}
+
+a := aaa{}
+a.z = 1
+`)
+	codeErrorTest(t,
+		`./bar.gop:3:1: a.z undefined (type struct{x int; y string} has no field or method z)`,
+		`
+a := struct{x int; y string}{}
+a.z = 1
+`)
+}
+
+func TestErrLabel(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:4:1: label foo already defined at ./bar.gop:2:1
+./bar.gop:2:1: label foo defined and not used`,
+		`x := 1
+foo:
+	i := 1
+foo:
+	i++
+`)
+	codeErrorTest(t,
+		`./bar.gop:2:6: label foo is not defined`,
+		`x := 1
+goto foo`)
+	codeErrorTest(t,
+		`./bar.gop:2:7: label foo is not defined`,
+		`x := 1
+break foo`)
+}
+
+func TestErrBranchStmt(t *testing.T) {
+	codeErrorTest(t,
+		`./bar.gop:2:2: fallthrough statement out of place`,
+		`func foo() {
+	fallthrough
+}`)
+}
+
+func TestErrNoEntrypoint(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: println1", `func main() {
+	println1 "hello"
+}
+`)
+	codeErrorTest(t,
+		"./bar.gop:1:1: undefined: println1", `println1 "hello"`)
+
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: println1", `
+	println1 "hello"
+`)
+	codeErrorTest(t,
+		"./bar.gop:2:2: undefined: println1", `package main
+	println1 "hello"
+`)
+	codeErrorTest(t,
+		`./bar.gop:1:9: undefined: abc`,
+		`println abc
+`)
+	codeErrorTestEx(t, "bar", "bar.gop",
+		`./bar.gop:2:9: undefined: abc`,
+		`package bar
+println abc
+`)
+}
+
+func TestErrTypeRedefine(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:9:6: Point redeclared in this block\n\tprevious declaration at ./bar.gop:5:6",
+		`import "fmt"
+func (p *Point) String() string {
+	return fmt.Sprintf("%v-%v",p.X,p.Y)
+}
+type Point struct {
+	X int
+	Y int
+}
+type Point struct {
+	X int
+	Y int
+}
+`)
+}
+
+func TestErrSwitchDuplicate(t *testing.T) {
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 100 in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case 100:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case int(100) (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case int(100):
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 50 + 50 (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n int
+switch n {
+	case 100:
+	case 50 + 50:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case int(100) (value 100) in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n interface{}
+switch n {
+	case 100:
+	case uint(100):
+	case int(100):
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:7: duplicate case 100.0 in switch\n\tprevious case at ./bar.gop:3:7",
+		`var n interface{}
+switch n {
+	case 100.0:
+	case 100.0:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case v (value 100) in switch\n\tprevious case at ./bar.gop:4:7",
+		`var n interface{}
+const v = 100.0
+switch n {
+	case 100.0:
+	case v:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:5:7: duplicate case v (value \"hello\") in switch\n\tprevious case at ./bar.gop:4:7",
+		`var n interface{}
+const v = "hello"
+switch n {
+	case "hello":
+	case v:
+}`)
+	codeErrorTest(t,
+		`./bar.gop:4:7: duplicate case 100 in switch
+	previous case at ./bar.gop:3:7
+./bar.gop:5:7: duplicate case 50 + 50 (value 100) in switch
+	previous case at ./bar.gop:3:7`,
+		`var n int
+switch n {
+	case 100:
+	case 100:
+	case 50 + 50:
+}`)
+	codeErrorTest(t,
+		"./bar.gop:4:2: multiple defaults in switch (first at ./bar.gop:3:2)",
+		`var n interface{}
+switch n {
+	default:
+	default:
+}`)
+	codeErrorTest(t, `./bar.gop:4:2: multiple defaults in switch (first at ./bar.gop:3:2)
+./bar.gop:5:2: multiple defaults in switch (first at ./bar.gop:3:2)`,
+		`var n interface{}
+switch n {
+	default:
+	default:
+	default:
+}`)
+}
+
+func TestErrTypeSwitchDuplicate(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:4:7: duplicate case int in type switch
+	previous case at ./bar.gop:3:7
+./bar.gop:5:7: duplicate case int in type switch
+	previous case at ./bar.gop:3:7`,
+		`var n interface{} = 100
+switch n.(type) {
+	case int:
+	case int:
+	case int:
+}
+`)
+	codeErrorTest(t, `./bar.gop:4:7: multiple nil cases in type switch (first at ./bar.gop:3:7)
+./bar.gop:5:7: multiple nil cases in type switch (first at ./bar.gop:3:7)`,
+		`var n interface{} = 100
+switch n.(type) {
+	case nil:
+	case nil:
+	case nil:
+}
+`)
+	codeErrorTest(t, `./bar.gop:4:2: multiple defaults in type switch (first at ./bar.gop:3:2)
+./bar.gop:5:2: multiple defaults in type switch (first at ./bar.gop:3:2)`,
+		`var n interface{} = 100
+switch n.(type) {
+	default:
+	default:
+	default:
+}
+`)
+}
+
+func TestErrAutoProperty(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:4:11: cannot refer to unexported name fmt.println`, `
+import "fmt"
+
+n, err := fmt.println
+`)
+}
+
+func TestFiledsNameRedecl(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:6:2: Id redeclared
+	./bar.gop:5:2 other declaration of Id
+./bar.gop:7:2: Id redeclared
+	./bar.gop:5:2 other declaration of Id
+./bar.gop:9:2: name redeclared
+	./bar.gop:8:2 other declaration of name`, `
+type Id struct {
+}
+type A struct {
+	Id   int
+	Id   string
+	Id
+	name string
+	name string
+}
+`)
+}
+
+func TestErrImportPkg(t *testing.T) {
+	root := filepath.Join(runtime.GOROOT(), "src", "fmt2")
+	where := "GOROOT"
+	ver := runtime.Version()[:6]
+	if ver >= "go1.21" {
+		where = "std"
+	}
+	codeErrorTest(t,
+		fmt.Sprintf(`./bar.gop:3:2: package fmt2 is not in `+where+` (%v)
+`, root), `
+import (
+	"fmt2"
+)
+`)
+
+	codeErrorTest(t, `./bar.gop:3:2: no required module provides package github.com/goplus/gop/fmt2; to add it:
+	go get github.com/goplus/gop/fmt2
+`, `
+import (
+	"github.com/goplus/gop/fmt2"
+)
+`)
+}
+
+func TestErrClassFileGopx(t *testing.T) {
+	codeErrorTestEx(t, "main", "Rect.gox",
+		`./Rect.gox:5:2: A redeclared
+	./Rect.gox:3:2 other declaration of A`, `
+var (
+	A
+	i int
+	A
+)
+type A struct{}
+println "hello"
+`)
+}
+
+func TestErrVarInFunc(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:6:10: too few arguments in call to set("box")
+	have (untyped string)
+	want (name string, v int)
+./bar.gop:7:10: undefined: a`, `
+func set(name string, v int) string {
+	return name
+}
+func test() {
+	var a = set("box")
+	println(a)
+}
+`)
+}
+
+func TestErrInt128(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:2:16: cannot use 1<<127 (type untyped int) as type github.com/goplus/gop/builtin/ng.Int128 in assignment`, `
+var a int128 = 1<<127
+`)
+	codeErrorTest(t, `./bar.gop:2:13: cannot convert 1<<127 (untyped int constant 170141183460469231731687303715884105728) to type Int128`, `
+a := int128(1<<127)
+`)
+	codeErrorTest(t, `./bar.gop:2:13: cannot convert -1<<127-1 (untyped int constant -170141183460469231731687303715884105729) to type Int128`, `
+a := int128(-1<<127-1)
+`)
+	codeErrorTest(t, `./bar.gop:3:13: cannot convert b (untyped int constant -170141183460469231731687303715884105729) to type Int128`, `
+const b = -1<<127-1
+a := int128(b)
+`)
+}
+
+func TestErrUint128(t *testing.T) {
+	codeErrorTest(t, `./bar.gop:2:17: cannot use 1<<128 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+var a uint128 = 1<<128
+`)
+	codeErrorTest(t, `./bar.gop:2:14: cannot convert 1<<128 (untyped int constant 340282366920938463463374607431768211456) to type Uint128`, `
+a := uint128(1<<128)
+`)
+	codeErrorTest(t, `./bar.gop:2:17: cannot use -1 (type untyped int) as type github.com/goplus/gop/builtin/ng.Uint128 in assignment`, `
+var a uint128 = -1
+`)
+	codeErrorTest(t, `./bar.gop:2:14: cannot convert -1 (untyped int constant -1) to type Uint128`, `
+a := uint128(-1)
+`)
+	codeErrorTest(t, `./bar.gop:3:14: cannot convert b (untyped int constant -1) to type Uint128`, `
+const b = -1
+a := uint128(b)
+`)
+}
diff --git a/cl/outline/cl/typeparams_test.go b/cl/outline/cl/typeparams_test.go
new file mode 100644
index 00000000..bb0a47f0
--- /dev/null
+++ b/cl/outline/cl/typeparams_test.go
@@ -0,0 +1,588 @@
+//go:build go1.18
+// +build go1.18
+
+package cl_test
+
+import (
+	"os"
+	"runtime"
+	"testing"
+
+	"github.com/goplus/gop/cl/outline/cl"
+	"github.com/goplus/gop/parser"
+	"github.com/goplus/gop/parser/fsx/memfs"
+	"github.com/goplus/gop/scanner"
+)
+
+func _TestTypeParams(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+type Data[X, Y any] struct {
+	v X
+}
+
+func (p *Data[T, R]) foo() {
+	fmt.Println(p.v)
+}
+`, `
+v := Data[int, float64]{1}
+v.foo()
+`, `
+`)
+}
+
+func TestTypeParamsFunc(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+type Number interface {
+	~int | ~uint | (float64)
+}
+
+func Sum[T Number](vec []T) T {
+	var sum T
+	for _, elt := range vec {
+		sum = sum + elt
+	}
+	return sum
+}
+
+func At[T interface{ ~[]E }, E any](x T, i int) E {
+	return x[i]
+}
+
+func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 {
+	return p1
+}
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+
+type Int []int
+var MyInts = Int{1,2,3,4}
+`, `
+_ = At[[]int]
+_ = At[[]int,int]
+_ = Sum[int]
+_ = Loader[*int,int]
+_ = Add[string,int]
+var s1 int = Sum([1, 2, 3])
+var s2 int = Sum[int]([1, 2, 3])
+var v1 int = At([1, 2, 3], 1)
+var v2 int = At[[]int]([1, 2, 3], 1)
+var v3 int = At[[]int, int]([1, 2, 3], 1)
+var n1 int = Add("hello", 1, 2, 3)
+var n2 int = Add[string]("hello", 1, 2, 3)
+var n3 int = Add[string, int]("hello", 1, 2, 3)
+var n4 int = Add("hello", [1, 2, 3]...)
+var n5 int = Add("hello", [1, 2, 3]...)
+var n6 int = Add[string]("hello", MyInts...)
+var n7 int = Add[string, int]("hello", [1, 2, 3]...)
+var p1 *int = Loader[*int](nil, 1)
+var p2 *int = Loader[*int, int](nil, 1)
+var fn1 func(p1 *int, p2 int) *int
+fn1 = Loader[*int, int]
+fn1(nil, 1)
+`, `package main
+
+func main() {
+	_ = At[[]int]
+	_ = At[[]int, int]
+	_ = Sum[int]
+	_ = Loader[*int, int]
+	_ = Add[string, int]
+	var s1 int = Sum([]int{1, 2, 3})
+	var s2 int = Sum[int]([]int{1, 2, 3})
+	var v1 int = At([]int{1, 2, 3}, 1)
+	var v2 int = At[[]int]([]int{1, 2, 3}, 1)
+	var v3 int = At[[]int, int]([]int{1, 2, 3}, 1)
+	var n1 int = Add("hello", 1, 2, 3)
+	var n2 int = Add[string]("hello", 1, 2, 3)
+	var n3 int = Add[string, int]("hello", 1, 2, 3)
+	var n4 int = Add("hello", []int{1, 2, 3}...)
+	var n5 int = Add("hello", []int{1, 2, 3}...)
+	var n6 int = Add[string]("hello", MyInts...)
+	var n7 int = Add[string, int]("hello", []int{1, 2, 3}...)
+	var p1 *int = Loader[*int](nil, 1)
+	var p2 *int = Loader[*int, int](nil, 1)
+	var fn1 func(p1 *int, p2 int) *int
+	fn1 = Loader[*int, int]
+	fn1(nil, 1)
+}
+`)
+}
+
+func _TestTypeParamsType(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+type Data[T any] struct {
+	v T
+}
+func(p *Data[T]) Set(v T) {
+	p.v = v
+}
+func(p *(Data[T1])) Set2(v T1) {
+	p.v = v
+}
+type sliceOf[E any] interface {
+	~[]E
+}
+type Slice[S sliceOf[T], T any] struct {
+	Data S
+}
+func (p *Slice[S, T]) Append(t ...T) S {
+	p.Data = append(p.Data, t...)
+	return p.Data
+}
+func (p *Slice[S1, T1]) Append2(t ...T1) S1 {
+	p.Data = append(p.Data, t...)
+	return p.Data
+}
+type (
+	DataInt = Data[int]
+	SliceInt = Slice[[]int,int]
+)
+`, `
+type DataString = Data[string]
+type SliceString = Slice[[]string,string]
+println(DataInt{1}.v)
+println(DataString{"hello"}.v)
+println(Data[int]{100}.v)
+println(Data[string]{"hello"}.v)
+println(Data[struct{X int;Y int}]{}.v.X)
+
+v1 := SliceInt{}
+v2 := SliceString{}
+v3 := Slice[[]int,int]{}
+v3.Append([1,2,3,4]...)
+v3.Append2([1,2,3,4]...)
+`, `package main
+
+import fmt "fmt"
+
+type DataString = Data[string]
+type SliceString = Slice[[]string, string]
+
+func main() {
+	fmt.Println(Data[int]{1}.v)
+	fmt.Println(Data[string]{"hello"}.v)
+	fmt.Println(Data[int]{100}.v)
+	fmt.Println(Data[string]{"hello"}.v)
+	fmt.Println(Data[struct {
+		X int
+		Y int
+	}]{}.v.X)
+	v1 := Slice[[]int, int]{}
+	v2 := Slice[[]string, string]{}
+	v3 := Slice[[]int, int]{}
+	v3.Append([]int{1, 2, 3, 4}...)
+	v3.Append2([]int{1, 2, 3, 4}...)
+}
+`)
+}
+
+func TestTypeParamsComparable(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+// Index returns the index of x in s, or -1 if not found.
+func Index[T comparable](s []T, x T) int {
+	for i, v := range s {
+		// v and x are type T, which has the comparable
+		// constraint, so we can use == here.
+		if v == x {
+			return i
+		}
+	}
+	return -1
+}
+
+var IndexInt = Index[int]
+`, `
+v1 := IndexInt([1,2,3,4],1)
+v2 := Index([1,2,3,4],1)
+v3 := Index[int]([1,2,3,4],1)
+v4 := Index(["a","b","c","d"],"b")
+`, `package main
+
+func main() {
+	v1 := IndexInt([]int{1, 2, 3, 4}, 1)
+	v2 := Index([]int{1, 2, 3, 4}, 1)
+	v3 := Index[int]([]int{1, 2, 3, 4}, 1)
+	v4 := Index([]string{"a", "b", "c", "d"}, "b")
+}
+`)
+}
+
+func mixedErrorTest(t *testing.T, msg, gocode, gopcode string) {
+	mixedErrorTestEx(t, "main", msg, gocode, gopcode)
+}
+
+func mixedErrorTestEx(t *testing.T, pkgname, msg, gocode, gopcode string) {
+	fs := memfs.TwoFiles("/foo", "a.go", gocode, "b.gop", gopcode)
+	pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", parser.Config{})
+	if err != nil {
+		scanner.PrintError(os.Stderr, err)
+		t.Fatal("parser.ParseFSDir failed")
+	}
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.WorkingDir = "/foo"
+	conf.TargetDir = "/foo"
+	bar := pkgs[pkgname]
+	_, err = cl.NewPackage("", bar, &conf)
+	if err == nil {
+		t.Fatal("no error?")
+	}
+	if ret := err.Error(); ret != msg {
+		t.Fatalf("\nError: \"%s\"\nExpected: \"%s\"\n", ret, msg)
+	}
+}
+
+func TestTypeParamsErrorInstantiate(t *testing.T) {
+	var msg string
+	switch runtime.Version()[:6] {
+	case "go1.18":
+		msg = `./b.gop:2:1: uint does not implement Number`
+	case "go1.19":
+		msg = `./b.gop:2:1: uint does not implement Number (uint missing in ~int | float64)`
+	default:
+		msg = `./b.gop:2:1: uint does not satisfy Number (uint missing in ~int | float64)`
+	}
+
+	mixedErrorTest(t, msg, `
+package main
+
+type Number interface {
+	~int | float64
+}
+
+func Sum[T Number](vec []T) T {
+	var sum T
+	for _, elt := range vec {
+		sum = sum + elt
+	}
+	return sum
+}
+
+var	SumInt = Sum[int]
+`, `
+Sum[uint]
+`)
+}
+
+func TestTypeParamsErrorMatch(t *testing.T) {
+	var msg string
+	switch runtime.Version()[:6] {
+	case "go1.18", "go1.19":
+		msg = `./b.gop:2:5: T does not match ~[]E`
+	case "go1.20":
+		msg = `./b.gop:2:5: int does not match ~[]E`
+	default:
+		msg = `./b.gop:2:5: T (type int) does not satisfy interface{interface{~[]E}}`
+	}
+	mixedErrorTest(t, msg, `
+package main
+
+func At[T interface{ ~[]E }, E any](x T, i int) E {
+	return x[i]
+}
+
+var	AtInt = At[[]int]
+`, `
+_ = At[int]
+`)
+}
+
+func TestTypeParamsErrInferFunc(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:5: cannot infer T2 (/foo/a.go:4:21)`, `
+package main
+
+func Loader[T1 any, T2 any](p1 T1, p2 T2) T1 {
+	return p1
+}
+`, `
+_ = Loader[int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: got 1 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data[int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: got 3 type arguments but Data[T1, T2 interface{}] has 2 type parameters`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data[int,int,int]
+`)
+}
+
+func TestTypeParamsErrArgumentsParameters3(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: got 3 type arguments but func[T1, T2 interface{}](t1 T1, t2 T2) has 2 type parameters`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test[int,int,int](1,2)
+`)
+}
+
+func TestTypeParamsErrCallArguments1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: not enough arguments in call to Test
+	have (untyped int)
+	want (T1, T2)`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test(1)
+`)
+}
+
+func TestTypeParamsErrCallArguments2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: too many arguments in call to Test
+	have (untyped int, untyped int, untyped int)
+	want (T1, T2)`, `
+package main
+
+func Test[T1 any, T2 any](t1 T1, t2 T2) {
+	println(t1,t2)
+}
+`, `
+Test(1,2,3)
+`)
+}
+
+func TestTypeParamsErrCallArguments3(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: too many arguments in call to Test
+	have (untyped int, untyped int)
+	want ()`, `
+package main
+
+func Test[T1 any, T2 any]() {
+	var t1 T1
+	var t2 T2
+	println(t1,t2)
+}
+`, `
+Test(1,2)
+`)
+}
+
+func TestTypeParamsErrCallVariadicArguments1(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: not enough arguments in call to Add
+	have ()
+	want (T1, ...T2)`, `
+package main
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+`, `
+Add()
+`)
+}
+
+func TestTypeParamsErrCallVariadicArguments2(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:1: cannot infer T2 (./a.go:4:18)`, `
+package main
+
+func Add[T1 any, T2 ~int|~uint](v1 T1, v2 ...T2) (sum T2) {
+	println(v1)
+	for _, v := range v2 {
+		sum += v
+	}
+	return sum
+}
+`, `
+Add(1)
+`)
+}
+
+func TestTypeParamsRecvTypeError1(t *testing.T) {
+	mixedErrorTest(t, `./a.go:7:9: cannot use generic type Data[T interface{}] without instantiation`, `
+package main
+
+type Data[T any] struct {
+	v T
+}
+func(p *Data) Test() {
+}
+`, `
+Data[int]{}.Test()
+`)
+}
+
+func _TestTypeParamsRecvTypeError2(t *testing.T) {
+	mixedErrorTest(t, `./a.go:7:9: got 2 arguments but 1 type parameters`, `
+package main
+
+type Data[T any] struct {
+	v T
+}
+func(p *Data[T1,T2]) Test() {
+}
+`, `
+Data[int]{}.Test()
+`)
+}
+
+func _TestTypeParamsRecvTypeError3(t *testing.T) {
+	mixedErrorTest(t, `./a.go:8:9: got 1 type parameter, but receiver base type declares 2`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+func(p *Data[T1]) Test() {
+}
+`, `
+Data[int,int]{}.Test()
+`)
+}
+
+func TestGenericTypeWithoutInst1(t *testing.T) {
+	mixedErrorTest(t, `./a.go:8:9: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+func(p *Data) Test() {
+}
+`, `
+var v Data[int,int]
+`)
+}
+
+func _TestGenericTypeWithoutInst2(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My[T any] struct {
+	Data
+}
+`, `
+var v My[int]
+`)
+}
+
+func _TestGenericTypeWithoutInst3(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My struct {
+	Data
+}
+`, `
+var v My
+`)
+}
+
+func _TestGenericTypeWithoutInst4(t *testing.T) {
+	mixedErrorTest(t, `./a.go:10:15: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+
+type My struct {
+	v map[string]Data
+}
+`, `
+var v My
+`)
+}
+
+func _TestGenericTypeWithoutInst5(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:7: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+var v Data
+`)
+}
+
+func _TestGenericTypeWithoutInst6(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:8: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+type T Data
+`)
+}
+
+func _TestGenericTypeWithoutInst7(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:3:2: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+type My struct {
+	Data
+}
+`)
+}
+
+func _TestGenericTypeWithoutInst8(t *testing.T) {
+	mixedErrorTest(t, `./b.gop:2:23: cannot use generic type Data[T1, T2 interface{}] without instantiation`, `
+package main
+
+type Data[T1 any, T2 any] struct {
+	v1 T1
+	v2 T2
+}
+`, `
+func test(v1 int, v2 *Data) {
+}
+`)
+}
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index 63b8b8dc..a35f9369 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -14,6 +14,28 @@ import (
 	"github.com/goplus/gop/parser/fsx/memfs"
 )
 
+func TestTypeParams(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+type Data[X, Y any] struct {
+	v X
+}
+
+func (p *Data[T, R]) foo() {
+	fmt.Println(p.v)
+}
+`, `
+v := Data[int, float64]{1}
+v.foo()
+`, `package main
+
+func main() {
+	v := Data[int, float64]{1}
+	v.foo()
+}
+`)
+}
+
 func TestTypeParamsFunc(t *testing.T) {
 	gopMixedClTest(t, "main", `package main