Repository: goplus/gop
Pull request #1699 (2024-02-01 11:48:41)

v1.2.0


Patch:
diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml
index 2e61bcb0..33c651c1 100644
--- a/.github/workflows/release-build.yml
+++ b/.github/workflows/release-build.yml
@@ -49,6 +49,7 @@ jobs:
         args: release --clean -p 4
       env:
         GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
         WINGET_PKGS_PRIVATE_KEY: ${{ secrets.WINGET_PKGS_PRIVATE_KEY }}
 
     - name: Upload deb/rpm to Fury.io
@@ -56,7 +57,12 @@ jobs:
         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/
+          CODE=`curl --write-out '%{http_code}' --output /dev/null -sS -F package=@$file https://$FURY_TOKEN@push.fury.io/$GITHUB_REPOSITORY_OWNER/`
+          if [ "$CODE" != "200" ]; then
+            echo "Upload failed with code $CODE"
+            exit 1
+          fi
         done
       env:
         FURY_TOKEN: ${{ secrets.FURY_TOKEN }}
+        GITHUB_REPOSITORY_OWNER: ${{ github.repository_owner }}
diff --git a/.goreleaser.yaml b/.goreleaser.yaml
index ef020d43..07fac388 100644
--- a/.goreleaser.yaml
+++ b/.goreleaser.yaml
@@ -96,7 +96,7 @@ winget:
     license: Apache-2.0
     skip_upload: false
     release_notes: "{{.Changelog}}"
-    release_notes_url: "https://github.com/goplus/gop/releases/tag/v{{.Version}}"
+    release_notes_url: "https://github.com/{{ .Env.GITHUB_REPOSITORY_OWNER }}/gop/releases/tag/v{{.Version}}"
     dependencies:
       - package_identifier: GoLang.Go
         minimum_version: 1.18.0
@@ -105,7 +105,7 @@ winget:
       name: winget-pkgs
       branch: "{{.ProjectName}}-v{{.Version}}"
       git:
-        url: "git@github.com:goplus/winget-pkgs.git"
+        url: "git@github.com:{{ .Env.GITHUB_REPOSITORY_OWNER }}/winget-pkgs.git"
         private_key: "{{ .Env.WINGET_PKGS_PRIVATE_KEY }}"
       pull_request:
         enabled: true
diff --git a/README.md b/README.md
index f0f460e8..008849cf 100644
--- a/README.md
+++ b/README.md
@@ -24,6 +24,17 @@
 
 </div>
 
+Our vision is to **enable everyone to create production-level applications**.
+
+#### Easy to learn
+
+* Simple and easy to understand
+* Smaller syntax set than Python in best practices
+
+#### Ready for large projects
+
+* Derived from Go and easy to build large projects from its good engineering foundation
+
 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.
@@ -33,13 +44,27 @@ The Go+ programming language is designed for engineering, STEM education, and da
 For more details, see [Quick Start](doc/docs.md).
 
 
+## Go+ Classfiles
+
+Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` is as important to Go+ as `interface` is to Go.
+
+In the design philosophy of Go+, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The Go+ philosophy about `SDF` is:
+
+```
+Don't define a language for specific domain.
+Abstract domain knowledge for it.
+```
+
+Go+ introduces `classfile` to abstract domain knowledge. See [Go+ Classfiles](doc/classfile.md).
+
+
 ## Key Features of Go+
 
 * A static typed language.
 * The simplest engineering language that can be mastered by children (script-like style).
 * Performance: as fast as Go (Go+'s main backend compiles to human-readable Go).
 * Fully compatible with [Go](https://github.com/golang/go) and can mix Go/Go+ code in the same package (see [Go/Go+ hybrid programming](doc/docs.md#gogo-hybrid-programming)).
-* No DSL (Domain Specific Language) support, but it's Specific Domain Friendly (see [DSL vs. SDF](doc/dsl-vs-sdf.md)).
+* No DSL (Domain Specific Language) support, but SDF ([Specific Domain Friendliness](doc/classfile.md)).
 * Support Go code generation (main backend) and [bytecode backend](https://github.com/goplus/igop) (REPL: see [iGo+](https://repl.goplus.org/)).
 * [Simplest way to interaction with C](doc/docs.md#calling-c-from-go) (cgo is supported but not recommended).
 * [Powerful built-in data processing capabilities](doc/docs.md#data-processing).
@@ -49,12 +74,6 @@ For more details, see [Quick Start](doc/docs.md).
 
 ### on Windows
 
-```sh
-winget install goplus
-```
-
-Or
-
 ```sh
 winget install goplus.gop
 ```
@@ -74,7 +93,7 @@ sudo bash -c 'echo -e "[goplus]\nname=Go+ Repo\nbaseurl=https://pkgs.goplus.org/
 sudo yum install gop
 ```
 
