Repository: goplus/gop
Pull request #2180 (2025-03-16 07:56:26)

cl: refactor Classfname, Classclone; ast: ClassFieldsDecl


Patch:
diff --git a/ast/ast_gop.go b/ast/ast_gop.go
index 8e66ff5c..960fb61f 100644
--- a/ast/ast_gop.go
+++ b/ast/ast_gop.go
@@ -483,6 +483,22 @@ func (f *File) HasPkgDecl() bool {
 	return f.Package != token.NoPos
 }
 
+// ClassFieldsDecl returns the class fields declaration.
+func (f *File) ClassFieldsDecl() *GenDecl {
+	if f.IsClass {
+		for _, decl := range f.Decls {
+			if g, ok := decl.(*GenDecl); ok {
+				if g.Tok == token.VAR {
+					return g
+				}
+				continue
+			}
+			break
+		}
+	}
+	return nil
+}
+
 // Pos returns position of first character belonging to the node.
 func (f *File) Pos() token.Pos {
 	if f.Package != token.NoPos {
diff --git a/cl/classfile.go b/cl/classfile.go
index 0bdf1c56..fad1a8ca 100644
--- a/cl/classfile.go
+++ b/cl/classfile.go
@@ -23,7 +23,6 @@ import (
 	"go/types"
 	"log"
 	"path/filepath"
-	"strconv"
 	"strings"
 
 	"github.com/goplus/gogen"
@@ -57,6 +56,7 @@ type gmxProject struct {
 	pkgImps    []gogen.PkgRef
 	pkgPaths   []string
 	autoimps   map[string]pkgImp // auto-import statement in gop.mod
+	classclone *types.Signature  // prototype of Classclone
 	hasScheds  bool
 	gameIsPtr  bool
 	isTest     bool
@@ -70,7 +70,7 @@ const (
 	spriteClassclone
 )
 
-func spriteFeatures(game gogen.Ref) (feats spriteFeat) {
+func spriteFeatures(game gogen.Ref) (feats spriteFeat, classclone *types.Signature) {
 	if mainFn := findMethod(game, "Main"); mainFn != nil {
 		sig := mainFn.Type().(*types.Signature)
 		if t, ok := gogen.CheckSigFuncEx(sig); ok {
@@ -87,10 +87,11 @@ func spriteFeatures(game gogen.Ref) (feats spriteFeat) {
 			}
 			if intf, ok := elt.(*types.Interface); ok {
 				for i, n := 0, intf.NumMethods(); i < n; i++ {
-					switch intf.Method(i).Name() {
+					switch m := intf.Method(i); m.Name() {
 					case "Classfname":
 						feats |= spriteClassfname
 					case "Classclone":
+						classclone = m.Type().(*types.Signature)
 						feats |= spriteClassclone
 					}
 				}
@@ -203,7 +204,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.spfeats, p.classclone = spriteFeatures(p.game)
 		}
 		p.sprite = make(map[string]spxObj)
 		for _, v := range gt.Works {
@@ -267,19 +268,6 @@ func getStringConst(spx gogen.PkgRef, name string) string {
 	return ""
 }
 
-func getFields(f *ast.File) []ast.Spec {
-	for _, decl := range f.Decls {
-		if g, ok := decl.(*ast.GenDecl); ok {
-			if g.Tok == token.VAR {
-				return g.Specs
-			}
-			continue
-		}
-		break
-	}
-	return nil
-}
-
 func setBodyHandler(ctx *blockCtx) {
 	if proj := ctx.proj; proj != nil { // in a Go+ class file
 		if scheds := proj.getScheds(ctx.cb); scheds != nil {
@@ -437,8 +425,9 @@ func genMainFunc(pkg *gogen.Package, gameClass string) {
 	if o := pkg.TryRef(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()
+		pkg.NewFunc(nil, "main", nil, nil, false).BodyStart(pkg).
+			Val(new).Val(o).Call(1).MemberVal("Main").Call(0).EndStmt().
+			End()
 	}
 }
 
@@ -473,64 +462,23 @@ func makeMainSig(recv *types.Var, f *types.Func) *types.Signature {
 	return types.NewSignatureType(recv, nil, nil, types.NewTuple(params...), nil, false)
 }
 
-func astFnClassfname(c *gmxClass) *ast.FuncDecl {
-	return &ast.FuncDecl{
-		Name: &ast.Ident{
-			Name: "Classfname",
-		},
-		Type: &ast.FuncType{
-			Params: &ast.FieldList{},
-			Results: &ast.FieldList{
-				List: []*ast.Field{
-					{Type: &ast.Ident{Name: "string"}},
-				},
-			},
-		},
-		Body: &ast.BlockStmt{
-			List: []ast.Stmt{
-				&ast.ReturnStmt{
-					Results: []ast.Expr{
-						&ast.BasicLit{Kind: token.STRING, Value: strconv.Quote(c.clsfile)},
-					},
-				},
-			},
-		},
-		Shadow: true,
-	}
+func genClassfname(ctx *blockCtx, c *gmxClass) {
+	pkg := ctx.pkg
+	recv := toRecv(ctx, ctx.classRecv)
+	ret := types.NewTuple(pkg.NewParam(token.NoPos, "", types.Typ[types.String]))
+	pkg.NewFunc(recv, "Classfname", nil, ret, false).BodyStart(pkg).
+		Val(c.clsfile).Return(1).
+		End()
 }
 
-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 genClassclone(ctx *blockCtx, classclone *types.Signature) {
+	pkg := ctx.pkg
+	recv := toRecv(ctx, ctx.classRecv)
+	ret := classclone.Results()
+	pkg.NewFunc(recv, "Classclone", nil, ret, false).BodyStart(pkg).
+		DefineVarStart(token.NoPos, "_gop_ret").VarVal("this").Elem().EndInit(1).
+		VarVal("_gop_ret").UnaryOp(gotoken.AND).Return(1).
+		End()
 }
 
 func astEmptyEntrypoint(f *ast.File) {
diff --git a/cl/compile.go b/cl/compile.go
index 2c56fbb3..09f3acb2 100644
--- a/cl/compile.go
+++ b/cl/compile.go
@@ -368,6 +368,7 @@ type blockCtx struct {
 	pystr_     gogen.Ref
 	relBaseDir string
 
+	classDecl *ast.GenDecl   // available when isClass
 	classRecv *ast.FieldList // available when isClass
 	baseClass types.Object   // available when isClass
 
@@ -727,9 +728,8 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	var baseTypeName string
 	var baseType types.Type
 	var spxProj string
+	var spfeats spriteFeat
 	var spxClass bool
-	var spxClassfname bool
-	var spxClassclone bool
 	var goxTestFile bool
 	var parent = ctx.pkgCtx
 	if f.IsClass {
@@ -766,9 +766,8 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				o := sp.obj
 				ctx.baseClass = o
 				baseTypeName, baseType, spxProj = o.Name(), o.Type(), sp.proj
-				spxClassfname = (proj.spfeats & spriteClassfname) != 0
-				spxClassclone = (proj.spfeats & spriteClassclone) != 0
 				spxClass = true
+				spfeats = proj.spfeats
 			}
 		}
 	}
@@ -785,7 +784,7 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 		}
 		syms := parent.syms
 		pos := f.Pos()
-		specs := getFields(f)
+		ctx.classDecl = f.ClassFieldsDecl()
 		ld := getTypeLoader(parent, syms, pos, classType)
 		ld.typ = func() {
 			if debugLoad {
@@ -826,32 +825,34 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 					}
 				}
 				rec := ctx.recorder()
-				for _, v := range specs {
-					spec := v.(*ast.ValueSpec)
-					typ := toType(ctx, spec.Type)
-					tag := toFieldTag(spec.Tag)
-					if len(spec.Names) == 0 {
-						name := parseTypeEmbedName(spec.Type)
-						if chk.chkRedecl(ctx, name.Name, spec.Type.Pos()) {
-							continue
-						}
-						fld := types.NewField(spec.Type.Pos(), pkg, name.Name, typ, true)
-						if rec != nil {
-							rec.Def(name, fld)
-						}
-						flds = append(flds, fld)
-						tags = append(tags, tag)
-					} else {
-						for _, name := range spec.Names {
-							if chk.chkRedecl(ctx, name.Name, name.Pos()) {
+				if classDecl := ctx.classDecl; classDecl != nil {
+					for _, v := range classDecl.Specs {
+						spec := v.(*ast.ValueSpec)
+						typ := toType(ctx, spec.Type)
+						tag := toFieldTag(spec.Tag)
+						if len(spec.Names) == 0 {
+							name := parseTypeEmbedName(spec.Type)
+							if chk.chkRedecl(ctx, name.Name, spec.Type.Pos()) {
 								continue
 							}
-							fld := types.NewField(name.Pos(), pkg, name.Name, typ, false)
+							fld := types.NewField(spec.Type.Pos(), pkg, name.Name, typ, true)
 							if rec != nil {
 								rec.Def(name, fld)
 							}
 							flds = append(flds, fld)
 							tags = append(tags, tag)
+						} else {
+							for _, name := range spec.Names {
+								if chk.chkRedecl(ctx, name.Name, name.Pos()) {
+									continue
+								}
+								fld := types.NewField(name.Pos(), pkg, name.Name, typ, false)
+								if rec != nil {
+									rec.Def(name, fld)
+								}
+								flds = append(flds, fld)
+								tags = append(tags, tag)
+							}
 						}
 					}
 				}
@@ -871,14 +872,6 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 				X: &ast.Ident{Name: classType},
 			},
 		}}}
-		// func Classfname() string
-		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 {
@@ -888,6 +881,25 @@ func preloadGopFile(p *gogen.Package, ctx *blockCtx, file string, f *ast.File, c
 	}
 
 	preloadFile(p, ctx, f, goFile, !conf.Outline)
+	if spfeats != 0 {
+		ld := getTypeLoader(parent, parent.syms, token.NoPos, classType)
+		if (spfeats & spriteClassfname) != 0 { // Classfname() string
+			ld.methods = append(ld.methods, func() {
+				old, _ := p.SetCurFile(goFile, true)
+				defer p.RestoreCurFile(old)
+				doInitType(ld)
+				genClassfname(ctx, c)
+			})
+		}
+		if (spfeats & spriteClassclone) != 0 { // Classclone() clonetype
+			ld.methods = append(ld.methods, func() {
+				old, _ := p.SetCurFile(goFile, true)
+				defer p.RestoreCurFile(old)
+				doInitType(ld)
+				genClassclone(ctx, proj.classclone)
+			})
+		}
+	}
 	if goxTestFile {
 		parent.inits = append(parent.inits, func() {
 			old, _ := p.SetCurFile(testingGoFile, true)
@@ -913,13 +925,10 @@ retry:
 
 func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, genFnBody bool) {
 	parent := ctx.pkgCtx
+	classDecl := ctx.classDecl
 	syms := parent.syms
 	old, _ := p.SetCurFile(goFile, true)
 	defer p.RestoreCurFile(old)
-	var skipClassFields bool
-	if f.IsClass {
-		skipClassFields = true
-	}
 
 	preloadFuncDecl := func(d *ast.FuncDecl) {
 		if ctx.classRecv != nil { // in class file (.spx/.gmx)
@@ -1081,8 +1090,7 @@ func preloadFile(p *gogen.Package, ctx *blockCtx, f *ast.File, goFile string, ge
 			case token.CONST:
 				preloadConst(d)
 			case token.VAR:
-				if skipClassFields {
-					skipClassFields = false
+				if d == classDecl { // skip class fields
 					continue
 				}
 				for _, spec := range d.Specs {
diff --git a/cl/internal/spx3/spx3.go b/cl/internal/spx3/spx3.go
index 3697a3a7..06aa8f44 100644
--- a/cl/internal/spx3/spx3.go
+++ b/cl/internal/spx3/spx3.go
@@ -40,11 +40,11 @@ func (p *Sprite) Name() string {
 
 func (p *Sprite) Main(name string) {}
 
-type iHandler interface {
+type Handler interface {
 	Main(name string)
 	Classfname() string
-	Classclone() any
+	Classclone() Handler
 }
 
-func Gopt_Game_Main(game interface{ initGame() }, workers ...iHandler) {
+func Gopt_Game_Main(game interface{ initGame() }, workers ...Handler) {
 }


Test patch:
diff --git a/cl/builtin_test.go b/cl/builtin_test.go
index 6c553c16..c18dac11 100644
--- a/cl/builtin_test.go
+++ b/cl/builtin_test.go
@@ -604,7 +604,6 @@ func TestClassFileEnd(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	f.Decls = append(f.Decls, astFnClassfname(&gmxClass{clsfile: "get"}))
 	if f.End() != f.ShadowEntry.End() {
 		t.Fatal("class file end not shadow entry")
 	}
diff --git a/cl/compile_spx_test.go b/cl/compile_spx_test.go
index 6f6a5952..4c6c1ac2 100644
--- a/cl/compile_spx_test.go
+++ b/cl/compile_spx_test.go
@@ -506,7 +506,7 @@ func (this *Kai) Main(_gop_arg0 string) {
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
-func (this *Kai) Classclone() interface{} {
+func (this *Kai) Classclone() spx3.Handler {
 	_gop_ret := *this
 	return &_gop_ret
 }
@@ -546,16 +546,16 @@ func (this *Game) MainEntry() {
 func (this *Game) Main() {
 	spx3.Gopt_Game_Main(this, new(Kai))
 }
+func (this *Kai) Main(_gop_arg0 string) {
+	this.Sprite.Main(_gop_arg0)
+}
 func (this *Kai) Classfname() string {
 	return "Kai"
 }
-func (this *Kai) Classclone() interface{} {
+func (this *Kai) Classclone() spx3.Handler {
 	_gop_ret := *this
 	return &_gop_ret
 }
-func (this *Kai) Main(_gop_arg0 string) {
-	this.Sprite.Main(_gop_arg0)
-}
 func main() {
 	new(Game).Main()
 }