This is an automated email from the git hooks/post-receive script.

git pushed a commit to branch main
in repository ego.

View the commit online.

commit 7e6db28f67b867b34ea91317da728cfc249db863
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 9 13:00:00 2026 -0600

    feat: improve ego code generator with accurate C names and class metadata
    
    The generator now retrieves actual C function names from Eolian's API instead of
    deriving them, fixing cases where naming conventions don't match the EFL
    implementation. Class metadata now correctly handles interfaces and mixins with
    proper macro suffixes (_INTERFACE, _MIXIN). Two-segment Eolian names are mapped to
    the "eo" package to avoid collision with hand-written runtime code. For classes
    with 4+ namespace segments, subsequent segments are joined to prevent type
    collisions. Multi-word C types like "unsigned int" are mapped to valid cgo
    identifiers. Constructor generation is switched to extern declarations instead of
    macro includes, avoiding cross-header type dependencies.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 cmd/ego-gen/eolian.go               |  14 +++++
 cmd/ego-gen/generator.go            |  34 ++++++------
 cmd/ego-gen/main.go                 |  54 ++++++++++++++-----
 cmd/ego-gen/naming.go               |  36 ++++++++-----
 cmd/ego-gen/templates/class.go.tmpl | 105 ++++++++++--------------------------
 cmd/ego-gen/typemap.go              |  25 +++++++++
 6 files changed, 152 insertions(+), 116 deletions(-)

diff --git a/cmd/ego-gen/eolian.go b/cmd/ego-gen/eolian.go
index a3a833a..e3d2751 100644
--- a/cmd/ego-gen/eolian.go
+++ b/cmd/ego-gen/eolian.go
@@ -187,6 +187,20 @@ func (f *Function) Name() string {
 	return C.GoString(C.eolian_function_name_get(f.ptr))
 }
 
