Repository: goplus/gop
Pull request #1733 (2024-02-12 10:52:39)

v1.2.1


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index cdf19d39..18c6fb3a 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -31,4 +31,4 @@ jobs:
       run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./...
 
     - name: Codecov
-      uses: codecov/codecov-action@v3
+      uses: codecov/codecov-action@v4
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 33c651c1..6e6e4154 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -34,6 +34,15 @@ jobs:
           echo "TAG: $tag"
           exit 1
         fi
+    
+    - name: Check pre-release
+      run: |
+        version=$(cat VERSION)
+        # check stable version format x.x.x
+        if ! [[ $version =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
+          echo "This is a pre-release"
+          echo "IS_PRERELEASE=true" >> $GITHUB_ENV
+        fi
 
     - name: Set up Go
       uses: actions/setup-go@v5
@@ -53,6 +62,7 @@ jobs:
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
 
     - name: Upload deb/rpm to Fury.io
+      if: env.IS_PRERELEASE != 'true'
       run: |
         for file in dist/*.{deb,rpm}
         do
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 07fac388..cffa2061 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -94,7 +94,7 @@ winget:
       - For STEM education: studying an engineering language that can be used for work in the future.
       - For data science: communicating with engineers in the same language.
     license: Apache-2.0
-    skip_upload: false
+    skip_upload: auto
     release_notes: "{{.Changelog}}"
     release_notes_url: "https://github.com/{{ .Env.GITHUB_REPOSITORY_OWNER }}/gop/releases/tag/v{{.Version}}"
     dependencies:
@@ -109,7 +109,7 @@ winget:
         private_key: "{{ .Env.WINGET_PKGS_PRIVATE_KEY }}"
       pull_request:
         enabled: true
-        draft: false
+        draft: true
         base:
           owner: microsoft
           name: winget-pkgs
diff --git a/README.md b/README.md
index 008849cf..8448ffd2 100644
--- a/README.md
+++ b/README.md
@@ -55,7 +55,59 @@ Don't define a language for specific domain.
 Abstract domain knowledge for it.
 ```
 
-Go+ introduces `classfile` to abstract domain knowledge. See [Go+ Classfiles](doc/classfile.md).
+Go+ introduces `classfile` to abstract domain knowledge.
+
+Sound a bit abstract? Let's take web programming as an example. First let us initialize a hello project:
+
+```sh
+gop mod init hello
+```
+
+Then we have it reference a classfile called `yap` as the HTTP Web Framework:
+
+```sh
+gop get github.com/goplus/yap@latest
+```
+
+We can use it to implement a static file server:
+
+```coffee
+static "/foo", FS("public")
+static "/"    # Equivalent to static "/", FS("static")
+
+run ":8080"
+```
+
+We can also add the ability to handle dynamic GET/POST requests:
+
+```coffee
+static "/foo", FS("public")
+static "/"    # Equivalent to static "/", FS("static")
+
+get "/p/:id", ctx => {
+	ctx.json {
+		"id": ctx.param("id"),
+	}
+}
+
+run ":8080"
+```
+
+Save this code to `hello_yap.gox` file and execute:
+
+```sh
+mkdir -p yap/static yap/public    # Static resources can be placed in these directories
+gop mod tidy
+gop run .
+```
+
+A simplest web program is running now. At this time, if you visit http://localhost:8080/p/123, you will get:
+
+```
+{"id":"123"}
+```
+
+Why is `yap` so easy to use? How does it do it? Click here to learn more about the [Go+ Classfiles](doc/classfile.md) mechanism and [YAP HTTP Web Framework](https://github.com/goplus/yap).
 
 
 ## Key Features of Go+
diff --git a/VERSION b/VERSION
deleted file mode 100644
index 79127d85..00000000
--- a/VERSION
+++ /dev/null
@@ -1,1 +0,0 @@
-v1.2.0
diff --git a/ast/ast.go b/ast/ast.go
index 423e60b9..17ef0e3e 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -709,7 +709,7 @@ type (
 		Body   *BlockStmt // CommClauses only
 	}
 
-	// A ForStmt represents a for statement.
+	// A ForStmt represents a `for init; cond; post { ... }` statement.
 	ForStmt struct {
 		For  token.Pos // position of "for" keyword
 		Init Stmt      // initialization statement; or nil
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 239a73a8..8603f501 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -190,7 +190,7 @@ func (*LambdaExpr2) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ForPhrase represents `for k, v <- container, cond` phrase.
+// ForPhrase represents `for k, v <- container if init; cond` phrase.
 type ForPhrase struct {
 	For        token.Pos // position of "for" keyword
 	Key, Value *Ident    // Key may be nil
diff --git a/cl/compile.go b/cl/compile.go
index 00b515fe..8f3aa742 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -19,7 +19,6 @@ package cl
 
 import (
 	"fmt"
-	"go/constant"
 	"go/types"
 	"log"
 	"reflect"
@@ -175,9 +174,6 @@ type Config struct {
 	// Fset provides source position information for syntax trees and types (required).
 	Fset *token.FileSet
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
-
 	// RelativeBase is the root directory of relative path.
 	RelativeBase string
 
@@ -193,9 +189,6 @@ type Config struct {
 	// See (*github.com/goplus/mod/gopmod.Module).LookupClass.
 	LookupClass func(ext string) (c *Project, ok bool)
 
-	// IsPkgtStandard checks a pkgPath is a Go standard package or not.
-	IsPkgtStandard func(pkgPath string) bool
-
 	// An Importer resolves import paths to Packages (optional).
 	Importer types.Importer
 
@@ -253,20 +246,21 @@ type baseLoader struct {
 	start token.Pos
 }
 
-func initLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name string, fn func(), genBody bool) {
+func initLoader(ctx *pkgCtx, syms map[string]loader, start token.Pos, name string, fn func(), genBody bool) bool {
 	if name == "_" {
 		if genBody {
 			ctx.inits = append(ctx.inits, fn)
 		}
-		return
+		return false
 	}
 	if old, ok := syms[name]; ok {
 		oldpos := ctx.Position(old.pos())
 		ctx.handleErrorf(
 			start, "%s redeclared in this block\n\tprevious declaration at %v", name, oldpos)
-		return
+		return false
 	}
 	syms[name] = &baseLoader{start: start, fn: fn}
+	return true
 }
 
 func (p *baseLoader) load() {
@@ -490,8 +484,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	confGox := &gox.Config{
 		Types:           conf.Types,
 		Fset:            fset,
-		Context:         conf.Context,
-		IsPkgtStandard:  conf.IsPkgtStandard,
 		Importer:        conf.Importer,
 		LoadNamed:       ctx.loadNamed,
 		HandleErr:       ctx.handleErr,
@@ -525,15 +517,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		Pkg: p, LookupPub: conf.LookupPub,
 	})
 
-	for file, gmx := range files {
-		if gmx.IsClass && !gmx.IsNormalGox {
-			if debugLoad {
-				log.Println("==> File", file, "normalGox:", gmx.IsNormalGox)
-			}
-			loadClass(ctx, p, file, gmx, conf)
-		}
-	}
-
 	// sort files
 	type File struct {
 		*ast.File
@@ -547,6 +530,16 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		return sfiles[i].path < sfiles[j].path
 	})
 
+	for _, f := range sfiles {
+		gmx := f.File
+		if gmx.IsClass && !gmx.IsNormalGox {
+			if debugLoad {
+				log.Println("==> File", f.path, "normalGox:", gmx.IsNormalGox)
+			}
+			loadClass(ctx, p, f.path, gmx, conf)
+		}
+	}
+
 	for _, f := range sfiles {
 		fileLine := !conf.NoFileLine
 		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), f.path)
@@ -624,10 +617,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	return
 }
 
-const (
-	gopPackage = "GopPackage"
-)
-
 func isOverloadFunc(name string) bool {
 	n := len(name)
 	return n > 3 && name[n-3:n-1] == "__"
@@ -651,9 +640,6 @@ func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
 			ctx.loadType(lbi.(*ast.Ident).Name)
 		}
 	}
-	if pkg.Types.Scope().Lookup(gopPackage) == nil {
-		pkg.Types.Scope().Insert(types.NewConst(token.NoPos, pkg.Types, gopPackage, types.Typ[types.UntypedBool], constant.MakeBool(true)))
-	}
 	gox.InitThisGopPkg(pkg.Types)
 }
 
@@ -920,7 +906,8 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 				defer p.RestoreCurFile(old)
 				loadFunc(ctx, nil, d, genFnBody)
 			}
-			if name.Name == "init" {
+			fname := name.Name
+			if fname == "init" {
 				if genFnBody {
 					if debugLoad {
 						log.Println("==> Preload func init")
@@ -929,9 +916,13 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile
 				}
 			} else {
 				if debugLoad {
-					log.Println("==> Preload func", name.Name)
+					log.Println("==> Preload func", fname)
+				}
+				if initLoader(parent, syms, name.Pos(), fname, fn, genFnBody) {
+					if strings.HasPrefix(fname, "Gopx_") { // Gopx_xxx func
+						ctx.lbinames = append(ctx.lbinames, fname)
+					}
 				}
-				initLoader(parent, syms, name.Pos(), name.Name, fn, genFnBody)
 			}
 		} else {
 			if name, ok := getRecvTypeName(parent, d.Recv, true); ok {
diff --git a/cl/expr.go b/cl/expr.go
index 9fc45f38..6aa0a865 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -57,7 +57,7 @@ Name lookup:
 // ---------------------------------------------------------------------------*/
 
 const (
-	clIdentCanAutoCall = 1 << iota
+	clIdentCanAutoCall = 1 << iota // allow auto property
 	clIdentAllowBuiltin
 	clIdentLHS
 	clIdentSelectorExpr // this ident is X of ast.SelectorExpr
@@ -134,6 +134,11 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg gox.PkgRef, k
 		}
 	}
 
+	// function alias
+	if compileFuncAlias(ctx, scope, ident, flags) {
+		return
+	}
+
 	// object from import . "xxx"
 	if compilePkgRef(ctx, gox.PkgRef{}, ident, flags, objPkgRef) {
 		return
@@ -391,6 +396,21 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 	}
 }
 
