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 1a13614ef083d831e58000527ef3ca6947099444
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 30 21:50:29 2026 -0600
feat(ego-gen): add function pointer introspection and callback data model
Add Typedecl wrapper, TypedeclFunctionPointer constant, and
Type.IsFunctionPointer() for Eolian introspection. Add
CallbackTypeData/CallbackParamData structs and buildCallbackTypeData
for extracting callback signatures from Eolian function pointer types.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
cmd/ego-gen/callback.go | 73 +++++++++++++++++++++++++++++++++++++
cmd/ego-gen/eolian.go | 49 +++++++++++++++++++++++++
cmd/ego-gen/eolian_test.go | 6 ++++
cmd/ego-gen/generator.go | 35 ++++++++++++++----
cmd/ego-gen/generator_test.go | 84 +++++++++++++++++++++++++++++++++++++++++++
5 files changed, 241 insertions(+), 6 deletions(-)
diff --git a/cmd/ego-gen/callback.go b/cmd/ego-gen/callback.go
new file mode 100644
index 0000000..6d4f63a
--- /dev/null
+++ b/cmd/ego-gen/callback.go
@@ -0,0 +1,73 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+)
+
+// isCallbackParam reports whether a method parameter is a function pointer type.
+func isCallbackParam(p *Parameter) bool {
+ pt := p.Type()
+ if pt == nil {
+ return false
+ }
+ return pt.IsFunctionPointer()
+}
+
+// buildCallbackTypeData extracts a CallbackTypeData from a function pointer parameter.
+func buildCallbackTypeData(p *Parameter) (*CallbackTypeData, error) {
+ pt := p.Type()
+ if pt == nil {
+ return nil, fmt.Errorf("parameter %q has nil type", p.Name())
+ }
+ td := pt.Typedecl()
+ if td == nil {
+ return nil, fmt.Errorf("parameter %q type has no typedecl", p.Name())
+ }
+ fp := td.FunctionPointer()
+ if fp == nil {
+ return nil, fmt.Errorf("parameter %q typedecl is not a function pointer", p.Name())
+ }
+
+ eolianName := td.Name()
+ goTrampoline := "egoCallback_" + eolianName
+ cTypedef := eolianName
+
+ var cbParams []ParamData
+ for _, cp := range fp.Parameters() {
+ cbParams = append(cbParams, buildParamData(cp))
+ }
+
+ var returnType, returnCType string
+ if rt := fp.ReturnType(FunctionMethod); rt != nil {
+ returnCType = rt.CType()
+ returnType = CTypeToGo(returnCType)
+ if returnCType == "void" || returnType == "" {
+ returnType = ""
+ returnCType = ""
+ }
+ }
+
+ var paramTypes []string
+ for _, cp := range cbParams {
+ if cp.IsEo {
+ paramTypes = append(paramTypes, "unsafe.Pointer")
+ } else {
+ paramTypes = append(paramTypes, cp.GoType)
+ }
+ }
+ goFuncType := "func(" + strings.Join(paramTypes, ", ") + ")"
+ if returnType != "" {
+ goFuncType += " " + returnType
+ }
+
+ return &CallbackTypeData{
+ EolianName: eolianName,
+ GoTrampolineName: goTrampoline,
+ CTypedef: cTypedef,
+ Params: cbParams,
+ ReturnType: returnType,
+ ReturnCType: returnCType,
+ GoFuncType: goFuncType,
+ }, nil
+}
diff --git a/cmd/ego-gen/eolian.go b/cmd/ego-gen/eolian.go
index 3aa1e3e..3e9843c 100644
--- a/cmd/ego-gen/eolian.go
+++ b/cmd/ego-gen/eolian.go
@@ -64,6 +64,13 @@ const (
BuiltinTypeIterator BuiltinType = C.EOLIAN_TYPE_BUILTIN_ITERATOR
)
+// TypedeclType represents the kind of an Eolian type declaration.
+type TypedeclType int
+
+const (
+ TypedeclFunctionPointer TypedeclType = C.EOLIAN_TYPEDECL_FUNCTION_POINTER
+)
+
// iterCollect drains an Eina_Iterator into a slice using the provided convert
// function. It frees the iterator when done. Returns nil if iter is nil.
func iterCollect[T any](iter *C.Eina_Iterator, convert func(unsafe.Pointer) T) []T {
@@ -330,6 +337,48 @@ func (t *Type) IsIterator() bool {
return t.BuiltinType() == BuiltinTypeIterator
}
+// Typedecl returns the type declaration for this type, or nil.
+func (t *Type) Typedecl() *Typedecl {
+ td := C.eolian_type_typedecl_get(t.ptr)
+ if td == nil {
+ return nil
+ }
+ return &Typedecl{ptr: td}
+}
+
+// IsFunctionPointer reports whether this type is a function pointer typedef.
+func (t *Type) IsFunctionPointer() bool {
+ td := t.Typedecl()
+ if td == nil {
+ return false
+ }
+ return td.Type() == TypedeclFunctionPointer
+}
+
+// Typedecl wraps an Eolian_Typedecl pointer.
+type Typedecl struct {
+ ptr *C.Eolian_Typedecl
+}
+
+// Type returns the kind of this type declaration.
+func (td *Typedecl) Type() TypedeclType {
+ return TypedeclType(C.eolian_typedecl_type_get(td.ptr))
+}
+
+// FunctionPointer returns the Function representing this callback typedef.
+func (td *Typedecl) FunctionPointer() *Function {
+ f := C.eolian_typedecl_function_pointer_get(td.ptr)
+ if f == nil {
+ return nil
+ }
+ return &Function{ptr: f}
+}
+
+// Name returns the short name of this type declaration.
+func (td *Typedecl) Name() string {
+ return C.GoString(C.eolian_typedecl_short_name_get(td.ptr))
+}
+
// Constructor wraps an Eolian_Constructor pointer.
type Constructor struct {
ptr *C.Eolian_Constructor
diff --git a/cmd/ego-gen/eolian_test.go b/cmd/ego-gen/eolian_test.go
index a0a1d6d..3aba06f 100644
--- a/cmd/ego-gen/eolian_test.go
+++ b/cmd/ego-gen/eolian_test.go
@@ -182,6 +182,12 @@ func TestBuiltinTypeIteratorConstant(t *testing.T) {
}
}
+func TestTypedeclFunctionPointerConstants(t *testing.T) {
+ if TypedeclFunctionPointer == 0 {
+ t.Error("TypedeclFunctionPointer should be non-zero")
+ }
+}
+
// TestClassTypes verifies that every parsed class has a type value within the
// known valid range (i.e. one of the four ClassType constants).
func TestClassTypes(t *testing.T) {
diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index ef5828d..0b0c37d 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -37,6 +37,8 @@ type ClassData struct {
HasIterators bool // true if IteratorMethods is non-empty (controls import)
IteratorStructTypes []IteratorMethodData // one entry per distinct struct elem type (for C helpers)
GlobalFunctions []GlobalFunctionData // package-level functions with no Eo receiver
+ CallbackTypes []CallbackTypeData // distinct callback typedefs used by this class
+ HasCallbacks bool // true if CallbackTypes is non-empty (controls imports/trampolines)
}
// ConstructorData describes a constructor option for an EFL class. Each entry
@@ -60,12 +62,13 @@ type ConstructorData struct {
// MethodData describes a single method on an EFL class.
type MethodData struct {
- GoName string
- CName string
- Params []ParamData
- ReturnType string // Go type, empty if void
- ReturnCType string
- OutParams []OutParamData // out-param struct returns
+ GoName string
+ CName string
+ Params []ParamData
+ ReturnType string // Go type, empty if void
+ ReturnCType string
+ OutParams []OutParamData // out-param struct returns
+ CallbackParams []CallbackParamData
}
// ParamData describes a single parameter of a method.
@@ -148,6 +151,26 @@ type IndexedPropertyData struct {
MultiSetValues []MultiValueData
}
+// CallbackTypeData describes an Eolian function pointer typedef used as a
+// callback parameter. It holds all information needed to generate the C
+// trampoline declaration and the Go wrapper signature.
+type CallbackTypeData struct {
+ EolianName string
+ GoTrampolineName string
+ CTypedef string
+ Params []ParamData
+ ReturnType string
+ ReturnCType string
+ GoFuncType string
+}
+
+// CallbackParamData describes a single callback (function pointer) parameter
+// of a method, pairing the Go parameter name with its full type metadata.
+type CallbackParamData struct {
+ GoName string
+ CallbackType *CallbackTypeData
+}
+
// OutParamData describes a single out-parameter of a method that returns
// a struct value. The codegen generates a C wrapper shim to decompose the
// struct into individual fields for cgo consumption.
diff --git a/cmd/ego-gen/generator_test.go b/cmd/ego-gen/generator_test.go
index ae17367..9182629 100644
--- a/cmd/ego-gen/generator_test.go
+++ b/cmd/ego-gen/generator_test.go
@@ -1140,3 +1140,87 @@ func TestGenerateClass_OutParamSlice(t *testing.T) {
}
}
}
+
+func TestGenerateClass_CallbackMethod(t *testing.T) {
+ g, outDir := newTestGenerator(t)
+
+ data := ClassData{
+ PackageName: "layout",
+ GoName: "Signal",
+ EolianName: "Efl.Layout.Signal",
+ CClassName: "EFL_LAYOUT_SIGNAL_CLASS",
+ CClassGetFunc: "efl_layout_signal_class_get",
+ CPrefix: "efl_layout_signal",
+ EOHeaderFile: "efl_layout_signal.eo.h",
+ IsAbstract: true,
+ HasCallbacks: true,
+ Methods: []MethodData{
+ {
+ GoName: "SignalCallbackAdd",
+ CName: "efl_layout_signal_callback_add",
+ ReturnType: "bool",
+ ReturnCType: "Eina_Bool",
+ Params: []ParamData{
+ {GoName: "emission", GoType: "string", CType: "const char *"},
+ {GoName: "source", GoType: "string", CType: "const char *"},
+ },
+ CallbackParams: []CallbackParamData{
+ {
+ GoName: "fn",
+ CallbackType: &CallbackTypeData{
+ EolianName: "EflLayoutSignalCb",
+ GoTrampolineName: "egoCallback_EflLayoutSignalCb",
+ CTypedef: "EflLayoutSignalCb",
+ Params: []ParamData{
+ {GoName: "object", GoType: "unsafe.Pointer", CType: "Eo *", IsEo: true},
+ {GoName: "emission", GoType: "string", CType: "const char *"},
+ {GoName: "source", GoType: "string", CType: "const char *"},
+ },
+ GoFuncType: "func(unsafe.Pointer, string, string)",
+ },
+ },
+ },
+ },
+ },
+ CallbackTypes: []CallbackTypeData{
+ {
+ EolianName: "EflLayoutSignalCb",
+ GoTrampolineName: "egoCallback_EflLayoutSignalCb",
+ CTypedef: "EflLayoutSignalCb",
+ Params: []ParamData{
+ {GoName: "object", GoType: "unsafe.Pointer", CType: "Eo *", IsEo: true},
+ {GoName: "emission", GoType: "string", CType: "const char *"},
+ {GoName: "source", GoType: "string", CType: "const char *"},
+ },
+ GoFuncType: "func(unsafe.Pointer, string, string)",
+ },
+ },
+ }
+
+ if err := g.GenerateClass(data); err != nil {
+ t.Fatalf("GenerateClass: %v", err)
+ }
+
+ outFile := filepath.Join(outDir, "layout", "signal.go")
+ content, err := os.ReadFile(outFile)
+ if err != nil {
+ t.Fatalf("output file not found at %s: %v", outFile, err)
+ }
+
+ src := string(content)
+
+ mustContain := []string{
+ "extern void egoCallback_EflLayoutSignalCb(",
+ "extern void egoCallbackFree(",
+ "func (o *Signal) SignalCallbackAdd(emission string, source string, fn func(unsafe.Pointer, string, string)) bool",
+ "efl.CallbackRegister(fn)",
+ "efl_layout_signal_callback_add",
+ "egoCallbackFree",
+ }
+
+ for _, want := range mustContain {
+ if !strings.Contains(src, want) {
+ t.Errorf("output missing expected pattern %q\nfile content:\n%s", want, src)
+ }
+ }
+}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.