-### on macOS/Linux(Homebrew)
+### on macOS/Linux (Homebrew)
 
 Install via [brew](https://brew.sh/)
 
@@ -110,6 +129,14 @@ all.bat
 * [Gobang](https://github.com/xushiwei/Gobang)
 * [Dinosaur](https://github.com/xushiwei/Dinosaur)
 
+### HTTP Web Framework
+
+* [yap: Yet Another Go/Go+ HTTP Web Framework](https://github.com/goplus/yap)
+
+### HTTP Test
+
+* [yaptest: HTTP Test Framework](https://github.com/goplus/yap/tree/main/ytest)
+
 ### DevOps tools
 
 * [Go+ DevOps Tools](https://github.com/goplus/gop/blob/main/doc/dsl-vs-sdf.md#demo-go-devops-tools)
diff --git a/VERSION b/VERSION
index 9b2699ff..b08635fd 100644
--- a/VERSION
+++ b/VERSION
@@ -1,1 +1,1 @@
-v1.2.0-pre.2
+v1.2.0-pre.1
diff --git a/cl/classfile.go b/cl/classfile.go
index d019783e..725811e0 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -47,6 +47,7 @@ type gmxProject struct {
 	schedStmts []goast.Stmt // nil or len(scheds) == 2 (delayload)
 	pkgImps    []gox.PkgRef
 	pkgPaths   []string
+	autoimps   map[string]pkgImp // auto-import statement in gop.mod
 	hasScheds  bool
 	gameIsPtr  bool
 	isTest     bool
@@ -99,6 +100,20 @@ func loadClass(ctx *pkgCtx, pkg *gox.Package, file string, f *ast.File, conf *Co
 			p.pkgImps[i] = pkg.Import(pkgPath)
 		}
 
+		if len(gt.Import) > 0 {
+			autoimps := make(map[string]pkgImp)
+			for _, imp := range gt.Import {
+				pkgi := pkg.Import(imp.Path)
+				name := imp.Name
+				if name == "" {
+					name = pkgi.Types.Name()
+				}
+				pkgName := types.NewPkgName(token.NoPos, pkg.Types, name, pkgi.Types)
+				autoimps[name] = pkgImp{pkgi, pkgName}
+			}
+			p.autoimps = autoimps
+		}
+
 		spx := p.pkgImps[0]
 		if gt.Class != "" {
 			p.game, p.gameIsPtr = spxRef(spx, gt.Class)
diff --git a/cl/compile.go b/cl/compile.go
index 50761274..00b515fe 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -26,7 +26,6 @@ import (
 	"sort"
 	"strconv"
 	"strings"
-	_ "unsafe"
 
 	"github.com/goplus/gop/ast"
 	"github.com/goplus/gop/ast/fromgo"
@@ -366,6 +365,7 @@ type blockCtx struct {
 	pkg        *gox.Package
 	cb         *gox.CodeBuilder
 	imports    map[string]pkgImp
+	autoimps   map[string]pkgImp
 	lookups    []gox.PkgRef
 	clookups   []cpackages.PkgRef
 	tlookup    *typeParamLookup
@@ -388,6 +388,9 @@ func (bc *blockCtx) recorder() *typesRecorder {
 
 func (bc *blockCtx) findImport(name string) (pi pkgImp, ok bool) {
 	pi, ok = bc.imports[name]
+	if !ok && bc.autoimps != nil {
+		pi, ok = bc.autoimps[name]
+	}
 	return
 }
 
@@ -630,9 +633,6 @@ func isOverloadFunc(name string) bool {
 	return n > 3 && name[n-3:n-1] == "__"
 }
 
-//go:linkname initThisGopPkg github.com/goplus/gox.initThisGopPkg
-func initThisGopPkg(pkg *types.Package)
-
 func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
 	for name, f := range ctx.syms {
 		if gopSyms[name] {
@@ -654,7 +654,7 @@ func initGopPkg(ctx *pkgCtx, pkg *gox.Package, gopSyms map[string]bool) {
 	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)
+	gox.InitThisGopPkg(pkg.Types)
 }
 
 func getEntrypoint(f *ast.File) string {
@@ -730,6 +730,7 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 		} else {
 			c := parent.classes[f]
 			proj, ctx.proj = c.proj, c.proj
+			ctx.autoimps = proj.autoimps
 			classType = c.tname
 			if isGoxTestFile(c.ext) { // test classfile
 				testType = c.tname
@@ -832,6 +833,10 @@ func preloadGopFile(p *gox.Package, ctx *blockCtx, file string, f *ast.File, con
 			}
 			parent.tylds = append(parent.tylds, ld)
 		}
+
+		// bugfix: see TestGopxNoFunc
+		parent.lbinames = append(parent.lbinames, classType)
+
 		ctx.classRecv = &ast.FieldList{List: []*ast.Field{{
 			Names: []*ast.Ident{
 				{Name: "this"},
diff --git a/cl/expr.go b/cl/expr.go
index fb6cc143..9fc45f38 100644
--- a/cl/expr.go
+++ b/cl/expr.go
@@ -64,6 +64,7 @@ const (
 	clIdentGoto
 	clCallWithTwoValue
 	clCommandWithoutArgs
+	clMayBuiltinNew // maybe is builtin directive `new`
 )
 
 const (
@@ -99,7 +100,7 @@ func compileIdent(ctx *blockCtx, ident *ast.Ident, flags int) (pkg gox.PkgRef, k
 			sig := fn.Ancestor().Type().(*types.Signature)
 			if recv := sig.Recv(); recv != nil {
 				ctx.cb.Val(recv)
-				chkFlag := flags &^ clCommandWithoutArgs
+				chkFlag := flags // &^ clCommandWithoutArgs (TODO: why?)
 				if chkFlag&clIdentSelectorExpr != 0 {
 					chkFlag = clIdentCanAutoCall
 				}
@@ -503,32 +504,20 @@ func (p *fnType) load(fnt types.Type) {
 	case *gox.TypeType:
 		p.initTypeType(v)
 	case *types.Signature:
-		if typ, ok := gox.CheckFuncEx(v); ok {
-			switch t := typ.(type) {
-			case *gox.TyOverloadFunc:
-				p.initFuncs(0, t.Funcs...)
-				return
-			case *gox.TyOverloadMethod:
-				p.initFuncs(0, t.Methods...)
-				return
-			case *gox.TyTemplateRecvMethod:
-				if tsig, ok := t.Func.Type().(*types.Signature); ok {
-					if ex, ok := gox.CheckFuncEx(tsig); ok {
-						if t, ok := ex.(*gox.TyOverloadFunc); ok {
-							p.initFuncs(1, t.Funcs...)
-							return
-						}
-					}
-				}
-				p.initFuncs(1, t.Func)
-				return
-			}
+		typ, objs := gox.CheckSigFuncExObjects(v)
+		switch typ.(type) {
+		case *gox.TyOverloadFunc, *gox.TyOverloadMethod:
+			p.initFuncs(0, objs)
+			return
+		case *gox.TyTemplateRecvMethod:
+			p.initFuncs(1, objs)
+			return
 		}
 		p.init(0, v)
 	}
 }
 
-func (p *fnType) initFuncs(base int, funcs ...types.Object) {
+func (p *fnType) initFuncs(base int, funcs []types.Object) {
 	for i, obj := range funcs {
 		if sig, ok := obj.Type().(*types.Signature); ok {
 			if i == 0 {
@@ -544,8 +533,12 @@ func (p *fnType) initFuncs(base int, funcs ...types.Object) {
 }
 
 func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
+	var flags gox.InstrFlags
 	switch fn := v.Fun.(type) {
 	case *ast.Ident:
+		if len(v.Args) == 1 && fn.Name == "new" { // maybe new(T)
+			flags = clMayBuiltinNew
+		}
 		compileIdent(ctx, fn, clIdentAllowBuiltin|inFlags)
 	case *ast.SelectorExpr:
 		compileSelectorExpr(ctx, fn, 0)
@@ -563,10 +556,9 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 		compileExpr(ctx, fn)
 	}
 	var fnt = ctx.cb.Get(-1).Type
-	var flags gox.InstrFlags
 	var ellipsis = v.Ellipsis != gotoken.NoPos
 	if ellipsis {
-		flags = gox.InstrFlagEllipsis
+		flags |= gox.InstrFlagEllipsis
 	}
 	if (inFlags & clCallWithTwoValue) != 0 {
 		flags |= gox.InstrFlagTwoValue
@@ -589,7 +581,9 @@ func compileCallExpr(ctx *blockCtx, v *ast.CallExpr, inFlags int) {
 }
 
 func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool, flags gox.InstrFlags) (err error) {
-	n := ctx.cb.InternalStack().Len()
+	cb := ctx.cb
+	stk := cb.InternalStack()
+	n := stk.Len()
 	defer func() {
 		if r := recover(); r != nil {
 			if e, ok := r.(error); ok {
@@ -598,7 +592,7 @@ func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool,
 				src := ctx.LoadExpr(v)
 				err = ctx.newCodeErrorf(v.Pos(), "compile func %v error: %v", src, r)
 			}
-			ctx.cb.InternalStack().SetLen(n)
+			stk.SetLen(n)
 		}
 	}()
 	for i, arg := range v.Args {
@@ -624,7 +618,7 @@ func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool,
 			}
 			typetype := fn.typetype && t != nil
 			if typetype {
-				ctx.cb.InternalStack().Pop()
+				stk.Pop()
 			}
 			compileSliceLit(ctx, expr, t)
 			if typetype {
@@ -634,10 +628,23 @@ func compileCallArgs(fn *fnType, ctx *blockCtx, v *ast.CallExpr, ellipsis bool,
 			compileExpr(ctx, arg)
 		}
 	}
-	ctx.cb.CallWith(len(v.Args), flags, v)
+	if (flags & clMayBuiltinNew) != 0 { // maybe new(T): see TestSpxNewObj
+		arg := stk.Get(-1)
+		if _, ok := arg.Type.(*gox.TypeType); ok && isNonInstr(stk.Get(-2).Type) {
+			stk.PopN(2)
+			cb.Val(ctx.pkg.Builtin().Ref("new"))
+			stk.Push(arg)
+		}
+	}
+	cb.CallWith(len(v.Args), flags, v)
 	return
 }
 
+func isNonInstr(t types.Type) bool {
+	_, ok := t.(*gox.TyInstruction)
+	return !ok
+}
+
 type clLambaFlag string
 
 const (
diff --git a/cl/internal/spx/game.go b/cl/internal/spx/game.go
index 9367eae7..e7b1f1c1 100644
--- a/cl/internal/spx/game.go
+++ b/cl/internal/spx/game.go
@@ -29,6 +29,9 @@ type MyGame struct {
 func Gopt_MyGame_Main(game interface{}) {
 }
 
+func (p *MyGame) InitGameApp(args ...string) {
+}
+
 func (p *MyGame) Broadcast__0(msg string) {
 }
 
diff --git a/cl/internal/spx3/jwt/jwt.go b/cl/internal/spx3/jwt/jwt.go
new file mode 100644
index 00000000..4e56fe6c
--- /dev/null
+++ b/cl/internal/spx3/jwt/jwt.go
@@ -0,0 +1,21 @@
+/*
+ * 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 jwt
+
+func Token(v string) string {
+	return "token: " + v
+}
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
index 6fe29474..3255e2ea 100644
--- a/cl/internal/spx3/spx3.go
+++ b/cl/internal/spx3/spx3.go
@@ -23,6 +23,10 @@ const (
 type Game struct {
 }
 
+func New() *Game {
+	return nil
+}
+
 func (p *Game) initGame() {}
 
 func (p *Game) Run() {}
@@ -30,6 +34,10 @@ func (p *Game) Run() {}
 type Sprite struct {
 }
 
+func (p *Sprite) Name() string {
+	return "sprite"
+}
+
 func (p *Sprite) initSprite() {}
 
 func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ initSprite() }) {
diff --git a/cmd/internal/gopget/get.go b/cmd/internal/gopget/get.go
index e3a909cf..b9b6756a 100644
--- a/cmd/internal/gopget/get.go
+++ b/cmd/internal/gopget/get.go
@@ -79,12 +79,8 @@ func get(pkgPath string) {
 
 	pkgMod, err := modload.Load(pkgModRoot)
 	check(err)
-	if pkgMod.HasProject() {
-		mod.Opt.AddImport(pkgModVer.Path)
-		fmt.Fprintf(os.Stderr, "gop get: import %s\n", pkgModVer.Path)
-	}
 
-	check(mod.AddRequire(pkgModVer.Path, pkgModVer.Version))
+	check(mod.AddRequire(pkgModVer.Path, pkgModVer.Version, pkgMod.HasProject()))
 	fmt.Fprintf(os.Stderr, "gop get: added %s %s\n", pkgModVer.Path, pkgModVer.Version)
 
 	check(mod.Save())
diff --git a/doc/classfile.md b/doc/classfile.md
new file mode 100644
index 00000000..6404498d
--- /dev/null
+++ b/doc/classfile.md
@@ -0,0 +1,416 @@
+Go+ Classfiles
+=====
+
+Rob Pike once said that if he could only introduce one feature to Go, he would choose `interface` instead of `goroutine`. `classfile` is as important to Go+ as `interface` is to Go.
+
+In the design philosophy of Go+, we do not recommend `DSL` (Domain Specific Language). But `SDF` (Specific Domain Friendliness) is very important. The Go+ philosophy about `SDF` is:
+
+```
+Don't define a language for specific domain.
+Abstract domain knowledge for it.
+```
+
+Go+ introduces `classfile` to abstract domain knowledge.
+
+
+### classfile: Unit Test
+
+Go+ has a built-in classfile to simplify unit testing. This classfile has the file suffix `_test.gox`.
+
+Suppose you have a function named `foo`:
+
+```go
+func foo(v int) int {
+	return v * 2
+}
+```
+
+Then you can create a `foo_test.gox` file to test it (see [unit-test/foo_test.gox](../testdata/unit-test/foo_test.gox)):
+
+```go
+if v := foo(50); v != 100 {
+	t.error "foo(50) 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"
+	}
+}
+```
+
+You don't need to define a series of `TestXXX` functions like Go, just write your test code directly.
+
+If you want to run a subtest case, use `t.run`.
+
+
+### yap: Yet Another Go/Go+ HTTP Web Framework
+
+This classfile has the file suffix `_yap.gox`.
+
+Before using `yap`, you need to add it to `go.mod` by using `go get`:
+
+```sh
+go get github.com/goplus/yap@latest
+```
+
+Find `require github.com/goplus/yap` statement in `go.mod` and add `//gop:class` at the end of the line:
+
+```go.mod
+require github.com/goplus/yap v0.7.2 //gop:class
+```
+
+#### Router and Parameters
+
+demo in Go+ classfile ([hello_yap.gox](https://github.com/goplus/yap/blob/v0.7.2/demo/classfile_hello/hello_yap.gox)):
+
+```coffee
+get "/p/:id", ctx => {
+	ctx.json {
+		"id": ctx.param("id"),
+	}
+}
+handle "/", ctx => {
+	ctx.html `<html><body>Hello, <a href="/p/123">Yap</a>!</body></html>`
+}
+
+run ":8080"
+```
+
+#### Static files
+
+Static files server demo in Go+ classfile ([staticfile_yap.gox](https://github.com/goplus/yap/blob/v0.7.2/demo/classfile_static/staticfile_yap.gox)):
+
+```coffee
+static "/foo", FS("public")
+static "/"
+
+run ":8080"
+```
+
+#### YAP Template
+
+demo in Go+ classfile ([blog_yap.gox](https://github.com/goplus/yap/blob/v0.7.2/demo/classfile_blog/blog_yap.gox), [article_yap.html](https://github.com/goplus/yap/blob/v0.7.2/demo/classfile_blog/yap/article_yap.html)):
+
+```coffee
+get "/p/:id", ctx => {
+	ctx.yap "article", {
+		"id": ctx.param("id"),
+	}
+}
+
+run ":8080"
+```
+
+### yaptest: HTTP Test Framework
+
+This classfile has the file suffix `_ytest.gox`.
+
+Before using `yaptest`, you need to add `yap` to `go.mod`:
+
+```go.mod
+require github.com/goplus/yap v0.7.2 //gop:class
+```
+
+Then you can create a `example_ytest.gox` file to test your HTTP server:
+
+```coffee
+host "https://example.com", "http://localhost:8080"
+testauth := oauth2("...")
+
+run "urlWithVar", => {
+	id := "123"
+	get "https://example.com/p/${id}"
+	ret
+	echo "code:", resp.code
+	echo "body:", resp.body
+}
+
+run "matchWithVar", => {
+	code := Var(int)
+	id := "123"
+	get "https://example.com/p/${id}"
+	ret code
+	echo "code:", code
+	match code, 200
+}
+
+run "postWithAuth", => {
+	id := "123"
+	title := "title"
+	author := "author"
+	post "https://example.com/p/${id}"
+	auth testauth
+	json {
+		"title":  title,
+		"author": author,
+	}
+	ret 200 # match resp.code, 200
+	echo "body:", resp.body
+}
+
+run "matchJsonObject", => {
+	title := Var(string)
+	author := Var(string)
+	id := "123"
+	get "https://example.com/p/${id}"
+	ret 200
+	json {
+		"title":  title,
+		"author": author,
+	}
+	echo "title:", title
+	echo "author:", author
+}
+```
+
+### spx: A Go+ 2D Game Engine for STEM education
+
+This classfile has the file suffix `.spx`. It is the earliest classfile in the world.
+
+Before using `spx`, you need to add it to `go.mod` by using `go get`:
+
+```sh
+go get github.com/goplus/spx@latest
+```
+
+It's also a built-in classfile so you don't need append `//gop:class` after `require github.com/goplus/spx`.
+
+#### tutorial/01-Weather
+
+![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/1.jpg) ![Screen Shot2](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/2.jpg)
+
+Through this example you can learn how to listen events and do somethings.
+
+Here are some codes in [Kai.spx](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/Kai.spx):
+
+```coffee
+onStart => {
+	say "Where do you come from?", 2
+	broadcast "1"
+}
+
+onMsg "2", => {
+	say "What's the climate like in your country?", 3
+	broadcast "3"
+}
+
+onMsg "4", => {
+	say "Which seasons do you like best?", 3
+	broadcast "5"
+}
+```
+
+We call `onStart` and `onMsg` to listen events. `onStart` is called when the program is started. And `onMsg` is called when someone calls `broadcast` to broadcast a message.
+
+When the program starts, Kai says `Where do you come from?`, and then broadcasts the message `1`. Who will recieve this message? Let's see codes in [Jaime.spx](https://github.com/goplus/spx/blob/main/tutorial/01-Weather/Jaime.spx):
+
+```coffee
+onMsg "1", => {
+	say "I come from England.", 2
+	broadcast "2"
+}
+
+onMsg "3", => {
+	say "It's mild, but it's not always pleasant.", 4
+	# ...
+	broadcast "4"
+}
+```
+
+Yes, Jaime recieves the message `1` and says `I come from England.`. Then he broadcasts the message `2`. Kai recieves it and says `What's the climate like in your country?`.
+
+The following procedures are very similar. In this way you can implement dialogues between multiple actors.
+
+#### tutorial/02-Dragon
+
+![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/1.jpg)
+
+Through this example you can learn how to define variables and show them on the stage.
+
+Here are all the codes of [Dragon](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/Dragon.spx):
+
+```coffee
+var (
+	score int
+)
+
+onStart => {
+	score = 0
+	for {
+		turn rand(-30, 30)
+		step 5
+		if touching("Shark") {
+			score++
+			play chomp, true
+			step -100
+		}
+	}
+}
+```
+
+We define a variable named `score` for `Dragon`. After the program starts, it moves randomly. And every time it touches `Shark`, it gains one score.
+
+How to show the `score` on the stage? You don't need write code, just add a `stageMonitor` object into [assets/index.json](https://github.com/goplus/spx/blob/main/tutorial/02-Dragon/assets/index.json):
+
+```json
+{
+  "zorder": [
+    {
+      "type": "stageMonitor",
+      "target": "Dragon",
+      "val": "getVar:score",
+      "color": 15629590,
+      "label": "score",
+      "mode": 1,
+      "x": 5,
+      "y": 5,
+      "visible": true
+    }
+  ]
+}
+```
+
+#### tutorial/03-Clone
+
+![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/1.png)
+
+Through this example you can learn:
+* Clone sprites and destory them.
+* Distinguish between sprite variables and shared variables that can access by all sprites.
+
+Here are some codes in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx):
+
+```coffee
+var (
+	id int
+)
+
+onClick => {
+	clone
+}
+
+onCloned => {
+	gid++
+	...
+}
+```
+
+When we click the sprite `Calf`, it receives an `onClick` event. Then it calls `clone` to clone itself. And after cloning, the new `Calf` sprite will receive an `onCloned` event.
+
+In `onCloned` event, the new `Calf` sprite uses a variable named `gid`. It doesn't define in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx), but in [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx).
+
+
+Here are all the codes of [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx):
+
+```coffee
+var (
+	Arrow Arrow
+	Calf  Calf
+	gid   int
+)
+
+run "res", {Title: "Clone and Destory (by Go+)"}
+```
+
+All these three variables in [main.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/main.spx) are shared by all sprites. `Arrow` and `Calf` are sprites that exist in this project. `gid` means `global id`. It is used to allocate id for all cloned `Calf` sprites.
+
+Let's back to [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx) to see the full codes of `onCloned`:
+
+```coffee
+onCloned => {
+	gid++
+	id = gid
+	step 50
+	say id, 0.5
+}
+```
+
+It increases `gid` value and assigns it to sprite `id`. This makes all these `Calf` sprites have different `id`. Then the cloned `Calf` moves forward 50 steps and says `id` of itself.
+
+Why these `Calf` sprites need different `id`? Because we want destory one of them by its `id`.
+
+Here are all the codes in [Arrow.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Arrow.spx):
+
+```coffee
+onClick => {
+	broadcast "undo", true
+	gid--
+}
+```
+
+When we click `Arrow`, it broadcasts an "undo" message (NOTE: We pass the second parameter `true` to broadcast to indicate we wait all sprites to finish processing this message).
+
+All `Calf` sprites receive this message, but only the last cloned sprite finds its `id` is equal to `gid` then destroys itself. Here are the related codes in [Calf.spx](https://github.com/goplus/spx/blob/main/tutorial/03-Clone/Calf.spx):
+
+```coffee
+onMsg "undo", => {
+	if id == gid {
+		destroy
+	}
+}
+```
+
+#### tutorial/04-Bullet
+
+![Screen Shot1](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/1.jpg)
+
+Through this example you can learn:
+* How to keep a sprite following mouse position.
+* How to fire bullets.
+
+It's simple to keep a sprite following mouse position. Here are some related codes in [MyAircraft.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/MyAircraft.spx):
+
+
+```coffee
+onStart => {
+	for {
+		# ...
+		setXYpos mouseX, mouseY
+	}
+}
+```
+
+Yes, we just need to call `setXYpos mouseX, mouseY` to follow mouse position.
+
+But how to fire bullets? Let's see all codes of [MyAircraft.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/MyAircraft.spx):
+
+```coffee
+onStart => {
+	for {
+		wait 0.1
+		Bullet.clone
+		setXYpos mouseX, mouseY
+	}
+}
+```
+
+In this example, `MyAircraft` fires bullets every 0.1 seconds. It just calls `Bullet.clone` to create a new bullet. All the rest things are the responsibility of `Bullet`.
+
+Here are all the codes in [Bullet.spx](https://github.com/goplus/spx/blob/main/tutorial/04-Bullet/Bullet.spx):
+
+```coffee
+onCloned => {
+	setXYpos MyAircraft.xpos, MyAircraft.ypos+5
+	show
+	for {
+		wait 0.04
+		changeYpos 10
+		if touching(Edge) {
+			destroy
+		}
+	}
+}
+```
+
+When a `Bullet` is cloned, it calls `setXYpos MyAircraft.xpos, MyAircraft.ypos+5` to follow `MyAircraft`'s position and shows itself (the default state of a `Bullet` is hidden). Then the `Bullet` moves forward every 0.04 seconds and this is why we see the `Bullet` is flying.
+
+At last, when the `Bullet` touches screen `Edge` or any enemy (in this example we don't have enemies), it destroys itself.
+
+These are all things about firing bullets.
diff --git a/doc/goodbye-printf.md b/doc/goodbye-printf.md
new file mode 100644
index 00000000..d329d1ed
--- /dev/null
+++ b/doc/goodbye-printf.md
@@ -0,0 +1,47 @@
+Goodbye printf
+=====
+
+For professional programmers, `printf` is a very familiar function, and it can be found in basically every language. However, `printf` is one of the most difficult functions for beginners to master.
+
+Unfortunately, formatting a piece of information and displaying it to the user is a very common operation, so one has to remember their usage. While finding its documentation over the Internet can somewhat ease the burden of using it every time, it's far from a pleasant experience.
+
+The most primitive way to format information is to use `string concat`:
+
+```go
+age := 10
+println "age = " + age.string
+```
+
+And the most classic way of formatting information is to use `printf`:
+
+```go
+age := 10
+printf "age = %d\n", age
+```
+
+Here `%d` means to format an integer value and `\n` means a newline.
+
+To simplify format information in most cases, Go+ introduces `${expr}` expressions in string literals. For above example, you can replace `age.string` to `"${age}"`:
+
+```go
+age := 10
+println "age = ${age}"
+```
+
+Here is a more complex example of `${expr}`:
+
+```go
+host := "foo.com"
+page := 0
+limit := 20
+println "https://${host}/items?page=${page+1}&limit=${limit}" // https://foo.com/items?page=1&limit=20
+println "$$" // $
+```
+
+This is a bit like how you feel at the `*nix` command line, right? To be more like it, we introduced a new builtin `echo` as an alias for `println`:
+
+```go
+age := 10
+echo "age = ${age}"
+```
+
diff --git a/doc/overload.md b/doc/overload.md
new file mode 100644
index 00000000..d977c562
--- /dev/null
+++ b/doc/overload.md
@@ -0,0 +1,155 @@
+Overload Func/Method/Operator/Types in Go+
+=====
+
+### Overload Funcs
+
+Define `overload func` in `inline func literal` style (see [overloadfunc1/add.gop](testdata/overloadfunc1/add.gop)):
+
+```go
+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")
+```
+
+Define `overload func` in `ident` style (see [overloadfunc2/mul.gop](testdata/overloadfunc2/mul.gop)):
+
+```go
+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)
+```
+
+### Overload Methods
+
+Define `overload method` (see [overloadmethod/method.gop](testdata/overloadmethod/method.gop)):
+
+```go
+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)
+```
+
+### Overload Unary Operators
+
+Define `overload unary operator` (see [overloadop1/overloadop.gop](testdata/overloadop1/overloadop.gop)):
+
+```go
+type foo struct {
+}
+
+func -(a foo) (ret foo) {
+	println "-a"
+	return
+}
+
+func ++(a foo) {
+	println "a++"
+}
+
+var a foo
+var b = -a
+a++
+```
+
+### Overload Binary Operators
+
+Define `overload binary operator` (see [overloadop1/overloadop.gop](testdata/overloadop1/overloadop.gop)):
+
+```go
+type foo struct {
+}
+
+func (a foo) * (b foo) (ret foo) {
+	println "a * b"
+	return
+}
+
+func (a foo) != (b foo) bool {
+	println "a != b"
+	return true
+}
+
+var a, b foo
+var c = a * b
+var d = a != b
+```
+
+However, `binary operator` usually need to support interoperability between multiple types. In this case it becomes more complex (see [overloadop2/overloadop.gop](testdata/overloadop2/overloadop.gop)):
+
+```go
+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
+```
+
+### Overload Types
+
+TODO
+
+### Overload Typecast
+
+TODO
+
diff --git a/go.mod b/go.mod
index bb02a687..d2e72725 100644
--- a/go.mod
+++ b/go.mod
@@ -4,10 +4,10 @@ go 1.18
 
 require (
 	github.com/fsnotify/fsnotify v1.7.0
-	github.com/goplus/c2go v0.7.20
-	github.com/goplus/gox v1.14.1
-	github.com/goplus/mod v0.12.3
-	github.com/qiniu/x v1.13.2
+	github.com/goplus/c2go v0.7.21
+	github.com/goplus/gox v1.14.2
+	github.com/goplus/mod v0.13.0
+	github.com/qiniu/x v1.13.3
 	golang.org/x/tools v0.17.0
 )
 
diff --git a/go.sum b/go.sum
index f4de2102..ec0dff2a 100644
--- a/go.sum
+++ b/go.sum
@@ -4,14 +4,12 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
 github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
-github.com/goplus/c2go v0.7.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/goplus/c2go v0.7.21 h1:bRWda6KtQdY0ph+tnPEOGzZMq3OUkAI9cJBzrr5N6AM=
+github.com/goplus/c2go v0.7.21/go.mod h1:45zjbWGW8NDuGfTOrFchCIpH9TbiH7TPg8/zF6EdEyk=
+github.com/goplus/gox v1.14.2 h1:sc2W+R3D6j1EZ6G+/KWMUDSu3m6dcscZCvnTH2M4XG4=
+github.com/goplus/gox v1.14.2/go.mod h1:G7Hz+cAOUyJyN9pPHrpqhfQPDUtiJNmoRVTwoaQ9nw0=
+github.com/goplus/mod v0.13.0 h1:M/VsI/NWTcs2+vtWBG7793zmlCimKlSMY5LXaiztXV8=
+github.com/goplus/mod v0.13.0/go.mod h1:Qin2SxKoK+3cNFwtg4Jnk1F9Qn5vR6Kwlvr6lTCRJNI=
 github.com/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=
@@ -20,8 +18,8 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G
 github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/qiniu/x v1.13.2 h1:mgWOfB9Rpk6AEtlBoObZVxH+b2FHSntYrxc4KX5Ta98=
-github.com/qiniu/x v1.13.2/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
+github.com/qiniu/x v1.13.3 h1:NER9aJnVzjH0XapzIWrWNAn2SPwck0xGMyIIlfCMm84=
+github.com/qiniu/x v1.13.3/go.mod h1:INZ2TSWSJVWO/RuELQROERcslBwVgFG7MkTfEdaQz9E=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
diff --git a/parser/parser.go b/parser/parser.go
index 97423f23..1c011e92 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -365,7 +365,7 @@ func (p *parser) next() {
 		var comment *ast.CommentGroup
 		var endline int
 
-		if p.file.Line(p.pos) == p.file.Line(prev) {
+		if p.file.Line(p.pos) == p.file.Line(prev) || p.lit[0] == '#' {
 			// The comment is on same line as the previous token; it
 			// cannot be a lead comment but may be a line comment.
 			comment, endline = p.consumeCommentGroup(0)
diff --git a/x/typesutil/gopinfo.go b/x/typesutil/gopinfo.go
index b9843279..e1594d39 100644
--- a/x/typesutil/gopinfo.go
+++ b/x/typesutil/gopinfo.go
@@ -20,6 +20,7 @@ import (
 	"go/types"
 
 	"github.com/goplus/gop/ast"
+	"github.com/goplus/gox"
 	"github.com/qiniu/x/log"
 )
 
@@ -128,6 +129,9 @@ type Info struct {
 	// in source order. Variables without an initialization expression do not
 	// appear in this list.
 	// InitOrder []*Initializer
+
+	// Overloads maps identifiers to the overload objects.
+	Overloads map[*ast.Ident][]types.Object
 }
 
 // ObjectOf returns the object denoted by the specified id,
@@ -238,6 +242,16 @@ func (info gopRecorder) Use(id *ast.Ident, obj types.Object) {
 	if info.Uses != nil {
 		info.Uses[id] = obj
 	}
+	if info.Overloads != nil {
+		if sig, ok := obj.Type().(*types.Signature); ok {
+			if ext, objs := gox.CheckSigFuncExObjects(sig); len(objs) > 1 {
+				if debugVerbose {
+					log.Println("==> Overloads:", id, ext)
+				}
+				info.Overloads[id] = objs
+			}
+		}
+	}
 }
 
 // Implicit maps nodes to their implicitly declared objects, if any.


Test patch:
diff --git a/cl/compile_gop_test.go b/cl/compile_gop_test.go
index e3ff38b4..b09eaf65 100644
--- a/cl/compile_gop_test.go
+++ b/cl/compile_gop_test.go
@@ -255,6 +255,588 @@ func main() {
 `)
 }
 
+func TestOverload(t *testing.T) {
+	gopClTest(t, `
+import "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var (
+	m1 = &Mesh{}
+	m2 = &Mesh{}
+)
+
+foo.onKey "hello", => {
+}
+foo.onKey "hello", key => {
+}
+foo.onKey ["1"], => {
+}
+foo.onKey ["2"], key => {
+}
+foo.onKey [m1, m2], => {
+}
+foo.onKey [m1, m2], key => {
+}
+foo.onKey ["a"], ["b"], key => {
+}
+foo.onKey ["a"], [m1, m2], key => {
+}
+foo.onKey ["a"], nil, key => {
+}
+foo.onKey 100, 200
+
+n := &foo.N{}
+n.onKey "hello", => {
+}
+n.onKey "hello", key => {
+}
+n.onKey ["1"], => {
+}
+n.onKey ["2"], key => {
+}
+n.onKey [m1, m2], => {
+}
+n.onKey [m1, m2], key => {
+}
+n.onKey ["a"], ["b"], key => {
+}
+n.onKey ["a"], [m1, m2], key => {
+}
+n.onKey ["a"], nil, key => {
+}
+n.onKey 100, 200
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/overload/foo"
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var m1 = &Mesh{}
+var m2 = &Mesh{}
+
+func main() {
+	foo.OnKey__0("hello", func() {
+	})
+	foo.OnKey__1("hello", func(key string) {
+	})
+	foo.OnKey__2([]string{"1"}, func() {
+	})
+	foo.OnKey__3([]string{"2"}, func(key string) {
+	})
+	foo.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	foo.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	foo.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	foo.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	foo.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	foo.OnKey__8(100, 200)
+	n := &foo.N{}
+	n.OnKey__0("hello", func() {
+	})
+	n.OnKey__1("hello", func(key string) {
+	})
+	n.OnKey__2([]string{"1"}, func() {
+	})
+	n.OnKey__3([]string{"2"}, func(key string) {
+	})
+	n.OnKey__4([]foo.Mesher{m1, m2}, func() {
+	})
+	n.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
+	})
+	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	n.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
+	})
+	n.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	n.OnKey__8(100, 200)
+}
+`)
+}
+
+func TestMixedOverload(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+type Mesher interface {
+	Name() string
+}
+
+type N struct {
+}
+
+func (m *N) OnKey__0(a string, fn func()) {
+}
+
+func (m *N) OnKey__1(a string, fn func(key string)) {
+}
+
+func (m *N) OnKey__2(a []string, fn func()) {
+}
+
+func (m *N) OnKey__3(a []string, fn func(key string)) {
+}
+
+func (m *N) OnKey__4(a []Mesher, fn func()) {
+}
+
+func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) {
+}
+
+func (m *N) OnKey__6(a []string, b []string, fn func(key string)) {
+}
+
+func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) {
+}
+
+func (m *N) OnKey__8(x int, y int) {
+}
+
+
+func OnKey__0(a string, fn func()) {
+}
+
+func OnKey__1(a string, fn func(key string)) {
+}
+
+func OnKey__2(a []string, fn func()) {
+}
+
+func OnKey__3(a []string, fn func(key string)) {
+}
+
+func OnKey__4(a []Mesher, fn func()) {
+}
+
+func OnKey__5(a []Mesher, fn func(key Mesher)) {
+}
+
+func OnKey__6(a []string, b []string, fn func(key string)) {
+}
+
+func OnKey__7(a []string, b []Mesher, fn func(key string)) {
+}
+
+func OnKey__8(x int, y int) {
+}
+
+func OnKey__9(a, b string, fn ...func(x int) int) {
+}
+
+func OnKey__a(a, b string, v ...int) {
+}
+`, `
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var (
+	m1 = &Mesh{}
+	m2 = &Mesh{}
+)
+
+OnKey "hello", => {
+}
+OnKey "hello", key => {
+}
+OnKey ["1"], => {
+}
+OnKey ["2"], key => {
+}
+OnKey [m1, m2], => {
+}
+OnKey [m1, m2], key => {
+}
+OnKey ["a"], ["b"], key => {
+}
+OnKey ["a"], [m1, m2], key => {
+}
+OnKey ["a"], nil, key => {
+}
+OnKey 100, 200
+OnKey "a", "b", x => x * x, x => {
+	return x * 2
+}
+OnKey "a", "b", 1, 2, 3
+OnKey("a", "b", [1, 2, 3]...)
+
+n := &N{}
+n.onKey "hello", => {
+}
+n.onKey "hello", key => {
+}
+n.onKey ["1"], => {
+}
+n.onKey ["2"], key => {
+}
+n.onKey [m1, m2], => {
+}
+n.onKey [m1, m2], key => {
+}
+n.onKey ["a"], ["b"], key => {
+}
+n.onKey ["a"], [m1, m2], key => {
+}
+n.onKey ["a"], nil, key => {
+}
+n.onKey 100, 200
+`, `package main
+
+type Mesh struct {
+}
+
+func (p *Mesh) Name() string {
+	return "hello"
+}
+
+var m1 = &Mesh{}
+var m2 = &Mesh{}
+
+func main() {
+	OnKey__0("hello", func() {
+	})
+	OnKey__1("hello", func(key string) {
+	})
+	OnKey__2([]string{"1"}, func() {
+	})
+	OnKey__3([]string{"2"}, func(key string) {
+	})
+	OnKey__4([]Mesher{m1, m2}, func() {
+	})
+	OnKey__5([]Mesher{m1, m2}, func(key Mesher) {
+	})
+	OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) {
+	})
+	OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	OnKey__8(100, 200)
+	OnKey__9("a", "b", func(x int) int {
+		return x * x
+	}, func(x int) int {
+		return x * 2
+	})
+	OnKey__a("a", "b", 1, 2, 3)
+	OnKey__a("a", "b", []int{1, 2, 3}...)
+	n := &N{}
+	n.OnKey__0("hello", func() {
+	})
+	n.OnKey__1("hello", func(key string) {
+	})
+	n.OnKey__2([]string{"1"}, func() {
+	})
+	n.OnKey__3([]string{"2"}, func(key string) {
+	})
+	n.OnKey__4([]Mesher{m1, m2}, func() {
+	})
+	n.OnKey__5([]Mesher{m1, m2}, func(key Mesher) {
+	})
+	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
+	})
+	n.OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) {
+	})
+	n.OnKey__6([]string{"a"}, nil, func(key string) {
+	})
+	n.OnKey__8(100, 200)
+}
+`)
+}
+
+func TestMixedOverloadOp(t *testing.T) {
+	gopMixedClTest(t, "main", `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() *foo {
+	fmt.Println("-a")
+	return &foo{}
+}
+func (a foo) Gop_Inc() {
+	fmt.Println("a++")
+}
+`, `
+var a, b foo
+var c = a - b
+var d = -a
+var e = a!=b
+`, `package main
+
+var a, b foo
+var c = (foo).Gop_Sub(a, b)
+var d = a.Gop_Neg()
+var e = (foo).Gop_NE(a, b)
+`)
+}
+
+func TestMixedVector3(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+type Vector3 struct {
+	x, y, z float64
+}
+func (a Vector3) Gop_Add__0(n int) Vector3 {
+	return Vector3{}
+}
+func (a Vector3) Gop_Add__1(n float64) Vector3 {
+	return Vector3{}
+}
+func (a Vector3) Gop_Add__2(n Vector3) Vector3 {
+	return Vector3{}
+}
+func (a *Vector3) Gop_AddAssign(n Vector3) {
+}
+
+func (a Vector3) Gop_Rcast__0() int {
+	return 0
+}
+func (a Vector3) Gop_Rcast__1() float64 {
+	return 0
+}
+
+func Vector3_Cast__0(x int) Vector3 {
+	return Vector3{}
+}
+func Vector3_Cast__1(x float64) Vector3 {
+	return Vector3{}
+}
+func Vector3_Init__0(x int) Vector3 {
+	return Vector3{}
+}
+func Vector3_Init__1(x float64) Vector3 {
+	return Vector3{}
+}
+`, `
+var a Vector3
+var b int
+var c float64
+_ = a+b
+_ = a+100
+_ = a+c
+_ = 100+a
+_ = Vector3(b)+a
+_ = b+int(a)
+a += b
+a += c
+`, `package main
+
+var a Vector3
+var b int
+var c float64
+
+func main() {
+	_ = (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))
+}
+`)
+}
+
+func TestMixedInterfaceOverload(t *testing.T) {
+	gopMixedClTest(t, "main", `
+package main
+
+type N[T any] struct {
+	v T
+}
+
+func (m *N[T]) OnKey__0(a string, fn func()) {
+}
+
+func (m *N[T]) OnKey__1(a string, fn func(key string)) {
+}
+
+func (m *N[T]) OnKey__2(a []string, fn func()) {
+}
+
+func (m *N[T]) OnKey__3(a []string, fn func(key string)) {
+}
+
+type I interface {
+	OnKey__0(a string, fn func())
+	OnKey__1(a string, fn func(key string))
+	OnKey__2(a []string, fn func())
+	OnKey__3(a []string, fn func(key string))
+}
+`, `
+n := &N[int]{}
+n.onKey "1", => {
+}
+keys := ["1","2"]
+n.onKey keys, key => {
+	println key
+}
+n.onKey keys, => {
+	println keys
+}
+
+var i I = n
+i.onKey "1", key => {
+	println key
+}
+i.onKey ["1","2"], key => {
+	println key
+}
+`, `package main
+
+import "fmt"
+
+func main() {
+	n := &N[int]{}
+	n.OnKey__0("1", func() {
+	})
+	keys := []string{"1", "2"}
+	n.OnKey__3(keys, func(key string) {
+		fmt.Println(key)
+	})
+	n.OnKey__2(keys, func() {
+		fmt.Println(keys)
+	})
+	var i I = n
+	i.OnKey__1("1", func(key string) {
+		fmt.Println(key)
+	})
+	i.OnKey__3([]string{"1", "2"}, func(key string) {
+		fmt.Println(key)
+	})
+}
+`)
+}
+
+func TestMixedOverloadCommand(t *testing.T) {
+	gopMixedClTest(t, "main", `package main
+
+func Test__0() {
+}
+func Test__1(n int) {
+}
+type N struct {
+}
+func (p *N) Test__0() {
+}
+func (p *N) Test__1(n int) {
+}`, `
+Test
+Test 100
+var n N
+n.test
+n.test 100
+`, `package main
+
+func main() {
+	Test__0()
+	Test__1(100)
+	var n N
+	n.Test__0()
+	n.Test__1(100)
+}
+`)
+}
+
+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 {
+	}]()
+}
+`)
+}
+
 func TestStringLitBasic(t *testing.T) {
 	gopClTest(t, `echo "$$"`, `package main
 
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 03f289e4..32041a5f 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -50,7 +50,8 @@ func lookupClass(ext string) (c *modfile.Project, ok bool) {
 		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
+			PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx3", "math"},
+			Import:   []*modfile.Import{{Path: "github.com/goplus/gop/cl/internal/spx3/jwt"}}}, true
 	case "_xtest.gox":
 		return &modfile.Project{
 			Ext: "_xtest.gox", Class: "App",
@@ -168,6 +169,8 @@ func onInit() {
 	for {
 	}
 }
+
+initGameApp
 `, `
 func onMsg(msg string) {
 	for {
@@ -181,18 +184,22 @@ import "github.com/goplus/gop/cl/internal/spx"
 type Game struct {
 	*spx.MyGame
 }
+type Kai struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) onInit() {
 	for {
 		spx.SchedNow()
 	}
 }
-
-type Kai struct {
-	spx.Sprite
-	*Game
+func (this *Game) MainEntry() {
+	this.InitGameApp()
+}
+func main() {
+	spx.Gopt_MyGame_Main(new(Game))
 }
-
 func (this *Kai) onMsg(msg string) {
 	for {
 		spx.Sched()
@@ -229,6 +236,10 @@ import (
 
 const Foo = 1
 
+type bar struct {
+	spx.Sprite
+	*index
+}
 type index struct {
 	*spx.MyGame
 }
@@ -267,6 +278,10 @@ import (
 type Game struct {
 	*spx.MyGame
 }
+type bar struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) onInit() {
 	spx.Sched()
@@ -274,12 +289,6 @@ func (this *Game) onInit() {
 	spx.TestIntValue = 1
 	x := math.Round(1.2)
 }
-
-type bar struct {
-	spx.Sprite
-	*Game
-}
-
 func (this *bar) onInit() {
 	this.SetCostume("kai-a")
 	this.Play("recordingWhere")
@@ -315,15 +324,15 @@ func onCloned() {
 
 import "github.com/goplus/gop/cl/internal/spx"
 
+type Game struct {
+	*spx.MyGame
+	Kai Kai
+}
 type Kai struct {
 	spx.Sprite
 	*Game
 	a int
 }
-type Game struct {
-	*spx.MyGame
-	Kai Kai
-}
 
 func (this *Game) onInit() {
 	spx.Gopt_Sprite_Clone__0(this.Kai)
@@ -361,15 +370,14 @@ type Kai struct {
 	spx.Sprite
 	*index
 }
-
-var x float64 = spx.Rand__1(1.2)
-
 type index struct {
 	*spx.MyGame
 	Kai Kai
 	t   spx.Sound
 }
 
+var x float64 = spx.Rand__1(1.2)
+
 func (this *index) MainEntry() {
 	spx.Gopt_MyGame_Run(this, "hzip://open.qiniu.us/weather/res.zip")
 }
@@ -390,12 +398,13 @@ var (
 
 run
 `, `
-echo "Hi"
+echo jwt.token("Hi")
 `, `package main
 
 import (
 	"fmt"
 	"github.com/goplus/gop/cl/internal/spx3"
+	"github.com/goplus/gop/cl/internal/spx3/jwt"
 )
 
 type Kai struct {
@@ -414,7 +423,40 @@ func main() {
 	spx3.Gopt_Game_Main(new(Game), new(Kai))
 }
 func (this *Kai) Main() {
-	fmt.Println("Hi")
+	fmt.Println(jwt.Token("Hi"))
+}
+`, "main_spx.gox", "Kai_spx.gox")
+}
+
+func TestSpxNewObj(t *testing.T) {
+	gopSpxTestEx(t, `
+a := new
+a.run
+b := new(Sprite)
+echo b.name
+`, ``, `package main
+
+import (
+	"fmt"
+	"github.com/goplus/gop/cl/internal/spx3"
+)
+
+type Kai struct {
+	spx3.Sprite
+	*Game
+}
+type Game struct {
+	spx3.Game
+}
+
+func (this *Game) MainEntry() {
+	a := spx3.New()
+	a.Run()
+	b := new(spx3.Sprite)
+	fmt.Println(b.Name())
+}
+func main() {
+	spx3.Gopt_Game_Main(new(Game), new(Kai))
 }
 `, "main_spx.gox", "Kai_spx.gox")
 }
@@ -435,6 +477,10 @@ import (
 type Game struct {
 	spx2.Game
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
@@ -442,12 +488,6 @@ func (this *Game) MainEntry() {
 func main() {
 	new(Game).Main()
 }
-
-type Kai struct {
-	spx2.Sprite
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx")
@@ -469,6 +509,10 @@ import (
 type Game struct {
 	spx2.Game
 }
+type Kai struct {
+	spx2.Sprite2
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("Hi, Sprite2")
@@ -476,12 +520,6 @@ func (this *Game) MainEntry() {
 func main() {
 	new(Game).Main()
 }
-
-type Kai struct {
-	spx2.Sprite2
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.t2gmx", "Kai.t2spx2")
@@ -503,15 +541,13 @@ import (
 type Dog struct {
 	spx2.Sprite
 }
-
-func (this *Dog) Main() {
-	fmt.Println("Hi, Sprite")
-}
-
 type Kai struct {
 	spx2.Sprite2
 }
 
+func (this *Dog) Main() {
+	fmt.Println("Hi, Sprite")
+}
 func (this *Kai) onMsg(msg string) {
 }
 `, "Dog_t3spx.gox", "Kai.t3spx2")
@@ -531,6 +567,10 @@ import "github.com/goplus/gop/cl/internal/spx2"
 type Game struct {
 	spx2.Game
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 }
@@ -547,14 +587,14 @@ var (
 
 import "github.com/goplus/gop/cl/internal/spx2"
 
-type Kai struct {
-	spx2.Sprite
-	*Game
-}
 type Game struct {
 	spx2.Game
 	Kai Kai
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 }
@@ -583,14 +623,14 @@ import (
 	"github.com/goplus/gop/cl/internal/spx2"
 )
 
-type Kai struct {
-	spx2.Sprite
-	*Game
-}
 type Game struct {
 	spx2.Game
 	Kai Kai
 }
+type Kai struct {
+	spx2.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
@@ -625,18 +665,16 @@ import "github.com/goplus/gop/cl/internal/spx"
 type Game struct {
 	*spx.MyGame
 }
+type Kai struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) onInit() {
 	for {
 		spx.SchedNow()
 	}
 }
-
-type Kai struct {
-	spx.Sprite
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 	for {
 		spx.Sched()
@@ -684,15 +722,15 @@ type info struct {
 	x int
 	y int
 }
+type Game struct {
+	*spx.MyGame
+	Kai Kai
+}
 type Kai struct {
 	spx.Sprite
 	*Game
 	a int
 }
-type Game struct {
-	*spx.MyGame
-	Kai Kai
-}
 
 func (this *Game) onInit() {
 	spx.Gopt_Sprite_Clone__0(this.Kai)
@@ -731,6 +769,10 @@ import "github.com/goplus/gop/cl/internal/spx"
 type Game struct {
 	*spx.MyGame
 }
+type Kai struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	this.SendMessage("Hi")
@@ -738,12 +780,6 @@ func (this *Game) MainEntry() {
 func main() {
 	spx.Gopt_MyGame_Main(new(Game))
 }
-
-type Kai struct {
-	spx.Sprite
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 }
 `, "Game.tgmx", "Kai.tspx")
@@ -766,6 +802,10 @@ import (
 type Game struct {
 	*spx.MyGame
 }
+type Kai struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
@@ -773,12 +813,6 @@ func (this *Game) MainEntry() {
 func main() {
 	spx.Gopt_MyGame_Main(new(Game))
 }
-
-type Kai struct {
-	spx.Sprite
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 	this.Position().Add__0(100, 200)
 }
@@ -815,6 +849,10 @@ import (
 type Game struct {
 	*spx.MyGame
 }
+type Kai struct {
+	spx.Sprite
+	*Game
+}
 
 func (this *Game) MainEntry() {
 	fmt.Println("hi")
@@ -822,12 +860,6 @@ func (this *Game) MainEntry() {
 func main() {
 	spx.Gopt_MyGame_Main(new(Game))
 }
-
-type Kai struct {
-	spx.Sprite
-	*Game
-}
-
 func (this *Kai) onMsg(msg string) {
 	fmt.Println(msg)
 	this.Position().Add__0(100, 200)
@@ -900,15 +932,15 @@ import "github.com/goplus/gop/cl/internal/spx"
 
 type Mesh struct {
 }
+type Game struct {
+	*spx.MyGame
+	Kai Kai
+}
 type Kai struct {
 	spx.Sprite
 	*Game
 	a int
 }
-type Game struct {
-	*spx.MyGame
-	Kai Kai
-}
 
 func (this *Game) onInit() {
 	spx.Gopt_Sprite_OnKey__1(this.Kai, "hello", func(key string) {
@@ -963,6 +995,10 @@ import (
 	"testing"
 )
 
+type caseFoo struct {
+	test.Case
+	*App
+}
 type App struct {
 	test.App
 }
@@ -970,12 +1006,6 @@ type App struct {
 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) {
@@ -1015,3 +1045,148 @@ func Test_foo(t *testing.T) {
 }
 `, "main.gox", "foo_xtest.gox", "_test")
 }
+
+func TestGopxNoFunc(t *testing.T) {
+	gopClTestFile(t, `
+var (
+	a int
+)
+`, `package main
+
+type foo struct {
+	a int
+}
+`, "foo.gox")
+}
+
+func TestClassFileGopx(t *testing.T) {
+	gopClTestFile(t, `
+var (
+	BaseClass
+	Width, Height float64
+	*AggClass
+)
+
+type BaseClass struct{
+	x int
+	y int
+}
+type AggClass struct{}
+
+func Area() float64 {
+	return Width * Height
+}
+`, `package main
+
+type BaseClass struct {
+	x int
+	y int
+}
+type AggClass struct {
+}
+type Rect struct {
+	BaseClass
+	Width  float64
+	Height float64
+	*AggClass
+}
+
+func (this *Rect) Area() float64 {
+	return this.Width * this.Height
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import "bytes"
+
+type Rect struct {
+	bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer
+)
+func test(){}
+`, `package main
+
+import "bytes"
+
+type Rect struct {
+	*bytes.Buffer
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+	gopClTestFile(t, `
+import "bytes"
+var (
+	*bytes.Buffer "spec:\"buffer\""
+	a int "json:\"a\""
+	b int
+)
+func test(){}
+`, `package main
+
+import "bytes"
+
+type Rect struct {
+	*bytes.Buffer `+"`spec:\"buffer\"`"+`
+	a             int `+"`json:\"a\"`"+`
+	b             int
+}
+
+func (this *Rect) test() {
+}
+`, "Rect.gox")
+}
+
+func TestClassFileMember(t *testing.T) {
+	gopClTestFile(t, `type Engine struct {
+}
+
+func (e *Engine) EnterPointerLock() {
+}
+
+func (e *Engine) SetEnable(b bool) {
+}
+
+func Engine() *Engine {
+	return &Engine{}
+}
+
+func Test() {
+	engine.setEnable true
+	engine.enterPointerLock
+}
+`, `package main
+
+type Engine struct {
+}
+type Rect struct {
+}
+
+func (e *Engine) EnterPointerLock() {
+}
+func (e *Engine) SetEnable(b bool) {
+}
+func (this *Rect) Engine() *Engine {
+	return &Engine{}
+}
+func (this *Rect) Test() {
+	this.Engine().SetEnable(true)
+	this.Engine().EnterPointerLock()
+}
+`, "Rect.gox")
+}
diff --git a/cl/compile_test.go b/cl/compile_test.go
index 8ddf572a..9c17a0b5 100644
--- a/cl/compile_test.go
+++ b/cl/compile_test.go
@@ -4113,255 +4113,6 @@ func main() {
 `)
 }
 
-func TestClassFileGopx(t *testing.T) {
-	gopClTestFile(t, `
-var (
-	BaseClass
-	Width, Height float64
-	*AggClass
-)
-
-type BaseClass struct{
-	x int
-	y int
-}
-type AggClass struct{}
-
-func Area() float64 {
-	return Width * Height
-}
-`, `package main
-
-type BaseClass struct {
-	x int
-	y int
-}
-type AggClass struct {
-}
-type Rect struct {
-	BaseClass
-	Width  float64
-	Height float64
-	*AggClass
-}
-
-func (this *Rect) Area() float64 {
-	return this.Width * this.Height
-}
-`, "Rect.gox")
-	gopClTestFile(t, `
-import "bytes"
-var (
-	bytes.Buffer
-)
-func test(){}
-`, `package main
-
-import "bytes"
-
-type Rect struct {
-	bytes.Buffer
-}
-
-func (this *Rect) test() {
-}
-`, "Rect.gox")
-	gopClTestFile(t, `
-import "bytes"
-var (
-	*bytes.Buffer
-)
-func test(){}
-`, `package main
-
-import "bytes"
-
-type Rect struct {
-	*bytes.Buffer
-}
-
-func (this *Rect) test() {
-}
-`, "Rect.gox")
-	gopClTestFile(t, `
-import "bytes"
-var (
-	*bytes.Buffer "spec:\"buffer\""
-	a int "json:\"a\""
-	b int
-)
-func test(){}
-`, `package main
-
-import "bytes"
-
-type Rect struct {
-	*bytes.Buffer `+"`spec:\"buffer\"`"+`
-	a             int `+"`json:\"a\"`"+`
-	b             int
-}
-
-func (this *Rect) test() {
-}
-`, "Rect.gox")
-}
-
-func TestOverload(t *testing.T) {
-	gopClTest(t, `
-import "github.com/goplus/gop/cl/internal/overload/foo"
-
-type Mesh struct {
-}
-
-func (p *Mesh) Name() string {
-	return "hello"
-}
-
-var (
-	m1 = &Mesh{}
-	m2 = &Mesh{}
-)
-
-foo.onKey "hello", => {
-}
-foo.onKey "hello", key => {
-}
-foo.onKey ["1"], => {
-}
-foo.onKey ["2"], key => {
-}
-foo.onKey [m1, m2], => {
-}
-foo.onKey [m1, m2], key => {
-}
-foo.onKey ["a"], ["b"], key => {
-}
-foo.onKey ["a"], [m1, m2], key => {
-}
-foo.onKey ["a"], nil, key => {
-}
-foo.onKey 100, 200
-
-n := &foo.N{}
-n.onKey "hello", => {
-}
-n.onKey "hello", key => {
-}
-n.onKey ["1"], => {
-}
-n.onKey ["2"], key => {
-}
-n.onKey [m1, m2], => {
-}
-n.onKey [m1, m2], key => {
-}
-n.onKey ["a"], ["b"], key => {
-}
-n.onKey ["a"], [m1, m2], key => {
-}
-n.onKey ["a"], nil, key => {
-}
-n.onKey 100, 200
-`, `package main
-
-import "github.com/goplus/gop/cl/internal/overload/foo"
-
-type Mesh struct {
-}
-
-func (p *Mesh) Name() string {
-	return "hello"
-}
-
-var m1 = &Mesh{}
-var m2 = &Mesh{}
-
-func main() {
-	foo.OnKey__0("hello", func() {
-	})
-	foo.OnKey__1("hello", func(key string) {
-	})
-	foo.OnKey__2([]string{"1"}, func() {
-	})
-	foo.OnKey__3([]string{"2"}, func(key string) {
-	})
-	foo.OnKey__4([]foo.Mesher{m1, m2}, func() {
-	})
-	foo.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
-	})
-	foo.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
-	})
-	foo.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
-	})
-	foo.OnKey__6([]string{"a"}, nil, func(key string) {
-	})
-	foo.OnKey__8(100, 200)
-	n := &foo.N{}
-	n.OnKey__0("hello", func() {
-	})
-	n.OnKey__1("hello", func(key string) {
-	})
-	n.OnKey__2([]string{"1"}, func() {
-	})
-	n.OnKey__3([]string{"2"}, func(key string) {
-	})
-	n.OnKey__4([]foo.Mesher{m1, m2}, func() {
-	})
-	n.OnKey__5([]foo.Mesher{m1, m2}, func(key foo.Mesher) {
-	})
-	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
-	})
-	n.OnKey__7([]string{"a"}, []foo.Mesher{m1, m2}, func(key string) {
-	})
-	n.OnKey__6([]string{"a"}, nil, func(key string) {
-	})
-	n.OnKey__8(100, 200)
-}
-`)
-}
-
-func TestClassFileMember(t *testing.T) {
-	gopClTestFile(t, `type Engine struct {
-}
-
-func (e *Engine) EnterPointerLock() {
-}
-
-func (e *Engine) SetEnable(b bool) {
-}
-
-
-func Engine() *Engine {
-	return &Engine{}
-}
-
-func Test() {
-	engine.setEnable true
-	engine.enterPointerLock
-}
-`, `package main
-
-type Engine struct {
-}
-
-func (e *Engine) EnterPointerLock() {
-}
-func (e *Engine) SetEnable(b bool) {
-}
-
-type Rect struct {
-}
-
-func (this *Rect) Engine() *Engine {
-	return &Engine{}
-}
-func (this *Rect) Test() {
-	this.Engine().SetEnable(true)
-	this.Engine().EnterPointerLock()
-}
-`, "Rect.gox")
-}
-
 func TestCommentLine(t *testing.T) {
 	gopClTestEx(t, gblConfLine, "main", `
 type Point struct {
@@ -4468,471 +4219,3 @@ func main() {
 `
 	gopClTestEx(t, &conf, "main", src, expected)
 }
-
-func TestMixedOverload(t *testing.T) {
-	gopMixedClTest(t, "main", `
-package main
-
-type Mesher interface {
-	Name() string
-}
-
-type N struct {
-}
-
-func (m *N) OnKey__0(a string, fn func()) {
-}
-
-func (m *N) OnKey__1(a string, fn func(key string)) {
-}
-
-func (m *N) OnKey__2(a []string, fn func()) {
-}
-
-func (m *N) OnKey__3(a []string, fn func(key string)) {
-}
-
-func (m *N) OnKey__4(a []Mesher, fn func()) {
-}
-
-func (m *N) OnKey__5(a []Mesher, fn func(key Mesher)) {
-}
-
-func (m *N) OnKey__6(a []string, b []string, fn func(key string)) {
-}
-
-func (m *N) OnKey__7(a []string, b []Mesher, fn func(key string)) {
-}
-
-func (m *N) OnKey__8(x int, y int) {
-}
-
-
-func OnKey__0(a string, fn func()) {
-}
-
-func OnKey__1(a string, fn func(key string)) {
-}
-
-func OnKey__2(a []string, fn func()) {
-}
-
-func OnKey__3(a []string, fn func(key string)) {
-}
-
-func OnKey__4(a []Mesher, fn func()) {
-}
-
-func OnKey__5(a []Mesher, fn func(key Mesher)) {
-}
-
-func OnKey__6(a []string, b []string, fn func(key string)) {
-}
-
-func OnKey__7(a []string, b []Mesher, fn func(key string)) {
-}
-
-func OnKey__8(x int, y int) {
-}
-
-func OnKey__9(a, b string, fn ...func(x int) int) {
-}
-
-func OnKey__a(a, b string, v ...int) {
-}
-`, `
-type Mesh struct {
-}
-
-func (p *Mesh) Name() string {
-	return "hello"
-}
-
-var (
-	m1 = &Mesh{}
-	m2 = &Mesh{}
-)
-
-OnKey "hello", => {
-}
-OnKey "hello", key => {
-}
-OnKey ["1"], => {
-}
-OnKey ["2"], key => {
-}
-OnKey [m1, m2], => {
-}
-OnKey [m1, m2], key => {
-}
-OnKey ["a"], ["b"], key => {
-}
-OnKey ["a"], [m1, m2], key => {
-}
-OnKey ["a"], nil, key => {
-}
-OnKey 100, 200
-OnKey "a", "b", x => x * x, x => {
-	return x * 2
-}
-OnKey "a", "b", 1, 2, 3
-OnKey("a", "b", [1, 2, 3]...)
-
-n := &N{}
-n.onKey "hello", => {
-}
-n.onKey "hello", key => {
-}
-n.onKey ["1"], => {
-}
-n.onKey ["2"], key => {
-}
-n.onKey [m1, m2], => {
-}
-n.onKey [m1, m2], key => {
-}
-n.onKey ["a"], ["b"], key => {
-}
-n.onKey ["a"], [m1, m2], key => {
-}
-n.onKey ["a"], nil, key => {
-}
-n.onKey 100, 200
-`, `package main
-
-type Mesh struct {
-}
-
-func (p *Mesh) Name() string {
-	return "hello"
-}
-
-var m1 = &Mesh{}
-var m2 = &Mesh{}
-
-func main() {
-	OnKey__0("hello", func() {
-	})
-	OnKey__1("hello", func(key string) {
-	})
-	OnKey__2([]string{"1"}, func() {
-	})
-	OnKey__3([]string{"2"}, func(key string) {
-	})
-	OnKey__4([]Mesher{m1, m2}, func() {
-	})
-	OnKey__5([]Mesher{m1, m2}, func(key Mesher) {
-	})
-	OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
-	})
-	OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) {
-	})
-	OnKey__6([]string{"a"}, nil, func(key string) {
-	})
-	OnKey__8(100, 200)
-	OnKey__9("a", "b", func(x int) int {
-		return x * x
-	}, func(x int) int {
-		return x * 2
-	})
-	OnKey__a("a", "b", 1, 2, 3)
-	OnKey__a("a", "b", []int{1, 2, 3}...)
-	n := &N{}
-	n.OnKey__0("hello", func() {
-	})
-	n.OnKey__1("hello", func(key string) {
-	})
-	n.OnKey__2([]string{"1"}, func() {
-	})
-	n.OnKey__3([]string{"2"}, func(key string) {
-	})
-	n.OnKey__4([]Mesher{m1, m2}, func() {
-	})
-	n.OnKey__5([]Mesher{m1, m2}, func(key Mesher) {
-	})
-	n.OnKey__6([]string{"a"}, []string{"b"}, func(key string) {
-	})
-	n.OnKey__7([]string{"a"}, []Mesher{m1, m2}, func(key string) {
-	})
-	n.OnKey__6([]string{"a"}, nil, func(key string) {
-	})
-	n.OnKey__8(100, 200)
-}
-`)
-}
-
-func TestMixedOverloadOp(t *testing.T) {
-	gopMixedClTest(t, "main", `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() *foo {
-	fmt.Println("-a")
-	return &foo{}
-}
-func (a foo) Gop_Inc() {
-	fmt.Println("a++")
-}
-`, `
-var a, b foo
-var c = a - b
-var d = -a
-var e = a!=b
-`, `package main
-
-var a, b foo
-var c = (foo).Gop_Sub(a, b)
-var d = a.Gop_Neg()
-var e = (foo).Gop_NE(a, b)
-`)
-}
-
-func TestMixedVector3(t *testing.T) {
-	gopMixedClTest(t, "main", `package main
-type Vector3 struct {
-	x, y, z float64
-}
-func (a Vector3) Gop_Add__0(n int) Vector3 {
-	return Vector3{}
-}
-func (a Vector3) Gop_Add__1(n float64) Vector3 {
-	return Vector3{}
-}
-func (a Vector3) Gop_Add__2(n Vector3) Vector3 {
-	return Vector3{}
-}
-func (a *Vector3) Gop_AddAssign(n Vector3) {
-}
-
-func (a Vector3) Gop_Rcast__0() int {
-	return 0
-}
-func (a Vector3) Gop_Rcast__1() float64 {
-	return 0
-}
-
-func Vector3_Cast__0(x int) Vector3 {
-	return Vector3{}
-}
-func Vector3_Cast__1(x float64) Vector3 {
-	return Vector3{}
-}
-func Vector3_Init__0(x int) Vector3 {
-	return Vector3{}
-}
-func Vector3_Init__1(x float64) Vector3 {
-	return Vector3{}
-}
-`, `
-var a Vector3
-var b int
-var c float64
-_ = a+b
-_ = a+100
-_ = a+c
-_ = 100+a
-_ = Vector3(b)+a
-_ = b+int(a)
-a += b
-a += c
-`, `package main
-
-var a Vector3
-var b int
-var c float64
-
-func main() {
-	_ = (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))
-}
-`)
-}
-
-func TestMixedInterfaceOverload(t *testing.T) {
-	gopMixedClTest(t, "main", `
-package main
-
-type N[T any] struct {
-	v T
-}
-
-func (m *N[T]) OnKey__0(a string, fn func()) {
-}
-
-func (m *N[T]) OnKey__1(a string, fn func(key string)) {
-}
-
-func (m *N[T]) OnKey__2(a []string, fn func()) {
-}
-
-func (m *N[T]) OnKey__3(a []string, fn func(key string)) {
-}
-
-type I interface {
-	OnKey__0(a string, fn func())
-	OnKey__1(a string, fn func(key string))
-	OnKey__2(a []string, fn func())
-	OnKey__3(a []string, fn func(key string))
-}
-`, `
-n := &N[int]{}
-n.onKey "1", => {
-}
-keys := ["1","2"]
-n.onKey keys, key => {
-	println key
-}
-n.onKey keys, => {
-	println keys
-}
-
-var i I = n
-i.onKey "1", key => {
-	println key
-}
-i.onKey ["1","2"], key => {
-	println key
-}
-`, `package main
-
-import "fmt"
-
-func main() {
-	n := &N[int]{}
-	n.OnKey__0("1", func() {
-	})
-	keys := []string{"1", "2"}
-	n.OnKey__3(keys, func(key string) {
-		fmt.Println(key)
-	})
-	n.OnKey__2(keys, func() {
-		fmt.Println(keys)
-	})
-	var i I = n
-	i.OnKey__1("1", func(key string) {
-		fmt.Println(key)
-	})
-	i.OnKey__3([]string{"1", "2"}, func(key string) {
-		fmt.Println(key)
-	})
-}
-`)
-}
-
-func TestMixedOverloadCommand(t *testing.T) {
-	gopMixedClTest(t, "main", `package main
-
-func Test__0() {
-}
-func Test__1(n int) {
-}
-type N struct {
-}
-func (p *N) Test__0() {
-}
-func (p *N) Test__1(n int) {
-}`, `
-Test
-Test 100
-var n N
-n.test
-n.test 100
-`, `package main
-
-func main() {
-	Test__0()
-	Test__1(100)
-	var n N
-	n.Test__0()
-	n.Test__1(100)
-}
-`)
-}
-
-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/testdata/overloadop1/overloadop.gop b/testdata/overloadop1/overloadop.gop
index d7b489b6..ccc1dca5 100644
--- a/testdata/overloadop1/overloadop.gop
+++ b/testdata/overloadop1/overloadop.gop
@@ -28,7 +28,7 @@ func (a foo) != (b foo) bool {
 var a, b foo
 var c = a + b
 var d = a - b
-var e = -a       // TODO: -a have no return value!
+var e = -a
 var f = a != b
 println f
 a++
diff --git a/testdata/unit-test/foo_test.gox b/testdata/unit-test/foo_test.gox
index 4e238df3..13f35792 100644
--- a/testdata/unit-test/foo_test.gox
+++ b/testdata/unit-test/foo_test.gox
@@ -1,7 +1,5 @@
-if v := foo(50); v == 100 {
-	t.log "test foo(50) ok"
-} else {
-	t.error "foo() ret: ${v}"
+if v := foo(50); v != 100 {
+	t.error "foo(50) ret: ${v}"
 }
 
 t.run "foo -10", t => {
diff --git a/x/build/build_test.go b/x/build/build_test.go
index b43a127f..8e2f621f 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -391,14 +391,13 @@ import (
 	"github.com/goplus/gop/cl/internal/spx"
 )
 
-func main() {
-	spx.Gopt_MyGame_Main(new(spx.MyGame))
-}
-
 type Cat struct {
 	spx.Sprite
 }
 
+func main() {
+	spx.Gopt_MyGame_Main(new(spx.MyGame))
+}
 func (this *Cat) Main() {
 	fmt.Println("hi")
 }
diff --git a/x/typesutil/check_test.go b/x/typesutil/check_test.go
index 703118c1..5ec8afe7 100644
--- a/x/typesutil/check_test.go
+++ b/x/typesutil/check_test.go
@@ -78,6 +78,7 @@ func checkInfo(fset *token.FileSet, files []*ast.File, gofiles []*goast.File) (*
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
+		Overloads:  make(map[*ast.Ident][]types.Object),
 	}
 	ginfo := &types.Info{
 		Types:      make(map[goast.Expr]types.TypeAndValue),
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index addddcaa..2bbbda6d 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -4,10 +4,10 @@ import (
 	"fmt"
 
 	goast "go/ast"
-	"go/constant"
 	goformat "go/format"
 	goparser "go/parser"
 
+	"go/constant"
 	"go/importer"
 	"go/types"
 	"sort"
@@ -23,18 +23,52 @@ import (
 	"github.com/goplus/gop/x/typesutil"
 	"github.com/goplus/mod/env"
 	"github.com/goplus/mod/gopmod"
+	"github.com/goplus/mod/modfile"
+	"github.com/goplus/mod/modload"
 )
 
-func parserMixedSource(fset *token.FileSet, src string, gosrc string) (*typesutil.Info, *types.Info, error) {
-	f, err := parser.ParseEntry(fset, "main.gop", src, parser.Config{
-		Mode: parser.ParseComments,
-	})
+var spxProject = &modfile.Project{
+	Ext: ".tgmx", Class: "*MyGame",
+	Works:    []*modfile.Class{{Ext: ".tspx", Class: "Sprite"}},
+	PkgPaths: []string{"github.com/goplus/gop/cl/internal/spx", "math"}}
+
+var spxMod *gopmod.Module
+
+func init() {
+	spxMod = gopmod.New(modload.Default)
+	spxMod.Opt.Projects = append(spxMod.Opt.Projects, spxProject)
+	spxMod.ImportClasses()
+}
+
+func lookupClass(ext string) (c *modfile.Project, ok bool) {
+	switch ext {
+	case ".tgmx", ".tspx":
+		return spxProject, true
+	}
+	return
+}
+
+func spxParserConf() parser.Config {
+	return parser.Config{
+		ClassKind: func(fname string) (isProj bool, ok bool) {
+			ext := modfile.ClassExt(fname)
+			c, ok := lookupClass(ext)
+			if ok {
+				isProj = c.IsProj(ext, fname)
+			}
+			return
+		},
+	}
+}
+
+func parserMixedSource(mod *gopmod.Module, fset *token.FileSet, name, src string, goname string, gosrc string, parserConf parser.Config) (*typesutil.Info, *types.Info, error) {
+	f, err := parser.ParseEntry(fset, name, src, parserConf)
 	if err != nil {
 		return nil, nil, err
 	}
 	var gofiles []*goast.File
 	if len(gosrc) > 0 {
-		f, err := goparser.ParseFile(fset, "main.go", gosrc, goparser.ParseComments)
+		f, err := goparser.ParseFile(fset, goname, gosrc, goparser.ParseComments)
 		if err != nil {
 			return nil, nil, err
 		}
@@ -46,7 +80,7 @@ func parserMixedSource(fset *token.FileSet, src string, gosrc string) (*typesuti
 	chkOpts := &typesutil.Config{
 		Types: types.NewPackage("main", "main"),
 		Fset:  fset,
-		Mod:   gopmod.Default,
+		Mod:   mod,
 	}
 	info := &typesutil.Info{
 		Types:      make(map[ast.Expr]types.TypeAndValue),
@@ -55,6 +89,7 @@ func parserMixedSource(fset *token.FileSet, src string, gosrc string) (*typesuti
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
+		Overloads:  make(map[*ast.Ident][]types.Object),
 	}
 	ginfo := &types.Info{
 		Types:      make(map[goast.Expr]types.TypeAndValue),
@@ -91,6 +126,7 @@ func parserSource(fset *token.FileSet, filename string, src interface{}, mode pa
 		Implicits:  make(map[ast.Node]types.Object),
 		Selections: make(map[*ast.SelectorExpr]*types.Selection),
 		Scopes:     make(map[ast.Node]*types.Scope),
+		Overloads:  make(map[*ast.Ident][]types.Object),
 	}
 	check := typesutil.NewChecker(conf, chkOpts, nil, info)
 	err = check.Files(nil, []*ast.File{f})
@@ -120,8 +156,16 @@ func parserGoSource(fset *token.FileSet, filename string, src interface{}, mode
 }
 
 func testGopInfo(t *testing.T, src string, gosrc string, expect string) {
+	testGopInfoEx(t, gopmod.Default, "main.gop", src, "main.go", gosrc, expect, parser.Config{})
+}
+
+func testSpxInfo(t *testing.T, name string, src string, expect string) {
+	testGopInfoEx(t, spxMod, name, src, "main.go", "", expect, spxParserConf())
+}
+
+func testGopInfoEx(t *testing.T, mod *gopmod.Module, name string, src string, goname string, gosrc string, expect string, parseConf parser.Config) {
 	fset := token.NewFileSet()
-	info, _, err := parserMixedSource(fset, src, gosrc)
+	info, _, err := parserMixedSource(mod, fset, name, src, goname, gosrc, parseConf)
 	if err != nil {
 		t.Fatal("parserMixedSource error", err)
 	}
@@ -1431,7 +1475,6 @@ d := bar.Var(bar.M)
 }
 
 func TestMixedOverloadNamed(t *testing.T) {
-	//gox.SetDebug(gox.DbgFlagAll)
 	testGopInfo(t, `
 var a Var[int]
 var b Var[M]
@@ -1494,7 +1537,6 @@ func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 }
 
 func TestMixedRawNamed(t *testing.T) {
-	//gox.SetDebug(gox.DbgFlagAll)
 	testGopInfo(t, `
 var a Var__0[int]
 var b Var__1[M]
@@ -1555,3 +1597,74 @@ func Gopx_Var_Cast__1[T map[string]any]() *Var__1[T] {
 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`)
 }
+
+func TestSpxInfo(t *testing.T) {
+	testSpxInfo(t, "Kai.tspx", `
+var (
+	a int
+)
+
+type info struct {
+	x int
+	y int
+}
+
+func onInit() {
+	a = 1
+	clone
+	clone info{1,2}
+	clone &info{1,2}
+}
+
+func onCloned() {
+	say("Hi")
+}
+`, `== types ==
+000:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
+001:  3: 4 | int                 *ast.Ident                     | type    : int | type
+002:  6:11 | struct {
+	x int
+	y int
+} *ast.StructType                | type    : struct{x int; y int} | type
+003:  7: 4 | int                 *ast.Ident                     | type    : int | type
+004:  8: 4 | int                 *ast.Ident                     | type    : int | type
+005: 12: 2 | a                   *ast.Ident                     | var     : int | variable
+006: 12: 6 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+007: 13: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}) | value
+008: 14: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
+009: 14: 2 | clone info{1, 2}    *ast.CallExpr                  | void    : () | no value
+010: 14: 8 | info                *ast.Ident                     | type    : main.info | type
+011: 14: 8 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+012: 14:13 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+013: 14:15 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+014: 15: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
+015: 15: 2 | clone &info{1, 2}   *ast.CallExpr                  | void    : () | no value
+016: 15: 8 | &info{1, 2}         *ast.UnaryExpr                 | value   : *main.info | value
+017: 15: 9 | info                *ast.Ident                     | type    : main.info | type
+018: 15: 9 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+019: 15:14 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+020: 15:16 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+021: 19: 2 | say                 *ast.Ident                     | value   : func(msg string, secs ...float64) | value
+022: 19: 2 | say("Hi")           *ast.CallExpr                  | void    : () | no value
+023: 19: 6 | "Hi"                *ast.BasicLit                  | value   : untyped string = "Hi" | constant
+== defs ==
+000:  0: 0 | this                | var this *main.Kai
+001:  3: 2 | a                   | field a int
+002:  6: 6 | info                | type main.info struct{x int; y int}
+003:  7: 2 | x                   | field x int
+004:  8: 2 | y                   | field y int
+005: 11: 6 | onInit              | func (*main.Kai).onInit()
+006: 18: 6 | onCloned            | func (*main.Kai).onCloned()
+== uses ==
+000:  0: 0 | Kai                 | type main.Kai struct{github.com/goplus/gop/cl/internal/spx.Sprite; a int}
+001:  3: 4 | int                 | type int
+002:  7: 4 | int                 | type int
+003:  8: 4 | int                 | type int
+004: 12: 2 | a                   | field a int
+005: 13: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__0(sprite interface{})
+006: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
+007: 14: 8 | info                | type main.info struct{x int; y int}
+008: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
+009: 15: 9 | info                | type main.info struct{x int; y int}
+010: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)`)
+}