+func compileFuncAlias(ctx *blockCtx, scope *types.Scope, x *ast.Ident, flags int) bool {
+	name := x.Name
+	if c := name[0]; c >= 'a' && c <= 'z' {
+		name = string(rune(c)+('A'-'a')) + name[1:]
+		o := scope.Lookup(name)
+		if o == nil && ctx.loadSymbol(name) {
+			o = scope.Lookup(name)
+		}
+		if o != nil {
+			return identVal(ctx, x, flags, o, true)
+		}
+	}
+	return false
+}
+
 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:]
@@ -437,28 +457,37 @@ func lookupPkgRef(ctx *blockCtx, pkg gox.PkgRef, x *ast.Ident, pkgKind int) (o t
 // allow at.Types to be nil
 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 {
 			if rec := ctx.recorder(); rec != nil {
 				rec.Use(x, v)
 			}
-			cb.VarRef(v, x)
-		} else {
-			autoprop := alias && (flags&clIdentCanAutoCall) != 0
-			if autoprop && !gox.HasAutoProperty(v.Type()) {
+			ctx.cb.VarRef(v, x)
+			return true
+		}
+		return identVal(ctx, x, flags, v, alias)
+	}
+	return false
+}
+
+func identVal(ctx *blockCtx, x *ast.Ident, flags int, v types.Object, alias bool) bool {
+	autocall := false
+	if alias {
+		if (flags & clCommandWithoutArgs) != 0 {
+			autocall = true
+		} else if autocall = (flags & clIdentCanAutoCall) != 0; autocall {
+			if !gox.HasAutoProperty(v.Type()) {
 				return false
 			}
-			if rec := ctx.recorder(); rec != nil {
-				rec.Use(x, v)
-			}
-			cb.Val(v, x)
-			if autoprop {
-				cb.CallWith(0, 0, x)
-			}
 		}
-		return true
 	}
-	return false
+	if rec := ctx.recorder(); rec != nil {
+		rec.Use(x, v)
+	}
+	cb := ctx.cb.Val(v, x)
+	if autocall {
+		cb.CallWith(0, 0, x)
+	}
+	return true
 }
 
 type fnType struct {
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index e7b1f1c1..0b7123f9 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -17,7 +17,7 @@
 package spx
 
 const (
-	GopPackage = true
+	GopPackage = "github.com/goplus/gop/cl/internal/spx/pkg"
 	Gop_sched  = "Sched,SchedNow"
 )
 
diff --git a/cl/internal/spx/pkg/pkg.go b/cl/internal/spx/pkg/pkg.go
index 9c77e7e7..bd2ff494 100644
--- a/cl/internal/spx/pkg/pkg.go
+++ b/cl/internal/spx/pkg/pkg.go
@@ -1,6 +1,6 @@
 package pkg
 
-var (
+const (
 	GopPackage = true
 )
 
diff --git a/cl/outline/outline.go b/cl/outline/outline.go
index 9e97dadd..7adab5ac 100644
--- a/cl/outline/outline.go
+++ b/cl/outline/outline.go
@@ -47,9 +47,6 @@ type Config struct {
 	// LookupClass lookups a class by specified file extension.
 	LookupClass func(ext string) (c *Project, ok bool)
 
-	// IsPkgtStandard checks a pkgPath is a Go standard package or not.
-	IsPkgtStandard func(pkgPath string) bool
-
 	// An Importer resolves import paths to Packages.
 	Importer types.Importer
 }
@@ -66,7 +63,6 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (_ Package, err
 		C2goBase:       conf.C2goBase,
 		LookupPub:      conf.LookupPub,
 		LookupClass:    conf.LookupClass,
-		IsPkgtStandard: conf.IsPkgtStandard,
 		Importer:       conf.Importer,
 		NoFileLine:     true,
 		NoAutoGenMain:  true,
diff --git a/cmd/gop/main.go b/cmd/gop/main.go
index fd8a0d84..16a78dd1 100644
--- a/cmd/gop/main.go
+++ b/cmd/gop/main.go
@@ -42,6 +42,7 @@ import (
 	"github.com/goplus/gop/cmd/internal/test"
 	"github.com/goplus/gop/cmd/internal/version"
 	"github.com/goplus/gop/cmd/internal/watch"
+	"github.com/goplus/gox"
 )
 
 func mainUsage() {
@@ -86,6 +87,7 @@ func main() {
 		help.Help(os.Stderr, args[1:])
 		return
 	}
+	gox.GeneratedHeader = "// Code generated by gop (Go+); DO NOT EDIT.\n\n"
 
 BigCmdLoop:
 	for bigCmd := base.Gop; ; {
diff --git a/cmd/internal/build/build.go b/cmd/internal/build/build.go
index 82aaf6b9..fdee3a44 100644
--- a/cmd/internal/build/build.go
+++ b/cmd/internal/build/build.go
@@ -28,7 +28,6 @@ import (
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/x/gocmd"
-	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
@@ -75,9 +74,11 @@ func runCmd(cmd *base.Command, args []string) {
 		log.Panicln("too many arguments:", args)
 	}
 
-	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
-	confCmd := &gocmd.BuildConfig{Gop: gopEnv}
+	conf, err := gop.NewDefaultConf(".")
+	if err != nil {
+		log.Panicln("gop.NewDefaultConf:", err)
+	}
+	confCmd := &gocmd.BuildConfig{Gop: conf.Gop}
 	if *flagOutput != "" {
 		output, err := filepath.Abs(*flagOutput)
 		if err != nil {
diff --git a/cmd/internal/gengo/go.go b/cmd/internal/gengo/go.go
index ee55eb1f..be9fa033 100644
--- a/cmd/internal/gengo/go.go
+++ b/cmd/internal/gengo/go.go
@@ -33,15 +33,15 @@ import (
 
 // gop go
 var Cmd = &base.Command{
-	UsageLine: "gop go [-v] [packages]",
-	Short:     "Convert Go+ packages into Go packages",
+	UsageLine: "gop go [-v] [packages|files]",
+	Short:     "Convert Go+ code into Go code",
 }
 
 var (
 	flag                 = &Cmd.Flag
 	flagVerbose          = flag.Bool("v", false, "print verbose information")
 	flagCheckMode        = flag.Bool("t", false, "do check syntax only, no generate gop_autogen.go")
-	flagSingleMode       = flag.Bool("s", false, "run in single file mode")
+	flagSingleMode       = flag.Bool("s", false, "run in single file mode for package")
 	flagIgnoreNotatedErr = flag.Bool(
 		"ignore-notated-error", false, "ignore notated errors, only available together with -t (check mode)")
 )
@@ -71,12 +71,16 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	var conf *gop.Config
+	conf, err := gop.NewDefaultConf(".")
+	if err != nil {
+		log.Panicln("gop.NewDefaultConf:", err)
+	}
+
 	flags := gop.GenFlagPrintError | gop.GenFlagPrompt
 	if *flagCheckMode {
 		flags |= gop.GenFlagCheckOnly
 		if *flagIgnoreNotatedErr {
-			conf = &gop.Config{IgnoreNotatedError: true}
+			conf.IgnoreNotatedError = true
 		}
 	}
 	if *flagSingleMode {
@@ -88,6 +92,8 @@ func runCmd(cmd *base.Command, args []string) {
 			_, _, err = gop.GenGoEx(v.Dir, conf, true, flags)
 		case *gopprojs.PkgPathProj:
 			_, _, err = gop.GenGoPkgPathEx("", v.Path, conf, true, flags)
+		case *gopprojs.FilesProj:
+			_, err = gop.GenGoFiles("", v.Files, conf)
 		default:
 			log.Panicln("`gop go` doesn't support", reflect.TypeOf(v))
 		}
diff --git a/cmd/internal/install/install.go b/cmd/internal/install/install.go
index f5d52f48..f7962a7b 100644
--- a/cmd/internal/install/install.go
+++ b/cmd/internal/install/install.go
@@ -27,7 +27,6 @@ import (
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/x/gocmd"
-	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 	"github.com/goplus/mod/modfetch"
@@ -72,9 +71,11 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
-	confCmd := &gocmd.Config{Gop: gopEnv}
+	conf, err := gop.NewDefaultConf(".")
+	if err != nil {
+		log.Panicln("gop.NewDefaultConf:", err)
+	}
+	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
 		install(proj, conf, confCmd)
diff --git a/cmd/internal/run/run.go b/cmd/internal/run/run.go
index 525ae141..e2a8b004 100644
--- a/cmd/internal/run/run.go
+++ b/cmd/internal/run/run.go
@@ -26,7 +26,6 @@ import (
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/x/gocmd"
-	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 	"github.com/qiniu/x/log"
@@ -81,9 +80,11 @@ func runCmd(cmd *base.Command, args []string) {
 	}
 
 	noChdir := *flagNoChdir
-	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
-	confCmd := &gocmd.Config{Gop: gopEnv}
+	conf, err := gop.NewDefaultConf(".")
+	if err != nil {
+		log.Panicln("gop.NewDefaultConf:", err)
+	}
+	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	run(proj, args, !noChdir, conf, confCmd)
 }
diff --git a/doc/classfile.md b/doc/classfile.md
index 6404498d..09a940e7 100644
--- a/doc/classfile.md
+++ b/doc/classfile.md
@@ -12,6 +12,128 @@ Abstract domain knowledge for it.
 
 Go+ introduces `classfile` to abstract domain knowledge.
 
+Sound a bit abstract? Let's take web programming as an example. First let us initialize a hello project:
+
+```sh
+gop mod init hello
+```
+
+Then we have it reference a classfile called `yap` as the HTTP Web Framework:
+
+```sh
+gop get github.com/goplus/yap@latest
+```
+
+We can use it to implement a static file server:
+
+```coffee
+static "/foo", FS("public")
+static "/"    # Equivalent to static "/", FS("static")
+
+run ":8080"
+```
+
+We can also add the ability to handle dynamic GET/POST requests:
+
+```coffee
+static "/foo", FS("public")
+static "/"    # Equivalent to static "/", FS("static")
+
+get "/p/:id", ctx => {
+	ctx.json {
+		"id": ctx.param("id"),
+	}
+}
+
+run ":8080"
+```
+
+Save this code to `hello_yap.gox` file and execute:
+
+```sh
+mkdir -p yap/static yap/public    # Static resources can be placed in these directories
+gop mod tidy
+gop run .
+```
+
+A simplest web program is running now. At this time, if you visit http://localhost:8080/p/123, you will get:
+
+```
+{"id":"123"}
+```
+
+Why is `yap` so easy to use? How does it do it? Let us analyze the principles one by one.
+
+
+### What's classfile
+
+What's a classfile? And why it is called `classfile`?
+
+First let's create a file called `Rect.gox`:
+
+```go
+var (
+	Width, Height int
+)
+
+func Area() int {
+	return Width * Height
+}
+```
+
+Then we create `hello.gop` file in the same directory:
+
+```go
+rect := &Rect{10, 20}
+printlnrect.area
+```
+
+Then we execute `gop run .` to run it and get the result:
+
+```sh
+200
+```
+
+This shows that the `Rect.gox` file actually defines a class named `Rect`. If we express it in Go syntax, it looks like this:
+
+```go
+type Rect struct {
+	Width, Height int
+}
+
+func (this *Rect) Area() int {
+	return this.Width * this.Height
+}
+```
+
+So the name `classfile` comes from the fact that it actually defines a class.
+
+You may ask: What is the value of doing this?
+
+The value lies in its ease of use, especially for children and non-expert programmers. Let's look at this syntax:
+
+```go
+var (
+	Width, Height int
+)
+
+func Area() int {
+	return Width * Height
+}
+```
+
+Defining variables and defining functions are all familiar to them while learning sequential programming. They can define new types using syntax they already know by heart. This will be valuable in getting a wider community to learn Go+.
+
+Of course, this is not enough to make classfiles an exciting feature. What's more important is its ability to abstract domain knowledge. It is accomplished by defining `base class` for a class and defining `relationships between multiple classes`.
+
+What is a `classfile`? Usually it consists of a `project class` and multiple `worker classes`. The classfile not only specifies the `base class` of all `project class` and `worker classes`, but also organizes all these classes together by the base class of project class. There can be no worker classes, that is, the entire classfile consists of only one project class.
+
+This is a bit abstract. Let's take the [2D Game Engine spx](https://github.com/goplus/spx) as an example. The base class of project class of `spx classfile` is called `Game`. The base class of worker class is called `Sprite`. Obviously, there will only be one Game instance in a game, but there are many types of sprites, so many types of worker classes are needed, but they all have the same base class called `Sprite`. Go+'s classfile allows you to specify different base classes for different worker classes. Although this is rare, it can be done.
+
+How does Go+ identify various class files of a classfile? by its filename. By convention, if we define a classfile called `foo`, then its project class is usually called `main_foo.gox`, and the worker class is usually called `xxx_foo.gox`. If this classfile does not have a worker class, then the project class only needs to ensure that the suffix is `_foo.gox`, and the class name can be freely chosen. For example, our previous `yap` framework only has project class, so a file name like `hello_yap.gox` can be correctly recognized.
+
+The earliest version of Go+ allows classfiles to be identified through custom file extensions. For example, the project class of the `spx classfile` is called `main.spx`, and the worker class is called `xxx.spx`. Although this ability to customize extensions is still retained for now, we do not recommend its use and there is no guarantee that it will continue to be available in the future. `spx` will likely be the only classfile to use a custom extension.
+
 
 ### classfile: Unit Test
 
diff --git a/gengo.go b/gengo.go
index 79100c0e..29becfdb 100644
--- a/gengo.go
+++ b/gengo.go
@@ -83,7 +83,7 @@ func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenF
 		} else {
 			fn = func(path string, d fs.DirEntry, err error) error {
 				if err == nil && d.IsDir() {
-					if strings.HasPrefix(d.Name(), "_") { // skip _
+					if strings.HasPrefix(d.Name(), "_") || (path != dir && hasMod(path)) { // skip _
 						return filepath.SkipDir
 					}
 					if e := genGoIn(path, conf, genTestPkg, flags); e != nil && notIgnNotated(e, conf) {
@@ -122,6 +122,11 @@ func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenF
 	return
 }
 
+func hasMod(dir string) bool {
+	_, err := os.Lstat(dir + "/go.mod")
+	return err == nil
+}
+
 func notIgnNotated(e error, conf *Config) bool {
 	return !(conf != nil && conf.IgnoreNotatedError && IgnoreNotated(e))
 }
diff --git a/go.mod b/go.mod
index d2e72725..d67977e1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,10 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.21
-	github.com/goplus/gox v1.14.2
-	github.com/goplus/mod v0.13.0
-	github.com/qiniu/x v1.13.3
+	github.com/goplus/c2go v0.7.22
+	github.com/goplus/gox v1.14.10
+	github.com/goplus/mod v0.13.5
+	github.com/qiniu/x v1.13.5
 	golang.org/x/tools v0.17.0
 )
 
@@ -15,7 +15,7 @@ require (
 	github.com/json-iterator/go v1.1.12 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
-	golang.org/x/mod v0.14.0 // indirect
+	golang.org/x/mod v0.15.0 // indirect
 	golang.org/x/sys v0.16.0 // indirect
 )
 
diff --git a/go.sum b/go.sum
index ec0dff2a..bebcbb3f 100644
--- a/go.sum
+++ b/go.sum
@@ -4,12 +4,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.21 h1:bRWda6KtQdY0ph+tnPEOGzZMq3OUkAI9cJBzrr5N6AM=
-github.com/goplus/c2go v0.7.21/go.mod h1:45zjbWGW8NDuGfTOrFchCIpH9TbiH7TPg8/zF6EdEyk=
-github.com/goplus/gox v1.14.2 h1:sc2W+R3D6j1EZ6G+/KWMUDSu3m6dcscZCvnTH2M4XG4=
-github.com/goplus/gox v1.14.2/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
-github.com/goplus/mod v0.13.0 h1:M/VsI/NWTcs2+vtWBG7793zmlCimKlSMY5LXaiztXV8=
-github.com/goplus/mod v0.13.0/go.mod h1:Qin2SxKoK+3cNFwtg4Jnk1F9Qn5vR6Kwlvr6lTCRJNI=
+github.com/goplus/c2go v0.7.22 h1:TnOf6VJTvVKFhp8+zTP6POwJ894mdNGV7qKRYY1o2oY=
+github.com/goplus/c2go v0.7.22/go.mod h1:f3v+Td4JQwhToKfQK5tGvjMoMBrIR9IJjHNqzDC6NIE=
+github.com/goplus/gox v1.14.10 h1:fbcA/BzFqEOp28BQjt27UR3DkYYpT4uXObWxZXEDkbU=
+github.com/goplus/gox v1.14.10/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/mod v0.13.5 h1:rEQ4UFmLKU1J5IK82DwC/sAKIhAMJZ2VbBrKU/k448c=
+github.com/goplus/mod v0.13.5/go.mod h1:PBGRYvD381NxJdeI+KyYHiDX9fgnDiuttzyCTG9LfHE=
 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=
@@ -18,8 +18,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qiniu/x v1.13.3 h1:NER9aJnVzjH0XapzIWrWNAn2SPwck0xGMyIIlfCMm84=
-github.com/qiniu/x v1.13.3/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
+github.com/qiniu/x v1.13.5 h1:YJz5i3RD/7srMLEmp7UAcYmZ9qrhOPk0EL0pGiuxGbA=
+github.com/qiniu/x v1.13.5/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
@@ -31,8 +31,9 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
-golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
 golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
+golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
+golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
diff --git a/imp.go b/imp.go
index 7de0b4b3..4b18f3bf 100644
--- a/imp.go
+++ b/imp.go
@@ -73,6 +73,9 @@ func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) {
 	if mod := p.mod; hasModfile(mod) {
 		ret, e := mod.Lookup(pkgPath)
 		if e != nil {
+			if isPkgInMod(pkgPath, "github.com/qiniu/x") {
+				return p.impFrom.ImportFrom(pkgPath, p.gop.Root, 0)
+			}
 			return nil, e
 		}
 		switch ret.Type {
@@ -125,6 +128,14 @@ func (p *Importer) genGoExtern(dir string, isExtern bool) (err error) {
 	return
 }
 
+func isPkgInMod(pkgPath, modPath string) bool {
+	if strings.HasPrefix(pkgPath, modPath) {
+		suffix := pkgPath[len(modPath):]
+		return suffix == "" || suffix[0] == '/'
+	}
+	return false
+}
+
 func defaultGoMod(modPath string) []byte {
 	return []byte(`module ` + modPath + `
 
diff --git a/load.go b/load.go
index 79890fb4..214a14ee 100644
--- a/load.go
+++ b/load.go
@@ -22,7 +22,6 @@ import (
 	"io/fs"
 	"os"
 	"strings"
-	"syscall"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
@@ -37,13 +36,13 @@ import (
 )
 
 var (
-	ErrNotFound      = syscall.ENOENT
+	ErrNotFound      = gopmod.ErrNotFound
 	ErrIgnoreNotated = errors.New("notated error ignored")
 )
 
 // NotFound returns if cause err is ErrNotFound or not
 func NotFound(err error) bool {
-	return errors.Err(err) == ErrNotFound
+	return gopmod.IsNotFound(err)
 }
 
 // IgnoreNotated returns if cause err is ErrIgnoreNotated or not.
@@ -118,13 +117,24 @@ func isNotatedErr(err error, pkg *ast.Package, fset *token.FileSet) (notatedErr
 type Config struct {
 	Gop      *env.Gop
 	Fset     *token.FileSet
-	Filter   func(fs.FileInfo) bool
+	Mod      *gopmod.Module
 	Importer types.Importer
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
+	Filter func(fs.FileInfo) bool
 
 	IgnoreNotatedError bool
+	DontUpdateGoMod    bool
+}
+
+func NewDefaultConf(dir string) (conf *Config, err error) {
+	mod, err := LoadMod(dir)
+	if err != nil {
+		return
+	}
+	gop := gopenv.Get()
+	fset := token.NewFileSet()
+	imp := NewImporter(mod, gop, fset)
+	return &Config{Gop: gop, Fset: fset, Mod: mod, Importer: imp}, nil
 }
 
 func LoadMod(dir string) (mod *gopmod.Module, err error) {
@@ -151,14 +161,18 @@ func hasModfile(mod *gopmod.Module) bool {
 // -----------------------------------------------------------------------------
 
 func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (out, test *gox.Package, err error) {
-	mod, err := LoadMod(dir)
-	if err != nil {
-		return
-	}
-
 	if conf == nil {
 		conf = new(Config)
 	}
+
+	mod := conf.Mod
+	if mod == nil {
+		if mod, err = LoadMod(dir); err != nil {
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			return
+		}
+	}
+
 	fset := conf.Fset
 	if fset == nil {
 		fset = token.NewFileSet()
@@ -175,24 +189,22 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 		return nil, nil, ErrNotFound
 	}
 
+	gop := conf.Gop
+	if gop == nil {
+		gop = gopenv.Get()
+	}
 	imp := conf.Importer
 	if imp == nil {
-		gop := conf.Gop
-		if gop == nil {
-			gop = gopenv.Get()
-		}
 		imp = NewImporter(mod, gop, fset)
 	}
 
 	var pkgTest *ast.Package
 	var clConf = &cl.Config{
-		Fset:           fset,
-		Context:        conf.Context,
-		RelativeBase:   relativeBaseOf(mod),
-		Importer:       imp,
-		IsPkgtStandard: mod.IsPkgtStandard,
-		LookupClass:    mod.LookupClass,
-		LookupPub:      c2go.LookupPub(mod),
+		Fset:         fset,
+		RelativeBase: relativeBaseOf(mod),
+		Importer:     imp,
+		LookupClass:  mod.LookupClass,
+		LookupPub:    c2go.LookupPub(mod),
 	}
 
 	for name, pkg := range pkgs {
@@ -223,9 +235,29 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 	if out == nil {
 		return nil, nil, ErrNotFound
 	}
-	if pkgTest != nil && genTestPkg {
+	if genTestPkg && pkgTest != nil {
 		test, err = cl.NewPackage("", pkgTest, clConf)
 	}
+	saveWithGopMod(mod, gop, out, test, conf)
+	return
+}
+
+func saveWithGopMod(mod *gopmod.Module, gop *env.Gop, out, test *gox.Package, conf *Config) {
+	if !conf.DontUpdateGoMod && mod.HasModfile() {
+		flags := checkGopDeps(out)
+		if test != nil {
+			flags |= checkGopDeps(test)
+		}
+		if flags != 0 {
+			mod.SaveWithGopMod(gop, flags)
+		}
+	}
+}
+
+func checkGopDeps(pkg *gox.Package) (flags int) {
+	pkg.ForEachFile(func(fname string, file *gox.File) {
+		flags |= file.CheckGopDeps(pkg)
+	})
 	return
 }
 
@@ -240,15 +272,18 @@ func relativeBaseOf(mod *gopmod.Module) string {
 // -----------------------------------------------------------------------------
 
 func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err error) {
-	mod, err := LoadMod(dir)
-	if err != nil {
-		err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
-		return
-	}
-
 	if conf == nil {
 		conf = new(Config)
 	}
+
+	mod := conf.Mod
+	if mod == nil {
+		if mod, err = LoadMod(dir); err != nil {
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			return
+		}
+	}
+
 	fset := conf.Fset
 	if fset == nil {
 		fset = token.NewFileSet()
@@ -272,13 +307,11 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err
 			imp = NewImporter(mod, gop, fset)
 		}
 		clConf := &cl.Config{
-			Fset:           fset,
-			Context:        conf.Context,
-			RelativeBase:   relativeBaseOf(mod),
-			Importer:       imp,
-			LookupClass:    mod.LookupClass,
-			IsPkgtStandard: mod.IsPkgtStandard,
-			LookupPub:      c2go.LookupPub(mod),
+			Fset:         fset,
+			RelativeBase: relativeBaseOf(mod),
+			Importer:     imp,
+			LookupClass:  mod.LookupClass,
+			LookupPub:    c2go.LookupPub(mod),
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
@@ -288,6 +321,7 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err
 		}
 		break
 	}
+	saveWithGopMod(mod, gop, out, nil, conf)
 	return
 }
 
diff --git a/outline.go b/outline.go
index 06334797..050b8a80 100644
--- a/outline.go
+++ b/outline.go
@@ -30,6 +30,7 @@ import (
 	"github.com/goplus/gop/x/c2go"
 	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/mod/gopmod"
+	"github.com/qiniu/x/errors"
 )
 
 // -----------------------------------------------------------------------------
@@ -38,14 +39,19 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 	if dir, err = filepath.Abs(dir); err != nil {
 		return
 	}
-	mod, err := LoadMod(dir)
-	if err != nil {
-		return
-	}
 
 	if conf == nil {
 		conf = new(Config)
 	}
+
+	mod := conf.Mod
+	if mod == nil {
+		if mod, err = LoadMod(dir); err != nil {
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", dir)
+			return
+		}
+	}
+
 	filterConf := conf.Filter
 	filter := func(fi fs.FileInfo) bool {
 		if filterConf != nil && !filterConf(fi) {
@@ -112,7 +118,14 @@ func Outline(dir string, conf *Config) (out outline.Package, err error) {
 // -----------------------------------------------------------------------------
 
 func OutlinePkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (out outline.Package, err error) {
-	mod, err := gopmod.Load(workDir)
+	mod := conf.Mod
+	if mod == nil {
+		if mod, err = LoadMod(workDir); err != nil {
+			err = errors.NewWith(err, `LoadMod(dir)`, -2, "gop.LoadMod", workDir)
+			return
+		}
+	}
+
 	if NotFound(err) && allowExtern {
 		remotePkgPathDo(pkgPath, func(pkgDir, modDir string) {
 			modFile := chmodModfile(modDir)
diff --git a/parser/_nofmt/printvariadic/parser.expect b/parser/_nofmt/printvariadic/parser.expect
new file mode 100644
index 00000000..ae693de7
--- /dev/null
+++ b/parser/_nofmt/printvariadic/parser.expect
@@ -0,0 +1,33 @@
+package main
+
+file printv.gop
+noEntrypoint
+ast.FuncDecl:
+  Name:
+    ast.Ident:
+      Name: main
+  Type:
+    ast.FuncType:
+      Params:
+        ast.FieldList:
+  Body:
+    ast.BlockStmt:
+      List:
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: x
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: y
diff --git a/parser/_nofmt/printvariadic/printv.gop b/parser/_nofmt/printvariadic/printv.gop
new file mode 100644
index 00000000..aa3f9a4d
--- /dev/null
+++ b/parser/_nofmt/printvariadic/printv.gop
@@ -0,0 +1,6 @@
+println(
+	x...
+)
+println(
+	y...,
+)
diff --git a/parser/parser.go b/parser/parser.go
index 1c011e92..9133f72d 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -466,8 +466,8 @@ func (p *parser) expectClosing(tok token.Token, context string) token.Pos {
 }
 
 func (p *parser) expectSemi() {
-	// semicolon is optional before a closing ')' or '}' or EOF
-	if p.tok != token.RPAREN && p.tok != token.RBRACE && p.tok != token.EOF {
+	// semicolon is optional before a closing ')' or '}'
+	if p.tok != token.RPAREN && p.tok != token.RBRACE {
 		switch p.tok {
 		case token.COMMA:
 			// permit a ',' instead of a ';' but complain
@@ -1955,9 +1955,6 @@ func (p *parser) parseCallOrConversion(fun ast.Expr, isCmd bool) *ast.CallExpr {
 		if p.tok == token.ELLIPSIS {
 			ellipsis = p.pos
 			p.next()
-			if p.tok != token.RPAREN {
-				break
-			}
 		}
 		if isCmd && p.tok == token.RBRACE {
 			break
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 34f549f2..54013c62 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -263,7 +263,14 @@ func ParseFiles(fset *token.FileSet, files []string, mode Mode) (map[string]*ast
 
 func ParseFSFiles(fset *token.FileSet, fs FileSystem, files []string, mode Mode) (map[string]*ast.Package, error) {
 	ret := map[string]*ast.Package{}
+	fabs := (mode & SaveAbsFile) != 0
+	if fabs {
+		mode &^= SaveAbsFile
+	}
 	for _, file := range files {
+		if fabs {
+			file, _ = fs.Abs(file)
+		}
 		f, err := ParseFSFile(fset, fs, file, nil, mode)
 		if err != nil {
 			return nil, err
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 08189102..1c7a546a 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -53,6 +53,7 @@ type Scanner struct {
 	offset     int  // character offset
 	rdOffset   int  // reading offset (position after current character)
 	lineOffset int  // current line offset
+	nParen     int
 	insertSemi bool // insert a semicolon before next newline
 
 	// public state - ok to modify
@@ -783,6 +784,11 @@ func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Tok
 	return tok0
 }
 
+func (s *Scanner) tokSEMICOLON() token.Token {
+	s.nParen = 0
+	return token.SEMICOLON
+}
+
 // Scan scans the next token and returns the token position, the token,
 // and its literal string if applicable. The source end is indicated by
 // token.EOF.
@@ -850,7 +856,7 @@ scanAgain:
 		case -1:
 			if s.insertSemi {
 				s.insertSemi = false // EOF consumed
-				return pos, token.SEMICOLON, "\n"
+				return pos, s.tokSEMICOLON(), "\n"
 			}
 			tok = token.EOF
 		case '\n':
@@ -858,7 +864,7 @@ scanAgain:
 			// set in the first place and exited early
 			// from s.skipWhitespace()
 			s.insertSemi = false // newline consumed
-			return pos, token.SEMICOLON, "\n"
+			return pos, s.tokSEMICOLON(), "\n"
 		case '"':
 			insertSemi = true
 			tok = token.STRING
@@ -879,16 +885,21 @@ scanAgain:
 			if s.ch == '.' && s.peek() == '.' {
 				s.next()
 				s.next() // consume last '.'
+				if s.nParen == 0 {
+					insertSemi = true
+				}
 				tok = token.ELLIPSIS
 			}
 		case ',':
 			tok = token.COMMA
 		case ';':
-			tok = token.SEMICOLON
+			tok = s.tokSEMICOLON()
 			lit = ";"
 		case '(':
+			s.nParen++
 			tok = token.LPAREN
 		case ')':
+			s.nParen--
 			insertSemi = true
 			tok = token.RPAREN
 		case '[':
@@ -919,7 +930,7 @@ scanAgain:
 				s.offset = s.file.Offset(pos)
 				s.rdOffset = s.offset + 1
 				s.insertSemi = false // newline consumed
-				return pos, token.SEMICOLON, "\n"
+				return pos, s.tokSEMICOLON(), "\n"
 			}
 			comment := s.scanComment()
 			if s.mode&ScanComments == 0 {
@@ -938,7 +949,7 @@ scanAgain:
 					s.offset = s.file.Offset(pos)
 					s.rdOffset = s.offset + 1
 					s.insertSemi = false // newline consumed
-					return pos, token.SEMICOLON, "\n"
+					return pos, s.tokSEMICOLON(), "\n"
 				}
 				comment := s.scanComment()
 				if s.mode&ScanComments == 0 {
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index f6803f47..03a3a5ef 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -69,9 +69,6 @@ type Config struct {
 	// Fset provides source position information for syntax trees and types (required).
 	Fset *token.FileSet
 
-	// Context represents all things between packages (optional).
-	Context *gox.Context
-
 	// WorkingDir is the directory in which to run gop compiler (optional).
 	// If WorkingDir is not set, os.Getwd() is used.
 	WorkingDir string
@@ -147,12 +144,10 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	_, err = cl.NewPackage(pkgTypes.Path(), pkg, &cl.Config{
 		Types:          pkgTypes,
 		Fset:           fset,
-		Context:        opts.Context,
 		C2goBase:       opts.C2goBase,
 		LookupPub:      c2go.LookupPub(mod),
 		LookupClass:    mod.LookupClass,
-		IsPkgtStandard: mod.IsPkgtStandard,
-		Importer:       newImporter(conf.Importer, mod, nil, fset),
+		Importer:       conf.Importer,
 		Recorder:       gopRecorder{p.gopInfo},
 		NoFileLine:     true,
 		NoAutoGenMain:  true,
diff --git a/x/typesutil/imp.go b/x/typesutil/imp.go
deleted file mode 100644
index fa51c247..00000000
--- a/x/typesutil/imp.go
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (c) 2023 The GoPlus Authors (goplus.org). All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package typesutil
-
-import (
-	"go/types"
-	"syscall"
-
-	"github.com/goplus/gop"
-	"github.com/goplus/gop/token"
-	"github.com/goplus/gop/x/gopenv"
-	"github.com/goplus/mod/env"
-	"github.com/goplus/mod/gopmod"
-)
-
-// -----------------------------------------------------------------------------
-
-type nilImporter struct{}
-
-func (p nilImporter) Import(path string) (ret *types.Package, err error) {
-	return nil, syscall.ENOENT
-}
-
-// -----------------------------------------------------------------------------
-
-type importer struct {
-	imp types.Importer
-	alt *gop.Importer
-
-	mod  *gopmod.Module
-	gop  *env.Gop
-	fset *token.FileSet
-}
-
-func newImporter(imp types.Importer, mod *gopmod.Module, gop *env.Gop, fset *token.FileSet) types.Importer {
-	if imp == nil {
-		imp = nilImporter{}
-	}
-	if gop == nil {
-		gop = gopenv.Get()
-	}
-	return &importer{imp, nil, mod, gop, fset}
-}
-
-func (p *importer) Import(path string) (ret *types.Package, err error) {
-	ret, err = p.imp.Import(path)
-	if err == nil {
-		return
-	}
-	if p.alt == nil {
-		p.alt = gop.NewImporter(p.mod, p.gop, p.fset)
-	}
-	return p.alt.Import(path)
-}
-
-// -----------------------------------------------------------------------------


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 3d03c7ad..c44fa00e 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,21 @@ func getGoxConf() *gox.Config {
 	return &gox.Config{Fset: fset, Importer: imp}
 }
 
+func TestCompileFuncAlias(t *testing.T) {
+	ctx := &blockCtx{
+		pkgCtx: &pkgCtx{
+			syms: map[string]loader{"Foo": &baseLoader{
+				fn: func() {},
+			}},
+		},
+	}
+	scope := types.NewScope(nil, 0, 0, "")
+	x := ast.NewIdent("foo")
+	if compileFuncAlias(ctx, scope, x, 0) {
+		t.Fatal("compileFuncAlias: ok?")
+	}
+}
+
 func TestErrStringLit(t *testing.T) {
 	defer func() {
 		if e := recover(); e == nil {
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index b09eaf65..8c1acbbf 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -20,6 +20,63 @@ import (
 	"testing"
 )
 
+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
+
+func (f foo) Ls(args ...string) {
+}
+
+var f foo
+
+func main() {
+	f.Ls()
+}
+`)
+}
+
+func TestCommandInPkg(t *testing.T) {
+	gopClTest(t, `
+func Ls(args ...string) {
+}
+
+ls
+`, `package main
+
+func Ls(args ...string) {
+}
+func main() {
+	Ls()
+}
+`)
+}
+
+func TestFuncAlias(t *testing.T) {
+	gopClTest(t, `
+func Foo(a ...int) {}
+
+foo 100
+foo
+`, `package main
+
+func Foo(a ...int) {
+}
+func main() {
+	Foo(100)
+	Foo()
+}
+`)
+}
+
 func TestOverloadOp(t *testing.T) {
 	gopClTest(t, `
 type foo struct {
@@ -117,6 +174,8 @@ println 10 * a
 
 import "fmt"
 
+const GopPackage = true
+
 type foo struct {
 }
 
@@ -169,6 +228,8 @@ var d = a.mul(c)
 
 import "fmt"
 
+const GopPackage = true
+
 type foo struct {
 }
 
@@ -206,6 +267,8 @@ println add("Hello", "World")
 
 import "fmt"
 
+const GopPackage = true
+
 func add__0(a int, b int) int {
 	return a + b
 }
@@ -240,6 +303,7 @@ println mul(1.2, 3.14)
 
 import "fmt"
 
+const GopPackage = true
 const Gopo_mul = "mulInt,mulFloat"
 
 func mulInt(a int, b int) int {
@@ -500,6 +564,8 @@ n.onKey ["a"], nil, key => {
 n.onKey 100, 200
 `, `package main
 
+const GopPackage = true
+
 type Mesh struct {
 }
 
@@ -652,6 +718,8 @@ a += b
 a += c
 `, `package main
 
+const GopPackage = true
+
 var a Vector3
 var b int
 var c float64
@@ -718,6 +786,8 @@ i.onKey ["1","2"], key => {
 
 import "fmt"
 
+const GopPackage = true
+
 func main() {
 	n := &N[int]{}
 	n.OnKey__0("1", func() {
@@ -760,6 +830,8 @@ n.test
 n.test 100
 `, `package main
 
+const GopPackage = true
+
 func main() {
 	Test__0()
 	Test__1(100)
@@ -825,6 +897,8 @@ c := Var(string)
 d := Var(M)
 `, `package main
 
+const GopPackage = true
+
 var a Var__0[int]
 var b Var__1[map[string]interface {
 }]
@@ -1025,6 +1099,90 @@ func main() {
 `, false)
 }
 
+func TestTypeAsParamsFunc(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+import (
+	"fmt"
+	"reflect"
+)
+
+type basetype interface {
+	int | string
+}
+
+func Gopx_Row__0[T basetype](name string) {
+}
+
+func Gopx_Row__1[Array any](v int) {
+}
+
+func Gopx_Col[T any](name string) {
+	fmt.Printf("%v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name)
+}
+
+type Table struct {
+}
+
+func Gopt_Table_Gopx_Col__0[T basetype](p *Table, name string) {
+}
+
+func Gopt_Table_Gopx_Col__1[Array any](p *Table, v int) {
+}
+`, `
+var tbl *Table
+
+col string, "name"
+col int, "age"
+
+row string, 100
+
+tbl.col string, "foo"
+tbl.col int, 100
+`, `package main
+
+const GopPackage = true
+
+var tbl *Table
+
+func main() {
+	Gopx_Col[string]("name")
+	Gopx_Col[int]("age")
+	Gopx_Row__1[string](100)
+	Gopt_Table_Gopx_Col__0[string](tbl, "foo")
+	Gopt_Table_Gopx_Col__1[int](tbl, 100)
+}
+`)
+}
+
+func TestYaptest(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+import (
+	"github.com/goplus/gop/cl/internal/test"
+)
+
+type Class struct {
+	test.Case
+}
+`, `var c Class
+var a int
+
+c.match a, "b"
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/test"
+
+var c Class
+var a int
+
+func main() {
+	test.Gopt_Case_MatchAny(c, a, "b")
+}
+`)
+}
+
 func Test_RangeExpressionIf_Issue1243(t *testing.T) {
 	gopClTest(t, `
 for i <- :10, i%3 == 0 {
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 9c17a0b5..6c5d68d5 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -122,40 +122,6 @@ func gopClTestFS(t *testing.T, conf *cl.Config, fs parser.FileSystem, pkgname, e
 	}
 }
 
-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
-
-func (f foo) Ls(args ...string) {
-}
-
-var f foo
-
-func main() {
-	f.Ls()
-}
-`)
-}
-
-func _TestCommandInPkg(t *testing.T) {
-	gopClTest(t, `
-func Ls(args ...string) {
-}
-
-ls
-`, `
-`)
-}
-
 func Test_CastSlice_Issue1240(t *testing.T) {
 	gopClTest(t, `
 type fvec []float64
diff --git a/cl/internal/test/case.go b/cl/internal/test/case.go
new file mode 100644
index 00000000..1dfd5425
--- /dev/null
+++ b/cl/internal/test/case.go
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2024 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 test
+
+import (
+	"testing"
+	"time"
+)
+
+// -----------------------------------------------------------------------------
+
+type CaseT interface {
+	// Name returns the name of the running (sub-) test or benchmark.
+	//
+	// The name will include the name of the test along with the names of
+	// any nested sub-tests. If two sibling sub-tests have the same name,
+	// Name will append a suffix to guarantee the returned name is unique.
+	Name() string
+
+	// Fail marks the function as having failed but continues execution.
+	Fail()
+
+	// Failed reports whether the function has failed.
+	Failed() bool
+
+	// FailNow marks the function as having failed and stops its execution
+	// by calling runtime.Goexit (which then runs all deferred calls in the
+	// current goroutine).
+	// Execution will continue at the next test or benchmark.
+	// FailNow must be called from the goroutine running the
+	// test or benchmark function, not from other goroutines
+	// created during the test. Calling FailNow does not stop
+	// those other goroutines.
+	FailNow()
+
+	// Log formats its arguments using default formatting, analogous to Println,
+	// and records the text in the error log. For tests, the text will be printed only if
+	// the test fails or the -test.v flag is set. For benchmarks, the text is always
+	// printed to avoid having performance depend on the value of the -test.v flag.
+	Log(args ...any)
+
+	// Logf formats its arguments according to the format, analogous to Printf, and
+	// records the text in the error log. A final newline is added if not provided. For
+	// tests, the text will be printed only if the test fails or the -test.v flag is
+	// set. For benchmarks, the text is always printed to avoid having performance
+	// depend on the value of the -test.v flag.
+	Logf(format string, args ...any)
+
+	// Errorln is equivalent to Log followed by Fail.
+	Errorln(args ...any)
+
+	// Errorf is equivalent to Logf followed by Fail.
+	Errorf(format string, args ...any)
+
+	// Fatal is equivalent to Log followed by FailNow.
+	Fatal(args ...any)
+
+	// Fatalf is equivalent to Logf followed by FailNow.
+	Fatalf(format string, args ...any)
+
+	// Skip is equivalent to Log followed by SkipNow.
+	Skip(args ...any)
+
+	// Skipf is equivalent to Logf followed by SkipNow.
+	Skipf(format string, args ...any)
+
+	// SkipNow marks the test as having been skipped and stops its execution
+	// by calling runtime.Goexit.
+	// If a test fails (see Error, Errorf, Fail) and is then skipped,
+	// it is still considered to have failed.
+	// Execution will continue at the next test or benchmark. See also FailNow.
+	// SkipNow must be called from the goroutine running the test, not from
+	// other goroutines created during the test. Calling SkipNow does not stop
+	// those other goroutines.
+	SkipNow()
+
+	// Skipped reports whether the test was skipped.
+	Skipped() bool
+
+	// Helper marks the calling function as a test helper function.
+	// When printing file and line information, that function will be skipped.
+	// Helper may be called simultaneously from multiple goroutines.
+	Helper()
+
+	// Cleanup registers a function to be called when the test (or subtest) and all its
+	// subtests complete. Cleanup functions will be called in last added,
+	// first called order.
+	Cleanup(f func())
+
+	// TempDir returns a temporary directory for the test to use.
+	// The directory is automatically removed by Cleanup when the test and
+	// all its subtests complete.
+	// Each subsequent call to t.TempDir returns a unique directory;
+	// if the directory creation fails, TempDir terminates the test by calling Fatal.
+	TempDir() string
+
+	// Run runs f as a subtest of t called name.
+	//
+	// Run may be called simultaneously from multiple goroutines, but all such calls
+	// must return before the outer test function for t returns.
+	Run(name string, f func()) bool
+
+	// Deadline reports the time at which the test binary will have
+	// exceeded the timeout specified by the -timeout flag.
+	//
+	// The ok result is false if the -timeout flag indicates “no timeout” (0).
+	Deadline() (deadline time.Time, ok bool)
+}
+
+// -----------------------------------------------------------------------------
+
+type TestingT struct {
+	*testing.T
+}
+
+// NewT creates a testing object.
+func NewT(t *testing.T) TestingT {
+	return TestingT{t}
+}
+
+// Errorln is equivalent to Log followed by Fail.
+func (p TestingT) Errorln(args ...any) {
+	t := p.T
+	t.Helper()
+	t.Error(args...)
+}
+
+// Run runs f as a subtest of t called name.
+//
+// Run may be called simultaneously from multiple goroutines, but all such calls
+// must return before the outer test function for t returns.
+func (p TestingT) Run(name string, doSth func()) bool {
+	return p.T.Run(name, func(t *testing.T) {
+		doSth()
+	})
+}
+
+// -----------------------------------------------------------------------------
diff --git a/cl/internal/test/match.go b/cl/internal/test/match.go
new file mode 100644
index 00000000..5cba9386
--- /dev/null
+++ b/cl/internal/test/match.go
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2024 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 test
+
+const (
+	GopPackage = true
+)
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Case struct {
+	CaseT
+}
+
+const (
+	Gopo_Gopt_Case_Match = "Gopt_Case_MatchTBase,Gopt_Case_MatchAny"
+)
+
+func Gopt_Case_MatchTBase[T basetype](t CaseT, got, expected T, name ...string) {
+}
+
+func Gopt_Case_MatchAny(t CaseT, got, expected any, name ...string) {
+}
diff --git a/cmd/internal/test/test.go b/cmd/internal/test/test.go
index 65655165..a9d119ae 100644
--- a/cmd/internal/test/test.go
+++ b/cmd/internal/test/test.go
@@ -27,7 +27,6 @@ import (
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/cmd/internal/base"
 	"github.com/goplus/gop/x/gocmd"
-	"github.com/goplus/gop/x/gopenv"
 	"github.com/goplus/gop/x/gopprojs"
 	"github.com/goplus/gox"
 )
@@ -70,9 +69,11 @@ func runCmd(cmd *base.Command, args []string) {
 		cl.SetDisableRecover(true)
 	}
 
-	gopEnv := gopenv.Get()
-	conf := &gop.Config{Gop: gopEnv}
-	confCmd := &gocmd.Config{Gop: gopEnv}
+	conf, err := gop.NewDefaultConf(".")
+	if err != nil {
+		log.Panicln("gop.NewDefaultConf:", err)
+	}
+	confCmd := &gocmd.Config{Gop: conf.Gop}
 	confCmd.Flags = pass.Args
 	for _, proj := range projs {
 		test(proj, conf, confCmd)
diff --git a/parser/_testdata/printvariadic/parser.expect b/parser/_testdata/printvariadic/parser.expect
index 61658c49..ae693de7 100644
--- a/parser/_testdata/printvariadic/parser.expect
+++ b/parser/_testdata/printvariadic/parser.expect
@@ -22,3 +22,12 @@ ast.FuncDecl:
               Args:
                 ast.Ident:
                   Name: x
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.Ident:
+                  Name: y
diff --git a/parser/_testdata/printvariadic/printv.gop b/parser/_testdata/printvariadic/printv.gop
index 72d734b3..6177ac88 100644
--- a/parser/_testdata/printvariadic/printv.gop
+++ b/parser/_testdata/printvariadic/printv.gop
@@ -1,1 +1,2 @@
 println x...
+println y...
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index d43bfcae..3bec85ac 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -25,6 +25,7 @@ import (
 	"syscall"
 	"testing"
 
+	"github.com/goplus/gop/parser/fsx"
 	"github.com/goplus/gop/parser/fsx/memfs"
 	"github.com/goplus/gop/parser/parsertest"
 	"github.com/goplus/gop/scanner"
@@ -68,7 +69,7 @@ func TestReadSource(t *testing.T) {
 
 func TestParseFiles(t *testing.T) {
 	fset := token.NewFileSet()
-	if _, err := ParseFiles(fset, []string{"/foo/bar/not-exists"}, PackageClauseOnly); err == nil {
+	if _, err := ParseFSFiles(fset, fsx.Local, []string{"/foo/bar/not-exists"}, PackageClauseOnly|SaveAbsFile); err == nil {
 		t.Fatal("ParseFiles failed: no error?")
 	}
 }
diff --git a/testdata/gop-sample/a.gop b/testdata/gop-sample/a.gop
new file mode 100644
index 00000000..abb3b861
--- /dev/null
+++ b/testdata/gop-sample/a.gop
@@ -0,0 +1,9 @@
+package main
+
+func a() {
+	println("a")
+}
+
+func Ab() {
+	println("ab")
+}
diff --git a/testdata/gop-sample/b.gop b/testdata/gop-sample/b.gop
new file mode 100644
index 00000000..f87ce8ec
--- /dev/null
+++ b/testdata/gop-sample/b.gop
@@ -0,0 +1,9 @@
+package main
+
+import (
+	ab "github.com/goplus/gop/testdata/gop-sample/cpkag/b"
+)
+
+a()
+Ab()
+ab.Ab()
diff --git a/testdata/gop-sample/cpkag/b/ab.go b/testdata/gop-sample/cpkag/b/ab.go
new file mode 100644
index 00000000..114172f3
--- /dev/null
+++ b/testdata/gop-sample/cpkag/b/ab.go
@@ -0,0 +1,5 @@
+package ab
+
+func Ab() {
+	println("ab")
+}
diff --git a/testdata/typeasparamsfunc/col.go b/testdata/typeasparamsfunc/col.go
new file mode 100644
index 00000000..292cfe4b
--- /dev/null
+++ b/testdata/typeasparamsfunc/col.go
@@ -0,0 +1,10 @@
+package main
+
+import (
+	"fmt"
+	"reflect"
+)
+
+func Gopx_Col[T any](name string) {
+	fmt.Printf("%v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name)
+}
diff --git a/testdata/typeasparamsfunc/typeAsParamsFunc.gop b/testdata/typeasparamsfunc/typeAsParamsFunc.gop
new file mode 100644
index 00000000..fed8257f
--- /dev/null
+++ b/testdata/typeasparamsfunc/typeAsParamsFunc.gop
@@ -0,0 +1,2 @@
+col string, "name"
+col int, "age"
diff --git a/testdata/typeasparamsmethod/col.go b/testdata/typeasparamsmethod/col.go
new file mode 100644
index 00000000..e8e01ccc
--- /dev/null
+++ b/testdata/typeasparamsmethod/col.go
@@ -0,0 +1,21 @@
+package main
+
+import (
+	"fmt"
+	"reflect"
+)
+
+type basetype interface {
+	int | string
+}
+
+type Table struct {
+}
+
+func Gopt_Table_Gopx_Col__0[T basetype](p *Table, name string) {
+	fmt.Printf("Gopt_Table_Gopx_Col__0 %v: %s\n", reflect.TypeOf((*T)(nil)).Elem(), name)
+}
+
+func Gopt_Table_Gopx_Col__1[Array any](p *Table, name string) {
+	fmt.Printf("Gopt_Table_Gopx_Col__1 %v: %s\n", reflect.TypeOf((*Array)(nil)).Elem(), name)
+}
diff --git a/testdata/typeasparamsmethod/typeAsParamsMethod.gop b/testdata/typeasparamsmethod/typeAsParamsMethod.gop
new file mode 100644
index 00000000..46c0d353
--- /dev/null
+++ b/testdata/typeasparamsmethod/typeAsParamsMethod.gop
@@ -0,0 +1,4 @@
+var tbl Table
+
+tbl.col int, "age"
+tbl.col bool, "male"
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
index 5ec8afe7..5e784e16 100644
--- a/x/typesutil/check_test.go
+++ b/x/typesutil/check_test.go
@@ -193,6 +193,6 @@ func TestBadFile(t *testing.T) {
 	opt.Fset = token.NewFileSet()
 	opt.Types = types.NewPackage("", "main")
 	checker := typesutil.NewChecker(conf, opt, nil, nil)
-	_ = checker.Files([]*goast.File{&goast.File{Name: goast.NewIdent("main")}},
-		[]*ast.File{&ast.File{Name: ast.NewIdent("main")}})
+	_ = checker.Files([]*goast.File{{Name: goast.NewIdent("main")}},
+		[]*ast.File{{Name: ast.NewIdent("main")}})
 }
diff --git a/x/typesutil/imp_test.go b/x/typesutil/imp_test.go
deleted file mode 100644
index 9751a4ed..00000000
--- a/x/typesutil/imp_test.go
+++ /dev/null
@@ -1,20 +0,0 @@
-package typesutil
-
-import (
-	"go/token"
-	"testing"
-)
-
-func TestNilImport(t *testing.T) {
-	_, err := (&nilImporter{}).Import("fmt")
-	if err == nil {
-		t.Fatal("no error")
-	}
-	imp := newImporter(nil, nil, nil, token.NewFileSet())
-	if imp.(*importer).imp == nil {
-		t.Fatal("nilImporter")
-	}
-	if imp.(*importer).gop == nil {
-		t.Fatal("gopenv.Get")
-	}
-}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 2bbbda6d..14a31fb0 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -213,9 +213,9 @@ func testItems(t *testing.T, name string, items []string, goitems []string) {
 			name, len(items), len(goitems),
 			text, gotext)
 	} else {
-		t.Log(fmt.Sprintf(`====== check %v pass (count: %v) ======
+		t.Logf(`====== check %v pass (count: %v) ======
 %v
-`, name, len(items), text))
+`, name, len(items), text)
 	}
 }
 
@@ -338,6 +338,7 @@ func goUsesList(fset *token.FileSet, uses map[*goast.Ident]types.Object) []strin
 	return sortItems(items)
 }
 
+/*
 func selectionList(fset *token.FileSet, sels map[*ast.SelectorExpr]*types.Selection) []string {
 	var items []string
 	for expr, sel := range sels {
@@ -365,6 +366,7 @@ func goSelectionList(fset *token.FileSet, sels map[*goast.SelectorExpr]*types.Se
 	}
 	return sortItems(items)
 }
+*/
 
 func mode(tv types.TypeAndValue) string {
 	switch {