Repository: goplus/gop
Pull request #1814 (2024-03-10 14:49:54)

gmxProjMain: generate gameClass.Main()


Patch:
diff --git a/cl/classfile.go b/cl/classfile.go
index 2a705bab..2eecbcd5 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -19,6 +19,7 @@ package cl
 import (
 	goast "go/ast"
 	"go/constant"
+	gotoken "go/token"
 	"go/types"
 	"log"
 	"path/filepath"
@@ -42,7 +43,7 @@ type gmxClass struct {
 
 type gmxProject struct {
 	gameClass  string               // <gmtype>.gmx
-	game       gogen.Ref            // Game
+	game       gogen.Ref            // Game (project base class)
 	sprite     map[string]gogen.Ref // .spx => Sprite
 	sptypes    []string             // <sptype>.spx
 	scheds     []string
@@ -245,67 +246,108 @@ func genTestFunc(pkg *gogen.Package, name, testType, param, paramType string) {
 		End()
 }
 
-func gmxMainFunc(pkg *gogen.Package, ctx *pkgCtx, noAutoGenMain bool) func() {
-	var proj *gmxProject
+func gmxCheckProjs(pkg *gogen.Package, ctx *pkgCtx) (proj *gmxProject, multi bool) {
 	for _, v := range ctx.projs {
 		if v.isTest {
 			continue
 		} else if proj != nil {
-			return nil
+			multi = true
+		} else {
+			proj = v
+		}
+		if v.game != nil { // just to make testcase happy
+			gmxProjMain(pkg, ctx, v)
 		}
-		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
+	return
+}
+
+func gmxProjMain(pkg *gogen.Package, parent *pkgCtx, proj *gmxProject) {
+	base := proj.game
+	classType := proj.gameClass
+	if classType == "" {
+		classType = base.Name()
+		proj.gameClass = classType
+	}
+
+	ld := getTypeLoader(parent, parent.syms, token.NoPos, proj.gameClass)
+	if ld.typ == nil {
+		ld.typ = func() {
+			if debugLoad {
+				log.Println("==> Load > NewType", classType)
 			}
-		} 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")
-
-				// force remove //line comments for main func
-				cb.SetComments(nil, false)
-
-				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)
-					}
+			old, _ := pkg.SetCurFile(defaultGoFile, true)
+			defer pkg.RestoreCurFile(old)
+
+			baseType := base.Type()
+			if proj.gameIsPtr {
+				baseType = types.NewPointer(baseType)
+			}
+			name := base.Name()
+			flds := []*types.Var{
+				types.NewField(token.NoPos, pkg.Types, name, baseType, true),
+			}
+			decl := pkg.NewTypeDefs().NewType(classType)
+			ld.typInit = func() { // decycle
+				if debugLoad {
+					log.Println("==> Load > InitType", classType)
 				}
+				old, _ := pkg.SetCurFile(defaultGoFile, true)
+				defer pkg.RestoreCurFile(old)
 
-				cb.Call(narg).EndStmt().End()
+				decl.InitType(pkg, types.NewStruct(flds, nil))
 			}
+			parent.tylds = append(parent.tylds, ld)
 		}
 	}
-	return nil
+	ld.methods = append(ld.methods, func() {
+		old, _ := pkg.SetCurFile(defaultGoFile, true)
+		defer pkg.RestoreCurFile(old)
+		doInitType(ld)
+
+		t := pkg.Ref(classType).Type()
+		recv := types.NewParam(token.NoPos, pkg.Types, "this", types.NewPointer(t))
+		fn := pkg.NewFunc(recv, "Main", nil, nil, false)
+
+		parent.inits = append(parent.inits, func() {
+			old, _ := pkg.SetCurFile(defaultGoFile, true)
+			defer pkg.RestoreCurFile(old)
+
+			cb := fn.BodyStart(pkg).Typ(base.Type()).MemberVal("Main")
+
+			// force remove //line comments for main func
+			cb.SetComments(nil, false)
+
+			sigParams := cb.Get(-1).Type.(*types.Signature).Params()
+			if _, ok := sigParams.At(0).Type().(*types.Pointer); !ok {
+				cb.Val(recv) // template recv method
+			} else {
+				cb.Val(recv).MemberRef(base.Name()).UnaryOp(gotoken.AND)
+			}
+			narg := sigParams.Len()
+			if narg > 1 {
+				narg = 1 + len(proj.sptypes)
+				new := pkg.Builtin().Ref("new")
+				for _, spt := range proj.sptypes {
+					sp := pkg.Ref(spt)
+					cb.Val(new).Val(sp).Call(1)
+				}
+			}
+
+			cb.Call(narg).EndStmt().End()
+		})
+	})
 }
 
-func gmxMainNarg(sig *types.Signature) int {
-	if fex, ok := gogen.CheckFuncEx(sig); ok {
-		if trm, ok := fex.(*gogen.TyTemplateRecvMethod); ok {
-			sig = trm.Func.Type().(*types.Signature)
-			return sig.Params().Len() - 1
+func gmxMainFunc(pkg *gogen.Package, proj *gmxProject) func() {
+	return func() {
+		if o := pkg.TryRef(proj.gameClass); o != nil {
+			// new(gameClass).Main()
+			new := pkg.Builtin().Ref("new")
+			pkg.NewFunc(nil, "main", nil, nil, false).
+				BodyStart(pkg).Val(new).Val(o).Call(1).MemberVal("Main").Call(0).EndStmt().End()
 		}
 	}
-	return sig.Params().Len()
-}
-
-func hasMethod(o types.Object, name string) bool {
-	return findMethod(o, name) != nil
 }
 
 func findMethod(o types.Object, name string) *types.Func {
@@ -364,19 +406,6 @@ func astFnClassfname(c *gmxClass) *ast.FuncDecl {
 	}
 }
 
-func astEmptyFunc(entry string) *ast.FuncDecl {
-	return &ast.FuncDecl{
-		Name: &ast.Ident{
-			Name: entry,
-		},
-		Type: &ast.FuncType{
-			Params: &ast.FieldList{},
-		},
-		Body:   &ast.BlockStmt{},
-		Shadow: true,
-	}
-}
-
 func astEmptyEntrypoint(f *ast.File) {
 	var entry = getEntrypoint(f)
 	var hasEntry bool
@@ -389,7 +418,16 @@ func astEmptyEntrypoint(f *ast.File) {
 		}
 	}
 	if !hasEntry {
-		f.Decls = append(f.Decls, astEmptyFunc(entry))
+		f.Decls = append(f.Decls, &ast.FuncDecl{
+			Name: &ast.Ident{
+				Name: entry,
+			},
+			Type: &ast.FuncType{
+				Params: &ast.FieldList{},
+			},
+			Body:   &ast.BlockStmt{},
+			Shadow: true,
+		})
 	}
 }
 
diff --git a/cl/compile.go b/cl/compile.go
index 232c5a25..c5948cfd 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -548,7 +548,7 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		gmx := f.File
 		if gmx.IsClass && !gmx.IsNormalGox {
 			if debugLoad {
-				log.Println("==> File", f.path, "normalGox:", gmx.IsNormalGox)
+				log.Println("==> ClassFile", f.path)
 			}
 			loadClass(ctx, p, f.path, gmx, conf)
 		}
@@ -568,6 +568,8 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 		preloadGopFile(p, ctx, f.path, f.File, conf)
 	}
 
+	proj, multi := gmxCheckProjs(p, ctx)
+
 	gopSyms := make(map[string]bool) // TODO: remove this map
 	for name := range ctx.syms {
 		gopSyms[name] = true
@@ -599,8 +601,11 @@ func NewPackage(pkgPath string, pkg *ast.Package, conf *Config) (p *gogen.Packag
 			loadFile(ctx, f.File)
 		}
 	}
+
 	if genMain { // make classfile main func if need
-		gen = gmxMainFunc(p, ctx, conf.NoAutoGenMain)
+		if proj != nil && !multi { // only one project file
+			gen = gmxMainFunc(p, proj)
+		}
 	}
 
 	for _, f := range sfiles {
@@ -840,11 +845,13 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 			f.Decls = append(f.Decls, astFnClassfname(c))
 		}
 	}
+
 	if d := f.ShadowEntry; d != nil {
 		d.Name.Name = getEntrypoint(f)
-	} else if f.IsProj && !conf.NoAutoGenMain && inMainPkg(f) {
+	} else if baseTypeName != "" { // isClass && not isNormalGox
 		astEmptyEntrypoint(f)
 	}
+
 	preloadFile(p, ctx, f, goFile, !conf.Outline)
 	if goxTestFile {
 		parent.inits = append(parent.inits, func() {
diff --git a/go.mod b/go.mod
index f71d1939..8d05cc69 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.18
 require (
 	github.com/fsnotify/fsnotify v1.7.0
 	github.com/goplus/c2go v0.7.25
-	github.com/goplus/gogen v1.15.0
+	github.com/goplus/gogen v1.15.1-0.20240310120426-b07061159519
 	github.com/goplus/mod v0.13.9
 	github.com/qiniu/x v1.13.9
 	golang.org/x/tools v0.19.0
diff --git a/go.sum b/go.sum
index 5ac4a157..2246e1d8 100644
--- a/go.sum
+++ b/go.sum
@@ -8,6 +8,8 @@ github.com/goplus/c2go v0.7.25 h1:QvQfOwGVGKFYOTLry8i4p9U55jnIOQWeLsnSyFg0hhc=
 github.com/goplus/c2go v0.7.25/go.mod h1:e9oe4jDVhGFMJLEGmPSrVkLuXbLZAEmAu0/uD6fSz5E=
 github.com/goplus/gogen v1.15.0 h1:mQphpjiw/Fka46lEVnDSWwql3s6VF9VGjKc0EohlEYI=
 github.com/goplus/gogen v1.15.0/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
+github.com/goplus/gogen v1.15.1-0.20240310120426-b07061159519 h1:H97lXGGP+GMLElTt4llQJu9G990DcLKyQ8NkY6933w0=
+github.com/goplus/gogen v1.15.1-0.20240310120426-b07061159519/go.mod h1:92qEzVgv7y8JEFICWG9GvYI5IzfEkxYdsA1DbmnTkqk=
 github.com/goplus/mod v0.13.9 h1:B9zZoHi2AzMltTSOFqZNVjqGlSMlhhNTWwEzVqhTQzg=
 github.com/goplus/mod v0.13.9/go.mod h1:MibsLSftGmxaQq78YzUzNviyFwB9RtpMaoscufvEKH4=
 github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index d8666ce1..5837b440 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -252,12 +252,15 @@ func TestErrParseTypeEmbedName(t *testing.T) {
 	parseTypeEmbedName(&ast.StructType{})
 }
 
-func TestGmxMainFunc(t *testing.T) {
-	gmxMainFunc(nil, &pkgCtx{
+func TestGmxCheckProjs(t *testing.T) {
+	_, multi := gmxCheckProjs(nil, &pkgCtx{
 		projs: map[string]*gmxProject{
 			".a": {}, ".b": {},
 		},
-	}, false)
+	})
+	if !multi {
+		t.Fatal("gmxCheckProjs: not multi?")
+	}
 }
 
 func TestNodeInterp(t *testing.T) {
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index bb284bc7..a51deb70 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -92,6 +92,9 @@ func gopSpxTestExConf(t *testing.T, name string, conf *cl.Config, gmx, spxcode,
 		defer cl.SetDisableRecover(false)
 
 		fs := memfs.TwoFiles("/foo", spxfile, spxcode, gmxfile, gmx)
+		if gmxfile == "" {
+			fs = memfs.SingleFile("/foo", spxfile, spxcode)
+		}
 		pkgs, err := parser.ParseFSDir(gblFset, fs, "/foo", spxParserConf())
 		if err != nil {
 			scanner.PrintError(os.Stderr, err)
@@ -135,6 +138,34 @@ func gopSpxErrorTestEx(t *testing.T, msg, gmx, spxcode, gmxfile, spxfile string)
 	}
 }
 
+func TestSpxNoGame(t *testing.T) {
+	gopSpxTestEx(t, ``, `
+`, `package main
+
+import "github.com/goplus/gop/cl/internal/spx"
+
+type Kai struct {
+	spx.Sprite
+	*MyGame
+}
+type MyGame struct {
+	*spx.MyGame
+}
+
+func (this *Kai) Classfname() string {
+	return "Kai"
+}
+func (this *Kai) Main() {
+}
+func (this *MyGame) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
+func main() {
+	new(MyGame).Main()
+}
+`, "", "Kai.tspx")
+}
+
 func TestSpxError(t *testing.T) {
 	gopSpxErrorTestEx(t, `Game.tgmx:6:2: userScore redeclared
 	Game.tgmx:5:2 other declaration of userScore`, `
@@ -197,8 +228,8 @@ func (this *Game) onInit() {
 func (this *Game) MainEntry() {
 	this.InitGameApp()
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(Game))
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *Kai) onMsg(msg string) {
 	for {
@@ -209,6 +240,11 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -253,9 +289,19 @@ func (this *index) onInit() {
 	this.bar()
 	fmt.Println("Hi")
 }
+func (this *index) MainEntry() {
+}
+func (this *index) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (this *bar) Classfname() string {
 	return "bar"
 }
+func (this *bar) Main() {
+}
+func main() {
+	new(index).Main()
+}
 `)
 }
 
@@ -280,12 +326,17 @@ type index struct {
 func (this *index) MainEntry() {
 	fmt.Println(this.Gop_Env("PATH"), this.Gop_Env("id"))
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(index))
+func (this *index) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *bar) Classfname() string {
 	return "bar"
 }
+func (this *bar) Main() {
+}
+func main() {
+	new(index).Main()
+}
 `)
 }
 
@@ -311,12 +362,17 @@ type index struct {
 func (this *index) MainEntry() {
 	fmt.Println(strconv.Itoa(this.Gop_Env("PATH")))
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(index))
+func (this *index) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *bar) Classfname() string {
 	return "bar"
 }
+func (this *bar) Main() {
+}
+func main() {
+	new(index).Main()
+}
 `)
 }
 
@@ -350,12 +406,17 @@ func (this *index) MainEntry() {
 		this.Gop_Exec("ls", "-l")
 	})
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(index))
+func (this *index) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *bar) Classfname() string {
 	return "bar"
 }
+func (this *bar) Main() {
+}
+func main() {
+	new(index).Main()
+}
 `)
 }
 
@@ -395,6 +456,11 @@ func (this *Game) onInit() {
 	spx.TestIntValue = 1
 	x := math.Round(1.2)
 }
+func (this *Game) MainEntry() {
+}
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (this *bar) onInit() {
 	this.SetCostume("kai-a")
 	this.Play("recordingWhere")
@@ -404,6 +470,11 @@ func (this *bar) onInit() {
 func (this *bar) Classfname() string {
 	return "bar"
 }
+func (this *bar) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "bar.tspx")
 }
 
@@ -447,6 +518,11 @@ func (this *Game) onInit() {
 	spx.Gopt_Sprite_Clone__0(this.Kai)
 	this.Broadcast__0("msg1")
 }
+func (this *Game) MainEntry() {
+}
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (this *Kai) onInit() {
 	this.a = 1
 }
@@ -456,6 +532,11 @@ func (this *Kai) onCloned() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -493,8 +574,8 @@ var x float64 = spx.Rand__1(1.2)
 func (this *index) MainEntry() {
 	spx.Gopt_MyGame_Run(this, "hzip://open.qiniu.us/weather/res.zip")
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(index))
+func (this *index) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *Kai) Main() {
 	fmt.Println("Hi")
@@ -502,6 +583,9 @@ func (this *Kai) Main() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func main() {
+	new(index).Main()
+}
 `, "index.tgmx", "Kai.tspx")
 }
 
@@ -534,8 +618,8 @@ type Game struct {
 func (this *Game) MainEntry() {
 	this.Run()
 }
-func main() {
-	spx3.Gopt_Game_Main(new(Game), new(Kai))
+func (this *Game) Main() {
+	spx3.Gopt_Game_Main(this, new(Kai))
 }
 func (this *Kai) Main(_gop_arg0 string) {
 	this.Sprite.Main(_gop_arg0)
@@ -544,6 +628,9 @@ func (this *Kai) Main(_gop_arg0 string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func main() {
+	new(Game).Main()
+}
 `, "main_spx.gox", "Kai_spx.gox")
 }
 
@@ -574,12 +661,18 @@ func (this *Game) MainEntry() {
 	b := new(spx3.Sprite)
 	fmt.Println(b.Name())
 }
-func main() {
-	spx3.Gopt_Game_Main(new(Game), new(Kai))
+func (this *Game) Main() {
+	spx3.Gopt_Game_Main(this, new(Kai))
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main(_gop_arg0 string) {
+	this.Sprite.Main(_gop_arg0)
+}
+func main() {
+	new(Game).Main()
+}
 `, "main_spx.gox", "Kai_spx.gox")
 }
 
@@ -607,14 +700,19 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
 }
-func main() {
-	new(Game).Main()
+func (this *Game) Main() {
+	(*spx2.Game).Main(&this.Game)
 }
 func (this *Kai) onMsg(msg string) {
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.t2gmx", "Kai.t2spx")
 }
 
@@ -642,14 +740,19 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	fmt.Println("Hi, Sprite2")
 }
-func main() {
-	new(Game).Main()
+func (this *Game) Main() {
+	(*spx2.Game).Main(&this.Game)
 }
 func (this *Kai) onMsg(msg string) {
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.t2gmx", "Kai.t2spx2")
 }
 
@@ -684,6 +787,8 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
 `, "Dog_t3spx.gox", "Kai.t3spx2")
 }
 
@@ -708,12 +813,17 @@ type Kai struct {
 
 func (this *Game) MainEntry() {
 }
-func main() {
-	new(Game).Main()
+func (this *Game) Main() {
+	(*spx2.Game).Main(&this.Game)
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.t2gmx", "Kai.t2spx", "")
 	gopSpxTestExConf(t, "OnlyGmx", &conf, `
 var (
@@ -735,12 +845,17 @@ type Kai struct {
 
 func (this *Game) MainEntry() {
 }
-func main() {
-	new(Game).Main()
+func (this *Game) Main() {
+	(*spx2.Game).Main(&this.Game)
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.t2gmx", "Kai.t2spx", "")
 
 	gopSpxTestExConf(t, "KaiAndGmx", &conf, `
@@ -775,8 +890,8 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
 }
-func main() {
-	new(Game).Main()
+func (this *Game) Main() {
+	(*spx2.Game).Main(&this.Game)
 }
 func (this *Kai) Main() {
 	fmt.Println("Hello")
@@ -786,6 +901,9 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func main() {
+	new(Game).Main()
+}
 `, "Game.t2gmx", "Kai.t2spx", "")
 }
 
@@ -818,6 +936,11 @@ func (this *Game) onInit() {
 		spx.SchedNow()
 	}
 }
+func (this *Game) MainEntry() {
+}
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (this *Kai) onMsg(msg string) {
 	for {
 		spx.Sched()
@@ -827,6 +950,11 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -882,6 +1010,11 @@ func (this *Game) onInit() {
 	spx.Gopt_Sprite_Clone__0(this.Kai)
 	this.Broadcast__0("msg1")
 }
+func (this *Game) MainEntry() {
+}
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (this *Kai) onInit() {
 	this.a = 1
 	spx.Gopt_Sprite_Clone__0(this)
@@ -894,6 +1027,11 @@ func (this *Kai) onCloned() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -926,14 +1064,19 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	this.SendMessage("Hi")
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(Game))
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *Kai) onMsg(msg string) {
 }
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -962,8 +1105,8 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	fmt.Println("Hi")
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(Game))
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *Kai) onMsg(msg string) {
 	this.Position().Add__0(100, 200)
@@ -971,6 +1114,11 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -1012,8 +1160,8 @@ type Kai struct {
 func (this *Game) MainEntry() {
 	fmt.Println("hi")
 }
-func main() {
-	spx.Gopt_MyGame_Main(new(Game))
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
 }
 func (this *Kai) onMsg(msg string) {
 	fmt.Println(msg)
@@ -1033,6 +1181,11 @@ func (this *Kai) onMsg(msg string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Main() {
+}
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
@@ -1104,6 +1257,11 @@ func (this *Game) onInit() {
 	spx.Gopt_Sprite_OnKey__1(this.Kai, "hello", func(key string) {
 	})
 }
+func (this *Game) MainEntry() {
+}
+func (this *Game) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func (p *Mesh) Name() string {
 	return "hello"
 }
@@ -1137,6 +1295,9 @@ func (this *Kai) Main() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func main() {
+	new(Game).Main()
+}
 `, "Game.tgmx", "Kai.tspx")
 }
 
diff --git a/x/build/build_test.go b/x/build/build_test.go
index fed51751..2d26bfe9 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -380,8 +380,11 @@ type MyGame struct {
 func (this *MyGame) MainEntry() {
 	fmt.Println("hi")
 }
+func (this *MyGame) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
 func main() {
-	spx.Gopt_MyGame_Main(new(MyGame))
+	new(MyGame).Main()
 }
 `)
 	gopClTestEx(t, "Cat.tspx", `println "hi"`, `package main
@@ -393,17 +396,24 @@ import (
 
 type Cat struct {
 	spx.Sprite
+	*MyGame
 }
-
-func main() {
-	spx.Gopt_MyGame_Main(new(spx.MyGame))
+type MyGame struct {
+	spx.MyGame
 }
+
 func (this *Cat) Main() {
 	fmt.Println("hi")
 }
 func (this *Cat) Classfname() string {
 	return "Cat"
 }
+func (this *MyGame) Main() {
+	spx.Gopt_MyGame_Main(this)
+}
+func main() {
+	new(MyGame).Main()
+}
 `)
 }
 
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index cda5e112..07b8dfdd 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -1622,56 +1622,60 @@ func onCloned() {
 }
 `, `== types ==
 000:  0: 0 | "Kai"               *ast.BasicLit                  | value   : untyped string = "Kai" | constant
-001:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
-002:  0: 0 | string              *ast.Ident                     | type    : string | type
-003:  3: 4 | int                 *ast.Ident                     | type    : int | type
-004:  6:11 | struct {
+001:  0: 0 | *MyGame             *ast.StarExpr                  | type    : *main.MyGame | type
+002:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
+003:  0: 0 | MyGame              *ast.Ident                     | type    : main.MyGame | type
+004:  0: 0 | string              *ast.Ident                     | type    : string | type
+005:  3: 4 | int                 *ast.Ident                     | type    : int | type
+006:  6:11 | struct {
 	x int
 	y int
 } *ast.StructType                | type    : struct{x int; y int} | type
-005:  7: 4 | int                 *ast.Ident                     | type    : int | type
-006:  8: 4 | int                 *ast.Ident                     | type    : int | type
-007: 12: 2 | a                   *ast.Ident                     | var     : int | variable
-008: 12: 6 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-009: 13: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}) | value
-010: 14: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
-011: 14: 2 | clone info{1, 2}    *ast.CallExpr                  | void    : () | no value
-012: 14: 8 | info                *ast.Ident                     | type    : main.info | type
-013: 14: 8 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
-014: 14:13 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-015: 14:15 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
-016: 15: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
-017: 15: 2 | clone &info{1, 2}   *ast.CallExpr                  | void    : () | no value
-018: 15: 8 | &info{1, 2}         *ast.UnaryExpr                 | value   : *main.info | value
-019: 15: 9 | info                *ast.Ident                     | type    : main.info | type
-020: 15: 9 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
-021: 15:14 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
-022: 15:16 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
-023: 19: 2 | say                 *ast.Ident                     | value   : func(msg string, secs ...float64) | value
-024: 19: 2 | say("Hi")           *ast.CallExpr                  | void    : () | no value
-025: 19: 6 | "Hi"                *ast.BasicLit                  | value   : untyped string = "Hi" | constant
+007:  7: 4 | int                 *ast.Ident                     | type    : int | type
+008:  8: 4 | int                 *ast.Ident                     | type    : int | type
+009: 12: 2 | a                   *ast.Ident                     | var     : int | variable
+010: 12: 6 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+011: 13: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}) | value
+012: 14: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
+013: 14: 2 | clone info{1, 2}    *ast.CallExpr                  | void    : () | no value
+014: 14: 8 | info                *ast.Ident                     | type    : main.info | type
+015: 14: 8 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+016: 14:13 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+017: 14:15 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+018: 15: 2 | clone               *ast.Ident                     | value   : func(sprite interface{}, data interface{}) | value
+019: 15: 2 | clone &info{1, 2}   *ast.CallExpr                  | void    : () | no value
+020: 15: 8 | &info{1, 2}         *ast.UnaryExpr                 | value   : *main.info | value
+021: 15: 9 | info                *ast.Ident                     | type    : main.info | type
+022: 15: 9 | info{1, 2}          *ast.CompositeLit              | value   : main.info | value
+023: 15:14 | 1                   *ast.BasicLit                  | value   : untyped int = 1 | constant
+024: 15:16 | 2                   *ast.BasicLit                  | value   : untyped int = 2 | constant
+025: 19: 2 | say                 *ast.Ident                     | value   : func(msg string, secs ...float64) | value
+026: 19: 2 | say("Hi")           *ast.CallExpr                  | void    : () | no value
+027: 19: 6 | "Hi"                *ast.BasicLit                  | value   : untyped string = "Hi" | constant
 == defs ==
 000:  0: 0 | Classfname          | func (*main.Kai).Classfname() string
-001:  0: 0 | this                | var this *main.Kai
-002:  3: 2 | a                   | field a int
-003:  6: 6 | info                | type main.info struct{x int; y int}
-004:  7: 2 | x                   | field x int
-005:  8: 2 | y                   | field y int
-006: 11: 6 | onInit              | func (*main.Kai).onInit()
-007: 18: 6 | onCloned            | func (*main.Kai).onCloned()
+001:  0: 0 | Main                | func (*main.Kai).Main()
+002:  0: 0 | this                | var this *main.Kai
+003:  3: 2 | a                   | field a int
+004:  6: 6 | info                | type main.info struct{x int; y int}
+005:  7: 2 | x                   | field x int
+006:  8: 2 | y                   | field y int
+007: 11: 6 | onInit              | func (*main.Kai).onInit()
+008: 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:  0: 0 | string              | type string
-002:  3: 4 | int                 | type int
-003:  7: 4 | int                 | type int
-004:  8: 4 | int                 | type int
-005: 12: 2 | a                   | field a int
-006: 13: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__0(sprite interface{})
-007: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
-008: 14: 8 | info                | type main.info struct{x int; y int}
-009: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
-010: 15: 9 | info                | type main.info struct{x int; y int}
-011: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)`)
+000:  0: 0 | Kai                 | type main.Kai struct{github.com/goplus/gop/cl/internal/spx.Sprite; *main.MyGame; a int}
+001:  0: 0 | MyGame              | type main.MyGame struct{*github.com/goplus/gop/cl/internal/spx.MyGame}
+002:  0: 0 | string              | type string
+003:  3: 4 | int                 | type int
+004:  7: 4 | int                 | type int
+005:  8: 4 | int                 | type int
+006: 12: 2 | a                   | field a int
+007: 13: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__0(sprite interface{})
+008: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
+009: 14: 8 | info                | type main.info struct{x int; y int}
+010: 15: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite interface{}, data interface{})
+011: 15: 9 | info                | type main.info struct{x int; y int}
+012: 19: 2 | say                 | func (*github.com/goplus/gop/cl/internal/spx.Sprite).Say(msg string, secs ...float64)`)
 }
 
 func TestScopesInfo(t *testing.T) {