Repository: goplus/gop
Pull request #1679 (2024-01-28 12:41:51)

v1.2.0-pre.2


Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 0add4d50..cdf19d39 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -27,9 +27,6 @@ jobs:
         git config --global user.name "build robot"
         go test -v cmd/make_test.go
 
-    - name: Compile gop and related tools
-      run: go install ./...
-
     - name: Run testcases
       run: go test -v -coverprofile="coverage.txt" -covermode=atomic ./...
 
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 6f0a55ab..2e61bcb0 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -10,6 +10,11 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v4
+      with:
+        fetch-depth: 0
+
+    - name: Install Snapcraft
+      uses: samuelmeuli/action-snapcraft@v2
 
     - name: Checkout tag
       run: |
@@ -35,12 +40,23 @@ jobs:
       with:
         go-version: "1.21"
 
-    - name: Install goreleaser
-      run: |
-        go install github.com/goreleaser/goreleaser@latest
-
-    - name: Release
+    - name: Release with goreleaser
+      uses: goreleaser/goreleaser-action@v5
+      with:
+        # either 'goreleaser' (default) or 'goreleaser-pro'
+        distribution: goreleaser
+        version: latest
+        args: release --clean -p 4
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
-      run: goreleaser release -p 4
+
+    - name: Upload deb/rpm to Fury.io
+      run: |
+        for file in dist/*.{deb,rpm}
+        do
+          echo "Uploading $file to Fury.io"
+          curl -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/goplus/
+        done
+      env:
+        FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index 10495b0d..ef020d43 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -68,12 +68,25 @@ changelog:
       - "^test:"
 
 winget:
-  - name: gop
+  - name: goplus
     homepage: "https://goplus.org/"
     publisher: goplus
     publisher_url: https://github.com/goplus/gop
     publisher_support_url: "https://github.com/goplus/gop/issues/new"
     package_identifier: goplus.gop
+    path: "manifests/g/goplus/gop/{{.Version}}"
+    tags:
+      - golang
+      - go
+      - gop
+      - goplus
+      - programming
+      - language
+      - compiler
+      - interpreter
+      - data science
+      - engineering
+      - education
     short_description: The Go+ Programming Language
     description: |
       The Go+ programming language is designed for engineering, STEM education, and data science.
@@ -101,3 +114,148 @@ winget:
           owner: microsoft
           name: winget-pkgs
           branch: master
+
+nfpms:
+  - package_name: gop
+    vendor: goplus
+    homepage: https://goplus.org/
+    maintainer: Li Jie <cpunion@gmail.com>
+    license: Apache-2.0
+    description: |
+      The Go+ programming language is designed for engineering, STEM education, and data science.
+      - For engineering: working in the simplest language that can be mastered by children.
+      - 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.
+    formats:
+      - "deb"
+      - "rpm"
+    overrides:
+      deb:
+        dependencies:
+          - "golang-go (>= 1.18.0)"
+      rpm:
+        dependencies:
+          - "golang-bin >= 1.18.0"
+    file_name_template: >-
+      {{ .ProjectName }}_v{{.Version}}_
+      {{- title .Os }}_
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+    bindir: /usr/lib/{{ .ProjectName }}
+    contents:
+      # source folder
+      - src: LICENSE
+        dst: /usr/lib/{{ .ProjectName }}/LICENSE
+      - src: "README.md"
+        dst: /usr/lib/{{ .ProjectName }}/README.md
+      - src: "go.mod"
+        dst: /usr/lib/{{ .ProjectName }}/go.mod
+      - src: "go.sum"
+        dst: /usr/lib/{{ .ProjectName }}/go.sum
+      - src: "*.go"
+        dst: /usr/lib/{{ .ProjectName }}/
+      - src: ast
+        dst: /usr/lib/{{ .ProjectName }}/ast
+      - src: builtin
+        dst: /usr/lib/{{ .ProjectName }}/builtin
+      - src: cl
+        dst: /usr/lib/{{ .ProjectName }}/cl
+      - src: cmd
+        dst: /usr/lib/{{ .ProjectName }}/cmd
+      - src: doc
+        dst: /usr/lib/{{ .ProjectName }}/doc
+      - src: env
+        dst: /usr/lib/{{ .ProjectName }}/env
+      - src: format
+        dst: /usr/lib/{{ .ProjectName }}/format
+      - src: parser
+        dst: /usr/lib/{{ .ProjectName }}/parser
+      - src: printer
+        dst: /usr/lib/{{ .ProjectName }}/parser
+      - src: scanner
+        dst: /usr/lib/{{ .ProjectName }}/scanner
+      - src: token
+        dst: /usr/lib/{{ .ProjectName }}/token
+      - src: watcher
+        dst: /usr/lib/{{ .ProjectName }}/watcher
+      - src: "x"
+        dst: /usr/lib/{{ .ProjectName }}/x
+      # symlinks to binaries
+      - src: "/usr/lib/{{ .ProjectName }}/bin/gop"
+        dst: /usr/bin/gop
+        type: symlink
+      - src: "/usr/lib/{{ .ProjectName }}/bin/gopfmt"
+        dst: /usr/bin/gopfmt
+        type: symlink
+
+snapcrafts:
+  - id: gop
+    name: gop
+    title: The Go+ Programming Language
+    summary: The Go+ Programming Language
+    description: |
+      The Go+ programming language is designed for engineering, STEM education, and data science.
+      - For engineering: working in the simplest language that can be mastered by children.
+      - 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.
+    confinement: classic
+    license: Apache-2.0
+    name_template: >-
+      {{ .ProjectName }}_v{{.Version}}_
+      {{- title .Os }}_
+      {{- if eq .Arch "amd64" }}x86_64
+      {{- else if eq .Arch "386" }}i386
+      {{- else }}{{ .Arch }}{{ end }}
+      {{- if .Arm }}v{{ .Arm }}{{ end }}
+    extra_files:
+      # source folder
+      - source: LICENSE
+        destination: LICENSE
+      - source: "README.md"
+        destination: README.md
+      - source: "go.mod"
+        destination: go.mod
+      - source: "go.sum"
+        destination: go.sum
+      - source: ast
+        destination: ast
+      - source: builtin
+        destination: builtin
+      - source: cl
+        destination: cl
+      - source: cmd
+        destination: cmd
+      - source: doc
+        destination: doc
+      - source: env
+        destination: env
+      - source: format
+        destination: format
+      - source: parser
+        destination: parser
+      - source: printer
+        destination: parser
+      - source: scanner
+        destination: scanner
+      - source: token
+        destination: token
+      - source: watcher
+        destination: watcher
+      - source: "x"
+        destination: x
+    apps:
+      gop:
+        command: "gop"
+        aliases: ["gop"]
+        environment:
+          GOPROOT: "$SNAP"
+      gopfmt:
+        command: "gopfmt"
+        aliases: ["gopfmt"]
+        environment:
+          GOPROOT: "$SNAP"
+
+checksum:
+  name_template: "{{ .ProjectName }}_v{{ .Version }}_checksums.txt"
diff --git a/README.md b/README.md
index 397bc498..f0f460e8 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,41 @@ For more details, see [Quick Start](doc/docs.md).
 
 ## How to install
 
+### on Windows
+
+```sh
+winget install goplus
+```
+
+Or
+
+```sh
+winget install goplus.gop
+```
+
+### on Debian/Ubuntu
+
+```sh
+sudo bash -c ' echo "deb [trusted=yes] https://pkgs.goplus.org/apt/ /" > /etc/apt/sources.list.d/goplus.list'
+sudo apt update
+sudo apt install gop
+```
+
+### on RedHat/CentOS/Fedora
+
+```sh
+sudo bash -c 'echo -e "[goplus]\nname=Go+ Repo\nbaseurl=https://pkgs.goplus.org/yum/\nenabled=1\ngpgcheck=0" > /etc/yum.repos.d/goplus.repo'
+sudo yum install gop
+```
+
+### on macOS/Linux(Homebrew)
+
+Install via [brew](https://brew.sh/)
+
+```sh
+$ brew install goplus
+```
+
 ### from source code
 
 For now, we suggest you install Go+ from source code.
@@ -63,14 +98,6 @@ cd gop
 all.bat
 ```
 
-### on macOS/Linux
-
-Install via [brew](https://brew.sh/)
-```sh
-$ brew install goplus
-```
-
-
 ## Go+ Applications
 
 ### 2D Games powered by Go+
diff --git a/all.bash b/all.bash
index 8fe94a61..fad561e8 100755
--- a/all.bash
+++ b/all.bash
@@ -18,4 +18,4 @@
 
 set -ex
 
-go run cmd/make.go --install --autoproxy
+go run cmd/make.go --install --regtest --autoproxy
diff --git a/all.bat b/all.bat
index 5395a855..c21e69d3 100644
--- a/all.bat
+++ b/all.bat
@@ -1,1 +1,1 @@
-go run cmd/make.go --install --autoproxy
+go run cmd/make.go --install --regtest --autoproxy
diff --git a/ast/ast.go b/ast/ast.go
index e174dbb1..423e60b9 100644
--- a/ast/ast.go
+++ b/ast/ast.go
@@ -176,13 +176,6 @@ type (
 		Elt      Expr      // ellipsis element type (parameter lists only); or nil
 	}
 
-	// A BasicLit node represents a literal of basic type.
-	BasicLit struct {
-		ValuePos token.Pos   // literal position
-		Kind     token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, token.STRING or token.CSTRING
-		Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
-	}
-
 	// A FuncLit node represents a function literal.
 	FuncLit struct {
 		Type *FuncType  // function type
@@ -1060,6 +1053,7 @@ type (
 		Body     *BlockStmt    // function body; or nil for external (non-Go) function
 		Operator bool          // is operator or not
 		Shadow   bool          // is a shadow entry
+		IsClass  bool          // recv set by class
 	}
 )
 
@@ -1141,7 +1135,7 @@ type File struct {
 }
 
 // There is no entrypoint func to indicate the module entry point.
-func (f *File) NoEntrypoint() bool {
+func (f *File) HasShadowEntry() bool {
 	return f.ShadowEntry != nil
 }
 
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index b0c398b6..239a73a8 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -22,6 +22,69 @@ import (
 
 // -----------------------------------------------------------------------------
 
+// OverloadFuncDecl node represents an overload function declaration:
+//
+// `func name = (overloadFuncs)`
+// `func (T).nameOrOp = (overloadFuncs)`
+//
+// here overloadFunc represents
+//
+// `func(params) {...}`
+// `funcName`
+// `(*T).methodName`
+type OverloadFuncDecl struct {
+	Doc      *CommentGroup // associated documentation; or nil
+	Func     token.Pos     // position of "func" keyword
+	Recv     *FieldList    // receiver (methods); or nil (functions)
+	Name     *Ident        // function/method name
+	Assign   token.Pos     // position of token "="
+	Lparen   token.Pos     // position of "("
+	Funcs    []Expr        // overload functions. here `Expr` can be *FuncLit, *Ident or *SelectorExpr
+	Rparen   token.Pos     // position of ")"
+	Operator bool          // is operator or not
+	IsClass  bool          // recv set by class
+}
+
+// Pos - position of first character belonging to the node.
+func (p *OverloadFuncDecl) Pos() token.Pos {
+	return p.Func
+}
+
+// End - position of first character immediately after the node.
+func (p *OverloadFuncDecl) End() token.Pos {
+	return p.Rparen + 1
+}
+
+func (*OverloadFuncDecl) declNode() {}
+
+// -----------------------------------------------------------------------------
+
+// A BasicLit node represents a literal of basic type.
+type BasicLit struct {
+	ValuePos token.Pos    // literal position
+	Kind     token.Token  // token.INT, token.FLOAT, token.IMAG, token.RAT, token.CHAR, token.STRING, token.CSTRING
+	Value    string       // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 3r, 'a', '\x7f', "foo" or `\m\n\o`
+	Extra    *StringLitEx // optional (only available when Kind == token.STRING)
+}
+
+type StringLitEx struct {
+	Parts []any // can be (val string) or (xval Expr)
+}
+
+// NextPartPos - position of first character of next part.
+// pos - position of this part (not including quote character).
+func NextPartPos(pos token.Pos, part any) (nextPos token.Pos) {
+	switch v := part.(type) {
+	case string: // normal string literal or end with "$$"
+		return pos + token.Pos(len(v))
+	case Expr:
+		return v.End()
+	}
+	panic("NextPartPos: unexpected parameters")
+}
+
+// -----------------------------------------------------------------------------
+
 // A SliceLit node represents a slice literal.
 type SliceLit struct {
 	Lbrack     token.Pos // position of "["
@@ -30,12 +93,12 @@ type SliceLit struct {
 	Incomplete bool      // true if (source) expressions are missing in the Elts list
 }
 
-// Pos - position of first character belonging to the node
+// Pos - position of first character belonging to the node.
 func (p *SliceLit) Pos() token.Pos {
 	return p.Lbrack
 }
 
-// End - position of first character immediately after the node
+// End - position of first character immediately after the node.
 func (p *SliceLit) End() token.Pos {
 	return p.Rbrack + 1
 }
@@ -44,7 +107,7 @@ func (*SliceLit) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`
+// ErrWrapExpr represents `expr!`, `expr?` or `expr?: defaultValue`.
 type ErrWrapExpr struct {
 	X       Expr
 	Tok     token.Token // ! or ?
@@ -52,12 +115,12 @@ type ErrWrapExpr struct {
 	Default Expr // can be nil
 }
 
-// Pos - position of first character belonging to the node
+// Pos - position of first character belonging to the node.
 func (p *ErrWrapExpr) Pos() token.Pos {
 	return p.X.Pos()
 }
 
-// End - position of first character immediately after the node
+// End - position of first character immediately after the node.
 func (p *ErrWrapExpr) End() token.Pos {
 	if p.Default != nil {
 		return p.Default.End()
@@ -69,13 +132,16 @@ func (*ErrWrapExpr) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// LambdaExpr represents
-//    `(x, y, ...) => exprOrExprTuple`
-//    `x => exprOrExprTuple`
-//    `=> exprOrExprTuple`
+// LambdaExpr represents one of the following expressions:
+//
+//	`(x, y, ...) => exprOrExprTuple`
+//	`x => exprOrExprTuple`
+//	`=> exprOrExprTuple`
+//
 // here exprOrExprTuple represents
-//    `expr`
-//    `(expr1, expr2, ...)`
+//
+//	`expr`
+//	`(expr1, expr2, ...)`
 type LambdaExpr struct {
 	First       token.Pos
 	Lhs         []*Ident
@@ -86,10 +152,11 @@ type LambdaExpr struct {
 	RhsHasParen bool
 }
 
-// LambdaExpr2 represents
-//    `(x, y, ...) => { ... }`
-//    `x => { ... }`
-//    `=> { ... }`
+// LambdaExpr2 represents one of the following expressions:
+//
+//	`(x, y, ...) => { ... }`
+//	`x => { ... }`
+//	`=> { ... }`
 type LambdaExpr2 struct {
 	First       token.Pos
 	Lhs         []*Ident
@@ -98,18 +165,22 @@ type LambdaExpr2 struct {
 	LhsHasParen bool
 }
 
+// Pos - position of first character belonging to the node.
 func (p *LambdaExpr) Pos() token.Pos {
 	return p.First
 }
 
+// End - position of first character immediately after the node.
 func (p *LambdaExpr) End() token.Pos {
 	return p.Last
 }
 
+// Pos - position of first character belonging to the node.
 func (p *LambdaExpr2) Pos() token.Pos {
 	return p.First
 }
 
+// End - position of first character immediately after the node.
 func (p *LambdaExpr2) End() token.Pos {
 	return p.Body.End()
 }
@@ -119,7 +190,7 @@ func (*LambdaExpr2) exprNode() {}
 
 // -----------------------------------------------------------------------------
 
-// ForPhrase represents `for k, v <- container, cond`
+// ForPhrase represents `for k, v <- container, cond` phrase.
 type ForPhrase struct {
 	For        token.Pos // position of "for" keyword
 	Key, Value *Ident    // Key may be nil
@@ -138,11 +209,12 @@ func (p *ForPhrase) End() token.Pos { return p.X.End() }
 
 func (p *ForPhrase) exprNode() {}
 
-// ComprehensionExpr represents
-//    `[vexpr for k1, v1 <- container1, cond1 ...]` or
-//    `{vexpr for k1, v1 <- container1, cond1 ...}` or
-//    `{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or
-//    `{for k1, v1 <- container1, cond1 ...}` or
+// ComprehensionExpr represents one of the following expressions:
+//
+//	`[vexpr for k1, v1 <- container1, cond1 ...]` or
+//	`{vexpr for k1, v1 <- container1, cond1 ...}` or
+//	`{kexpr: vexpr for k1, v1 <- container1, cond1 ...}` or
+//	`{for k1, v1 <- container1, cond1 ...}` or
 type ComprehensionExpr struct {
 	Lpos token.Pos   // position of "[" or "{"
 	Tok  token.Token // token.LBRACK '[' or token.LBRACE '{'
@@ -151,12 +223,12 @@ type ComprehensionExpr struct {
 	Rpos token.Pos // position of "]" or "}"
 }
 
-// Pos - position of first character belonging to the node
+// Pos - position of first character belonging to the node.
 func (p *ComprehensionExpr) Pos() token.Pos {
 	return p.Lpos
 }
 
-// End - position of first character immediately after the node
+// End - position of first character immediately after the node.
 func (p *ComprehensionExpr) End() token.Pos {
 	return p.Rpos + 1
 }
@@ -171,12 +243,12 @@ type ForPhraseStmt struct {
 	Body *BlockStmt
 }
 
-// Pos - position of first character belonging to the node
+// Pos - position of first character belonging to the node.
 func (p *ForPhraseStmt) Pos() token.Pos {
 	return p.For
 }
 
-// End - position of first character immediately after the node
+// End - position of first character immediately after the node.
 func (p *ForPhraseStmt) End() token.Pos {
 	return p.Body.End()
 }
@@ -194,7 +266,7 @@ type RangeExpr struct {
 	Expr3  Expr      // step (or max) of composite elements; or nil
 }
 
-// Pos - position of first character belonging to the node
+// Pos - position of first character belonging to the node.
 func (p *RangeExpr) Pos() token.Pos {
 	if p.First != nil {
 		return p.First.Pos()
@@ -202,7 +274,7 @@ func (p *RangeExpr) Pos() token.Pos {
 	return p.To
 }
 
-// End - position of first character immediately after the node
+// End - position of first character immediately after the node.
 func (p *RangeExpr) End() token.Pos {
 	if p.Expr3 != nil {
 		return p.Expr3.End()
diff --git a/ast/walk.go b/ast/walk.go
index 3cdc64e9..08874611 100644
--- a/ast/walk.go
+++ b/ast/walk.go
@@ -98,9 +98,18 @@ func Walk(v Visitor, node Node) {
 		}
 
 	// Expressions
-	case *BadExpr, *Ident, *BasicLit:
+	case *BadExpr, *Ident:
 		// nothing to do
 
+	case *BasicLit:
+		if n.Extra != nil { // Go+ extended
+			for _, part := range n.Extra.Parts {
+				if e, ok := part.(Expr); ok {
+					Walk(v, e)
+				}
+			}
+		}
+
 	case *Ellipsis:
 		if n.Elt != nil {
 			Walk(v, n.Elt)
@@ -364,7 +373,9 @@ func Walk(v Visitor, node Node) {
 		if n.Doc != nil {
 			Walk(v, n.Doc)
 		}
-		Walk(v, n.Name)
+		if !n.NoPkgDecl {
+			Walk(v, n.Name)
+		}
 		walkDeclList(v, n.Decls)
 		// don't walk n.Comments - they have been
 		// visited already through the individual
@@ -375,7 +386,7 @@ func Walk(v Visitor, node Node) {
 			Walk(v, f)
 		}
 
-	// Go+ expr and stmt
+	// Go+ extended expr and stmt
 	case *SliceLit:
 		walkExprList(v, n.Elts)
 
@@ -431,6 +442,16 @@ func Walk(v Visitor, node Node) {
 			Walk(v, n.Default)
 		}
 
+	case *OverloadFuncDecl:
+		if n.Doc != nil {
+			Walk(v, n.Doc)
+		}
+		if n.Recv != nil {
+			Walk(v, n.Recv)
+		}
+		Walk(v, n.Name)
+		walkExprList(v, n.Funcs)
+
 	default:
 		panic(fmt.Sprintf("ast.Walk: unexpected node type %T", n))
 	}
diff --git a/build_install_run.go b/build_install_run.go
index 9972ee65..0a665241 100644
--- a/build_install_run.go
+++ b/build_install_run.go
@@ -26,6 +26,8 @@ import (
 
 // -----------------------------------------------------------------------------
 
+// InstallDir installs a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -34,6 +36,8 @@ func InstallDir(dir string, conf *Config, install *gocmd.InstallConfig) (err err
 	return gocmd.Install(dir, install)
 }
 
+// InstallPkgPath installs a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallPkgPath(workDir, pkgPath string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, true)
 	if err != nil {
@@ -51,6 +55,8 @@ func cwdParam(recursively bool) string {
 	return "."
 }
 
+// InstallFiles installs specified Go+ files.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func InstallFiles(files []string, conf *Config, install *gocmd.InstallConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
@@ -73,6 +79,8 @@ func chdir(dir string) string {
 
 // -----------------------------------------------------------------------------
 
+// BuildDir builds a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -81,6 +89,8 @@ func BuildDir(dir string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	return gocmd.Build(dir, build)
 }
 
+// BuildPkgPath builds a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
@@ -91,6 +101,8 @@ func BuildPkgPath(workDir, pkgPath string, conf *Config, build *gocmd.BuildConfi
 	return gocmd.Build(cwdParam(recursively), build)
 }
 
+// BuildFiles builds specified Go+ files.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func BuildFiles(files []string, conf *Config, build *gocmd.BuildConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
@@ -116,6 +128,8 @@ func restoreDirAndMod(old string, mod os.FileMode) {
 
 // -----------------------------------------------------------------------------
 
+// RunDir runs an application from a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
 	_, _, err = GenGo(dir, conf, false)
 	if err != nil {
@@ -124,6 +138,8 @@ func RunDir(dir string, args []string, conf *Config, run *gocmd.RunConfig) (err
 	return gocmd.RunDir(dir, args, run)
 }
 
+// RunPkgPath runs an application from a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *gocmd.RunConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath("", pkgPath, conf, true)
 	if err != nil {
@@ -140,6 +156,8 @@ func RunPkgPath(pkgPath string, args []string, chDir bool, conf *Config, run *go
 	return gocmd.RunDir(localDir, args, run)
 }
 
+// RunFiles runs an application from specified Go+ files.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func RunFiles(autogen string, files []string, args []string, conf *Config, run *gocmd.RunConfig) (err error) {
 	files, err = GenGoFiles(autogen, files, conf)
 	if err != nil {
@@ -150,6 +168,8 @@ func RunFiles(autogen string, files []string, args []string, conf *Config, run *
 
 // -----------------------------------------------------------------------------
 
+// TestDir tests a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 	_, _, err = GenGo(dir, conf, true)
 	if err != nil {
@@ -158,6 +178,8 @@ func TestDir(dir string, conf *Config, test *gocmd.TestConfig) (err error) {
 	return gocmd.Test(dir, test)
 }
 
+// TestPkgPath tests a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig) (err error) {
 	localDir, recursively, err := GenGoPkgPath(workDir, pkgPath, conf, false)
 	if err != nil {
@@ -168,6 +190,8 @@ func TestPkgPath(workDir, pkgPath string, conf *Config, test *gocmd.TestConfig)
 	return gocmd.Test(cwdParam(recursively), test)
 }
 
+// TestFiles tests specified Go+ files.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func TestFiles(files []string, conf *Config, test *gocmd.TestConfig) (err error) {
 	files, err = GenGoFiles("", files, conf)
 	if err != nil {
diff --git a/cl/builtin.go b/cl/builtin.go
index 3058e246..dda8c6d3 100644
--- a/cl/builtin.go
+++ b/cl/builtin.go
@@ -25,23 +25,22 @@ import (
 
 // -----------------------------------------------------------------------------
 
-func initMathBig(pkg *gox.Package, conf *gox.Config, big *gox.PkgRef) {
-	big.EnsureImported()
+func initMathBig(pkg *gox.Package, conf *gox.Config, big gox.PkgRef) {
 	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) {
+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) {
+func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, buil gox.PkgRef) {
 	scope := builtin.Scope()
-	if ng != nil {
+	if ng.Types != nil {
 		typs := []string{"bigint", "bigrat", "bigfloat"}
 		for _, typ := range typs {
 			name := string(typ[0]-('a'-'A')) + typ[1:]
@@ -50,25 +49,26 @@ func initBuiltin(pkg *gox.Package, builtin *types.Package, os, fmt, ng, iox, bui
 		scope.Insert(types.NewTypeName(token.NoPos, builtin, "uint128", ng.Ref("Uint128").Type()))
 		scope.Insert(types.NewTypeName(token.NoPos, builtin, "int128", ng.Ref("Int128").Type()))
 	}
-	if fmt != nil {
+	if fmt.Types != nil {
+		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "echo", fmt.Ref("Println")))
 		initBuiltinFns(builtin, scope, fmt, []string{
 			"print", "println", "printf", "errorf",
 			"fprint", "fprintln", "fprintf",
 			"sprint", "sprintln", "sprintf",
 		})
 	}
-	if os != nil {
+	if os.Types != nil {
 		initBuiltinFns(builtin, scope, os, []string{
 			"open", "create",
 		})
 	}
-	if iox != nil {
+	if iox.Types != nil {
 		initBuiltinFns(builtin, scope, iox, []string{
 			"lines",
 		})
 		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "blines", iox.Ref("BLines")))
 	}
-	if buil != nil {
+	if buil.Types != nil {
 		scope.Insert(gox.NewOverloadFunc(token.NoPos, builtin, "newRange", buil.Ref("NewRange__0")))
 	}
 	scope.Insert(types.NewTypeName(token.NoPos, builtin, "any", gox.TyEmptyInterface))
@@ -83,7 +83,7 @@ func newBuiltinDefault(pkg *gox.Package, conf *gox.Config) *types.Package {
 	iox := pkg.TryImport("github.com/goplus/gop/builtin/iox")
 	pkg.TryImport("strconv")
 	pkg.TryImport("strings")
-	if ng != nil {
+	if ng.Types != nil {
 		initMathBig(pkg, conf, ng)
 	}
 	initBuiltin(pkg, builtin, os, fmt, ng, iox, buil)
diff --git a/cl/c.go b/cl/c.go
index b203bbb2..bf0d15ae 100644
--- a/cl/c.go
+++ b/cl/c.go
@@ -57,12 +57,12 @@ func c2goBase(base string) string {
 
 // -----------------------------------------------------------------------------
 
-func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) *gox.PkgRef {
+func loadC2goPkg(ctx *blockCtx, realPath string, src *ast.BasicLit) (ret gox.PkgRef) {
 	cpkg, err := ctx.cpkgs.Import(realPath)
 	if err != nil {
 		ctx.handleErrorf(src.Pos(),
 			"%v not found or not a valid C package (c2go.a.pub file not found).\n", realPath)
-		return nil
+		return
 	}
 	ctx.clookups = append(ctx.clookups, cpkg)
 	return cpkg.Pkg()
diff --git a/cl/classfile.go b/cl/classfile.go
index e3490163..d019783e 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -20,6 +20,7 @@ import (
 	goast "go/ast"
 	"go/constant"
 	"go/types"
+	"log"
 	"path/filepath"
 	"strings"
 
@@ -31,19 +32,27 @@ import (
 
 // -----------------------------------------------------------------------------
 
-type gmxSettings struct {
-	gameClass  string
-	game       gox.Ref
-	sprite     map[string]gox.Ref
+type gmxClass struct {
+	tname string // class type
+	ext   string
+	proj  *gmxProject
+}
+
+type gmxProject struct {
+	gameClass  string             // <gmtype>.gmx
+	game       gox.Ref            // Game
+	sprite     map[string]gox.Ref // .spx => Sprite
+	sptypes    []string           // <sptype>.spx
 	scheds     []string
 	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
-	pkgImps    []*gox.PkgRef
+	pkgImps    []gox.PkgRef
 	pkgPaths   []string
 	hasScheds  bool
 	gameIsPtr  bool
+	isTest     bool
 }
 
-func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
+func (p *gmxProject) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
 	if p == nil || !p.hasScheds {
 		return nil
 	}
@@ -60,7 +69,7 @@ func (p *gmxSettings) getScheds(cb *gox.CodeBuilder) []goast.Stmt {
 	return p.schedStmts
 }
 
-func classNameAndExt(file string) (name, ext string) {
+func ClassNameAndExt(file string) (name, ext string) {
 	fname := filepath.Base(file)
 	name, ext = modfile.SplitFname(fname)
 	if idx := strings.Index(name, "."); idx > 0 {
@@ -69,45 +78,59 @@ func classNameAndExt(file string) (name, ext string) {
 	return
 }
 
-func newGmx(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxSettings {
-	fname := filepath.Base(file)
-	ext := modfile.ClassExt(fname)
+func isGoxTestFile(ext string) bool {
+	return strings.HasSuffix(ext, "test.gox")
+}
+
+func loadClass(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Config) *gmxProject {
+	tname, ext := ClassNameAndExt(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
-			}
+	p, ok := ctx.projs[gt.Ext]
+	if !ok {
+		pkgPaths := gt.PkgPaths
+		p = &gmxProject{pkgPaths: pkgPaths}
+		ctx.projs[gt.Ext] = p
+
+		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
 		}
 	}
-	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 f.IsProj {
+		if tname == "main" {
+			tname = gt.Class
+		}
+		if p.gameClass != "" {
+			log.Panicln("multiple project files found:", tname, p.gameClass)
+		}
+		p.gameClass = tname
+	} else {
+		p.sptypes = append(p.sptypes, tname)
 	}
-	if x := getStringConst(spx, "Gop_sched"); x != "" {
-		p.scheds, p.hasScheds = strings.SplitN(x, ",", 2), true
+	ctx.classes[f] = gmxClass{tname, ext, p}
+	if debugLoad {
+		log.Println("==> InitClass", tname, "isProj:", f.IsProj)
 	}
 	return p
 }
 
-func spxLookup(pkgImps []*gox.PkgRef, name string) gox.Ref {
+func spxLookup(pkgImps []gox.PkgRef, name string) gox.Ref {
 	for _, pkg := range pkgImps {
 		if o := pkg.TryRef(name); o != nil {
 			return o
@@ -116,7 +139,7 @@ func spxLookup(pkgImps []*gox.PkgRef, name string) gox.Ref {
 	panic("spxLookup: symbol not found - " + name)
 }
 
-func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) {
+func spxTryRef(spx gox.PkgRef, typ string) (obj types.Object, isPtr bool) {
 	if strings.HasPrefix(typ, "*") {
 		typ, isPtr = typ[1:], true
 	}
@@ -124,7 +147,7 @@ func spxTryRef(spx *gox.PkgRef, typ string) (obj types.Object, isPtr bool) {
 	return
 }
 
-func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) {
+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")
@@ -132,7 +155,7 @@ func spxRef(spx *gox.PkgRef, typ string) (obj gox.Ref, isPtr bool) {
 	return
 }
 
-func getStringConst(spx *gox.PkgRef, name string) string {
+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())
@@ -155,8 +178,8 @@ func getFields(f *ast.File) []ast.Spec {
 }
 
 func setBodyHandler(ctx *blockCtx) {
-	if ctx.isClass { // in a Go+ class file
-		if scheds := ctx.getScheds(ctx.cb); scheds != nil {
+	if proj := ctx.proj; proj != nil { // in a Go+ class file
+		if scheds := proj.getScheds(ctx.cb); scheds != nil {
 			ctx.cb.SetBodyHandler(func(body *goast.BlockStmt, kind int) {
 				idx := 0
 				if len(body.List) == 0 {
@@ -168,14 +191,105 @@ func setBodyHandler(ctx *blockCtx) {
 	}
 }
 
-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()
+const (
+	casePrefix = "case"
+)
+
+func testNameSuffix(testType string) string {
+	if c := testType[0]; c >= 'A' && c <= 'Z' {
+		return testType
+	}
+	return "_" + testType
+}
+
+func gmxTestFunc(pkg *gox.Package, testType string, isProj bool) {
+	if isProj {
+		genTestFunc(pkg, "TestMain", testType, "m", "M")
+	} else {
+		name := testNameSuffix(testType)
+		genTestFunc(pkg, "Test"+name, casePrefix+name, "t", "T")
+	}
+}
+
+func genTestFunc(pkg *gox.Package, name, testType, param, paramType string) {
+	testing := pkg.Import("testing")
+	objT := testing.Ref(paramType)
+	paramT := types.NewParam(token.NoPos, pkg.Types, param, types.NewPointer(objT.Type()))
+	params := types.NewTuple(paramT)
+
+	pkg.NewFunc(nil, name, params, nil, false).BodyStart(pkg).
+		Val(pkg.Builtin().Ref("new")).Val(pkg.Ref(testType)).Call(1).
+		MemberVal("TestMain").Val(paramT).Call(1).EndStmt().
+		End()
+}
+
+func gmxMainFunc(pkg *gox.Package, ctx *pkgCtx, noAutoGenMain bool) func() {
+	var proj *gmxProject
+	for _, v := range ctx.projs {
+		if v.isTest {
+			continue
+		} else if proj != nil {
+			return nil
+		}
+		proj = v
+	}
+	if proj != nil { // only one project file
+		scope := pkg.Types.Scope()
+		var o types.Object
+		if proj.gameClass != "" {
+			o = scope.Lookup(proj.gameClass)
+			if noAutoGenMain && o != nil && hasMethod(o, "MainEntry") {
+				noAutoGenMain = false
+			}
+		} else {
+			o = proj.game
+		}
+		if !noAutoGenMain && o != nil {
+			// new(Game).Main()
+			// new(Game).Main(workers...)
+			fn := pkg.NewFunc(nil, "main", nil, nil, false)
+			return func() {
+				new := pkg.Builtin().Ref("new")
+				cb := fn.BodyStart(pkg).Val(new).Val(o).Call(1).MemberVal("Main")
+
+				sig := cb.Get(-1).Type.(*types.Signature)
+				narg := gmxMainNarg(sig)
+				if narg > 0 {
+					narg = len(proj.sptypes)
+					for _, spt := range proj.sptypes {
+						sp := scope.Lookup(spt)
+						cb.Val(new).Val(sp).Call(1)
+					}
+				}
+
+				cb.Call(narg).EndStmt().End()
+			}
+		}
+	}
+	return nil
+}
+
+func gmxMainNarg(sig *types.Signature) int {
+	if fex, ok := gox.CheckFuncEx(sig); ok {
+		if trm, ok := fex.(*gox.TyTemplateRecvMethod); ok {
+			sig = trm.Func.Type().(*types.Signature)
+			return sig.Params().Len() - 1
+		}
+	}
+	return sig.Params().Len()
+}
+
+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/compile.go b/cl/compile.go
index d1193145..50761274 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -24,6 +24,7 @@ import (
 	"log"
 	"reflect"
 	"sort"
+	"strconv"
 	"strings"
 	_ "unsafe"
 
@@ -41,6 +42,7 @@ type dbgFlags int
 const (
 	DbgFlagLoad dbgFlags = 1 << iota
 	DbgFlagLookup
+	FlagNoMarkAutogen
 	DbgFlagAll = DbgFlagLoad | DbgFlagLookup
 )
 
@@ -49,8 +51,9 @@ var (
 )
 
 var (
-	debugLoad   bool
-	debugLookup bool
+	debugLoad     bool
+	debugLookup   bool
+	noMarkAutogen bool // add const _ = true
 )
 
 func SetDisableRecover(disableRecover bool) {
@@ -60,6 +63,7 @@ func SetDisableRecover(disableRecover bool) {
 func SetDebug(flags dbgFlags) {
 	debugLoad = (flags & DbgFlagLoad) != 0
 	debugLookup = (flags & DbgFlagLookup) != 0
+	noMarkAutogen = (flags & FlagNoMarkAutogen) != 0
 }
 
 // -----------------------------------------------------------------------------
@@ -172,6 +176,9 @@ 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
 
@@ -187,6 +194,9 @@ 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
 
@@ -330,13 +340,15 @@ func doInitMethods(ld *typeLoader) {
 
 type pkgCtx struct {
 	*nodeInterp
-	*gmxSettings
-	fset  *token.FileSet
-	cpkgs *cpackages.Importer
-	syms  map[string]loader
-	inits []func()
-	tylds []*typeLoader
-	errs  errors.List
+	projs    map[string]*gmxProject // .gmx => project
+	classes  map[*ast.File]gmxClass
+	fset     *token.FileSet
+	cpkgs    *cpackages.Importer
+	syms     map[string]loader
+	lbinames []any // names that should load before initGopPkg (can be string/func or *ast.Ident/type)
+	inits    []func()
+	tylds    []*typeLoader
+	errs     errors.List
 
 	generics map[string]bool // generic type record
 	idents   []*ast.Ident    // toType ident recored
@@ -344,17 +356,18 @@ type pkgCtx struct {
 }
 
 type pkgImp struct {
-	*gox.PkgRef
+	gox.PkgRef
 	pkgName *types.PkgName
 }
 
 type blockCtx struct {
 	*pkgCtx
+	proj       *gmxProject
 	pkg        *gox.Package
 	cb         *gox.CodeBuilder
 	imports    map[string]pkgImp
-	lookups    []*gox.PkgRef
-	clookups   []*cpackages.PkgRef
+	lookups    []gox.PkgRef
+	clookups   []cpackages.PkgRef
 	tlookup    *typeParamLookup
 	c2goBase   string // default is `github.com/goplus/`
 	relBaseDir string
@@ -464,12 +477,18 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		fset: fset, files: files, relBaseDir: relBaseDir,
 	}
 	ctx := &pkgCtx{
-		fset: fset,
-		syms: make(map[string]loader), nodeInterp: interp, generics: make(map[string]bool),
+		fset:       fset,
+		nodeInterp: interp,
+		projs:      make(map[string]*gmxProject),
+		classes:    make(map[*ast.File]gmxClass),
+		syms:       make(map[string]loader),
+		generics:   make(map[string]bool),
 	}
 	confGox := &gox.Config{
 		Types:           conf.Types,
 		Fset:            fset,
+		Context:         conf.Context,
+		IsPkgtStandard:  conf.IsPkgtStandard,
 		Importer:        conf.Importer,
 		LoadNamed:       ctx.loadNamed,
 		HandleErr:       ctx.handleErr,
@@ -494,40 +513,40 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		}()
 	}
 	p = gox.NewPackage(pkgPath, pkg.Name, confGox)
+
+	if !noMarkAutogen {
+		p.CB().NewConstStart(nil, "_").Val(true).EndInit(1)
+	}
+
 	ctx.cpkgs = cpackages.NewImporter(&cpackages.Config{
 		Pkg: p, LookupPub: conf.LookupPub,
 	})
+
 	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
+		if gmx.IsClass && !gmx.IsNormalGox {
+			if debugLoad {
+				log.Println("==> File", file, "normalGox:", gmx.IsNormalGox)
 			}
+			loadClass(ctx, p, file, gmx, conf)
 		}
 	}
 
-	gofiles := make([]*ast.File, 0, len(pkg.GoFiles))
-	for fpath, gof := range pkg.GoFiles {
-		f := fromgo.ASTFile(gof, 0)
-		gofiles = append(gofiles, f)
-		ctx := &blockCtx{
-			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir,
-			imports: make(map[string]pkgImp),
-		}
-		preloadFile(p, ctx, fpath, f, false, false)
+	// sort files
+	type File struct {
+		*ast.File
+		path string
 	}
-
-	initGopPkg(ctx, p)
-
+	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
+	})
+
+	for _, f := range sfiles {
 		fileLine := !conf.NoFileLine
-		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), fpath)
+		fileScope := types.NewScope(p.Types.Scope(), f.Pos(), f.End(), f.path)
 		ctx := &blockCtx{
 			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir, fileScope: fileScope,
 			fileLine: fileLine, isClass: f.IsClass, rec: rec,
@@ -536,31 +555,46 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 		if rec := ctx.rec; rec != nil {
 			rec.Scope(f, fileScope)
 		}
-		preloadGopFile(p, ctx, fpath, f, conf)
+		preloadGopFile(p, ctx, f.path, f.File, conf)
 	}
 
-	// sort files
-	type File struct {
-		*ast.File
-		path string
+	gopSyms := make(map[string]bool) // TODO: remove this map
+	for name := range ctx.syms {
+		gopSyms[name] = true
 	}
-	sfiles := make([]*File, 0, len(files))
-	for fpath, f := range files {
-		sfiles = append(sfiles, &File{f, fpath})
+
+	gofiles := make([]*ast.File, 0, len(pkg.GoFiles))
+	for fpath, gof := range pkg.GoFiles {
+		f := fromgo.ASTFile(gof, 0)
+		gofiles = append(gofiles, f)
+		ctx := &blockCtx{
+			pkg: p, pkgCtx: ctx, cb: p.CB(), relBaseDir: relBaseDir,
+			imports: make(map[string]pkgImp),
+		}
+		preloadFile(p, ctx, fpath, f, skippingGoFile, false)
+	}
+
+	initGopPkg(ctx, p, gopSyms)
+
+	// genMain = true if it is main package and no main func
+	var genMain bool
+	var gen func()
+	if pkg.Name == "main" {
+		_, hasMain := ctx.syms["main"]
+		genMain = !hasMain
 	}
-	sort.Slice(sfiles, func(i, j int) bool {
-		return sfiles[i].path < sfiles[j].path
-	})
 
 	for _, f := range sfiles {
 		if f.IsProj {
 			loadFile(ctx, f.File)
-			gmxMainFunc(p, ctx)
-			break
 		}
 	}
+	if genMain { // make classfile main func if need
+		gen = gmxMainFunc(p, ctx, conf.NoAutoGenMain)
+	}
+
 	for _, f := range sfiles {
-		if !f.IsProj { // only one .gmx file
+		if !f.IsProj {
 			loadFile(ctx, f.File)
 		}
 	}
@@ -577,12 +611,12 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gox.Package,
 	}
 	err = ctx.complete()
 
-	if !conf.NoAutoGenMain && pkg.Name == "main" {
-		if obj := p.Types.Scope().Lookup("main"); obj == nil {
-			old, _ := p.SetCurFile(defaultGoFile, false)
-			p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End()
-			p.RestoreCurFile(old)
-		}
+	if gen != nil { // generate classfile main func
+		gen()
+	} else if genMain && !conf.NoAutoGenMain { // generate empty main func
+		old, _ := p.SetCurFile(defaultGoFile, false)
+		p.NewFunc(nil, "main", nil, nil, false).BodyStart(p).End()
+		p.RestoreCurFile(old)
 	}
 	return
 }
@@ -599,33 +633,30 @@ func isOverloadFunc(name string) bool {
 //go:linkname initThisGopPkg github.com/goplus/gox.initThisGopPkg
 func initThisGopPkg(pkg *types.Package)
 
-func initGopPkg(ctx *pkgCtx, pkg *gox.Package) {
+func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
 	for name, f := range ctx.syms {
+		if gopSyms[name] {
+			continue
+		}
 		if _, ok := f.(*typeLoader); ok {
 			ctx.loadType(name)
 		} else if isOverloadFunc(name) {
 			ctx.loadSymbol(name)
 		}
 	}
+	for _, lbi := range ctx.lbinames {
+		if name, ok := lbi.(string); ok {
+			ctx.loadSymbol(name)
+		} else {
+			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)))
 	}
 	initThisGopPkg(pkg.Types)
 }
 
-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
-}
-
 func getEntrypoint(f *ast.File) string {
 	switch {
 	case f.IsProj:
@@ -642,17 +673,6 @@ func getEntrypoint(f *ast.File) string {
 func loadFile(ctx *pkgCtx, f *ast.File) {
 	for _, decl := range f.Decls {
 		switch d := decl.(type) {
-		case *ast.FuncDecl:
-			if d.Recv == nil {
-				name := d.Name.Name
-				if name != "init" {
-					ctx.loadSymbol(name)
-				}
-			} else {
-				if name, ok := getRecvTypeName(ctx, d.Recv, false); ok {
-					getTypeLoader(ctx, ctx.syms, token.NoPos, name).load()
-				}
-			}
 		case *ast.GenDecl:
 			switch d.Tok {
 			case token.TYPE:
@@ -666,51 +686,78 @@ func loadFile(ctx *pkgCtx, f *ast.File) {
 					}
 				}
 			}
+		case *ast.FuncDecl:
+			if d.Recv == nil {
+				name := d.Name.Name
+				if name != "init" {
+					ctx.loadSymbol(name)
+				}
+			} else {
+				if name, ok := getRecvTypeName(ctx, d.Recv, false); ok {
+					getTypeLoader(ctx, ctx.syms, token.NoPos, name).load()
+				}
+			}
 		}
 	}
 }
 
-func genGoFile(file string, gopFile bool) string {
-	if gopFile {
-		if strings.HasSuffix(file, "_test.gop") {
-			return testingGoFile
-		}
-		return defaultGoFile
+// gen testingGoFile for:
+//
+//	*_test.gop
+//	*test.gox
+func genGoFile(file string, goxTestFile bool) string {
+	if goxTestFile || strings.HasSuffix(file, "_test.gop") {
+		return testingGoFile
 	}
-	return skippingGoFile
+	return defaultGoFile
 }
 
 func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, conf *Config) {
-	var parent = ctx.pkgCtx
+	var proj *gmxProject
 	var classType string
+	var testType 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:
-		var classExt string
-		classType, classExt = classNameAndExt(file)
-		if parent.gmxSettings != nil {
-			o, ok := parent.sprite[classExt]
-			if ok {
+	var goxTestFile bool
+	var parent = ctx.pkgCtx
+	if f.IsClass {
+		if f.IsNormalGox {
+			classType, _ = ClassNameAndExt(file)
+			if classType == "main" {
+				classType = "_main"
+			}
+		} else {
+			c := parent.classes[f]
+			proj, ctx.proj = c.proj, c.proj
+			classType = c.tname
+			if isGoxTestFile(c.ext) { // test classfile
+				testType = c.tname
+				goxTestFile, proj.isTest = true, true
+				if !f.IsProj {
+					classType = casePrefix + testNameSuffix(testType)
+				}
+			}
+			if f.IsProj {
+				o := proj.game
+				baseTypeName, baseType = o.Name(), o.Type()
+				if proj.gameIsPtr {
+					baseType = types.NewPointer(baseType)
+				}
+			} else {
+				o := proj.sprite[c.ext]
 				baseTypeName, baseType, spxClass = o.Name(), o.Type(), true
 			}
 		}
 	}
+	goFile := genGoFile(file, goxTestFile)
 	if classType != "" {
 		if debugLoad {
 			log.Println("==> Preload type", classType)
 		}
-		if parent.gmxSettings != nil {
-			ctx.lookups = make([]*gox.PkgRef, len(parent.pkgPaths))
-			for i, pkgPath := range parent.pkgPaths {
+		if proj != nil {
+			ctx.lookups = make([]gox.PkgRef, len(proj.pkgPaths))
+			for i, pkgPath := range proj.pkgPaths {
 				ctx.lookups[i] = p.Import(pkgPath)
 			}
 		}
@@ -722,22 +769,28 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			if debugLoad {
 				log.Println("==> Load > NewType", classType)
 			}
-			decl := p.NewType(classType)
+			old, _ := p.SetCurFile(goFile, true)
+			defer p.RestoreCurFile(old)
+
+			decl := p.NewTypeDefs().NewType(classType)
 			ld.typInit = func() { // decycle
 				if debugLoad {
 					log.Println("==> Load > InitType", classType)
 				}
+				old, _ := p.SetCurFile(goFile, true)
+				defer p.RestoreCurFile(old)
+
 				pkg := p.Types
 				var flds []*types.Var
 				var tags []string
 				chk := newCheckRedecl()
-				if len(baseTypeName) != 0 {
+				if baseTypeName != "" {
 					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}})
+				if spxClass && proj.gameClass != "" {
+					typ := toType(ctx, &ast.StarExpr{X: &ast.Ident{Name: proj.gameClass}})
 					name := getTypeName(typ)
 					if !chk.chkRedecl(ctx, name, pos) {
 						fld := types.NewField(pos, pkg, name, typ, true)
@@ -788,9 +841,10 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			},
 		}}}
 	}
-	// check class project no MainEntry and auto added
-	if f.IsProj && !conf.NoAutoGenMain && !f.NoEntrypoint() && f.Name.Name == "main" {
-		entry := getEntrypoint(f)
+	if d := f.ShadowEntry; d != nil {
+		d.Name.Name = getEntrypoint(f)
+	} else if f.IsProj && !conf.NoAutoGenMain && f.Name.Name == "main" {
+		var entry = getEntrypoint(f)
 		var hasEntry bool
 		for _, decl := range f.Decls {
 			switch d := decl.(type) {
@@ -802,16 +856,25 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 		}
 		if !hasEntry {
 			f.Decls = append(f.Decls, &ast.FuncDecl{
-				Name: ast.NewIdent(entry),
-				Type: &ast.FuncType{Params: &ast.FieldList{}},
-				Body: &ast.BlockStmt{},
+				Name: &ast.Ident{
+					Name: entry,
+				},
+				Type: &ast.FuncType{
+					Params: &ast.FieldList{},
+				},
+				Body:   &ast.BlockStmt{},
+				Shadow: true,
 			})
 		}
 	}
-	if d := f.ShadowEntry; d != nil {
-		d.Name.Name = getEntrypoint(f)
+	preloadFile(p, ctx, file, f, goFile, !conf.Outline)
+	if goxTestFile {
+		parent.inits = append(parent.inits, func() {
+			old, _ := p.SetCurFile(testingGoFile, true)
+			gmxTestFunc(p, testType, f.IsProj)
+			p.RestoreCurFile(old)
+		})
 	}
-	preloadFile(p, ctx, file, f, true, !conf.Outline)
 }
 
 func parseTypeEmbedName(typ ast.Expr) *ast.Ident {
@@ -828,60 +891,84 @@ retry:
 	panic("TODO: parseTypeEmbedName unexpected")
 }
 
-func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFile, genFnBody bool) {
+func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, goFile string, genFnBody bool) {
 	parent := ctx.pkgCtx
 	syms := parent.syms
-	goFile := genGoFile(file, gopFile)
 	old, _ := p.SetCurFile(goFile, true)
 	defer p.RestoreCurFile(old)
 	var skipClassFields bool
 	if f.IsClass {
 		skipClassFields = true
 	}
-	for _, decl := range f.Decls {
-		switch d := decl.(type) {
-		case *ast.FuncDecl:
-			if ctx.classRecv != nil { // in class file (.spx/.gmx)
-				if d.Recv == nil {
-					d.Recv = ctx.classRecv
+
+	preloadFuncDecl := func(d *ast.FuncDecl) {
+		if ctx.classRecv != nil { // in class file (.spx/.gmx)
+			if d.Recv == nil {
+				d.Recv = ctx.classRecv
+				d.IsClass = true
+			}
+		}
+		if d.Recv == nil {
+			name := d.Name
+			fn := func() {
+				old, _ := p.SetCurFile(goFile, true)
+				defer p.RestoreCurFile(old)
+				loadFunc(ctx, nil, d, genFnBody)
+			}
+			if name.Name == "init" {
+				if genFnBody {
+					if debugLoad {
+						log.Println("==> Preload func init")
+					}
+					parent.inits = append(parent.inits, fn)
+				}
+			} else {
+				if debugLoad {
+					log.Println("==> Preload func", name.Name)
 				}
+				initLoader(parent, syms, name.Pos(), name.Name, fn, genFnBody)
 			}
-			if d.Recv == nil {
-				name := d.Name
+		} else {
+			if name, ok := getRecvTypeName(parent, d.Recv, true); ok {
+				if debugLoad {
+					log.Printf("==> Preload method %s.%s\n", name, d.Name.Name)
+				}
+				ld := getTypeLoader(parent, syms, token.NoPos, name)
 				fn := func() {
 					old, _ := p.SetCurFile(goFile, true)
 					defer p.RestoreCurFile(old)
-					loadFunc(ctx, nil, d, genFnBody)
-				}
-				if name.Name == "init" {
-					if genFnBody {
-						if debugLoad {
-							log.Println("==> Preload func init")
-						}
-						parent.inits = append(parent.inits, fn)
-					}
-				} else {
-					if debugLoad {
-						log.Println("==> Preload func", name.Name)
-					}
-					initLoader(parent, syms, name.Pos(), name.Name, fn, genFnBody)
+					doInitType(ld)
+					recv := toRecv(ctx, d.Recv)
+					loadFunc(ctx, recv, d, genFnBody)
 				}
-			} else {
-				if name, ok := getRecvTypeName(parent, d.Recv, true); ok {
-					if debugLoad {
-						log.Printf("==> Preload method %s.%s\n", name, d.Name.Name)
-					}
-					ld := getTypeLoader(parent, syms, token.NoPos, name)
-					fn := func() {
-						old, _ := p.SetCurFile(goFile, true)
-						defer p.RestoreCurFile(old)
-						doInitType(ld)
-						recv := toRecv(ctx, d.Recv)
-						loadFunc(ctx, recv, d, genFnBody)
+				ld.methods = append(ld.methods, fn)
+			}
+		}
+	}
+
+	preloadConst := func(d *ast.GenDecl) {
+		pkg := ctx.pkg
+		cdecl := pkg.NewConstDefs(pkg.Types.Scope())
+		for _, spec := range d.Specs {
+			vSpec := spec.(*ast.ValueSpec)
+			if debugLoad {
+				log.Println("==> Preload const", vSpec.Names)
+			}
+			setNamesLoader(parent, syms, vSpec.Names, func() {
+				if c := cdecl; c != nil {
+					cdecl = nil
+					loadConstSpecs(ctx, c, d.Specs)
+					for _, s := range d.Specs {
+						v := s.(*ast.ValueSpec)
+						removeNames(syms, v.Names)
 					}
-					ld.methods = append(ld.methods, fn)
 				}
-			}
+			})
+		}
+	}
+
+	for _, decl := range f.Decls {
+		switch d := decl.(type) {
 		case *ast.GenDecl:
 			switch d.Tok {
 			case token.IMPORT:
@@ -899,7 +986,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 					pos := tName.Pos()
 					ld := getTypeLoader(parent, syms, pos, name)
 					defs := ctx.pkg.NewTypeDefs()
-					if gopFile {
+					if goFile != skippingGoFile { // is Go+ file
 						ld.typ = func() {
 							old, _ := p.SetCurFile(goFile, true)
 							defer p.RestoreCurFile(old)
@@ -955,24 +1042,7 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 					}
 				}
 			case token.CONST:
-				pkg := ctx.pkg
-				cdecl := pkg.NewConstDefs(pkg.Types.Scope())
-				for _, spec := range d.Specs {
-					vSpec := spec.(*ast.ValueSpec)
-					if debugLoad {
-						log.Println("==> Preload const", vSpec.Names)
-					}
-					setNamesLoader(parent, syms, vSpec.Names, func() {
-						if c := cdecl; c != nil {
-							cdecl = nil
-							loadConstSpecs(ctx, c, d.Specs)
-							for _, s := range d.Specs {
-								v := s.(*ast.ValueSpec)
-								removeNames(syms, v.Names)
-							}
-						}
-					})
-				}
+				preloadConst(d)
 			case token.VAR:
 				if skipClassFields {
 					skipClassFields = false
@@ -996,10 +1066,120 @@ func preloadFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, gopFil
 			default:
 				log.Panicln("TODO - tok:", d.Tok, "spec:", reflect.TypeOf(d.Specs).Elem())
 			}
+
+		case *ast.FuncDecl:
+			preloadFuncDecl(d)
+
+		case *ast.OverloadFuncDecl:
+			var recv *ast.Ident
+			if d.Recv != nil {
+				otyp, ok := d.Recv.List[0].Type.(*ast.Ident)
+				if !ok {
+					log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: invalid recv")
+				}
+				recv = otyp
+			}
+			onames := make([]string, 0, 4)
+			exov := false
+			name := d.Name
+			for idx, fn := range d.Funcs {
+				switch expr := fn.(type) {
+				case *ast.Ident:
+					checkOverloadFunc(d)
+					onames = append(onames, expr.Name)
+					ctx.lbinames = append(ctx.lbinames, expr.Name)
+					exov = true
+				case *ast.SelectorExpr:
+					checkOverloadMethod(d)
+					checkOverloadMethodRecvType(recv, expr.X)
+					onames = append(onames, "."+expr.Sel.Name)
+					ctx.lbinames = append(ctx.lbinames, recv)
+					exov = true
+				case *ast.FuncLit:
+					checkOverloadFunc(d)
+					name1 := overloadFuncName(name.Name, idx)
+					ctx.lbinames = append(ctx.lbinames, name1)
+					preloadFuncDecl(&ast.FuncDecl{
+						Doc:  d.Doc,
+						Name: &ast.Ident{NamePos: name.NamePos, Name: name1},
+						Type: expr.Type,
+						Body: expr.Body,
+					})
+				default:
+					log.Panicf("TODO - cl.preloadFile OverloadFuncDecl: unknown func - %T\n", expr)
+				}
+			}
+			if exov { // need Gopo_xxx
+				oname := overloadName(recv, name.Name, d.Operator)
+				oval := strings.Join(onames, ",")
+				preloadConst(&ast.GenDecl{
+					Doc: d.Doc,
+					Tok: token.CONST,
+					Specs: []ast.Spec{
+						&ast.ValueSpec{
+							Names:  []*ast.Ident{{Name: oname}},
+							Values: []ast.Expr{stringLit(oval)},
+						},
+					},
+				})
+				ctx.lbinames = append(ctx.lbinames, oname)
+			}
+
 		default:
-			log.Panicln("TODO - gopkg.Package.load: unknown decl -", reflect.TypeOf(decl))
+			log.Panicf("TODO - cl.preloadFile: unknown decl - %T\n", decl)
+		}
+	}
+}
+
+func checkOverloadFunc(d *ast.OverloadFuncDecl) {
+	if d.Recv != nil && !d.Operator {
+		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc")
+	}
+}
+
+func checkOverloadMethod(d *ast.OverloadFuncDecl) {
+	if d.Recv == nil {
+		log.Panicln("TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod")
+	}
+}
+
+func checkOverloadMethodRecvType(ot *ast.Ident, recv ast.Expr) {
+	rtyp, _ := getRecvType(recv)
+	rt, ok := rtyp.(*ast.Ident)
+	if !ok || ot.Name != rt.Name {
+		log.Panicln("TODO - checkOverloadMethodRecvType:", recv)
+	}
+}
+
+const (
+	indexTable = "0123456789abcdefghijklmnopqrstuvwxyz"
+)
+
+func overloadFuncName(name string, idx int) string {
+	return name + "__" + indexTable[idx:idx+1]
+}
+
+func overloadName(recv *ast.Ident, name string, isOp bool) string {
+	if isOp {
+		if oname, ok := binaryGopNames[name]; ok {
+			name = oname
+		} else {
+			log.Panicln("TODO - can't overload operator", name)
 		}
 	}
+	sep := "_"
+	if strings.ContainsRune(name, '_') || (recv != nil && strings.ContainsRune(recv.Name, '_')) {
+		sep = "__"
+	}
+	typ := ""
+	if recv != nil {
+		typ = recv.Name + sep
+	}
+	return "Gopo" + sep + typ + name
+}
+
+func stringLit(val string) *ast.BasicLit {
+	return &ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(val)}
 }
 
 func newType(pkg *types.Package, pos token.Pos, name string) *types.Named {
@@ -1059,8 +1239,11 @@ func loadFunc(ctx *blockCtx, recv *types.Var, d *ast.FuncDecl, genBody bool) {
 	if genBody {
 		if body := d.Body; body != nil {
 			if recv != nil {
+				file := pkg.CurFile()
 				ctx.inits = append(ctx.inits, func() { // interface issue: #795
+					old := pkg.RestoreCurFile(file)
 					loadFuncBody(ctx, fn, body, d)
+					pkg.RestoreCurFile(old)
 				})
 			} else {
 				loadFuncBody(ctx, fn, body, d)
@@ -1140,13 +1323,13 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 			}
 		}()
 	}
-	var pkg *gox.PkgRef
+	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 {
+		if pkg = loadC2goPkg(ctx, realPath, spec.Path); pkg.Types == nil {
 			return
 		}
 	} else {
@@ -1176,7 +1359,7 @@ func loadImport(ctx *blockCtx, spec *ast.ImportSpec) {
 			return
 		}
 		if name == "_" {
-			pkg.MarkForceUsed()
+			pkg.MarkForceUsed(ctx.pkg)
 			return
 		}
 	}
diff --git a/cl/expr.go b/cl/expr.go
index 15356c2e..fb6cc143 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -74,7 +74,7 @@ const (
 
 const errorPkgPath = "github.com/qiniu/x/errors"
 
-func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef, kind int) {
+func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg gox.PkgRef, kind int) {
 	fvalue := (flags&clIdentSelectorExpr) != 0 || (flags&clIdentLHS) == 0
 	name := ident.Name
 	if name == "_" {
@@ -122,7 +122,8 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef,
 	// pkgRef object
 	if (flags & clIdentSelectorExpr) != 0 {
 		if name == "C" && len(ctx.clookups) > 0 {
-			return nil, objCPkgRef
+			kind = objCPkgRef
+			return
 		}
 		if pi, ok := ctx.findImport(name); ok {
 			if rec := ctx.recorder(); rec != nil {
@@ -133,7 +134,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg *gox.PkgRef,
 	}
 
 	// object from import . "xxx"
-	if compilePkgRef(ctx, nil, ident, flags, objPkgRef) {
+	if compilePkgRef(ctx, gox.PkgRef{}, ident, flags, objPkgRef) {
 		return
 	}
 
@@ -389,7 +390,7 @@ func compileSelectorExpr(ctx *blockCtx, v *ast.SelectorExpr, flags int) {
 	}
 }
 
-func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
+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()) {
@@ -400,8 +401,9 @@ func pkgRef(at *gox.PkgRef, name string) (o types.Object, alias bool) {
 	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 {
+// allow pkg.Types to be nil
+func lookupPkgRef(ctx *blockCtx, pkg gox.PkgRef, x *ast.Ident, pkgKind int) (o types.Object, alias bool) {
+	if pkg.Types != nil {
 		return pkgRef(pkg, x.Name)
 	}
 	if pkgKind == objPkgRef {
@@ -416,7 +418,7 @@ func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o
 			}
 		}
 	} else {
-		var cpkg *cpackages.PkgRef
+		var cpkg cpackages.PkgRef
 		for _, at := range ctx.clookups {
 			if o2 := at.Lookup(x.Name); o2 != nil {
 				if o != nil {
@@ -431,7 +433,8 @@ func lookupPkgRef(ctx *blockCtx, pkg *gox.PkgRef, x *ast.Ident, pkgKind int) (o
 	return
 }
 
-func compilePkgRef(ctx *blockCtx, at *gox.PkgRef, x *ast.Ident, flags, pkgKind int) bool {
+// 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 {
@@ -778,7 +781,46 @@ func compileBasicLit(ctx *blockCtx, v *ast.BasicLit) {
 		}
 		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)
+		if v.Extra == nil {
+			basicLit(cb, v)
+			return
+		}
+		compileStringLitEx(ctx, cb, v)
+	}
+}
+
+func basicLit(cb *gox.CodeBuilder, v *ast.BasicLit) {
+	cb.Val(&goast.BasicLit{Kind: gotoken.Token(v.Kind), Value: v.Value}, v)
+}
+
+func compileStringLitEx(ctx *blockCtx, cb *gox.CodeBuilder, lit *ast.BasicLit) {
+	pos := lit.ValuePos + 1
+	quote := lit.Value[:1]
+	notFirst := false
+	for _, part := range lit.Extra.Parts {
+		switch v := part.(type) {
+		case string: // normal string literal or end with "$$"
+			next := pos + token.Pos(len(v))
+			if strings.HasSuffix(v, "$$") {
+				v = v[:len(v)-1]
+			}
+			basicLit(cb, &ast.BasicLit{ValuePos: pos - 1, Value: quote + v + quote, Kind: token.STRING})
+			pos = next
+		case ast.Expr:
+			compileExpr(ctx, v)
+			t := cb.Get(-1).Type
+			if t.Underlying() != types.Typ[types.String] {
+				cb.Member("string", gox.MemberFlagAutoProperty)
+			}
+			pos = v.End()
+		default:
+			panic("compileStringLitEx TODO: unexpected part")
+		}
+		if notFirst {
+			cb.BinaryOp(gotoken.ADD)
+		} else {
+			notFirst = true
+		}
 	}
 }
 
@@ -1133,7 +1175,7 @@ func compileErrWrapExpr(ctx *blockCtx, v *ast.ErrWrapExpr, inFlags int) {
 			retName = "_gop_ret" + strconv.Itoa(i+1)
 		}
 	}
-	sig := types.NewSignature(nil, nil, types.NewTuple(ret...), false)
+	sig := types.NewSignatureType(nil, nil, nil, nil, types.NewTuple(ret...), false)
 	if useClosure {
 		cb.NewClosureWith(sig).BodyStart(pkg)
 	} else {
diff --git a/cl/func_type_and_var.go b/cl/func_type_and_var.go
index ca8a37f1..03fa6fdb 100644
--- a/cl/func_type_and_var.go
+++ b/cl/func_type_and_var.go
@@ -21,7 +21,6 @@ import (
 	"go/types"
 	"log"
 	"math/big"
-	"reflect"
 	"strconv"
 
 	"github.com/goplus/gop/ast"
@@ -179,9 +178,10 @@ func toType(ctx *blockCtx, typ ast.Expr) (t types.Type) {
 		return toIndexType(ctx, v)
 	case *ast.IndexListExpr:
 		return toIndexListType(ctx, v)
+	default:
+		ctx.handleErrorf(v.Pos(), "toType unexpected: %T", v)
+		return types.Typ[types.Invalid]
 	}
-	log.Panicln("toType: unknown -", reflect.TypeOf(typ))
-	return nil
 }
 
 var (
@@ -251,7 +251,7 @@ func toIdentType(ctx *blockCtx, ident *ast.Ident) (ret types.Type) {
 		obj = t
 		return t.Type()
 	}
-	if v, _ := lookupPkgRef(ctx, nil, ident, objPkgRef); v != nil {
+	if v, _ := lookupPkgRef(ctx, gox.PkgRef{}, ident, objPkgRef); v != nil {
 		if t, ok := v.(*types.TypeName); ok {
 			obj = t
 			return t.Type()
@@ -438,28 +438,30 @@ func toInterfaceType(ctx *blockCtx, v *ast.InterfaceType) types.Type {
 	return intf
 }
 
-func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
+func instantiate(ctx *blockCtx, exprX ast.Expr, indices ...ast.Expr) types.Type {
 	ctx.inInst++
 	defer func() {
 		ctx.inInst--
 	}()
-	ctx.cb.Typ(toType(ctx, v.X), v.X)
-	ctx.cb.Typ(toType(ctx, v.Index), v.Index)
-	ctx.cb.Index(1, false, v)
-	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+
+	x := toType(ctx, exprX)
+	idx := make([]types.Type, len(indices))
+	for i, index := range indices {
+		idx[i] = toType(ctx, index)
+	}
+	typ := ctx.pkg.Instantiate(x, idx, exprX)
+	if rec := ctx.recorder(); rec != nil {
+		rec.instantiate(exprX, x, typ)
+	}
+	return typ
+}
+
+func toIndexType(ctx *blockCtx, v *ast.IndexExpr) types.Type {
+	return instantiate(ctx, v.X, v.Index)
 }
 
 func toIndexListType(ctx *blockCtx, v *ast.IndexListExpr) types.Type {
-	ctx.inInst++
-	defer func() {
-		ctx.inInst--
-	}()
-	ctx.cb.Typ(toType(ctx, v.X), v.X)
-	for _, index := range v.Indices {
-		ctx.cb.Typ(toType(ctx, index), index)
-	}
-	ctx.cb.Index(len(v.Indices), false, v)
-	return ctx.cb.InternalStack().Pop().Type.(*gox.TypeType).Type()
+	return instantiate(ctx, v.X, v.Indices...)
 }
 
 // -----------------------------------------------------------------------------
diff --git a/cl/internal/overload/bar/bar.go b/cl/internal/overload/bar/bar.go
new file mode 100644
index 00000000..b675663b
--- /dev/null
+++ b/cl/internal/overload/bar/bar.go
@@ -0,0 +1,33 @@
+package bar
+
+const GopPackage = true
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	val T
+}
+
+func (p *Var__0[T]) Value() T {
+	return p.val
+}
+
+type Var__1[T map[string]any] struct {
+	val T
+}
+
+func (p *Var__1[T]) Value() T {
+	return p.val
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
new file mode 100644
index 00000000..6fe29474
--- /dev/null
+++ b/cl/internal/spx3/spx3.go
@@ -0,0 +1,36 @@
+/*
+ * 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 spx3
+
+const (
+	GopPackage = true
+)
+
+type Game struct {
+}
+
+func (p *Game) initGame() {}
+
+func (p *Game) Run() {}
+
+type Sprite struct {
+}
+
+func (p *Sprite) initSprite() {}
+
+func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ initSprite() }) {
+}
diff --git a/cl/outline/outline.go b/cl/outline/outline.go
index 7adab5ac..9e97dadd 100644
--- a/cl/outline/outline.go
+++ b/cl/outline/outline.go
@@ -47,6 +47,9 @@ 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
 }
@@ -63,6 +66,7 @@ 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/cl/recorder.go b/cl/recorder.go
index 5ce8e9cb..3ffb0e02 100644
--- a/cl/recorder.go
+++ b/cl/recorder.go
@@ -76,6 +76,26 @@ func (rec *typesRecorder) Type(expr ast.Expr, tv types.TypeAndValue) {
 	rec.Recorder.Type(expr, tv)
 }
 
+func (rec *typesRecorder) instantiate(expr ast.Expr, org, typ types.Type) {
+	// check gox TyOverloadNamed
+	if tv, ok := rec.types[expr]; ok {
+		tv.Type = typ
+		rec.Recorder.Type(expr, tv)
+	}
+	var ident *ast.Ident
+	switch id := expr.(type) {
+	case *ast.Ident:
+		ident = id
+	case *ast.SelectorExpr:
+		ident = id.Sel
+	}
+	if ident != nil {
+		if named, ok := typ.(*types.Named); ok {
+			rec.Use(ident, named.Obj())
+		}
+	}
+}
+
 func newTypeRecord(rec Recorder) *typesRecorder {
 	return &typesRecorder{rec, make(map[ast.Expr]types.TypeAndValue)}
 }
diff --git a/cmd/internal/mod/tidy.go b/cmd/internal/mod/tidy.go
index 23879dd3..2a79d340 100644
--- a/cmd/internal/mod/tidy.go
+++ b/cmd/internal/mod/tidy.go
@@ -39,7 +39,7 @@ func runTidy(cmd *base.Command, args []string) {
 	err := gop.Tidy(".", gopenv.Get())
 	if err != nil {
 		if gop.NotFound(err) {
-			fmt.Fprintln(os.Stderr, "gop.mod not found")
+			fmt.Fprintln(os.Stderr, "go.mod not found")
 		} else {
 			fmt.Fprintln(os.Stderr, err)
 		}
diff --git a/cmd/make.go b/cmd/make.go
index d04c52c4..6ecf458a 100644
--- a/cmd/make.go
+++ b/cmd/make.go
@@ -548,10 +548,28 @@ func releaseNewVersion(tag string) {
 	println("Released new version:", version)
 }
 
+func runRegtests() {
+	println("\nStart running regtests.")
+
+	cmd := exec.Command(filepath.Join(gopRoot, "bin/"+gopBinFiles[0]), "go", "./...")
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	cmd.Dir = filepath.Join(gopRoot, "testdata")
+	err := cmd.Run()
+	if err != nil {
+		code := cmd.ProcessState.ExitCode()
+		if code == 0 {
+			code = 1
+		}
+		os.Exit(code)
+	}
+}
+
 func main() {
 	isInstall := flag.Bool("install", false, "Install Go+")
 	isBuild := flag.Bool("build", false, "Build Go+ tools")
 	isTest := flag.Bool("test", false, "Run testcases")
+	isRegtest := flag.Bool("regtest", false, "Run regtests")
 	isUninstall := flag.Bool("uninstall", false, "Uninstall Go+")
 	isGoProxy := flag.Bool("proxy", false, "Set GOPROXY for people in China")
 	isAutoProxy := flag.Bool("autoproxy", false, "Check to set GOPROXY automatically")
@@ -572,10 +590,11 @@ func main() {
 		},
 		isUninstall: uninstall,
 		isTest:      runTestcases,
+		isRegtest:   runRegtests,
 	}
 
 	// Sort flags, for example: install flag should be checked earlier than test flag.
-	flags := []*bool{isBuild, isInstall, isTest, isUninstall}
+	flags := []*bool{isBuild, isInstall, isTest, isRegtest, isUninstall}
 	hasActionDone := false
 
 	if *tag != "" {
diff --git a/doc/docs.md b/doc/docs.md
index f2434cf9..d10ae9fc 100644
--- a/doc/docs.md
+++ b/doc/docs.md
@@ -266,7 +266,7 @@ s += "world"
 println s // Hello world
 ```
 
-All operators in Go+ must have values of the same type on both sides. You cannot concatenate an
+Most Go+ operators must have values of the same type on both sides. You cannot concatenate an
 integer to a string:
 
 ```go failcompile
@@ -281,6 +281,24 @@ age := 10
 println "age = " + age.string
 ```
 
+However, you can replace `age.string` to `"${age}"`:
+
+```go
+age := 10
+println "age = ${age}"
+```
+
+Here is a more complex example of `${expr}`:
+
+```go
+host := "example.com"
+page := 0
+limit := 20
+println "https://${host}/items?page=${page+1}&limit=${limit}" // https://example.com/items?page=1&limit=20
+println "$$" // $
+```
+
+
 <h5 align="right"><a href="#table-of-contents">⬆ back to toc</a></h5>
 
 
diff --git a/doc/dsl-vs-sdf.md b/doc/dsl-vs-sdf.md
index f539a4fd..b3df6dfc 100644
--- a/doc/dsl-vs-sdf.md
+++ b/doc/dsl-vs-sdf.md
@@ -142,7 +142,6 @@ total 72
 -rw-r--r--  1 xushiwei  staff    365 Jun 19 00:25 example.gsh
 -rw-r--r--  1 xushiwei  staff    126 Jun 19 09:33 go.mod
 -rw-r--r--  1 xushiwei  staff    165 Jun 19 09:33 go.sum
--rw-r--r--  1 xushiwei  staff    110 Jun 19 09:33 gop.mod
 -rw-r--r--  1 xushiwei  staff   1938 Jun 19 10:00 gop_autogen.go
 ```
 
diff --git a/gengo.go b/gengo.go
index 4e0103f8..79100c0e 100644
--- a/gengo.go
+++ b/gengo.go
@@ -48,10 +48,14 @@ const (
 
 // -----------------------------------------------------------------------------
 
+// GenGo generates gop_autogen.go for a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGo(dir string, conf *Config, genTestPkg bool) (string, bool, error) {
 	return GenGoEx(dir, conf, genTestPkg, 0)
 }
 
+// GenGoEx generates gop_autogen.go for a Go+ package directory.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string, bool, error) {
 	recursively := strings.HasSuffix(dir, "/...")
 	if recursively {
@@ -61,6 +65,9 @@ func GenGoEx(dir string, conf *Config, genTestPkg bool, flags GenFlags) (string,
 }
 
 func genGoDir(dir string, conf *Config, genTestPkg, recursively bool, flags GenFlags) (err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
 	if recursively {
 		var (
 			list errors.List
@@ -201,6 +208,8 @@ const (
 	modReadonly = 0555
 )
 
+// GenGoPkgPath generates gop_autogen.go for a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoPkgPath(workDir, pkgPath string, conf *Config, allowExtern bool) (localDir string, recursively bool, err error) {
 	return GenGoPkgPathEx(workDir, pkgPath, conf, allowExtern, 0)
 }
@@ -218,6 +227,8 @@ func remotePkgPath(pkgPath string, conf *Config, recursively bool, flags GenFlag
 	return
 }
 
+// GenGoPkgPathEx generates gop_autogen.go for a Go+ package.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoPkgPathEx(workDir, pkgPath string, conf *Config, allowExtern bool, flags GenFlags) (localDir string, recursively bool, err error) {
 	recursively = strings.HasSuffix(pkgPath, "/...")
 	if recursively {
@@ -259,7 +270,12 @@ func remotePkgPathDo(pkgPath string, doSth func(pkgDir, modDir string), onErr fu
 
 // -----------------------------------------------------------------------------
 
+// GenGoFiles generates gop_autogen.go for specified Go+ files.
+// if conf != nil && conf.Context == nil, it will be set with `gox.NewContext()`.
 func GenGoFiles(autogen string, files []string, conf *Config) (result []string, err error) {
+	if conf == nil {
+		conf = new(Config)
+	}
 	if autogen == "" {
 		autogen = "gop_autogen.go"
 		if len(files) == 1 {
diff --git a/go.mod b/go.mod
index 6734b2b5..bb02a687 100644
--- a/go.mod
+++ b/go.mod
@@ -4,11 +4,11 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.19
-	github.com/goplus/gox v1.13.2
-	github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51
+	github.com/goplus/c2go v0.7.20
+	github.com/goplus/gox v1.14.1
+	github.com/goplus/mod v0.12.3
 	github.com/qiniu/x v1.13.2
-	golang.org/x/tools v0.16.1
+	golang.org/x/tools v0.17.0
 )
 
 require (
@@ -16,7 +16,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
 	golang.org/x/mod v0.14.0 // indirect
-	golang.org/x/sys v0.15.0 // indirect
+	golang.org/x/sys v0.16.0 // indirect
 )
 
 retract v1.1.12
diff --git a/go.sum b/go.sum
index 185d4182..f4de2102 100644
--- a/go.sum
+++ b/go.sum
@@ -4,13 +4,14 @@ 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.19 h1:pPMarGN1RiNJFAYlL8oZlM7VNXeg054B4czDgBlck0o=
-github.com/goplus/c2go v0.7.19/go.mod h1:EJgnt9CEHnXuWQc4y78w3DhKErL1aBzgE41NZaEa/3c=
-github.com/goplus/gox v1.13.2 h1:XZZtLcZasRVKOYNzTnJ9E2Cu350TWu9K+CYMJTItkis=
-github.com/goplus/gox v1.13.2/go.mod h1:iIchh0wp8Ye0DOPcFgHc5d0qlMOx8/OJ+DBQfe7hcTs=
-github.com/goplus/mod v0.12.0/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
-github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51 h1:SXuaj7lYEL2fAQTrKzbCAUuRre2s2nMfGEyBqbUWooc=
-github.com/goplus/mod v0.12.2-0.20240107203906-5044606d0c51/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
+github.com/goplus/c2go v0.7.20 h1:90HiO7tuX8hHfEnl7HZRmLsAPH9q2wsYIvHiJMpwtU8=
+github.com/goplus/c2go v0.7.20/go.mod h1:hToieb5V4BMY3c431DQ7DEg13JQ+q9qGmls4+GgymrA=
+github.com/goplus/gox v1.14.0/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/gox v1.14.1 h1:ppqY4xzwc6UaeProYf4pn5fMa4xSDe80PsSsBfCYZ3U=
+github.com/goplus/gox v1.14.1/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/mod v0.12.2/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
+github.com/goplus/mod v0.12.3 h1:qLU5/F27CzUTQhCQRN1WruiCepUn5GdLKQTB41OsYfk=
+github.com/goplus/mod v0.12.3/go.mod h1:ZtlS9wHOcAVxZ/zq7WLdKVes1HG/8Yn3KNuWZGcpeTs=
 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=
@@ -28,7 +29,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
-golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
 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=
@@ -40,13 +41,13 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
-golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
+golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
-golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE=
-golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
+golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
+golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -55,14 +56,14 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
-golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
+golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
-golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@@ -75,6 +76,6 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
-golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
-golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
+golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
+golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/imp.go b/imp.go
index 0ea6b318..7de0b4b3 100644
--- a/imp.go
+++ b/imp.go
@@ -86,16 +86,9 @@ func (p *Importer) Import(pkgPath string) (pkg *types.Package, err error) {
 			modDir := ret.ModDir
 			goModfile := filepath.Join(modDir, "go.mod")
 			if _, e := os.Lstat(goModfile); e != nil { // no go.mod
-				gopModfile := filepath.Join(modDir, "gop.mod")
-				if _, e := os.Lstat(gopModfile); e == nil { // has gop.mod
-					if err = p.genGoExtern(ret.Dir, isExtern); err != nil {
-						return
-					}
-				} else { // maybe a old Go package without go.mod
-					os.Chmod(modDir, modWritable)
-					defer os.Chmod(modDir, modReadonly)
-					os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644)
-				}
+				os.Chmod(modDir, modWritable)
+				defer os.Chmod(modDir, modReadonly)
+				os.WriteFile(goModfile, defaultGoMod(ret.ModPath), 0644)
 			}
 			return p.impFrom.ImportFrom(pkgPath, ret.ModDir, 0)
 		case gopmod.PkgtModule, gopmod.PkgtLocal:
diff --git a/load.go b/load.go
index 17990ce4..79890fb4 100644
--- a/load.go
+++ b/load.go
@@ -121,6 +121,9 @@ type Config struct {
 	Filter   func(fs.FileInfo) bool
 	Importer types.Importer
 
+	// Context represents all things between packages (optional).
+	Context *gox.Context
+
 	IgnoreNotatedError bool
 }
 
@@ -183,11 +186,13 @@ func LoadDir(dir string, conf *Config, genTestPkg bool, promptGenGo ...bool) (ou
 
 	var pkgTest *ast.Package
 	var clConf = &cl.Config{
-		Fset:         fset,
-		RelativeBase: relativeBaseOf(mod),
-		Importer:     imp,
-		LookupClass:  mod.LookupClass,
-		LookupPub:    c2go.LookupPub(mod),
+		Fset:           fset,
+		Context:        conf.Context,
+		RelativeBase:   relativeBaseOf(mod),
+		Importer:       imp,
+		IsPkgtStandard: mod.IsPkgtStandard,
+		LookupClass:    mod.LookupClass,
+		LookupPub:      c2go.LookupPub(mod),
 	}
 
 	for name, pkg := range pkgs {
@@ -267,11 +272,13 @@ func LoadFiles(dir string, files []string, conf *Config) (out *gox.Package, err
 			imp = NewImporter(mod, gop, fset)
 		}
 		clConf := &cl.Config{
-			Fset:         fset,
-			RelativeBase: relativeBaseOf(mod),
-			Importer:     imp,
-			LookupClass:  mod.LookupClass,
-			LookupPub:    c2go.LookupPub(mod),
+			Fset:           fset,
+			Context:        conf.Context,
+			RelativeBase:   relativeBaseOf(mod),
+			Importer:       imp,
+			LookupClass:    mod.LookupClass,
+			IsPkgtStandard: mod.IsPkgtStandard,
+			LookupPub:      c2go.LookupPub(mod),
 		}
 		out, err = cl.NewPackage("", pkg, clConf)
 		if err != nil {
diff --git a/parser/interface.go b/parser/interface.go
index d9cff8e0..7fd616af 100644
--- a/parser/interface.go
+++ b/parser/interface.go
@@ -20,6 +20,7 @@ package parser
 
 import (
 	goparser "go/parser"
+	"go/scanner"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/token"
@@ -45,12 +46,15 @@ const (
 	AllErrors = Mode(goparser.AllErrors)
 	// SkipObjectResolution - don't resolve identifiers to objects - see ParseFile
 	SkipObjectResolution = Mode(goparser.SkipObjectResolution)
+
 	// ParseGoAsGoPlus - parse Go files by gop/parser
 	ParseGoAsGoPlus Mode = 1 << 16
 	// ParserGoPlusClass - parse Go+ classfile by gop/parser
 	ParseGoPlusClass Mode = 1 << 17
 	// SaveAbsFile - parse and save absolute path to pkg.Files
 	SaveAbsFile Mode = 1 << 18
+
+	goReservedFlags Mode = ((1 << 16) - 1)
 )
 
 // -----------------------------------------------------------------------------
@@ -119,7 +123,7 @@ func parseFile(fset *token.FileSet, filename string, src interface{}, mode Mode)
 
 // ParseExprFrom is a convenience function for parsing an expression.
 // The arguments have the same meaning as for ParseFile, but the source must
-// be a valid Go (type or value) expression. Specifically, fset must not
+// be a valid Go/Go+ (type or value) expression. Specifically, fset must not
 // be nil.
 //
 // If the source couldn't be read, the returned AST is nil and the error
@@ -160,6 +164,42 @@ func ParseExprFrom(fset *token.FileSet, filename string, src any, mode Mode) (ex
 	return
 }
 
+// parseExprEx is a convenience function for parsing an expression.
+// The arguments have the same meaning as for ParseFile, but the source must
+// be a valid Go/Go+ (type or value) expression. Specifically, fset must not
+// be nil.
+//
+// If the source couldn't be read, the returned AST is nil and the error
+// indicates the specific failure. If the source was read but syntax
+// errors were found, the result is a partial AST (with ast.Bad* nodes
+// representing the fragments of erroneous source code). Multiple errors
+// are returned via a scanner.ErrorList which is sorted by source position.
+func parseExprEx(file *token.File, src []byte, offset int, mode Mode) (expr ast.Expr, err scanner.ErrorList) {
+	var p parser
+	defer func() {
+		if e := recover(); e != nil {
+			// resume same panic if it's not a bailout
+			if _, ok := e.(bailout); !ok {
+				panic(e)
+			}
+		}
+		err = p.errors
+	}()
+
+	// parse expr
+	p.initSub(file, src, offset, mode)
+	expr = p.parseRHS()
+
+	// If a semicolon was inserted, consume it;
+	// report an error if there's more tokens.
+	if p.tok == token.SEMICOLON && p.lit == "\n" {
+		p.next()
+	}
+	p.expect(token.EOF)
+
+	return
+}
+
 // ParseExpr is a convenience function for obtaining the AST of an expression x.
 // The position information recorded in the AST is undefined. The filename used
 // in error messages is the empty string.
diff --git a/parser/parser.go b/parser/parser.go
index ef1d8a64..97423f23 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -109,6 +109,16 @@ func (p *parser) init(fset *token.FileSet, filename string, src []byte, mode Mod
 	p.next()
 }
 
+func (p *parser) initSub(file *token.File, src []byte, offset int, mode Mode) {
+	p.file = file
+	eh := func(pos token.Position, msg string) { p.errors.Add(pos, msg) }
+	p.scanner.InitEx(p.file, src, offset, eh, 0)
+
+	p.mode = mode
+	p.trace = mode&Trace != 0 // for convenience (p.trace is used frequently)
+	p.next()
+}
+
 // ----------------------------------------------------------------------------
 // Scoping support
 
@@ -456,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 '}'
-	if p.tok != token.RPAREN && p.tok != token.RBRACE {
+	// semicolon is optional before a closing ')' or '}' or EOF
+	if p.tok != token.RPAREN && p.tok != token.RBRACE && p.tok != token.EOF {
 		switch p.tok {
 		case token.COMMA:
 			// permit a ',' instead of a ';' but complain
@@ -1590,6 +1600,90 @@ func (p *parser) parseFuncTypeOrLit() ast.Expr {
 	return &ast.FuncLit{Type: typ, Body: body}
 }
 
+func (p *parser) stringLit(pos token.Pos, val string) *ast.StringLitEx {
+	parts := p.stringLitEx(nil, pos+1, val[1:len(val)-1])
+	if parts != nil {
+		return &ast.StringLitEx{Parts: parts}
+	}
+	return nil
+}
+
+func (p *parser) stringLitEx(parts []any, pos token.Pos, text string) []any {
+	extra := false
+loop:
+	at := strings.IndexByte(text, '$')
+	if at < 0 || at+1 == len(text) { // no '$' or end with '$'
+		if extra {
+			goto normal
+		}
+		return nil
+	}
+	switch text[at+1] {
+	case '{': // ${
+		from := at + 2
+		left := text[from:]
+		if left == "" { // "...${" (string end with "${")
+			goto normal
+		}
+		end := strings.IndexByte(left, '}')
+		if end < 0 {
+			p.error(pos+token.Pos(at+1), "invalid $ expression: ${ doesn't end with }")
+			goto normal
+		}
+		if at != 0 {
+			parts = append(parts, text[:at])
+		}
+		to := pos + token.Pos(from+end)
+		parts = p.stringLitExpr(parts, pos+token.Pos(from), to)
+		pos = to + 1
+		text = left[end+1:]
+	case '$': // $$
+		parts = append(parts, text[:at+2])
+		pos += token.Pos(at + 2)
+		text = text[at+2:]
+	default:
+		if extra || hasExtra(text[at+1:]) {
+			p.error(pos+token.Pos(at), "invalid $ expression: neither `${ ... }` nor `$$`")
+		}
+		return nil
+	}
+	if text != "" {
+		extra = true
+		goto loop
+	}
+	return parts
+normal:
+	parts = append(parts, text)
+	return parts
+}
+
+func hasExtra(text string) bool {
+	for {
+		at := strings.IndexByte(text, '$')
+		if at < 0 || at+1 == len(text) { // no '$' or end with '$'
+			return false
+		}
+		ch := text[at+1]
+		if ch == '{' || ch == '$' {
+			return true
+		}
+		text = text[at+2:]
+	}
+}
+
+func (p *parser) stringLitExpr(parts []any, off, end token.Pos) []any {
+	file := p.file
+	base := file.Base()
+	src := p.scanner.CodeTo(int(end) - base)
+	expr, err := parseExprEx(p.file, src, int(off)-base, 0)
+	if err != nil {
+		p.errors = append(p.errors, err...)
+		expr = &ast.BadExpr{From: off, To: end}
+	}
+	parts = append(parts, expr)
+	return parts
+}
+
 // parseOperand may return an expression or a raw type (incl. array
 // types of the form [...]T. Callers must verify the result.
 // If lhs is set and the result is an identifier, it is not resolved.
@@ -1607,7 +1701,11 @@ func (p *parser) parseOperand(lhs, allowTuple, allowCmd bool) (x ast.Expr, isTup
 		return
 
 	case token.STRING, token.CSTRING, token.INT, token.FLOAT, token.IMAG, token.CHAR, token.RAT:
-		x = &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
+		bl := &ast.BasicLit{ValuePos: p.pos, Kind: p.tok, Value: p.lit}
+		if p.tok == token.STRING {
+			bl.Extra = p.stringLit(p.pos, p.lit)
+		}
+		x = bl
 		if debugParseOutput {
 			log.Printf("ast.BasicLit{Kind: %v, Value: %v}\n", p.tok, p.lit)
 		}
@@ -3486,9 +3584,65 @@ func isOverloadOps(tok token.Token) bool {
 	return int(tok) < len(overloadOps) && overloadOps[tok] != 0
 }
 
-func (p *parser) parseFuncDeclOrCall() (*ast.FuncDecl, *ast.CallExpr) {
+// `funcName`
+// `(*T).methodName`
+// `func(params) results {...}`
+func (p *parser) parseOverloadFunc() (ast.Expr, bool) {
+	switch p.tok {
+	case token.IDENT:
+		return p.parseIdent(), true
+	case token.FUNC:
+		return p.parseFuncTypeOrLit(), true
+	case token.LPAREN:
+		x, _ := p.parsePrimaryExpr(false, false, false)
+		return x, true
+	}
+	return nil, false
+}
+
+// `= (overloadFuncs)`
+//
+// here overloadFunc represents
+//
+// `funcName`
+// `(*T).methodName`
+// `func(params) results {...}`
+func (p *parser) parseOverloadDecl(decl *ast.OverloadFuncDecl) *ast.OverloadFuncDecl {
+	decl.Assign = p.expect(token.ASSIGN)
+	decl.Lparen = p.expect(token.LPAREN)
+	funcs := make([]ast.Expr, 0, 4)
+	for {
+		f, ok := p.parseOverloadFunc()
+		if !ok {
+			break
+		}
+		funcs = append(funcs, f)
+		if p.tok == token.SEMICOLON {
+			p.next()
+		}
+	}
+	decl.Funcs = funcs
+	decl.Rparen = p.expect(token.RPAREN)
+	p.expectSemi()
+	if debugParseOutput {
+		var recvt ast.Expr
+		if recv := decl.Recv; recv != nil {
+			recvt = recv.List[0].Type
+		}
+		log.Printf("ast.OverloadFuncDecl{Recv: %v, Name: %v, ...}\n", recvt, decl.Name)
+	}
+	return decl
+}
+
+// `func identOrOp(params) results {...}`
+// `func identOrOp = (overloadFuncs)`
+//
+// `func (recv) identOrOp(params) results { ... }`
+// `func (T).identOrOp = (overloadFuncs)`
+// `func (params) results { ... }()`
+func (p *parser) parseFuncDeclOrCall() (ast.Decl, *ast.CallExpr) {
 	if p.trace {
-		defer un(trace(p, "FunctionDecl"))
+		defer un(trace(p, "FunctionDeclOrCall"))
 	}
 
 	doc := p.leadComment
@@ -3499,16 +3653,39 @@ func (p *parser) parseFuncDeclOrCall() (*ast.FuncDecl, *ast.CallExpr) {
 	var ident *ast.Ident
 	var isOp, isFunLit, ok bool
 
-	if p.tok != token.LPAREN { // func identOrOp(...)
+	if p.tok != token.LPAREN {
+		// func: `func identOrOp(...) results`
+		// overload: `func identOrOp = (overloadFuncs)`
 		ident, isOp = p.parseIdentOrOp()
+		if p.tok == token.ASSIGN {
+			// func identOrOp = (overloadFuncs)
+			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
+				Doc:      doc,
+				Func:     pos,
+				Name:     ident,
+				Operator: isOp,
+			}), nil
+		}
 		params, results = p.parseSignature(scope)
 	} else {
-		// method: func (recv) XXX(params) results { ... }
-		// funlit: func (params) results { ... }()
+		// method: `func (recv) identOrOp(params) results { ... }`
+		// overload: `func (T).identOrOp = (overloadFuncs)`
+		// funlit: `func (params) results { ... }()`
 		params = p.parseParameters(scope, true)
 		if p.tok == token.LPAREN {
 			// func (params) (results) { ... }()
 			isFunLit, results = true, p.parseParameters(scope, false)
+		} else if p.tok == token.PERIOD {
+			p.next()
+			// func (T).identOrOp = (overloadFuncs)
+			ident, isOp = p.parseIdentOrOp()
+			return p.parseOverloadDecl(&ast.OverloadFuncDecl{
+				Doc:      doc,
+				Func:     pos,
+				Recv:     params,
+				Name:     ident,
+				Operator: isOp,
+			}), nil
 		} else if isOp = isOverloadOps(p.tok); isOp {
 			oldtok, oldpos := p.tok, p.pos
 			p.next()
diff --git a/parser/parser_gop.go b/parser/parser_gop.go
index 77cf548c..34f549f2 100644
--- a/parser/parser_gop.go
+++ b/parser/parser_gop.go
@@ -161,7 +161,7 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk
 			filename := fs.Join(dir, fname)
 			if useGoParser {
 				if filedata, err := fs.ReadFile(filename); err == nil {
-					if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode)); err == nil {
+					if src, err := goparser.ParseFile(fset, filename, filedata, goparser.Mode(conf.Mode&goReservedFlags)); err == nil {
 						pkg := reqPkg(pkgs, src.Name.Name)
 						if pkg.GoFiles == nil {
 							pkg.GoFiles = make(map[string]*goast.File)
@@ -173,13 +173,19 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk
 				} else {
 					first = err
 				}
-			} else if f, err := ParseFSFile(fset, fs, filename, nil, mode); err == nil {
-				f.IsProj, f.IsClass = isProj, isClass
-				f.IsNormalGox = isNormalGox
-				pkg := reqPkg(pkgs, f.Name.Name)
-				pkg.Files[filename] = f
-			} else if first == nil {
-				first = err
+			} else {
+				f, err := ParseFSFile(fset, fs, filename, nil, mode)
+				if f != nil {
+					f.IsProj, f.IsClass = isProj, isClass
+					f.IsNormalGox = isNormalGox
+					if f.Name != nil {
+						pkg := reqPkg(pkgs, f.Name.Name)
+						pkg.Files[filename] = f
+					}
+				}
+				if err != nil && first == nil {
+					first = err
+				}
 			}
 		}
 	}
@@ -190,32 +196,23 @@ func ParseFSDir(fset *token.FileSet, fs FileSystem, dir string, conf Config) (pk
 // Compared to ParseFSFile, ParseFSEntry detects fileKind by its filename.
 func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src interface{}, conf Config) (f *ast.File, err error) {
 	fname := fs.Base(filename)
-	fnameRmGox := fname
 	ext := path.Ext(fname)
-	var isProj, isClass, isNormalGox, rmGox bool
+	var isProj, isClass, isNormalGox bool
 	switch ext {
 	case ".gop", ".go":
 	case ".gox":
-		isClass = true
-		t := fname[:len(fname)-4]
-		if c := path.Ext(t); c != "" {
-			fnameRmGox, rmGox = t, true
-		} else {
-			isNormalGox = true
-		}
+		isNormalGox = true
 		fallthrough
 	default:
-		if !isNormalGox {
-			if conf.ClassKind == nil {
-				conf.ClassKind = defaultClassKind
-			}
-			if isProj, isClass = conf.ClassKind(fnameRmGox); !isClass {
-				if !rmGox {
-					return nil, ErrUnknownFileKind
-				}
-				// not found Go+ class by ext, but is a .gox file
-				isClass, isNormalGox = true, true
-			}
+		if conf.ClassKind == nil {
+			conf.ClassKind = defaultClassKind
+		}
+		if isProj, isClass = conf.ClassKind(fname); isClass {
+			isNormalGox = false
+		} else if isNormalGox { // not found Go+ class by ext, but is a .gox file
+			isClass = true
+		} else {
+			return nil, ErrUnknownFileKind
 		}
 	}
 	mode := conf.Mode
@@ -223,7 +220,7 @@ func ParseFSEntry(fset *token.FileSet, fs FileSystem, filename string, src inter
 		mode |= ParseGoPlusClass
 	}
 	f, err = ParseFSFile(fset, fs, filename, src, mode)
-	if err == nil {
+	if f != nil {
 		f.IsProj, f.IsClass = isProj, isClass
 		f.IsNormalGox = isNormalGox
 	}
diff --git a/printer/nodes.go b/printer/nodes.go
index 2a9e2fbb..4bbf900a 100644
--- a/printer/nodes.go
+++ b/printer/nodes.go
@@ -1705,7 +1705,9 @@ func (p *printer) spec(spec ast.Spec, n int, doIndent bool) {
 		p.setComment(s.Doc)
 		p.identList(s.Names, doIndent) // always present
 		if s.Type != nil {
-			p.print(blank)
+			if len(s.Names) > 0 {
+				p.print(blank)
+			}
 			p.expr(s.Type)
 		}
 		if s.Values != nil {
@@ -1980,7 +1982,7 @@ func (p *printer) funcDecl(d *ast.FuncDecl) {
 	// different line (all whitespace preceding the FUNC is emitted only when the
 	// FUNC is emitted).
 	startCol := p.out.Column - len("func ")
-	if d.Recv != nil {
+	if d.Recv != nil && !d.IsClass {
 		p.parameters(d.Recv) // method: print receiver
 		p.print(blank)
 	}
@@ -1992,6 +1994,28 @@ func (p *printer) funcDecl(d *ast.FuncDecl) {
 	p.funcBody(p.distanceFrom(d.Pos(), startCol), vtab, d.Body)
 }
 
+func (p *printer) overloadFuncDecl(d *ast.OverloadFuncDecl) {
+	if debugFormat {
+		log.Println("==> Format OverloadFunc", d.Name.Name)
+	}
+	p.setComment(d.Doc)
+
+	pos := d.Pos()
+	p.print(pos, token.FUNC, blank)
+	if d.Recv != nil && !d.IsClass {
+		p.parameters(d.Recv) // method: print receiver
+		p.print(token.PERIOD)
+	}
+	p.expr(d.Name)
+	p.print(blank, token.ASSIGN, blank, token.LPAREN, newline)
+	for _, fn := range d.Funcs {
+		p.print(indent)
+		p.expr1(fn, token.LowestPrec, 1)
+		p.print(unindent, newline)
+	}
+	p.print(token.RPAREN)
+}
+
 func (p *printer) decl(decl ast.Decl) {
 	switch d := decl.(type) {
 	case *ast.BadDecl:
@@ -2000,6 +2024,8 @@ func (p *printer) decl(decl ast.Decl) {
 		p.genDecl(d)
 	case *ast.FuncDecl:
 		p.funcDecl(d)
+	case *ast.OverloadFuncDecl:
+		p.overloadFuncDecl(d)
 	default:
 		panic("unreachable")
 	}
diff --git a/scanner/scanner.go b/scanner/scanner.go
index 5143470b..08189102 100644
--- a/scanner/scanner.go
+++ b/scanner/scanner.go
@@ -17,7 +17,6 @@
 // Package scanner implements a scanner for Go+ source text.
 // It takes a []byte as source which can then be tokenized
 // through repeated calls to the Scan method.
-//
 package scanner
 
 import (
@@ -36,13 +35,11 @@ import (
 // encountered and a handler was installed, the handler is called with a
 // position and an error message. The position points to the beginning of
 // the offending token.
-//
 type ErrorHandler = scanner.ErrorHandler
 
 // A Scanner holds the scanner's internal state while processing
 // a given text. It can be allocated as part of another data
 // structure but must be initialized via Init before use.
-//
 type Scanner struct {
 	// immutable state
 	file *token.File  // source file handle
@@ -66,7 +63,6 @@ const bom = 0xFEFF // byte order mark, only permitted as very first character
 
 // Read the next Unicode char into s.ch.
 // s.ch < 0 means end-of-file.
-//
 func (s *Scanner) next() {
 	if s.rdOffset < len(s.src) {
 		s.offset = s.rdOffset
@@ -110,7 +106,6 @@ func (s *Scanner) peek() byte {
 
 // A Mode value is a set of flags (or 0).
 // They control scanner behavior.
-//
 type Mode uint
 
 const (
@@ -133,12 +128,16 @@ const (
 //
 // Note that Init may call err if there is an error in the first character
 // of the file.
-//
 func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode) {
 	// Explicitly initialize all fields since a scanner may be reused.
 	if file.Size() != len(src) {
 		panic(fmt.Sprintf("file size (%d) does not match src len (%d)", file.Size(), len(src)))
 	}
+	s.InitEx(file, src, 0, err, mode)
+}
+
+// InitEx init the scanner with an offset (this means src[offset:] is all the code to scan).
+func (s *Scanner) InitEx(file *token.File, src []byte, offset int, err ErrorHandler, mode Mode) {
 	s.file = file
 	s.dir, _ = filepath.Split(file.Name())
 	s.src = src
@@ -147,7 +146,7 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode
 
 	s.ch = ' '
 	s.offset = 0
-	s.rdOffset = 0
+	s.rdOffset = offset
 	s.lineOffset = 0
 	s.insertSemi = false
 	s.ErrorCount = 0
@@ -158,6 +157,10 @@ func (s *Scanner) Init(file *token.File, src []byte, err ErrorHandler, mode Mode
 	}
 }
 
+func (s *Scanner) CodeTo(end int) []byte {
+	return s.src[:end]
+}
+
 func (s *Scanner) error(offs int, msg string) {
 	if s.err != nil {
 		s.err(s.file.Position(s.file.Pos(offs)), msg)
@@ -810,7 +813,6 @@ func (s *Scanner) switch4(tok0, tok1 token.Token, ch2 rune, tok2, tok3 token.Tok
 // Scan adds line information to the file added to the file
 // set with Init. Token positions are relative to that file
 // and thus relative to the file set.
-//
 func (s *Scanner) Scan() (pos token.Pos, tok token.Token, lit string) {
 scanAgain:
 	s.skipWhitespace()
diff --git a/x/build/build.go b/x/build/build.go
index 0b99626e..10fae033 100644
--- a/x/build/build.go
+++ b/x/build/build.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	goast "go/ast"
 	"go/types"
-	"path"
 	"path/filepath"
 
 	"github.com/goplus/gop/ast"
@@ -31,6 +30,7 @@ import (
 	"github.com/goplus/gop/token"
 	"github.com/goplus/gox"
 	"github.com/goplus/gox/packages"
+	"github.com/goplus/mod/modfile"
 )
 
 type Class = cl.Class
@@ -76,7 +76,7 @@ func (p *Package) ToAst() *goast.File {
 }
 
 func ClassKind(fname string) (isProj, ok bool) {
-	ext := path.Ext(fname)
+	ext := modfile.ClassExt(fname)
 	switch ext {
 	case ".gmx":
 		return true, true
diff --git a/x/c2go/c2go.go b/x/c2go/c2go.go
index d2240f11..33116cc8 100644
--- a/x/c2go/c2go.go
+++ b/x/c2go/c2go.go
@@ -18,7 +18,6 @@ package c2go
 
 import (
 	"path/filepath"
-	"syscall"
 
 	"github.com/goplus/mod/gopmod"
 )
@@ -28,9 +27,6 @@ import (
 // LookupPub returns a anonymous function required by cl.NewPackage.
 func LookupPub(mod *gopmod.Module) func(pkgPath string) (pubfile string, err error) {
 	return func(pkgPath string) (pubfile string, err error) {
-		if mod.File == nil { // no go.mod/gop.mod file
-			return "", syscall.ENOENT
-		}
 		pkg, err := mod.Lookup(pkgPath)
 		if err == nil {
 			pubfile = filepath.Join(pkg.Dir, "c2go.a.pub")
diff --git a/x/typesutil/check.go b/x/typesutil/check.go
index 88673790..f6803f47 100644
--- a/x/typesutil/check.go
+++ b/x/typesutil/check.go
@@ -69,6 +69,9 @@ 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
@@ -77,7 +80,7 @@ type Config struct {
 	// Default is github.com/goplus/.
 	C2goBase string
 
-	// Mod represents a gop.mod object (optional).
+	// Mod represents a Go+ module (optional).
 	Mod *gopmod.Module
 }
 
@@ -111,6 +114,9 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	gopfs := make(map[string]*ast.File)
 	for _, goFile := range goFiles {
 		f := fset.File(goFile.Pos())
+		if f == nil {
+			continue
+		}
 		file := f.Name()
 		fname := filepath.Base(file)
 		if strings.HasPrefix(fname, "gop_autogen") {
@@ -121,6 +127,9 @@ func (p *Checker) Files(goFiles []*goast.File, gopFiles []*ast.File) (err error)
 	}
 	for _, gopFile := range gopFiles {
 		f := fset.File(gopFile.Pos())
+		if f == nil {
+			continue
+		}
 		gopfs[f.Name()] = gopFile
 	}
 	if debugVerbose {
@@ -138,9 +147,11 @@ 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),
 		Recorder:       gopRecorder{p.gopInfo},
 		NoFileLine:     true,


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 759150d7..3d03c7ad 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,8 +39,154 @@ func getGoxConf() *gox.Config {
 	return &gox.Config{Fset: fset, Importer: imp}
 }
 
+func TestErrStringLit(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestErrStringLit: no panic?")
+		}
+	}()
+	compileStringLitEx(nil, nil, &ast.BasicLit{
+		Value: "Hello",
+		Extra: &ast.StringLitEx{
+			Parts: []any{1},
+		},
+	})
+}
+
+func TestErrPreloadFile(t *testing.T) {
+	pkg := gox.NewPackage("", "foo", goxConf)
+	ctx := &blockCtx{pkgCtx: &pkgCtx{}}
+	t.Run("overloadName", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - can't overload operator ++\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		overloadName(&ast.Ident{}, "++", true)
+	})
+	t.Run("checkOverloadFunc", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadFunc\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadFunc(&ast.OverloadFuncDecl{
+			Recv: &ast.FieldList{},
+		})
+	})
+	t.Run("checkOverloadMethod", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: checkOverloadMethod\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadMethod(&ast.OverloadFuncDecl{})
+	})
+	t.Run("checkOverloadMethodRecvType1", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: bar\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, &ast.Ident{Name: "bar"})
+	})
+	t.Run("checkOverloadMethodRecvType2", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - checkOverloadMethodRecvType: &{0 INT 123 <nil>}\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		expr := &ast.BasicLit{Kind: token.INT, Value: "123"}
+		checkOverloadMethodRecvType(&ast.Ident{Name: "foo"}, expr)
+	})
+	t.Run("OverloadFuncDecl: invalid recv", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: invalid recv\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		decls := []ast.Decl{
+			&ast.OverloadFuncDecl{
+				Name: &ast.Ident{Name: "add"},
+				Funcs: []ast.Expr{
+					&ast.FuncLit{},
+				},
+				Recv: &ast.FieldList{List: []*ast.Field{
+					{Type: &ast.StarExpr{}},
+				}},
+			},
+		}
+		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
+	})
+	t.Run("OverloadFuncDecl: unknown func", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile OverloadFuncDecl: unknown func - *ast.BasicLit\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		decls := []ast.Decl{
+			&ast.OverloadFuncDecl{
+				Name: &ast.Ident{Name: "add"},
+				Funcs: []ast.Expr{
+					&ast.BasicLit{},
+				},
+				Operator: true,
+			},
+		}
+		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
+	})
+	t.Run("unknown decl", func(t *testing.T) {
+		defer func() {
+			if e := recover(); e == nil || e != "TODO - cl.preloadFile: unknown decl - *ast.BadDecl\n" {
+				t.Fatal("TestErrPreloadFile:", e)
+			}
+		}()
+		decls := []ast.Decl{
+			&ast.BadDecl{},
+		}
+		preloadFile(pkg, ctx, "foo.gop", &ast.File{Decls: decls}, "", true)
+	})
+}
+
+func TestErrParseTypeEmbedName(t *testing.T) {
+	defer func() {
+		if e := recover(); e == nil {
+			t.Fatal("TestErrParseTypeEmbedName: no panic?")
+		}
+	}()
+	parseTypeEmbedName(&ast.StructType{})
+}
+
+func TestGmxMainFunc(t *testing.T) {
+	gmxMainFunc(nil, &pkgCtx{
+		projs: map[string]*gmxProject{
+			".a": {}, ".b": {},
+		},
+	}, false)
+}
+
+func TestNodeInterp(t *testing.T) {
+	ni := &nodeInterp{}
+	if v := ni.Caller(&ast.Ident{}); v != "the function call" {
+		t.Fatal("TestNodeInterp:", v)
+	}
+}
+
+func TestMarkAutogen(t *testing.T) {
+	old := noMarkAutogen
+	noMarkAutogen = false
+
+	NewPackage("", &ast.Package{Files: map[string]*ast.File{
+		"main.t2gmx": {IsProj: true},
+	}}, &Config{
+		LookupClass: lookupClassErr,
+	})
+
+	noMarkAutogen = old
+}
+
 func TestClassNameAndExt(t *testing.T) {
-	name, ext := classNameAndExt("/foo/bar.abc_yap.gox")
+	name, ext := ClassNameAndExt("/foo/bar.abc_yap.gox")
 	if name != "bar" || ext != "_yap.gox" {
 		t.Fatal("classNameAndExt:", name, ext)
 	}
@@ -172,7 +318,7 @@ func TestClRangeStmt(t *testing.T) {
 // -----------------------------------------------------------------------------
 
 func TestGetStringConst(t *testing.T) {
-	spx := &gox.PkgRef{Types: types.NewPackage("", "foo")}
+	spx := gox.PkgRef{Types: types.NewPackage("", "foo")}
 	if v := getStringConst(spx, "unknown"); v != "" {
 		t.Fatal("getStringConst:", v)
 	}
@@ -184,7 +330,7 @@ func TestSpxRef(t *testing.T) {
 			t.Fatal("TestSpxRef:", e)
 		}
 	}()
-	pkg := &gox.PkgRef{
+	pkg := gox.PkgRef{
 		Types: types.NewPackage("foo", "foo"),
 	}
 	spxRef(pkg, "bar")
@@ -202,20 +348,25 @@ func isError(e interface{}, msg string) bool {
 	return false
 }
 
-func TestGmxSettings(t *testing.T) {
+func TestGmxProject(t *testing.T) {
 	pkg := gox.NewPackage("", "foo", goxConf)
-	gmx := newGmx(nil, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
+	ctx := &pkgCtx{
+		projs:   make(map[string]*gmxProject),
+		classes: make(map[*ast.File]gmxClass),
+	}
+	gmx := loadClass(ctx, 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")
+		t.Fatal("TestGmxProject failed")
 	}
 	gmx.hasScheds = false
 	if gmx.getScheds(nil) != nil {
-		t.Fatal("TestGmxSettings failed: hasScheds?")
+		t.Fatal("TestGmxProject failed: hasScheds?")
 	}
-	_, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{
+
+	/* _, err := NewPackage("", &ast.Package{Files: map[string]*ast.File{
 		"main.t2gmx": {
 			IsProj: true,
 		},
@@ -224,7 +375,28 @@ func TestGmxSettings(t *testing.T) {
 	})
 	if e := err.Error(); e != `github.com/goplus/gop/cl/internal/libc.Game not found` {
 		t.Fatal("newGmx:", e)
-	}
+	} */
+
+	func() {
+		defer func() {
+			if e := recover(); e != "TODO: class not found" {
+				t.Fatal("TestGmxProject failed:", e)
+			}
+		}()
+		loadClass(nil, pkg, "main.abcx", &ast.File{IsProj: true}, &Config{
+			LookupClass: lookupClass,
+		})
+	}()
+	func() {
+		defer func() {
+			if e := recover(); e != "multiple project files found: Game Game\n" {
+				t.Fatal("TestGmxProject failed:", e)
+			}
+		}()
+		loadClass(ctx, pkg, "main.t2gmx", &ast.File{IsProj: true}, &Config{
+			LookupClass: lookupClass,
+		})
+	}()
 }
 
 func TestSpxLookup(t *testing.T) {
@@ -259,10 +431,13 @@ func lookupClassErr(ext string) (c *modfile.Project, ok bool) {
 }
 
 func TestGetGoFile(t *testing.T) {
-	if f := genGoFile("a_test.gop", true); f != testingGoFile {
+	if f := genGoFile("a_test.gop", false); f != testingGoFile {
+		t.Fatal("TestGetGoFile:", f)
+	}
+	if f := genGoFile("a_test.gox", true); f != testingGoFile {
 		t.Fatal("TestGetGoFile:", f)
 	}
-	if f := genGoFile("a_test.gop", false); f != skippingGoFile {
+	if f := genGoFile("a.gop", false); f != defaultGoFile {
 		t.Fatal("TestGetGoFile:", f)
 	}
 }
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
new file mode 100644
index 00000000..e3ff38b4
--- /dev/null
+++ b/cl/compile_gop_test.go
@@ -0,0 +1,462 @@
+/*
+ * 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 cl_test
+
+import (
+	"testing"
+)
+
+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"
+
+type foo struct {
+}
+
+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_NE(b foo) bool {
+	fmt.Println("a!=b")
+	return true
+}
+func (a foo) Gop_Neg() {
+	fmt.Println("-a")
+}
+func (a foo) Gop_Inc() {
+	fmt.Println("a++")
+}
+
+var a, b foo
+var c = (foo).Gop_Sub(a, b)
+var d = a.Gop_Neg()
+var e = (foo).Gop_NE(a, b)
+`)
+}
+
+func TestOverloadOp2(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (a foo) mulInt(b int) (ret foo) {
+	return
+}
+
+func (a foo) mulFoo(b foo) (ret foo) {
+	return
+}
+
+func intMulFoo(a int, b foo) (ret foo) {
+	return
+}
+
+func (foo).* = (
+	(foo).mulInt
+	(foo).mulFoo
+	intMulFoo
+)
+
+var a, b foo
+
+println a * 10
+println a * b
+println 10 * a
+`, `package main
+
+import "fmt"
+
+type foo struct {
+}
+
+const Gopo__foo__Gop_Mul = ".mulInt,.mulFoo,intMulFoo"
+
+func (a foo) mulInt(b int) (ret foo) {
+	return
+}
+func (a foo) mulFoo(b foo) (ret foo) {
+	return
+}
+func intMulFoo(a int, b foo) (ret foo) {
+	return
+}
+
+var a, b foo
+
+func main() {
+	fmt.Println((foo).mulInt(a, 10))
+	fmt.Println((foo).mulFoo(a, b))
+	fmt.Println(intMulFoo(10, a))
+}
+`)
+}
+
+func TestOverloadMethod(t *testing.T) {
+	gopClTest(t, `
+type foo struct {
+}
+
+func (a *foo) mulInt(b int) *foo {
+	println "mulInt"
+	return a
+}
+
+func (a *foo) mulFoo(b *foo) *foo {
+	println "mulFoo"
+	return a
+}
+
+func (foo).mul = (
+	(foo).mulInt
+	(foo).mulFoo
+)
+
+var a, b foo
+var c = a.mul(100)
+var d = a.mul(c)
+`, `package main
+
+import "fmt"
+
+type foo struct {
+}
+
+const Gopo_foo_mul = ".mulInt,.mulFoo"
+
+func (a *foo) mulInt(b int) *foo {
+	fmt.Println("mulInt")
+	return a
+}
+func (a *foo) mulFoo(b *foo) *foo {
+	fmt.Println("mulFoo")
+	return a
+}
+
+var a, b foo
+var c = a.mulInt(100)
+var d = a.mulFoo(c)
+`)
+}
+
+func TestOverloadFunc(t *testing.T) {
+	gopClTest(t, `
+func add = (
+	func(a, b int) int {
+		return a + b
+	}
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+println add(100, 7)
+println add("Hello", "World")
+`, `package main
+
+import "fmt"
+
+func add__0(a int, b int) int {
+	return a + b
+}
+func add__1(a string, b string) string {
+	return a + b
+}
+func main() {
+	fmt.Println(add__0(100, 7))
+	fmt.Println(add__1("Hello", "World"))
+}
+`)
+}
+
+func TestOverloadFunc2(t *testing.T) {
+	gopClTest(t, `
+func mulInt(a, b int) int {
+	return a * b
+}
+
+func mulFloat(a, b float64) float64 {
+	return a * b
+}
+
+func mul = (
+	mulInt
+	mulFloat
+)
+
+println mul(100, 7)
+println mul(1.2, 3.14)
+`, `package main
+
+import "fmt"
+
+const Gopo_mul = "mulInt,mulFloat"
+
+func mulInt(a int, b int) int {
+	return a * b
+}
+func mulFloat(a float64, b float64) float64 {
+	return a * b
+}
+func main() {
+	fmt.Println(mulInt(100, 7))
+	fmt.Println(mulFloat(1.2, 3.14))
+}
+`)
+}
+
+func TestStringLitBasic(t *testing.T) {
+	gopClTest(t, `echo "$$"`, `package main
+
+import "fmt"
+
+func main() {
+	fmt.Println("$")
+}
+`)
+}
+
+func TestStringLitVar(t *testing.T) {
+	gopClTest(t, `
+x := 1
+println "Hi, " + "a${x}b"`, `package main
+
+import (
+	"fmt"
+	"strconv"
+)
+
+func main() {
+	x := 1
+	fmt.Println("Hi, " + ("a" + strconv.Itoa(x) + "b"))
+}
+`)
+}
+
+func TestFileOpen(t *testing.T) {
+	gopClTest(t, `
+for line <- open("foo.txt")! {
+	println line
+}
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/builtin/iox"
+	"github.com/qiniu/x/errors"
+	"os"
+)
+
+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"
+	"github.com/goplus/gop/builtin/iox"
+	"os"
+)
+
+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"
+	"github.com/goplus/gop/builtin/iox"
+	"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"
+
+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"
+
+func main() {
+	for i := 0; i < 10; i += 1 {
+		if i%3 == 0 {
+			fmt.Println(i)
+		}
+	}
+}
+`)
+}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 314fdf2e..03f289e4 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -19,7 +19,6 @@ package cl_test
 import (
 	"bytes"
 	"os"
-	"path"
 	"testing"
 
 	"github.com/goplus/gop/cl"
@@ -47,6 +46,16 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 			Works: []*modfile.Class{{Ext: "_t3spx.gox", Class: "Sprite"},
 				{Ext: ".t3spx2", Class: "Sprite2"}},
 			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx2"}}, true
+	case "_spx.gox":
+		return &modfile.Project{
+			Ext: "_spx.gox", Class: "Game",
+			Works:    []*modfile.Class{{Ext: "_spx.gox", Class: "Sprite"}},
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"}}, true
+	case "_xtest.gox":
+		return &modfile.Project{
+			Ext: "_xtest.gox", Class: "App",
+			Works:    []*modfile.Class{{Ext: "_xtest.gox", Class: "Case"}},
+			PkgPaths: []string{"github.com/goplus/gop/test", "testing"}}, true
 	}
 	return
 }
@@ -54,10 +63,10 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 func spxParserConf() parser.Config {
 	return parser.Config{
 		ClassKind: func(fname string) (isProj bool, ok bool) {
-			ext := path.Ext(fname)
+			ext := modfile.ClassExt(fname)
 			c, ok := lookupClass(ext)
 			if ok {
-				isProj = (c.Ext == ext)
+				isProj = c.IsProj(ext, fname)
 			}
 			return
 		},
@@ -69,10 +78,14 @@ func gopSpxTest(t *testing.T, gmx, spxcode, expected string) {
 }
 
 func gopSpxTestEx(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile string) {
-	gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile)
+	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) {
+func gopSpxTestEx2(t *testing.T, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
+	gopSpxTestExConf(t, "gopSpxTest", gblConf, gmx, spxcode, expected, gmxfile, spxfile, resultFile)
+}
+
+func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode, expected, gmxfile, spxfile, resultFile string) {
 	t.Run(name, func(t *testing.T) {
 		cl.SetDisableRecover(true)
 		defer cl.SetDisableRecover(false)
@@ -89,7 +102,7 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 			t.Fatal("NewPackage:", err)
 		}
 		var b bytes.Buffer
-		err = pkg.WriteTo(&b)
+		err = pkg.WriteTo(&b, resultFile)
 		if err != nil {
 			t.Fatal("gox.WriteTo failed:", err)
 		}
@@ -369,6 +382,43 @@ func (this *Kai) Main() {
 `, "index.tgmx", "Kai.tspx")
 }
 
+func TestSpxRunWithWorkers(t *testing.T) {
+	gopSpxTestEx(t, `
+var (
+	Kai Kai
+)
+
+run
+`, `
+echo "Hi"
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx3"
+)
+
+type Kai struct {
+	spx3.Sprite
+	*Game
+}
+type Game struct {
+	spx3.Game
+	Kai Kai
+}
+
+func (this *Game) MainEntry() {
+	this.Run()
+}
+func main() {
+	spx3.Gopt_Game_Main(new(Game), new(Kai))
+}
+func (this *Kai) Main() {
+	fmt.Println("Hi")
+}
+`, "main_spx.gox", "Kai_spx.gox")
+}
+
 func TestSpx2(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi")
@@ -401,7 +451,9 @@ type Kai struct {
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx")
+}
 
+func TestSpx3(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi, Sprite2")
 `, `
@@ -433,7 +485,9 @@ type Kai struct {
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx2")
+}
 
+func TestSpx4(t *testing.T) {
 	gopSpxTestEx(t, `
 println("Hi, Sprite")
 `, `
@@ -483,7 +537,7 @@ func (this *Game) MainEntry() {
 func main() {
 	new(Game).Main()
 }
-`, "Game.t2gmx", "Kai.t2spx")
+`, "Game.t2gmx", "Kai.t2spx", "")
 	gopSpxTestExConf(t, "OnlyGmx", &conf, `
 var (
 	Kai Kai
@@ -507,7 +561,7 @@ func (this *Game) MainEntry() {
 func main() {
 	new(Game).Main()
 }
-`, "Game.t2gmx", "Kai.t2spx")
+`, "Game.t2gmx", "Kai.t2spx", "")
 
 	gopSpxTestExConf(t, "KaiAndGmx", &conf, `
 var (
@@ -549,7 +603,7 @@ func (this *Kai) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-`, "Game.t2gmx", "Kai.t2spx")
+`, "Game.t2gmx", "Kai.t2spx", "")
 }
 
 func TestSpxGoxBasic(t *testing.T) {
@@ -892,3 +946,72 @@ func (this *Kai) Main() {
 }
 `, "Game.tgmx", "Kai.tspx")
 }
+
+func TestTestClassFile(t *testing.T) {
+	gopSpxTestEx2(t, `
+println "Hi"
+`, `
+t.log "Hi"
+t.run "a test", t => {
+	t.fatal "failed"
+}
+`, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/test"
+	"testing"
+)
+
+type App struct {
+	test.App
+}
+
+func (this *App) MainEntry() {
+	fmt.Println("Hi")
+}
+
+type caseFoo struct {
+	test.Case
+	*App
+}
+
+func (this *caseFoo) Main() {
+	this.T().Log("Hi")
+	this.T().Run("a test", func(t *testing.T) {
+		t.Fatal("failed")
+	})
+}
+func TestFoo(t *testing.T) {
+	test.Gopt_Case_TestMain(new(caseFoo), t)
+}
+func TestMain(m *testing.M) {
+	test.Gopt_App_TestMain(new(App), m)
+}
+`, "main_xtest.gox", "Foo_xtest.gox", "_test")
+}
+
+func TestTestClassFile2(t *testing.T) {
+	gopSpxTestEx2(t, `
+println "Hi"
+`, `
+t.log "Hi"
+`, `package main
+
+import (
+	"github.com/goplus/gop/test"
+	"testing"
+)
+
+type case_foo struct {
+	test.Case
+}
+
+func (this *case_foo) Main() {
+	this.T().Log("Hi")
+}
+func Test_foo(t *testing.T) {
+	test.Gopt_Case_TestMain(new(case_foo), t)
+}
+`, "main.gox", "foo_xtest.gox", "_test")
+}
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 8220c126..8ddf572a 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -45,7 +45,7 @@ var (
 
 func init() {
 	gox.SetDebug(gox.DbgFlagAll)
-	cl.SetDebug(cl.DbgFlagAll)
+	cl.SetDebug(cl.DbgFlagAll | cl.FlagNoMarkAutogen)
 	gblFset = token.NewFileSet()
 	imp := gop.NewImporter(nil, &env.Gop{Root: gopRootDir, Version: "1.0"}, gblFset)
 	gblConf = &cl.Config{
@@ -156,184 +156,6 @@ ls
 `)
 }
 
-func TestFileOpen(t *testing.T) {
-	gopClTest(t, `
-for line <- open("foo.txt")! {
-	println line
-}
-`, `package main
-
-import (
-	"fmt"
-	"os"
-	"github.com/goplus/gop/builtin/iox"
-	"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"
-	"os"
-	"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"
-	"github.com/goplus/gop/builtin/iox"
-	"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"
-
-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"
-
-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
@@ -1474,9 +1296,9 @@ func foo(script string) {
 
 import (
 	"fmt"
+	"github.com/goplus/gop/ast/gopq"
 	"github.com/goplus/gop/ast/goptest"
 	"github.com/qiniu/x/errors"
-	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1508,9 +1330,9 @@ func foo(script string) {
 
 import (
 	"fmt"
+	"github.com/goplus/gop/ast/gopq"
 	"github.com/goplus/gop/ast/goptest"
 	"github.com/qiniu/x/errors"
-	"github.com/goplus/gop/ast/gopq"
 )
 
 func foo(script string) {
@@ -1539,8 +1361,8 @@ func add(x, y string) (int, error) {
 `, `package main
 
 import (
-	"strconv"
 	"github.com/qiniu/x/errors"
+	"strconv"
 )
 
 func add(x string, y string) (int, error) {
@@ -1713,7 +1535,7 @@ var z uint128 = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Uint128
-var z ng.Uint128 = x.Gop_Add__1(y)
+var z ng.Uint128 = (ng.Uint128).Gop_Add__1(x, y)
 `)
 }
 
@@ -1726,7 +1548,7 @@ var z int128 = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Int128
-var z ng.Int128 = x.Gop_Add__1(y)
+var z ng.Int128 = (ng.Int128).Gop_Add__1(x, y)
 `)
 }
 
@@ -1739,7 +1561,7 @@ var z bigint = x + y
 import "github.com/goplus/gop/builtin/ng"
 
 var x, y ng.Bigint
-var z ng.Bigint = x.Gop_Add(y)
+var z ng.Bigint = (ng.Bigint).Gop_Add(x, y)
 `)
 }
 
@@ -1821,8 +1643,8 @@ import (
 )
 
 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
+var y = (ng.Bigrat).Gop_Add(x, ng.Bigrat_Init__0(100))
+var z = (ng.Bigrat).Gop_Add(ng.Bigrat_Init__0(100), y)
 `)
 }
 
@@ -2936,71 +2758,6 @@ func (M) 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"
-
-type foo struct {
-}
-
-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_NE(b foo) bool {
-	fmt.Println("a!=b")
-	return true
-}
-func (a foo) Gop_Neg() {
-	fmt.Println("-a")
-}
-func (a foo) Gop_Inc() {
-	fmt.Println("a++")
-}
-
-var a, b foo
-var c = a.Gop_Sub(b)
-var d = a.Gop_Neg()
-var e = a.Gop_NE(b)
-`)
-}
-
 func TestCmdlineNoEOL(t *testing.T) {
 	gopClTest(t, `println "Hi"`, `package main
 
@@ -4939,9 +4696,9 @@ var e = a!=b
 `, `package main
 
 var a, b foo
-var c = a.Gop_Sub(b)
+var c = (foo).Gop_Sub(a, b)
 var d = a.Gop_Neg()
-var e = a.Gop_NE(b)
+var e = (foo).Gop_NE(a, b)
 `)
 }
 
@@ -5000,11 +4757,11 @@ var b int
 var c float64
 
 func main() {
-	_ = a.Gop_Add__0(b)
-	_ = a.Gop_Add__0(100)
-	_ = a.Gop_Add__1(c)
-	_ = Vector3_Init__0(100) + a
-	_ = Vector3_Cast__0(b).Gop_Add__2(a)
+	_ = (Vector3).Gop_Add__0(a, b)
+	_ = (Vector3).Gop_Add__0(a, 100)
+	_ = (Vector3).Gop_Add__1(a, c)
+	_ = (Vector3).Gop_Add__2(Vector3_Init__0(100), a)
+	_ = (Vector3).Gop_Add__2(Vector3_Cast__0(b), a)
 	_ = b + a.Gop_Rcast__0()
 	a.Gop_AddAssign(Vector3_Init__0(b))
 	a.Gop_AddAssign(Vector3_Init__1(c))
@@ -5112,3 +4869,70 @@ func main() {
 }
 `)
 }
+
+func TestOverloadNamed(t *testing.T) {
+	gopClTest(t, `
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+var a bar.Var[int]
+var b bar.Var[bar.M]
+c := bar.Var(string)
+d := bar.Var(bar.M)
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+var a bar.Var__0[int]
+var b bar.Var__1[map[string]any]
+
+func main() {
+	c := bar.Gopx_Var_Cast__0[string]()
+	d := bar.Gopx_Var_Cast__1[map[string]any]()
+}
+`)
+}
+
+func TestMixedOverloadNamed(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+const GopPackage = true
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	val T
+}
+
+type Var__1[T map[string]any] struct {
+	val T
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
+`, `
+var a Var[int]
+var b Var[M]
+c := Var(string)
+d := Var(M)
+`, `package main
+
+var a Var__0[int]
+var b Var__1[map[string]interface {
+}]
+
+func main() {
+	c := Gopx_Var_Cast__0[string]()
+	d := Gopx_Var_Cast__1[map[string]interface {
+	}]()
+}
+`)
+}
diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go
index fe2bf1df..4a4e1a58 100644
--- a/cl/error_msg_test.go
+++ b/cl/error_msg_test.go
@@ -23,6 +23,7 @@ import (
 	"runtime"
 	"testing"
 
+	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/cl"
 	"github.com/goplus/gop/parser"
 	"github.com/goplus/gop/parser/fsx/memfs"
@@ -53,6 +54,24 @@ func codeErrorTestEx(t *testing.T, pkgname, filename, msg, src string) {
 	}
 }
 
+func codeErrorTestAst(t *testing.T, pkgname, filename, msg, src string) {
+	f, _ := parser.ParseFile(gblFset, filename, src, parser.AllErrors)
+	pkg := &ast.Package{
+		Name:  pkgname,
+		Files: map[string]*ast.File{filename: f},
+	}
+	conf := *gblConf
+	conf.NoFileLine = false
+	conf.RelativeBase = "/foo"
+	_, err := cl.NewPackage("", pkg, &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)", `
@@ -969,3 +988,10 @@ func TestErrCompileFunc(t *testing.T) {
 printf("%+v\n", int32)
 `)
 }
+
+func TestToTypeError(t *testing.T) {
+	codeErrorTestAst(t, "main", "bar.gop", `bar.gop:3:3: toType unexpected: *ast.BadExpr`, `
+type
+a := 1
+`)
+}
diff --git a/cl/typeparams_test.go b/cl/typeparams_test.go
index 014d36b3..c4b53b2d 100644
--- a/cl/typeparams_test.go
+++ b/cl/typeparams_test.go
@@ -1,6 +1,3 @@
-//go:build go1.18
-// +build go1.18
-
 package cl_test
 
 import (
diff --git a/cmd/make_test.go b/cmd/make_test.go
index e1ae186d..d9dcc5e0 100644
--- a/cmd/make_test.go
+++ b/cmd/make_test.go
@@ -307,7 +307,7 @@ func TestInstallInNonGitRepo(t *testing.T) {
 	})
 
 	t.Run("install with VERSION file", func(t *testing.T) {
-		version := "v1.1.98"
+		version := "v1.2.98"
 		// Create VERSION file
 		if err := os.WriteFile(versionFile, []byte(version), 0644); err != nil {
 			t.Fatal(err)
diff --git a/parser/_testdata/goxtest/bar.gox b/parser/_testdata/goxtest1/bar.gox
similarity index 100%
rename from parser/_testdata/goxtest/bar.gox
rename to parser/_testdata/goxtest1/bar.gox
diff --git a/parser/_testdata/goxtest/parser.expect b/parser/_testdata/goxtest1/parser.expect
similarity index 100%
rename from parser/_testdata/goxtest/parser.expect
rename to parser/_testdata/goxtest1/parser.expect
diff --git a/parser/_testdata/goxtest2/bar.gox b/parser/_testdata/goxtest2/bar.gox
new file mode 100644
index 00000000..954f1881
--- /dev/null
+++ b/parser/_testdata/goxtest2/bar.gox
@@ -0,0 +1,3 @@
+var (
+	x.App
+)
diff --git a/parser/_testdata/goxtest2/parser.expect b/parser/_testdata/goxtest2/parser.expect
new file mode 100644
index 00000000..43479424
--- /dev/null
+++ b/parser/_testdata/goxtest2/parser.expect
@@ -0,0 +1,15 @@
+package main
+
+file bar.gox
+ast.GenDecl:
+  Tok: var
+  Specs:
+    ast.ValueSpec:
+      Type:
+        ast.SelectorExpr:
+          X:
+            ast.Ident:
+              Name: x
+          Sel:
+            ast.Ident:
+              Name: App
diff --git a/parser/_testdata/overload1/overload.gop b/parser/_testdata/overload1/overload.gop
new file mode 100644
index 00000000..d5b458bf
--- /dev/null
+++ b/parser/_testdata/overload1/overload.gop
@@ -0,0 +1,13 @@
+func foo = (
+	func(a, b float64) float64 {
+		return a + b
+	}
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+func bar = (
+	addComplex
+	(T).add
+)
diff --git a/parser/_testdata/overload1/parser.expect b/parser/_testdata/overload1/parser.expect
new file mode 100644
index 00000000..aa686f6c
--- /dev/null
+++ b/parser/_testdata/overload1/parser.expect
@@ -0,0 +1,94 @@
+package main
+
+file overload.gop
+ast.OverloadFuncDecl:
+  Name:
+    ast.Ident:
+      Name: foo
+  Funcs:
+    ast.FuncLit:
+      Type:
+        ast.FuncType:
+          Params:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Names:
+                    ast.Ident:
+                      Name: a
+                    ast.Ident:
+                      Name: b
+                  Type:
+                    ast.Ident:
+                      Name: float64
+          Results:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Type:
+                    ast.Ident:
+                      Name: float64
+      Body:
+        ast.BlockStmt:
+          List:
+            ast.ReturnStmt:
+              Results:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: b
+    ast.FuncLit:
+      Type:
+        ast.FuncType:
+          Params:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Names:
+                    ast.Ident:
+                      Name: a
+                    ast.Ident:
+                      Name: b
+                  Type:
+                    ast.Ident:
+                      Name: string
+          Results:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Type:
+                    ast.Ident:
+                      Name: string
+      Body:
+        ast.BlockStmt:
+          List:
+            ast.ReturnStmt:
+              Results:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: b
+ast.OverloadFuncDecl:
+  Name:
+    ast.Ident:
+      Name: bar
+  Funcs:
+    ast.Ident:
+      Name: addComplex
+    ast.SelectorExpr:
+      X:
+        ast.ParenExpr:
+          X:
+            ast.Ident:
+              Name: T
+      Sel:
+        ast.Ident:
+          Name: add
diff --git a/parser/_testdata/overload2/overload2.gop b/parser/_testdata/overload2/overload2.gop
new file mode 100644
index 00000000..6d0eeeea
--- /dev/null
+++ b/parser/_testdata/overload2/overload2.gop
@@ -0,0 +1,11 @@
+func (T).* = (
+	mul1
+	mul2
+)
+
+func (T).add = (
+	add1
+	func(a, b T) T {
+		return a + b
+	}
+)
diff --git a/parser/_testdata/overload2/parser.expect b/parser/_testdata/overload2/parser.expect
new file mode 100644
index 00000000..0a6f83f9
--- /dev/null
+++ b/parser/_testdata/overload2/parser.expect
@@ -0,0 +1,68 @@
+package main
+
+file overload2.gop
+ast.OverloadFuncDecl:
+  Recv:
+    ast.FieldList:
+      List:
+        ast.Field:
+          Type:
+            ast.Ident:
+              Name: T
+  Name:
+    ast.Ident:
+      Name: *
+  Funcs:
+    ast.Ident:
+      Name: mul1
+    ast.Ident:
+      Name: mul2
+ast.OverloadFuncDecl:
+  Recv:
+    ast.FieldList:
+      List:
+        ast.Field:
+          Type:
+            ast.Ident:
+              Name: T
+  Name:
+    ast.Ident:
+      Name: add
+  Funcs:
+    ast.Ident:
+      Name: add1
+    ast.FuncLit:
+      Type:
+        ast.FuncType:
+          Params:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Names:
+                    ast.Ident:
+                      Name: a
+                    ast.Ident:
+                      Name: b
+                  Type:
+                    ast.Ident:
+                      Name: T
+          Results:
+            ast.FieldList:
+              List:
+                ast.Field:
+                  Type:
+                    ast.Ident:
+                      Name: T
+      Body:
+        ast.BlockStmt:
+          List:
+            ast.ReturnStmt:
+              Results:
+                ast.BinaryExpr:
+                  X:
+                    ast.Ident:
+                      Name: a
+                  Op: +
+                  Y:
+                    ast.Ident:
+                      Name: b
diff --git a/parser/_testdata/printvariadic/parser.expect b/parser/_testdata/printvariadic/parser.expect
new file mode 100644
index 00000000..61658c49
--- /dev/null
+++ b/parser/_testdata/printvariadic/parser.expect
@@ -0,0 +1,24 @@
+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
diff --git a/parser/_testdata/printvariadic/printv.gop b/parser/_testdata/printvariadic/printv.gop
new file mode 100644
index 00000000..72d734b3
--- /dev/null
+++ b/parser/_testdata/printvariadic/printv.gop
@@ -0,0 +1,1 @@
+println x...
diff --git a/parser/_testdata/stringex1/parser.expect b/parser/_testdata/stringex1/parser.expect
new file mode 100644
index 00000000..ef2ca684
--- /dev/null
+++ b/parser/_testdata/stringex1/parser.expect
@@ -0,0 +1,73 @@
+package main
+
+file string_lit.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.BasicLit:
+                  Kind: STRING
+                  Value: "$"
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "$$"
+                    Extra:
+                      $$
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a$$b$"
+                    Extra:
+                      a$$
+                      b$
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a$$b$$"
+                    Extra:
+                      a$$
+                      b$$
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a$b$%"
diff --git a/parser/_testdata/stringex1/string_lit.gop b/parser/_testdata/stringex1/string_lit.gop
new file mode 100644
index 00000000..b7d7154f
--- /dev/null
+++ b/parser/_testdata/stringex1/string_lit.gop
@@ -0,0 +1,5 @@
+println "$"
+println "$$"
+println "a$$b$"
+println "a$$b$$"
+println "a$b$%"
diff --git a/parser/_testdata/stringex2/parser.expect b/parser/_testdata/stringex2/parser.expect
new file mode 100644
index 00000000..f8758a24
--- /dev/null
+++ b/parser/_testdata/stringex2/parser.expect
@@ -0,0 +1,67 @@
+package main
+
+file string_lit.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.BasicLit:
+                  Kind: STRING
+                  Value: "${"
+                    Extra:
+                      ${
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "${ b }"
+                    Extra:
+                      ast.Ident:
+                        Name: b
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a${"
+                    Extra:
+                      a${
+        ast.ExprStmt:
+          X:
+            ast.CallExpr:
+              Fun:
+                ast.Ident:
+                  Name: println
+              Args:
+                ast.BasicLit:
+                  Kind: STRING
+                  Value: "a${b}c"
+                    Extra:
+                      a
+                      ast.Ident:
+                        Name: b
+                      c
diff --git a/parser/_testdata/stringex2/string_lit.gop b/parser/_testdata/stringex2/string_lit.gop
new file mode 100644
index 00000000..47881474
--- /dev/null
+++ b/parser/_testdata/stringex2/string_lit.gop
@@ -0,0 +1,4 @@
+println "${"
+println "${ b }"
+println "a${"
+println "a${b}c"
diff --git a/parser/_testdata/stringex3/parser.expect b/parser/_testdata/stringex3/parser.expect
new file mode 100644
index 00000000..320578eb
--- /dev/null
+++ b/parser/_testdata/stringex3/parser.expect
@@ -0,0 +1,38 @@
+package main
+
+file string_lit.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.BasicLit:
+                  Kind: STRING
+                  Value: "file:${args[0]}?${query}"
+                    Extra:
+                      file:
+                      ast.IndexExpr:
+                        X:
+                          ast.Ident:
+                            Name: args
+                        Index:
+                          ast.BasicLit:
+                            Kind: INT
+                            Value: 0
+                      ?
+                      ast.Ident:
+                        Name: query
diff --git a/parser/_testdata/stringex3/string_lit.gop b/parser/_testdata/stringex3/string_lit.gop
new file mode 100644
index 00000000..fdb21995
--- /dev/null
+++ b/parser/_testdata/stringex3/string_lit.gop
@@ -0,0 +1,1 @@
+println "file:${args[0]}?${query}"
diff --git a/parser/parser_test.go b/parser/parser_test.go
index 9a90a7de..b0dd4b7e 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -63,6 +63,7 @@ func testErrCode(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
+	t.Helper()
 	fset := token.NewFileSet()
 	_, err := Parse(fset, "/foo/bar.gop", code, 0)
 	if err == nil || err.Error() != errExp {
@@ -78,6 +79,7 @@ func testErrCodeParseExpr(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
+	t.Helper()
 	_, err := ParseExpr(code)
 	if err == nil || err.Error() != errExp {
 		t.Fatal("testErrCodeParseExpr error:", err)
@@ -92,6 +94,7 @@ func testClassErrCode(t *testing.T, code string, errExp, panicExp string) {
 			}
 		}
 	}()
+	t.Helper()
 	fset := token.NewFileSet()
 	_, err := Parse(fset, "/foo/bar.gox", code, ParseGoPlusClass)
 	if err == nil || err.Error() != errExp {
@@ -504,4 +507,23 @@ func TestErrCompositeLiteral(t *testing.T) {
 `, `/foo/bar.gop:1:10: cannot parenthesize type in composite literal`, ``)
 }
 
+func TestErrSelectorExpr(t *testing.T) {
+	testErrCode(t, `
+x.
+*p
+`, `/foo/bar.gop:3:1: expected selector or type assertion, found '*'`, ``)
+}
+
+func TestErrStringLitEx(t *testing.T) {
+	testErrCode(t, `
+println "${ ... }"
+`, "/foo/bar.gop:2:13: expected operand, found '...'", ``)
+	testErrCode(t, `
+println "${b"
+`, "/foo/bar.gop:2:11: invalid $ expression: ${ doesn't end with }", ``)
+	testErrCode(t, `
+println "$a${b}"
+`, "/foo/bar.gop:2:10: invalid $ expression: neither `${ ... }` nor `$$`", ``)
+}
+
 // -----------------------------------------------------------------------------
diff --git a/parser/parserdir_test.go b/parser/parserdir_test.go
index ff925701..d43bfcae 100644
--- a/parser/parserdir_test.go
+++ b/parser/parserdir_test.go
@@ -100,6 +100,7 @@ func testFrom(t *testing.T, pkgDir, sel string, exclude Mode) {
 	if sel != "" && !strings.Contains(pkgDir, sel) {
 		return
 	}
+	t.Helper()
 	log.Println("Parsing", pkgDir)
 	fset := token.NewFileSet()
 	pkgs, err := ParseDir(fset, pkgDir, nil, (Trace|ParseComments|ParseGoAsGoPlus)&^exclude)
@@ -194,6 +195,30 @@ func TestParseEntry(t *testing.T) {
 	})
 }
 
+func TestParseEntry2(t *testing.T) {
+	fset := token.NewFileSet()
+	src, err := os.ReadFile("./_testdata/functype/functype.go")
+	if err != nil {
+		t.Fatal("os.ReadFile:", err)
+	}
+	conf := Config{}
+	conf.ClassKind = func(fname string) (isProj bool, ok bool) {
+		if strings.HasSuffix(fname, "_yap.gox") {
+			return true, true
+		}
+		return defaultClassKind(fname)
+	}
+	t.Run("_yap.gox file", func(t *testing.T) {
+		f, err := ParseEntry(fset, "./functype_yap.gox", src, conf)
+		if err != nil {
+			t.Fatal("ParseEntry failed:", err)
+		}
+		if !f.IsClass || !f.IsProj || f.IsNormalGox {
+			t.Fatal("ParseEntry functype_yap.gox:", f.IsClass, f.IsProj, f.IsNormalGox)
+		}
+	})
+}
+
 func TestSaveAbsFile(t *testing.T) {
 	fset := token.NewFileSet()
 	src, err := os.ReadFile("./_testdata/functype/functype.go")
diff --git a/parser/parsertest/parsertest.go b/parser/parsertest/parsertest.go
index 47f2821e..081d233a 100644
--- a/parser/parsertest/parsertest.go
+++ b/parser/parsertest/parsertest.go
@@ -84,6 +84,16 @@ func FprintNode(w io.Writer, lead string, v interface{}, prefix, indent string)
 					FprintNode(w, fmt.Sprintf("%s%v:\n", prefix, sf.Name), sfv, prefix+indent, indent)
 				}
 			}
+		} else if lit, ok := v.(*ast.StringLitEx); ok {
+			fmt.Fprintf(w, "%sExtra:\n", prefix)
+			prefix += indent
+			for _, part := range lit.Parts {
+				if val, ok := part.(string); ok {
+					fmt.Fprintf(w, "%s%v\n", prefix, val)
+				} else {
+					FprintNode(w, "", part, prefix, indent)
+				}
+			}
 		} else {
 			log.Panicln("FprintNode unexpected type:", t)
 		}
@@ -101,7 +111,7 @@ func Fprint(w io.Writer, pkg *ast.Package) {
 	for _, fpath := range paths {
 		fmt.Fprintf(w, "\nfile %s\n", filepath.Base(fpath))
 		file := pkg.Files[fpath]
-		if file.NoEntrypoint() {
+		if file.HasShadowEntry() {
 			fmt.Fprintf(w, "noEntrypoint\n")
 		}
 		FprintNode(w, "", file.Decls, "", "  ")
diff --git a/printer/gop_test.go b/printer/gop_test.go
index 30bd7aba..8aadd297 100644
--- a/printer/gop_test.go
+++ b/printer/gop_test.go
@@ -19,7 +19,6 @@ package printer_test
 import (
 	"bytes"
 	"io/fs"
-	"io/ioutil"
 	"os"
 	"path/filepath"
 	"strings"
@@ -114,7 +113,7 @@ func testFrom(t *testing.T, fpath, sel string, mode int) {
 	if sel != "" && !strings.Contains(fpath, sel) {
 		return
 	}
-	src, err := ioutil.ReadFile(fpath)
+	src, err := os.ReadFile(fpath)
 	if err != nil {
 		t.Fatal(err)
 	}
diff --git a/test/classfile.go b/test/classfile.go
new file mode 100644
index 00000000..8ca2593a
--- /dev/null
+++ b/test/classfile.go
@@ -0,0 +1,61 @@
+package test
+
+import (
+	"os"
+	"testing"
+)
+
+const (
+	GopPackage = true
+)
+
+// -----------------------------------------------------------------------------
+
+type testingT = testing.T
+
+// Case represents a Go+ testcase.
+type Case struct {
+	t *testingT
+}
+
+func (p *Case) initCase(t *testing.T) {
+	p.t = t
+}
+
+// T returns a *testing.T object.
+func (p Case) T() *testing.T { return p.t }
+
+// Run runs f as a subtest of t called name. It runs f in a separate goroutine
+// and blocks until f returns or calls t.Parallel to become a parallel test.
+// Run reports whether f succeeded (or at least did not fail before calling t.Parallel).
+func (p Case) Run(name string, f func(t *testing.T)) bool {
+	return p.t.Run(name, f)
+}
+
+// Gopt_Case_TestMain is required by Go+ compiler as the test case entry.
+func Gopt_Case_TestMain(c interface{ initCase(t *testing.T) }, t *testing.T) {
+	c.initCase(t)
+	c.(interface{ Main() }).Main()
+}
+
+// -----------------------------------------------------------------------------
+
+// App represents a Go+ testing main application.
+type App struct {
+	m *testing.M
+}
+
+func (p *App) initApp(m *testing.M) {
+	p.m = m
+}
+
+// Gopt_App_TestMain is required by Go+ compiler as the entry of a Go+ testing project.
+func Gopt_App_TestMain(app interface{ initApp(m *testing.M) }, m *testing.M) {
+	app.initApp(m)
+	if me, ok := app.(interface{ MainEntry() }); ok {
+		me.MainEntry()
+	}
+	os.Exit(m.Run())
+}
+
+// -----------------------------------------------------------------------------
diff --git a/testdata/helloc2go/README.md b/testdata/_helloc2go/README.md
similarity index 100%
rename from testdata/helloc2go/README.md
rename to testdata/_helloc2go/README.md
diff --git a/testdata/_helloc2go/go.mod b/testdata/_helloc2go/go.mod
new file mode 100644
index 00000000..050eb82f
--- /dev/null
+++ b/testdata/_helloc2go/go.mod
@@ -0,0 +1,31 @@
+module hello2go
+
+go 1.21.1
+
+require github.com/goplus/libc v0.3.15
+
+require (
+	github.com/davecgh/go-spew v1.1.1 // indirect
+	github.com/google/gofuzz v1.0.0 // indirect
+	github.com/goplus/c2go v0.7.15 // indirect
+	github.com/goplus/gox v1.12.0 // indirect
+	github.com/goplus/mod v0.11.5 // indirect
+	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
+	github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a // indirect
+	github.com/pmezard/go-difflib v1.0.0 // indirect
+	github.com/qiniu/x v1.13.0 // indirect
+	github.com/stretchr/objx v0.1.0 // indirect
+	github.com/stretchr/testify v1.3.0 // indirect
+	github.com/yuin/goldmark v1.4.13 // indirect
+	golang.org/x/crypto v0.13.0 // indirect
+	golang.org/x/mod v0.12.0 // indirect
+	golang.org/x/net v0.15.0 // indirect
+	golang.org/x/sync v0.3.0 // indirect
+	golang.org/x/sys v0.12.0 // indirect
+	golang.org/x/term v0.12.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
+	golang.org/x/tools v0.13.0 // indirect
+	golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 // indirect
+)
diff --git a/testdata/_helloc2go/go.sum b/testdata/_helloc2go/go.sum
new file mode 100644
index 00000000..63c018c3
--- /dev/null
+++ b/testdata/_helloc2go/go.sum
@@ -0,0 +1,60 @@
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/goplus/c2go v0.7.15/go.mod h1:SKSY0OvbO9JBkHRjyiJz3vz0rHpwPETjNj6AQ/rQZOg=
+github.com/goplus/gox v1.12.0/go.mod h1:Ek4bXv4xzfBiFuHC6yfkzHXrhUHXfrM9QyXtMusdkGo=
+github.com/goplus/libc v0.3.15 h1:xfo4Lc66Sf7Dyel+gAT+5fCG0XOB7PxRE4892MH5zgg=
+github.com/goplus/libc v0.3.15/go.mod h1:ExS/KlOepeNJkY10ykY1Qgg6GPql+AItHNNsXuIZwgw=
+github.com/goplus/mod v0.11.5/go.mod h1:NDC5E+XOT8vcJCMjqKhLDJHTHX7lyVN4Vbfi2U7dBhs=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a h1:VXRRto5GMJPNfB7MNbUVoFhtxwoYjBEsIt/NpWg42U0=
+github.com/petermattis/goid v0.0.0-20220331194723-8ee3e6ded87a/go.mod h1:pxMtw7cyUw6B2bRH0ZBANSPg+AoSud1I1iyJHI69jH4=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/qiniu/x v1.13.0/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/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
+golang.org/x/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/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=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o=
+golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/testdata/helloc2go/hello.gop b/testdata/_helloc2go/hello.gop
similarity index 100%
rename from testdata/helloc2go/hello.gop
rename to testdata/_helloc2go/hello.gop
diff --git a/testdata/mixgo-complex/bar.go b/testdata/mixgo-complex/bar.go
new file mode 100644
index 00000000..1711b214
--- /dev/null
+++ b/testdata/mixgo-complex/bar.go
@@ -0,0 +1,24 @@
+package main
+
+import (
+	"io"
+	"log"
+	"testing"
+)
+
+type ift interface {
+	io.Closer
+	f(int) string
+	g()
+}
+
+type impl struct {
+	a T
+}
+
+func Bar(t *testing.T) int {
+	log.Println("Hello")
+	t.Log("Hello",
+		"world")
+	return 0
+}
diff --git a/testdata/mixgo-complex/foo.gop b/testdata/mixgo-complex/foo.gop
new file mode 100644
index 00000000..f607a8d1
--- /dev/null
+++ b/testdata/mixgo-complex/foo.gop
@@ -0,0 +1,43 @@
+// Pkg doc
+
+import (
+	"io"
+	"log"
+)
+
+type ift2 interface {
+	io.Closer
+	f(int) string
+	g()
+}
+
+// T doc
+type T struct {
+	io.Closer
+}
+
+func (T) Close() (err error) {
+	log.Println("Hi!")
+	return
+}
+
+func (t T) g() {}
+
+func (t T) f(a int) (b string) {
+	_ = t.Closer
+	return
+}
+
+func Foo(i *impl) string {
+	i.a.f(0)
+	i.a.g()
+	return ""
+}
+
+// foo golang/go#61561: interface instances aren't concurrency-safe
+// as they are not completed by the type checker.
+func foo(a, b int) string {
+	return ""
+}
+
+println "Hello, world", T{}.f(0)
diff --git a/testdata/overloadfunc1/add.gop b/testdata/overloadfunc1/add.gop
new file mode 100644
index 00000000..c8fe0b8a
--- /dev/null
+++ b/testdata/overloadfunc1/add.gop
@@ -0,0 +1,11 @@
+func add = (
+	func(a, b int) int {
+		return a + b
+	}
+	func(a, b string) string {
+		return a + b
+	}
+)
+
+println add(100, 7)
+println add("Hello", "World")
diff --git a/testdata/overloadfunc2/mul.gop b/testdata/overloadfunc2/mul.gop
new file mode 100644
index 00000000..6b18ac0d
--- /dev/null
+++ b/testdata/overloadfunc2/mul.gop
@@ -0,0 +1,15 @@
+func mulInt(a, b int) int {
+	return a * b
+}
+
+func mulFloat(a, b float64) float64 {
+	return a * b
+}
+
+func mul = (
+	mulInt
+	mulFloat
+)
+
+println mul(100, 7)
+println mul(1.2, 3.14)
diff --git a/testdata/overloadmethod/method.gop b/testdata/overloadmethod/method.gop
new file mode 100644
index 00000000..a55ee9bb
--- /dev/null
+++ b/testdata/overloadmethod/method.gop
@@ -0,0 +1,21 @@
+type foo struct {
+}
+
+func (a *foo) mulInt(b int) *foo {
+	println "mulInt"
+	return a
+}
+
+func (a *foo) mulFoo(b *foo) *foo {
+	println "mulFoo"
+	return a
+}
+
+func (foo).mul = (
+	(foo).mulInt
+	(foo).mulFoo
+)
+
+var a, b *foo
+var c = a.mul(100)
+var d = a.mul(c)
diff --git a/testdata/overloadop1/overloadop.gop b/testdata/overloadop1/overloadop.gop
new file mode 100644
index 00000000..d7b489b6
--- /dev/null
+++ b/testdata/overloadop1/overloadop.gop
@@ -0,0 +1,34 @@
+type foo struct {
+}
+
+func (a foo) + (b foo) (ret foo) {
+	println "a + b"
+	return
+}
+
+func (a foo) - (b foo) (ret foo) {
+	println "a - b"
+	return
+}
+
+func -(a foo) (ret foo) {
+	println "-a"
+	return
+}
+
+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 - b
+var e = -a       // TODO: -a have no return value!
+var f = a != b
+println f
+a++
diff --git a/testdata/overloadop2/overloadop.gop b/testdata/overloadop2/overloadop.gop
new file mode 100644
index 00000000..516947f2
--- /dev/null
+++ b/testdata/overloadop2/overloadop.gop
@@ -0,0 +1,28 @@
+type foo struct {
+}
+
+func (a foo) mulInt(b int) (ret foo) {
+	println "a * int"
+	return
+}
+
+func (a foo) mulFoo(b foo) (ret foo) {
+	println "a * b"
+	return
+}
+
+func intMulFoo(a int, b foo) (ret foo) {
+	println "int * b"
+	return
+}
+
+func (foo).* = (
+	(foo).mulInt
+	(foo).mulFoo
+	intMulFoo
+)
+
+var a, b foo
+var c = a * 10
+var d = a * b
+var e = 10 * a
diff --git a/testdata/typeparamscast/foo.go b/testdata/typeparamscast/foo.go
new file mode 100644
index 00000000..b2d304f1
--- /dev/null
+++ b/testdata/typeparamscast/foo.go
@@ -0,0 +1,23 @@
+package main
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	Val T
+}
+
+type Var__1[T map[string]any] struct {
+	Val T
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
diff --git a/testdata/typeparamscast/typecast.gop b/testdata/typeparamscast/typecast.gop
new file mode 100644
index 00000000..c3c95e53
--- /dev/null
+++ b/testdata/typeparamscast/typecast.gop
@@ -0,0 +1,1 @@
+println Var(int)
diff --git a/testdata/unit-test/foo.gop b/testdata/unit-test/foo.gop
new file mode 100644
index 00000000..bfdedc1b
--- /dev/null
+++ b/testdata/unit-test/foo.gop
@@ -0,0 +1,3 @@
+func foo(v int) int {
+	return v * 2
+}
diff --git a/testdata/unit-test/foo_test.gox b/testdata/unit-test/foo_test.gox
new file mode 100644
index 00000000..4e238df3
--- /dev/null
+++ b/testdata/unit-test/foo_test.gox
@@ -0,0 +1,17 @@
+if v := foo(50); v == 100 {
+	t.log "test foo(50) ok"
+} else {
+	t.error "foo() ret: ${v}"
+}
+
+t.run "foo -10", t => {
+	if foo(-10) != -20 {
+		t.fatal "foo(-10) != -20"
+	}
+}
+
+t.run "foo 0", t => {
+	if foo(0) != 0 {
+		t.fatal "foo(0) != 0"
+	}
+}
diff --git a/x/build/build_test.go b/x/build/build_test.go
index cb867bd2..b43a127f 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -21,7 +21,6 @@ import (
 	"fmt"
 	"go/printer"
 	"go/types"
-	"io/ioutil"
 	"os"
 	"path"
 	"path/filepath"
@@ -38,12 +37,14 @@ var (
 )
 
 func init() {
+	cl.SetDebug(cl.FlagNoMarkAutogen)
 	ctx.LoadConfig = func(cfg *cl.Config) {
 		cfg.NoFileLine = true
 	}
 	build.RegisterClassFileType(".tspx", "MyGame", []*build.Class{
 		{Ext: ".tspx", Class: "Sprite"},
 	}, "github.com/goplus/gop/cl/internal/spx")
+	build.RegisterClassFileType("_yap.gox", "App", nil, "github.com/goplus/yap")
 }
 
 func gopClTest(t *testing.T, gopcode interface{}, expected string) {
@@ -76,6 +77,7 @@ func TestKind(t *testing.T) {
 	testKind(t, "main.gmx", true, true)
 	testKind(t, "Cat.tspx", false, true)
 	testKind(t, "main.tspx", true, true)
+	testKind(t, "blog_yap.gox", true, true)
 }
 
 func TestGop(t *testing.T) {
@@ -183,8 +185,8 @@ println "Go+"
 `, `package main
 
 import (
-	"fmt"
 	"bytes"
+	"fmt"
 )
 
 type Rect struct {
@@ -208,8 +210,8 @@ println "Go+"
 `, `package main
 
 import (
-	"fmt"
 	"bytes"
+	"fmt"
 )
 
 type Rect struct {
@@ -239,7 +241,7 @@ import (
 
 func main() {
 	a := ng.Bigrat_Init__2(big.NewRat(1, 2))
-	fmt.Println(a.Gop_Add(ng.Bigrat_Init__2(big.NewRat(1, 2))))
+	fmt.Println((ng.Bigrat).Gop_Add(a, ng.Bigrat_Init__2(big.NewRat(1, 2))))
 }
 `)
 }
@@ -300,8 +302,8 @@ println addSafe("10", "abc")
 
 import (
 	"fmt"
-	"strconv"
 	"github.com/qiniu/x/errors"
+	"strconv"
 )
 
 func add(x string, y string) (int, error) {
@@ -389,6 +391,10 @@ import (
 	"github.com/goplus/gop/cl/internal/spx"
 )
 
+func main() {
+	spx.Gopt_MyGame_Main(new(spx.MyGame))
+}
+
 type Cat struct {
 	spx.Sprite
 }
@@ -396,8 +402,6 @@ type Cat struct {
 func (this *Cat) Main() {
 	fmt.Println("hi")
 }
-func main() {
-}
 `)
 }
 
@@ -407,7 +411,7 @@ func testFromDir(t *testing.T, relDir string) {
 		t.Fatal("Getwd failed:", err)
 	}
 	dir = path.Join(dir, relDir)
-	fis, err := ioutil.ReadDir(dir)
+	fis, err := os.ReadDir(dir)
 	if err != nil {
 		t.Fatal("ReadDir failed:", err)
 	}
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
index fbbbe0ac..703118c1 100644
--- a/x/typesutil/check_test.go
+++ b/x/typesutil/check_test.go
@@ -185,3 +185,13 @@ var i int = "hello"
 		t.Fatal("no error")
 	}
 }
+
+func TestBadFile(t *testing.T) {
+	conf := &types.Config{}
+	opt := &typesutil.Config{}
+	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")}})
+}
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 4632f22f..addddcaa 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -1385,3 +1385,173 @@ func (p *N) Test__1(n int) {
 005:  6: 1 | n                   | var n main.N
 006:  6: 3 | test                | func (*main.N).Test__1(n int)`)
 }
+
+func TestOverloadNamed(t *testing.T) {
+	testGopInfo(t, `
+import "github.com/goplus/gop/cl/internal/overload/bar"
+
+var a bar.Var[int]
+var b bar.Var[bar.M]
+c := bar.Var(string)
+d := bar.Var(bar.M)
+`, ``, `== types ==
+000:  4: 7 | bar.Var             *ast.SelectorExpr              | type    : github.com/goplus/gop/cl/internal/overload/bar.Var__0[int] | type
+001:  4: 7 | bar.Var[int]        *ast.IndexExpr                 | type    : github.com/goplus/gop/cl/internal/overload/bar.Var__0[int] | type
+002:  4:15 | int                 *ast.Ident                     | type    : int | type
+003:  5: 7 | bar.Var             *ast.SelectorExpr              | type    : github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | type
+004:  5: 7 | bar.Var[bar.M]      *ast.IndexExpr                 | type    : github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | type
+005:  5:15 | bar.M               *ast.SelectorExpr              | type    : map[string]any | type
+006:  6: 6 | bar.Var             *ast.SelectorExpr              | value   : func[T github.com/goplus/gop/cl/internal/overload/bar.basetype]() *github.com/goplus/gop/cl/internal/overload/bar.Var__0[T] | value
+007:  6: 6 | bar.Var(string)     *ast.CallExpr                  | value   : *github.com/goplus/gop/cl/internal/overload/bar.Var__0[string] | value
+008:  6:14 | string              *ast.Ident                     | type    : string | type
+009:  7: 6 | bar.Var             *ast.SelectorExpr              | value   : func[T map[string]any]() *github.com/goplus/gop/cl/internal/overload/bar.Var__1[T] | value
+010:  7: 6 | bar.Var(bar.M)      *ast.CallExpr                  | value   : *github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any] | value
+011:  7:14 | bar.M               *ast.SelectorExpr              | var     : map[string]any | variable
+== defs ==
+000:  4: 5 | a                   | var main.a github.com/goplus/gop/cl/internal/overload/bar.Var__0[int]
+001:  5: 5 | b                   | var main.b github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any]
+002:  6: 1 | c                   | var c *github.com/goplus/gop/cl/internal/overload/bar.Var__0[string]
+003:  6: 1 | main                | func main.main()
+004:  7: 1 | d                   | var d *github.com/goplus/gop/cl/internal/overload/bar.Var__1[map[string]any]
+== uses ==
+000:  4: 7 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+001:  4:11 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var__0[T github.com/goplus/gop/cl/internal/overload/bar.basetype] struct{val T}
+002:  4:15 | int                 | type int
+003:  5: 7 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+004:  5:11 | Var                 | type github.com/goplus/gop/cl/internal/overload/bar.Var__1[T map[string]any] struct{val T}
+005:  5:15 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+006:  5:19 | M                   | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any
+007:  6: 6 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+008:  6:10 | Var                 | func github.com/goplus/gop/cl/internal/overload/bar.Gopx_Var_Cast__0[T github.com/goplus/gop/cl/internal/overload/bar.basetype]() *github.com/goplus/gop/cl/internal/overload/bar.Var__0[T]
+009:  6:14 | string              | type string
+010:  7: 6 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+011:  7:10 | Var                 | func github.com/goplus/gop/cl/internal/overload/bar.Gopx_Var_Cast__1[T map[string]any]() *github.com/goplus/gop/cl/internal/overload/bar.Var__1[T]
+012:  7:14 | bar                 | package bar ("github.com/goplus/gop/cl/internal/overload/bar")
+013:  7:18 | M                   | type github.com/goplus/gop/cl/internal/overload/bar.M = map[string]any`)
+}
+
+func TestMixedOverloadNamed(t *testing.T) {
+	//gox.SetDebug(gox.DbgFlagAll)
+	testGopInfo(t, `
+var a Var[int]
+var b Var[M]
+c := Var(string)
+d := Var(M)
+`, `
+package main
+
+const GopPackage = true
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	val T
+}
+
+type Var__1[T map[string]any] struct {
+	val T
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
+`, `== types ==
+000:  2: 7 | Var                 *ast.Ident                     | type    : main.Var__0[int] | type
+001:  2: 7 | Var[int]            *ast.IndexExpr                 | type    : main.Var__0[int] | type
+002:  2:11 | int                 *ast.Ident                     | type    : int | type
+003:  3: 7 | Var                 *ast.Ident                     | type    : main.Var__1[map[string]interface{}] | type
+004:  3: 7 | Var[M]              *ast.IndexExpr                 | type    : main.Var__1[map[string]interface{}] | type
+005:  3:11 | M                   *ast.Ident                     | type    : map[string]interface{} | type
+006:  4: 6 | Var                 *ast.Ident                     | value   : func[T main.basetype]() *main.Var__0[T] | value
+007:  4: 6 | Var(string)         *ast.CallExpr                  | value   : *main.Var__0[string] | value
+008:  4:10 | string              *ast.Ident                     | type    : string | type
+009:  5: 6 | Var                 *ast.Ident                     | value   : func[T map[string]interface{}]() *main.Var__1[T] | value
+010:  5: 6 | Var(M)              *ast.CallExpr                  | value   : *main.Var__1[map[string]interface{}] | value
+011:  5:10 | M                   *ast.Ident                     | type    : map[string]interface{} | type
+== defs ==
+000:  2: 5 | a                   | var main.a main.Var__0[int]
+001:  3: 5 | b                   | var main.b main.Var__1[map[string]interface{}]
+002:  4: 1 | c                   | var c *main.Var__0[string]
+003:  4: 1 | main                | func main.main()
+004:  5: 1 | d                   | var d *main.Var__1[map[string]interface{}]
+== uses ==
+000:  2: 7 | Var                 | type main.Var__0[T main.basetype] struct{val T}
+001:  2:11 | int                 | type int
+002:  3: 7 | Var                 | type main.Var__1[T map[string]any] struct{val T}
+003:  3:11 | M                   | type main.M = map[string]any
+004:  4: 6 | Var                 | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
+005:  4:10 | string              | type string
+006:  5: 6 | Var                 | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
+007:  5:10 | M                   | type main.M = map[string]any`)
+}
+
+func TestMixedRawNamed(t *testing.T) {
+	//gox.SetDebug(gox.DbgFlagAll)
+	testGopInfo(t, `
+var a Var__0[int]
+var b Var__1[M]
+c := Gopx_Var_Cast__0[string]
+d := Gopx_Var_Cast__1[M]
+`, `
+package main
+
+const GopPackage = true
+
+type M = map[string]any
+
+type basetype interface {
+	string | int | bool | float64
+}
+
+type Var__0[T basetype] struct {
+	val T
+}
+
+type Var__1[T map[string]any] struct {
+	val T
+}
+
+func Gopx_Var_Cast__0[T basetype]() *Var__0[T] {
+	return new(Var__0[T])
+}
+
+func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
+	return new(Var__1[T])
+}
+`, `== types ==
+000:  2: 7 | Var__0              *ast.Ident                     | type    : main.Var__0[int] | type
+001:  2: 7 | Var__0[int]         *ast.IndexExpr                 | type    : main.Var__0[int] | type
+002:  2:14 | int                 *ast.Ident                     | type    : int | type
+003:  3: 7 | Var__1              *ast.Ident                     | type    : main.Var__1[map[string]interface{}] | type
+004:  3: 7 | Var__1[M]           *ast.IndexExpr                 | type    : main.Var__1[map[string]interface{}] | type
+005:  3:14 | M                   *ast.Ident                     | type    : map[string]interface{} | type
+006:  4: 6 | Gopx_Var_Cast__0    *ast.Ident                     | value   : func[T main.basetype]() *main.Var__0[T] | value
+007:  4: 6 | Gopx_Var_Cast__0[string] *ast.IndexExpr                 | var     : func() *main.Var__0[string] | variable
+008:  4:23 | string              *ast.Ident                     | type    : string | type
+009:  5: 6 | Gopx_Var_Cast__1    *ast.Ident                     | value   : func[T map[string]interface{}]() *main.Var__1[T] | value
+010:  5: 6 | Gopx_Var_Cast__1[M] *ast.IndexExpr                 | var     : func() *main.Var__1[map[string]interface{}] | variable
+011:  5:23 | M                   *ast.Ident                     | type    : map[string]interface{} | type
+== defs ==
+000:  2: 5 | a                   | var main.a main.Var__0[int]
+001:  3: 5 | b                   | var main.b main.Var__1[map[string]interface{}]
+002:  4: 1 | c                   | var c func() *main.Var__0[string]
+003:  4: 1 | main                | func main.main()
+004:  5: 1 | d                   | var d func() *main.Var__1[map[string]interface{}]
+== uses ==
+000:  2: 7 | Var__0              | type main.Var__0[T main.basetype] struct{val T}
+001:  2:14 | int                 | type int
+002:  3: 7 | Var__1              | type main.Var__1[T map[string]any] struct{val T}
+003:  3:14 | M                   | type main.M = map[string]any
+004:  4: 6 | Gopx_Var_Cast__0    | func main.Gopx_Var_Cast__0[T main.basetype]() *main.Var__0[T]
+005:  4:23 | string              | type string
+006:  5: 6 | Gopx_Var_Cast__1    | func main.Gopx_Var_Cast__1[T map[string]any]() *main.Var__1[T]
+007:  5:23 | M                   | type main.M = map[string]any`)
+}