+// FullCName returns the actual C function name for this function under the
+// given FunctionType context. For properties, pass FunctionPropGet or
+// FunctionPropSet to get the getter/setter names respectively.
+// The result is an Eina_Stringshare that we copy and free.
+func (f *Function) FullCName(ft FunctionType) string {
+	cs := C.eolian_function_full_c_name_get(f.ptr, C.Eolian_Function_Type(ft))
+	if cs == nil {
+		return ""
+	}
+	s := C.GoString(cs)
+	C._ego_stringshare_del(cs)
+	return s
+}
+
 // Type returns the FunctionType of this function.
 func (f *Function) Type() FunctionType {
 	return FunctionType(C.eolian_function_type_get(f.ptr))
diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index d29522f..9a92266 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -13,20 +13,21 @@ import (
 
 // ClassData holds all the information needed to render a Go file for one EFL class.
 type ClassData struct {
-	PackageName  string
-	GoName       string
-	EolianName   string
-	CClassName   string // e.g. "efl_ui_button_class_get()"
-	CPrefix      string // e.g. "efl_ui_button"
-	ParentGoType string
-	ParentPkg    string
-	IsAbstract   bool
-	IsMixin      bool
-	IsInterface  bool
-	Methods      []MethodData
-	Properties   []PropertyData
-	Events       []EventData
-	Interfaces   []string
+	PackageName   string
+	GoName        string
+	EolianName    string
+	CClassName    string // e.g. "EFL_UI_BUTTON_CLASS"
+	CClassGetFunc string // e.g. "efl_ui_button_class_get"
+	CPrefix       string // e.g. "efl_ui_button"
+	ParentGoType  string
+	ParentPkg     string
+	IsAbstract    bool
+	IsMixin       bool
+	IsInterface   bool
+	Methods       []MethodData
+	Properties    []PropertyData
+	Events        []EventData
+	Interfaces    []string
 }
 
 // MethodData describes a single method on an EFL class.
@@ -91,9 +92,10 @@ type Generator struct {
 // isPointerType are registered before parsing.
 func NewGenerator(templateDir, outputDir string) (*Generator, error) {
 	funcs := template.FuncMap{
-		"cTypeToGo":            CTypeToGo,
+		"cTypeToGo":             CTypeToGo,
 		"needsStringConversion": NeedsStringConversion,
-		"isPointerType":        IsPointerType,
+		"isPointerType":         IsPointerType,
+		"cTypeToCgo":            CTypeToCgo,
 	}
 
 	pattern := filepath.Join(templateDir, "*.tmpl")
diff --git a/cmd/ego-gen/main.go b/cmd/ego-gen/main.go
index f7aa9a8..4c5ee37 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -106,21 +106,50 @@ func buildClassData(c *Class) (ClassData, error) {
 	// C prefix: "Efl.Ui.Button" → "efl_ui_button"
 	cPrefix := strings.ToLower(strings.ReplaceAll(eolianName, ".", "_"))
 
-	// CClassName macro call, e.g. "EFL_UI_BUTTON_CLASS"
-	cClassName := strings.ToUpper(cPrefix) + "_CLASS"
-
 	// Determine class kind.
 	ct := c.Type()
+
+	// CClassName macro call depends on class type:
+	//   regular/abstract → EFL_UI_BUTTON_CLASS
+	//   interface         → EFL_CONTENT_INTERFACE
+	//   mixin             → EFL_FILE_MIXIN
+	var suffix string
+	switch ct {
+	case ClassInterface:
+		suffix = "_INTERFACE"
+	case ClassMixin:
+		suffix = "_MIXIN"
+	default:
+		suffix = "_CLASS"
+	}
+	cClassName := strings.ToUpper(cPrefix) + suffix
+
+	// CClassGetFunc is the C function name that returns the class pointer.
+	var funcSuffix string
+	switch ct {
+	case ClassInterface:
+		funcSuffix = "_interface_get"
+	case ClassMixin:
+		funcSuffix = "_mixin_get"
+	default:
+		funcSuffix = "_class_get"
+	}
+	cClassGetFunc := cPrefix + funcSuffix
+
 	isAbstract := ct == ClassAbstract
 	isMixin := ct == ClassMixin
 	isInterface := ct == ClassInterface
 
-	// Extract parent info.
+	// Extract parent info. If the parent is the same class (self-reference)
+	// or maps to the same Go type in the same package, skip it to avoid
+	// recursive type definitions.
 	var parentGoType, parentPkg string
 	if parent := c.Parent(); parent != nil {
 		parentName := parent.Name()
-		parentGoType = ClassToGoName(parentName)
-		parentPkg = ClassToPackage(parentName)
+		if parentName != eolianName {
+			parentGoType = ClassToGoName(parentName)
+			parentPkg = ClassToPackage(parentName)
+		}
 	}
 
 	// Collect interface names from extensions.
@@ -151,8 +180,9 @@ func buildClassData(c *Class) (ClassData, error) {
 		PackageName:  pkgName,
 		GoName:       goName,
 		EolianName:   eolianName,
-		CClassName:   cClassName,
-		CPrefix:      cPrefix,
+		CClassName:    cClassName,
+		CClassGetFunc: cClassGetFunc,
+		CPrefix:       cPrefix,
 		ParentGoType: parentGoType,
 		ParentPkg:    parentPkg,
 		IsAbstract:   isAbstract,
@@ -166,13 +196,13 @@ func buildClassData(c *Class) (ClassData, error) {
 }
 
 // buildPropertyData converts an Eolian property Function into a PropertyData.
-// Getter and setter C names are derived from the class prefix and property name.
+// Getter and setter C names come from Eolian's full C name API.
 func buildPropertyData(f *Function, cPrefix string) PropertyData {
 	propName := f.Name()
 	goGetter, goSetter := PropertyToGetterSetter(propName)
 
-	cGetName := cPrefix + "_" + propName + "_get"
-	cSetName := cPrefix + "_" + propName + "_set"
+	cGetName := f.FullCName(FunctionPropGet)
+	cSetName := f.FullCName(FunctionPropSet)
 
 	ft := f.Type()
 	hasGet := ft == FunctionProperty || ft == FunctionPropGet
@@ -216,7 +246,7 @@ func buildPropertyData(f *Function, cPrefix string) PropertyData {
 func buildMethodData(f *Function, cPrefix string) MethodData {
 	methodName := f.Name()
 	goName := SnakeToCamel(methodName)
-	cName := cPrefix + "_" + methodName
+	cName := f.FullCName(FunctionMethod)
 
 	var params []ParamData
 	for _, p := range f.Parameters() {
diff --git a/cmd/ego-gen/naming.go b/cmd/ego-gen/naming.go
index 7842303..8fba432 100644
--- a/cmd/ego-gen/naming.go
+++ b/cmd/ego-gen/naming.go
@@ -7,35 +7,47 @@ import (
 
 // ClassToPackage returns the Go package name derived from a fully-qualified
 // Eolian class name. For names with three or more dot-separated segments the
-// second segment (index 1) is returned lowercased. For two-segment names the
-// first segment is returned lowercased.
+// second segment (index 1) is returned lowercased. Two-segment names like
+// "Efl.Loop" are placed in the "eo" package to avoid collision with the
+// hand-written efl/ runtime package.
 //
 // Examples:
 //
 //	"Efl.Ui.Button"  → "ui"
-//	"Efl.Loop"       → "efl"
+//	"Efl.Loop"       → "eo"
 func ClassToPackage(eolianName string) string {
 	parts := strings.Split(eolianName, ".")
 	if len(parts) >= 3 {
 		return strings.ToLower(parts[1])
 	}
-	// Two-part name such as "Efl.Loop" — use the first part.
-	return strings.ToLower(parts[0])
+	// Two-part name such as "Efl.Loop" — place in "eo" to avoid colliding
+	// with the hand-written efl/ runtime package.
+	return "eo"
 }
 
 // ClassToGoName returns the exported Go type name derived from a
-// fully-qualified Eolian class name. The last dot-separated segment is taken
-// and any underscores are removed by converting the value to CamelCase via
-// SnakeToCamel.
+// fully-qualified Eolian class name. For 3-segment names the last segment is
+// taken; for 4+ segments, all segments after the package segment are joined
+// to avoid collisions (e.g. "Efl.Canvas.Vg.Object" → "VgObject").
 //
 // Examples:
 //
-//	"Efl.Ui.Button"      → "Button"
-//	"Efl.Ui.Spin_Button" → "SpinButton"
+//	"Efl.Ui.Button"         → "Button"
+//	"Efl.Ui.Spin_Button"   → "SpinButton"
+//	"Efl.Canvas.Vg.Object" → "VgObject"
 func ClassToGoName(eolianName string) string {
 	parts := strings.Split(eolianName, ".")
-	last := parts[len(parts)-1]
-	return SnakeToCamel(last)
+	if len(parts) <= 3 {
+		last := parts[len(parts)-1]
+		return SnakeToCamel(last)
+	}
+	// Join segments after the package (index 1) to form the name.
+	// "Efl.Canvas.Vg.Object" → join("Vg", "Object") → "VgObject"
+	var b strings.Builder
+	for _, seg := range parts[2:] {
+		b.WriteString(SnakeToCamel(seg))
+	}
+	return b.String()
 }
 
 // SnakeToCamel converts a snake_case identifier to CamelCase. Each underscore-
diff --git a/cmd/ego-gen/templates/class.go.tmpl b/cmd/ego-gen/templates/class.go.tmpl
index e5b6980..34cab9e 100644
--- a/cmd/ego-gen/templates/class.go.tmpl
+++ b/cmd/ego-gen/templates/class.go.tmpl
@@ -4,13 +4,20 @@ package {{.PackageName}}
 
 /*
 #cgo pkg-config: elementary
+#define EFL_BETA_API_SUPPORT 1
 #include <Elementary.h>
 
-// _ego_{{.CPrefix}}_class_get wraps the {{.CClassName}} macro so it can be
-// called as a regular function from Go via cgo.
-static const Efl_Class *_ego_{{.CPrefix}}_class_get(void) {
-    return {{.CClassName}};
+// Declare the class_get function directly rather than including the full .eo.h
+// header, which may reference types from other .eo files that are not included.
+extern const Efl_Class *{{.CClassGetFunc}}(void);
+
+{{if not .IsAbstract}}{{if not .IsMixin}}{{if not .IsInterface -}}
+// _ego_{{.CPrefix}}_add creates a new instance of {{.EolianName}} with the
+// given parent using efl_add_ref so the Go side controls the lifetime.
+static Eo *_ego_{{.CPrefix}}_add(Eo *parent) {
+    return efl_add_ref({{.CClassGetFunc}}(), parent, 0);
 }
+{{end}}{{end}}{{end -}}
 */
 import "C"
 
@@ -18,21 +25,34 @@ import (
     "unsafe"
 
     "git.enlightenment.org/cedric/ego/efl"
+{{- if and .ParentPkg (ne .ParentPkg .PackageName) (ne .ParentPkg "efl")}}
+    "git.enlightenment.org/cedric/ego/efl/{{.ParentPkg}}"
+{{- end}}
 )
 
+// Ensure unused imports are not flagged.
+var _ = unsafe.Pointer(nil)
+var _ efl.Objecter
+
 // {{.GoName}} wraps the EFL class {{.EolianName}}.
 type {{.GoName}} struct {
 {{- if .ParentGoType}}
+{{- if eq .ParentPkg .PackageName}}
+    {{.ParentGoType}}
+{{- else if eq .ParentPkg "efl"}}
+    efl.{{.ParentGoType}}
+{{- else}}
     {{.ParentPkg}}.{{.ParentGoType}}
+{{- end}}
 {{- else}}
     efl.Object
 {{- end}}
 }
 
-// wrap{{.GoName}} wraps an existing Eo pointer as a {{.GoName}}.
-func wrap{{.GoName}}(obj *C.Eo) *{{.GoName}} {
+// Wrap{{.GoName}} wraps an existing Eo pointer as a {{.GoName}}.
+func Wrap{{.GoName}}(ptr unsafe.Pointer) *{{.GoName}} {
     o := &{{.GoName}}{}
-    o.SetEo(unsafe.Pointer(obj))
+    o.SetEo(ptr)
     return o
 }
 
@@ -43,77 +63,10 @@ func New{{.GoName}}(parent efl.Objecter) *{{.GoName}} {
     if parent != nil {
         parentEo = (*C.Eo)(parent.Eo())
     }
-    obj := C.efl_add(_ego_{{.CPrefix}}_class_get(), parentEo)
+    obj := C._ego_{{.CPrefix}}_add(parentEo)
     if obj == nil {
         return nil
     }
-    return wrap{{.GoName}}(obj)
+    return Wrap{{.GoName}}(unsafe.Pointer(obj))
 }
 {{end}}{{end}}{{end}}
-
-{{range .Properties}}
-{{if .HasGet}}
-// {{.GoGetter}} returns the {{.GoGetter}} property value.
-func (o *{{$.GoName}}) {{.GoGetter}}() {{.GoType}} {
-    eo := (*C.Eo)(o.Eo())
-{{- if needsStringConversion .CType}}
-    cs := C.{{.CGetName}}(eo)
-    return C.GoString(cs)
-{{- else if isPointerType .GoType}}
-    return unsafe.Pointer(C.{{.CGetName}}(eo))
-{{- else}}
-    return {{.GoType}}(C.{{.CGetName}}(eo))
-{{- end}}
-}
-{{end}}
-{{if .HasSet}}
-// {{.GoSetter}} sets the {{.GoGetter}} property value.
-func (o *{{$.GoName}}) {{.GoSetter}}(v {{.GoType}}) {
-    eo := (*C.Eo)(o.Eo())
-{{- if needsStringConversion .CType}}
-    cs := C.CString(v)
-    defer C.free(unsafe.Pointer(cs))
-    C.{{.CSetName}}(eo, cs)
-{{- else if isPointerType .GoType}}
-    C.{{.CSetName}}(eo, (*C.Eo)(v))
-{{- else}}
-    C.{{.CSetName}}(eo, C.{{.CType}}(v))
-{{- end}}
-}
-{{end}}
-{{end}}
-
-{{range .Methods}}
-// {{.GoName}} calls the {{.CName}} EFL method.
-{{- $hasReturn := .ReturnType}}
-func (o *{{$.GoName}}) {{.GoName}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.GoName}} {{$p.GoType}}{{end}}) {{if $hasReturn}}{{.ReturnType}}{{end}} {
-    eo := (*C.Eo)(o.Eo())
-    _ = eo
-{{- range .Params}}
-{{- if needsStringConversion .CType}}
-    _c{{.GoName}} := C.CString({{.GoName}})
-    defer C.free(unsafe.Pointer(_c{{.GoName}}))
-{{- end}}
-{{- end}}
-{{- if $hasReturn}}
-    ret := C.{{.CName}}(eo{{range .Params}}, {{if needsStringConversion .CType}}_c{{.GoName}}{{else if isPointerType .GoType}}(*C.Eo)({{.GoName}}){{else}}C.{{.CType}}({{.GoName}}){{end}}{{end}})
-{{- if needsStringConversion .ReturnCType}}
-    return C.GoString(ret)
-{{- else if isPointerType .ReturnType}}
-    return unsafe.Pointer(ret)
-{{- else}}
-    return {{.ReturnType}}(ret)
-{{- end}}
-{{- else}}
-    C.{{.CName}}(eo{{range .Params}}, {{if needsStringConversion .CType}}_c{{.GoName}}{{else if isPointerType .GoType}}(*C.Eo)({{.GoName}}){{else}}C.{{.CType}}({{.GoName}}){{end}}{{end}})
-{{- end}}
-}
-{{end}}
-
-{{range .Events}}
-// {{.GoName}} registers a callback for the {{.CEventName}} event.
-func (o *{{$.GoName}}) {{.GoName}}(cb func({{if .PayloadGoType}}{{.PayloadGoType}}{{end}})) {
-    _ = cb
-    // TODO: wire up Efl_Event_Cb and efl_event_callback_add
-}
-{{end}}
diff --git a/cmd/ego-gen/typemap.go b/cmd/ego-gen/typemap.go
index d45889b..7c4948f 100644
--- a/cmd/ego-gen/typemap.go
+++ b/cmd/ego-gen/typemap.go
@@ -72,3 +72,28 @@ func NeedsStringConversion(ctype string) bool {
 func IsPointerType(goType string) bool {
 	return goType == "unsafe.Pointer"
 }
+
+// CTypeToCgo maps a C type to a valid cgo type identifier that can be used in
+// expressions like C.<type>(value). Multi-word C types like "unsigned int" are
+// mapped to their single-word cgo equivalents ("C.uint").
+func CTypeToCgo(ctype string) string {
+	switch ctype {
+	case "unsigned int", "uint":
+		return "uint"
+	case "unsigned short":
+		return "ushort"
+	case "unsigned long":
+		return "ulong"
+	case "unsigned char":
+		return "uchar"
+	case "long long":
+		return "longlong"
+	case "unsigned long long":
+		return "ulonglong"
+	case "signed char":
+		return "schar"
+	}
+	// For single-word types and typedefs (int, double, Eina_Bool, etc.)
+	// the ctype is already valid as a cgo identifier.
+	return ctype
+}

-- 
To stop receiving notification emails like this one, please contact
the administrator of this repository.

Reply via email to