Repository: goplus/gop
Pull request #2178 (2025-03-14 16:13:46)

classfile support auto-generated clone
#2177


Patch:
diff --git a/cl/classfile.go b/cl/classfile.go
index badebbc2..0bdf1c56 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -47,8 +47,9 @@ type spxObj struct {
 }
 
 type gmxProject struct {
-	gameClass  string            // <gmtype>.gmx
-	game       gogen.Ref         // Game (project base class)
+	gameClass  string    // <gmtype>.gmx
+	game       gogen.Ref // Game (project base class)
+	spfeats    spriteFeat
 	sprite     map[string]spxObj // .spx => Sprite
 	sptypes    []string          // <sptype>.spx
 	scheds     []string
@@ -62,6 +63,43 @@ type gmxProject struct {
 	hasMain_   bool
 }
 
+type spriteFeat uint
+
+const (
+	spriteClassfname spriteFeat = 1 << iota
+	spriteClassclone
+)
+
+func spriteFeatures(game gogen.Ref) (feats spriteFeat) {
+	if mainFn := findMethod(game, "Main"); mainFn != nil {
+		sig := mainFn.Type().(*types.Signature)
+		if t, ok := gogen.CheckSigFuncEx(sig); ok {
+			if t, ok := t.(*gogen.TyTemplateRecvMethod); ok {
+				sig = t.Func.Type().(*types.Signature)
+			}
+		}
+		if sig.Variadic() {
+			in := sig.Params()
+			last := in.At(in.Len() - 1)
+			elt := last.Type().(*types.Slice).Elem()
+			if tn, ok := elt.(*types.Named); ok {
+				elt = tn.Underlying()
+			}
+			if intf, ok := elt.(*types.Interface); ok {
+				for i, n := 0, intf.NumMethods(); i < n; i++ {
+					switch intf.Method(i).Name() {
+					case "Classfname":
+						feats |= spriteClassfname
+					case "Classclone":
+						feats |= spriteClassclone
+					}
+				}
+			}
+		}
+	}
+	return
+}
+
 func (p *gmxProject) hasMain() bool {
 	if !p.hasMain_ {
 		imps := p.pkgImps
@@ -165,6 +203,7 @@ func loadClass(ctx *pkgCtx, pkg *gogen.Package, file string, f *ast.File, conf *
 		spx := p.pkgImps[0]
 		if gt.Class != "" {
 			p.game, p.gameIsPtr = spxRef(spx, gt.Class)
+			p.spfeats = spriteFeatures(p.game)
 		}
 		p.sprite = make(map[string]spxObj)
 		for _, v := range gt.Works {
@@ -460,6 +499,40 @@ func astFnClassfname(c *gmxClass) *ast.FuncDecl {
 	}
 }
 
+func astFnClassclone() *ast.FuncDecl {
+	ret := &ast.Ident{Name: "_gop_ret"}
+	return &ast.FuncDecl{
+		Name: &ast.Ident{
+			Name: "Classclone",
+		},
+		Type: &ast.FuncType{
+			Params: &ast.FieldList{},
+			Results: &ast.FieldList{
+				List: []*ast.Field{
+					{Type: &ast.Ident{Name: "any"}},
+				},
+			},
+		},
+		Body: &ast.BlockStmt{
+			List: []ast.Stmt{
+				&ast.AssignStmt{
+					Lhs: []ast.Expr{ret},
+					Tok: token.DEFINE,
+					Rhs: []ast.Expr{
+						&ast.StarExpr{X: &ast.Ident{Name: "this"}},
+					},
+				},
+				&ast.ReturnStmt{
+					Results: []ast.Expr{
+						&ast.UnaryExpr{Op: token.AND, X: ret},
+					},
+				},
+			},
+		},
+		Shadow: true,
+	}
+}
+
 func astEmptyEntrypoint(f *ast.File) {
 	var entry = getEntrypoint(f)
 	var hasEntry bool
diff --git a/cl/compile.go b/cl/compile.go
index eb51ba2f..2c56fbb3 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -226,6 +226,9 @@ func (p *nodeInterp) Caller(node ast.Node) string {
 
 func (p *nodeInterp) LoadExpr(node ast.Node) string {
 	start := node.Pos()
+	if start == token.NoPos {
+		return ""
+	}
 	pos := p.fset.Position(start)
 	f := p.files[pos.Filename]
 	n := int(node.End() - start)
@@ -725,6 +728,8 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	var baseType types.Type
 	var spxProj string
 	var spxClass bool
+	var spxClassfname bool
+	var spxClassclone bool
 	var goxTestFile bool
 	var parent = ctx.pkgCtx
 	if f.IsClass {
@@ -760,7 +765,10 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				sp := proj.sprite[c.ext]
 				o := sp.obj
 				ctx.baseClass = o
-				baseTypeName, baseType, spxProj, spxClass = o.Name(), o.Type(), sp.proj, true
+				baseTypeName, baseType, spxProj = o.Name(), o.Type(), sp.proj
+				spxClassfname = (proj.spfeats & spriteClassfname) != 0
+				spxClassclone = (proj.spfeats & spriteClassclone) != 0
+				spxClass = true
 			}
 		}
 	}
@@ -864,9 +872,13 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 			},
 		}}}
 		// func Classfname() string
-		if spxClass {
+		if spxClassfname {
 			f.Decls = append(f.Decls, astFnClassfname(c))
 		}
+		// func Classclone() any
+		if spxClassclone {
+			f.Decls = append(f.Decls, astFnClassclone())
+		}
 	}
 
 	if d := f.ShadowEntry; d != nil {
@@ -1298,7 +1310,7 @@ func loadFunc(ctx *blockCtx, recv *types.Var, name string, d *ast.FuncDecl, genB
 	var pkg = ctx.pkg
 	var sigBase *types.Signature
 	if d.Shadow {
-		if recv != nil {
+		if recv != nil && (name == "Main" || name == "MainEntry") {
 			if base := ctx.baseClass; base != nil {
 				if f := findMethod(base, name); f != nil {
 					sigBase = makeMainSig(recv, f)
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
index 551fcdb1..3697a3a7 100644
--- a/cl/internal/spx3/spx3.go
+++ b/cl/internal/spx3/spx3.go
@@ -40,5 +40,11 @@ func (p *Sprite) Name() string {
 
 func (p *Sprite) Main(name string) {}
 
-func Gopt_Game_Main(game interface{ initGame() }, workers ...interface{ Main(name string) }) {
+type iHandler interface {
+	Main(name string)
+	Classfname() string
+	Classclone() any
+}
+
+func Gopt_Game_Main(game interface{ initGame() }, workers ...iHandler) {
 }


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 0b4ad415..6c553c16 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -39,6 +39,13 @@ func getGoxConf() *gogen.Config {
 	return &gogen.Config{Fset: fset, Importer: imp}
 }
 
+func TestLoadExpr(t *testing.T) {
+	var ni nodeInterp
+	if v := ni.LoadExpr(&ast.Ident{Name: "x"}); v != "" {
+		t.Fatal("LoadExpr:", v)
+	}
+}
+
 func TestSimplifyPkgPath(t *testing.T) {
 	if simplifyPkgPath("c/lua") != "github.com/goplus/llgo/c/lua" {
 		t.Fatal("simplifyPkgPath: c/lua")
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index e6a2539e..6f6a5952 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -57,9 +57,6 @@ type MyGame struct {
 	*spx.MyGame
 }
 
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func (this *MyGame) Main() {
@@ -142,9 +139,6 @@ func (this *Kai) onMsg(msg string) {
 		this.Say("Hi")
 	}
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -199,9 +193,6 @@ 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() {
@@ -234,9 +225,6 @@ 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() {
@@ -270,9 +258,6 @@ 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() {
@@ -314,9 +299,6 @@ 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() {
@@ -372,9 +354,6 @@ func (this *bar) onInit() {
 	this.Say("Where do you come from?", 2)
 	this.Broadcast__0("msg2")
 }
-func (this *bar) Classfname() string {
-	return "bar"
-}
 func (this *bar) Main() {
 }
 func main() {
@@ -434,9 +413,6 @@ func (this *Kai) onInit() {
 func (this *Kai) onCloned() {
 	this.Say("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -485,9 +461,6 @@ func (this *index) Main() {
 func (this *Kai) Main() {
 	fmt.Println("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(index).Main()
 }
@@ -533,6 +506,10 @@ func (this *Kai) Main(_gop_arg0 string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Classclone() interface{} {
+	_gop_ret := *this
+	return &_gop_ret
+}
 func main() {
 	new(Game).Main()
 }
@@ -572,6 +549,10 @@ func (this *Game) Main() {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
+func (this *Kai) Classclone() interface{} {
+	_gop_ret := *this
+	return &_gop_ret
+}
 func (this *Kai) Main(_gop_arg0 string) {
 	this.Sprite.Main(_gop_arg0)
 }
@@ -610,9 +591,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -650,9 +628,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -684,14 +659,8 @@ type Kai struct {
 func (this *Dog) Main() {
 	fmt.Println("Hi, Sprite")
 }
-func (this *Dog) Classfname() string {
-	return "Dog"
-}
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 `, "Dog_t3spx.gox", "Kai.t3spx2")
@@ -721,9 +690,6 @@ func (this *Game) MainEntry() {
 func (this *Game) Main() {
 	(*spx2.Game).Main(&this.Game)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -753,9 +719,6 @@ func (this *Game) MainEntry() {
 func (this *Game) Main() {
 	(*spx2.Game).Main(&this.Game)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -803,9 +766,6 @@ func (this *Kai) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(Game).Main()
 }
@@ -852,9 +812,6 @@ func (this *Kai) onMsg(msg string) {
 		this.Say("Hi")
 	}
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -929,9 +886,6 @@ func (this *Kai) onInit() {
 func (this *Kai) onCloned() {
 	this.Say("Hi")
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -974,9 +928,6 @@ func (this *Game) Main() {
 }
 func (this *Kai) onMsg(msg string) {
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1016,9 +967,6 @@ func (this *Game) Main() {
 func (this *Kai) onMsg(msg string) {
 	this.Position().Add__0(100, 200)
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1083,9 +1031,6 @@ func (this *Kai) onMsg(msg string) {
 	fmt.Println(this.Vector().X)
 	fmt.Println(this.Vector().Self().Self())
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func (this *Kai) Main() {
 }
 func main() {
@@ -1197,9 +1142,6 @@ func (this *Kai) Main() {
 	spx.Gopt_Sprite_OnKey2(this, "hello", func(key string) {
 	})
 }
-func (this *Kai) Classfname() string {
-	return "Kai"
-}
 func main() {
 	new(Game).Main()
 }
@@ -1239,9 +1181,6 @@ func (this *caseFoo) Main() {
 		t.Fatal("failed")
 	})
 }
-func (this *caseFoo) Classfname() string {
-	return "Foo"
-}
 func TestFoo(t *testing.T) {
 	test.Gopt_Case_TestMain(new(caseFoo), t)
 }
@@ -1270,9 +1209,6 @@ type case_foo struct {
 func (this *case_foo) Main() {
 	this.T().Log("Hi")
 }
-func (this *case_foo) Classfname() string {
-	return "foo"
-}
 func Test_foo(t *testing.T) {
 	test.Gopt_Case_TestMain(new(case_foo), t)
 }
diff --git a/x/build/build_test.go b/x/build/build_test.go
index 1bebeca9..cdc10801 100644
--- a/x/build/build_test.go
+++ b/x/build/build_test.go
@@ -410,9 +410,6 @@ type MyGame struct {
 func (this *Cat) Main() {
 	fmt.Println("hi")
 }
-func (this *Cat) Classfname() string {
-	return "Cat"
-}
 func (this *MyGame) Main() {
 	spx.Gopt_MyGame_Main(this)
 }
diff --git a/x/typesutil/info_test.go b/x/typesutil/info_test.go
index 60075398..1cede6fa 100644
--- a/x/typesutil/info_test.go
+++ b/x/typesutil/info_test.go
@@ -1679,61 +1679,57 @@ func onCloned() {
 	say("Hi")
 }
 `, `== types ==
-000:  0: 0 | "Kai"               *ast.BasicLit                  | value   : untyped string = "Kai" | constant
-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 {
+000:  0: 0 | *MyGame             *ast.StarExpr                  | type    : *main.MyGame | type
+001:  0: 0 | Kai                 *ast.Ident                     | type    : main.Kai | type
+002:  0: 0 | MyGame              *ast.Ident                     | type    : main.MyGame | type
+003:  3: 4 | int                 *ast.Ident                     | type    : int | type
+004:  6:11 | struct {
 	x int
 	y int
 } *ast.StructType                | type    : struct{x int; y int} | type
-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 any) | value
-012: 14: 2 | clone               *ast.Ident                     | value   : func(sprite any, data any) | 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 any, data any) | 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
+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 any) | value
+010: 14: 2 | clone               *ast.Ident                     | value   : func(sprite any, data any) | 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 any, data any) | 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
 == defs ==
-000:  0: 0 | Classfname          | func (*main.Kai).Classfname() string
-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()
+000:  0: 0 | Main                | func (*main.Kai).Main()
+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()
 == uses ==
 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 any)
-008: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any)
-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 any, data any)
-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)
+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 any)
+007: 14: 2 | clone               | func github.com/goplus/gop/cl/internal/spx.Gopt_Sprite_Clone__1(sprite any, data any)
+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 any, data any)
+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)
 == overloads ==
 000: 13: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})
 001: 14: 2 | clone               | func (github.com/goplus/gop/cl/internal/spx.Sprite).Clone(__gop_overload_args__ interface{_()})