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 4d2c289fa88bd7948b2d2ac2787c6780af7ce105
Author: [email protected] <[email protected]>
AuthorDate: Mon Mar 30 16:46:40 2026 -0600
feat(ego-gen): add out-param struct and slice method support
Generate C wrapper shims for methods with struct/slice out-params.
Known struct out-params decompose to field values, Slice out-params
convert to []byte via C.GoBytes. Add Eina_Slice and Eina_Rw_Slice
to knownStructs.
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
cmd/ego-gen/generator.go | 15 ++++-
cmd/ego-gen/generator_test.go | 121 ++++++++++++++++++++++++++++++++++++
cmd/ego-gen/main.go | 33 ++++++----
cmd/ego-gen/templates/class.go.tmpl | 73 ++++++++++++++++++++++
cmd/ego-gen/typemap.go | 14 +++++
cmd/ego-gen/typemap_test.go | 21 +++++--
6 files changed, 260 insertions(+), 17 deletions(-)
diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index 7de63ff..ef5828d 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -63,8 +63,9 @@ type MethodData struct {
GoName string
CName string
Params []ParamData
- ReturnType string // Go type, empty if void
+ ReturnType string // Go type, empty if void
ReturnCType string
+ OutParams []OutParamData // out-param struct returns
}
// ParamData describes a single parameter of a method.
@@ -147,6 +148,18 @@ type IndexedPropertyData struct {
MultiSetValues []MultiValueData
}
+// 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.
+type OutParamData struct {
+ GoName string // e.g. "size"
+ GoType string // e.g. "efl.Size2D" or "[]byte"
+ CType string // e.g. "Eina_Size2D" or "Eina_Rw_Slice"
+ IsStruct bool // true for known struct types (not slice)
+ IsSlice bool // true for Eina_Slice / Eina_Rw_Slice → []byte
+ StructInfo *StructInfo // populated when IsStruct or IsSlice
+}
+
// GlobalFunctionData describes an EFL global (class-level) function with no
// Eo* receiver. These generate package-level Go functions instead of methods.
type GlobalFunctionData struct {
diff --git a/cmd/ego-gen/generator_test.go b/cmd/ego-gen/generator_test.go
index ca46415..6d17e40 100644
--- a/cmd/ego-gen/generator_test.go
+++ b/cmd/ego-gen/generator_test.go
@@ -1019,3 +1019,124 @@ func TestGenerateClass_GlobalFunctionString(t *testing.T) {
}
}
}
+
+func TestGenerateClass_OutParamStruct(t *testing.T) {
+ g, outDir := newTestGenerator(t)
+
+ data := ClassData{
+ PackageName: "layout",
+ GoName: "Calc",
+ EolianName: "Efl.Layout.Calc",
+ CClassName: "EFL_LAYOUT_CALC_CLASS",
+ CClassGetFunc: "efl_layout_calc_class_get",
+ CPrefix: "efl_layout_calc",
+ EOHeaderFile: "efl_layout_calc.eo.h",
+ IsAbstract: true,
+ Methods: []MethodData{
+ {
+ GoName: "CalcSizeMin",
+ CName: "efl_layout_calc_size_min",
+ OutParams: []OutParamData{
+ {
+ GoName: "size",
+ GoType: "efl.Size2D",
+ CType: "Eina_Size2D",
+ IsStruct: true,
+ StructInfo: &StructInfo{
+ GoType: "efl.Size2D",
+ CType: "Eina_Size2D",
+ GoFields: []string{"W", "H"},
+ CFields: []string{"w", "h"},
+ CFieldTypes: []string{"int", "int"},
+ },
+ },
+ },
+ },
+ },
+ }
+
+ if err := g.GenerateClass(data); err != nil {
+ t.Fatalf("GenerateClass: %v", err)
+ }
+
+ outFile := filepath.Join(outDir, "layout", "calc.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{
+ "_ego_outparam_efl_layout_calc_size_min",
+ "func (o *Calc) CalcSizeMin() efl.Size2D",
+ "efl.Size2D{",
+ }
+
+ for _, want := range mustContain {
+ if !strings.Contains(src, want) {
+ t.Errorf("output missing expected pattern %q\nfile content:\n%s", want, src)
+ }
+ }
+}
+
+func TestGenerateClass_OutParamSlice(t *testing.T) {
+ g, outDir := newTestGenerator(t)
+
+ data := ClassData{
+ PackageName: "io",
+ GoName: "Reader",
+ EolianName: "Efl.Io.Reader",
+ CClassName: "EFL_IO_READER_CLASS",
+ CClassGetFunc: "efl_io_reader_class_get",
+ CPrefix: "efl_io_reader",
+ EOHeaderFile: "efl_io_reader.eo.h",
+ IsAbstract: true,
+ Methods: []MethodData{
+ {
+ GoName: "Read",
+ CName: "efl_io_reader_read",
+ OutParams: []OutParamData{
+ {
+ GoName: "data",
+ GoType: "[]byte",
+ CType: "Eina_Rw_Slice",
+ IsSlice: true,
+ StructInfo: &StructInfo{
+ GoType: "[]byte",
+ CType: "Eina_Rw_Slice",
+ GoFields: []string{"Mem", "Len"},
+ CFields: []string{"mem", "len"},
+ CFieldTypes: []string{"void *", "size_t"},
+ IsSlice: true,
+ },
+ },
+ },
+ },
+ },
+ }
+
+ if err := g.GenerateClass(data); err != nil {
+ t.Fatalf("GenerateClass: %v", err)
+ }
+
+ outFile := filepath.Join(outDir, "io", "reader.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{
+ "_ego_outparam_efl_io_reader_read",
+ "func (o *Reader) Read() []byte",
+ "C.GoBytes(",
+ }
+
+ for _, want := range mustContain {
+ if !strings.Contains(src, want) {
+ t.Errorf("output missing expected pattern %q\nfile content:\n%s", want, src)
+ }
+ }
+}
diff --git a/cmd/ego-gen/main.go b/cmd/ego-gen/main.go
index 8438600..e1cdada 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -95,9 +95,6 @@ var methodBlocklist = map[string]bool{
"efl_access_component_screen_position_set": true,
// efl.Access.Text — not in Elementary.h
"efl_access_text_caret_offset_set": true,
- // efl.Layout.Calc — struct-valued params/returns that can't be wrapped simply
- "efl_layout_calc_size_min": true,
- "efl_layout_calc_parts_extends": true,
// efl.Layout.Signal — function has more params than Eolian reports
"efl_layout_signal_callback_add": true,
"efl_layout_signal_callback_del": true,
@@ -195,8 +192,6 @@ var methodBlocklist = map[string]bool{
"efl_ui_clock_time_min_set": true,
"efl_ui_clock_time_max_set": true,
"efl_ui_clock_time_set": true,
- // efl.Gfx.Path — out-param Eina_Rect, not a simple return
- "efl_gfx_path_bounds_get": true,
// efl.Gfx.Fill — fill setter now handled via known struct support.
// efl.Io — _fd property setters and internal state setters not in Elementary.h
"efl_io_writer_fd_set": true,
@@ -358,15 +353,11 @@ var methodBlocklist = map[string]bool{
"efl_animation_default_duration_set": true,
// efl.Canvas.GestureRecognizer — returns enum (not a pointer) through pointer path
"efl_gesture_recognizer_recognize": true,
- // efl.Canvas.GestureTouch — methods returning Eina_Vector2 by value (not properties)
- "efl_gesture_touch_delta": true,
- "efl_gesture_touch_distance": true,
// efl.Canvas.Gesture — hotspot setter now handled via known struct support.
// efl.Input_Text — imdata setter takes Eina_Slice struct by value
"efl_input_text_input_panel_imdata_set": true,
- // efl.Canvas.Scene — out-param Eina_Size2D; pointer_position has extra out-param
- "efl_canvas_scene_image_max_size_get": true,
- "efl_canvas_scene_pointer_position_get": true,
+ // efl.Canvas.Scene — pointer_position has extra out-param
+ "efl_canvas_scene_pointer_position_get": true,
// efl.Canvas.Group — not in Elementary.h (evas internal)
"efl_canvas_group_clipper_get": true,
// efl.Canvas.Object — not in Elementary.h (evas internal)
@@ -1217,9 +1208,26 @@ func buildMethodData(f *Function, cPrefix string) MethodData {
cName := f.FullCName(FunctionMethod)
var params []ParamData
+ var outParams []OutParamData
for _, p := range f.Parameters() {
if p.Direction() != ParamIn {
- continue // skip out/inout for now
+ if p.Direction() == ParamOut || p.Direction() == ParamInOut {
+ pt := p.Type()
+ if pt != nil {
+ ct := pt.CType()
+ if si := GetStructInfo(ct); si != nil {
+ outParams = append(outParams, OutParamData{
+ GoName: snakeToLowerCamel(p.Name()),
+ GoType: si.GoType,
+ CType: ct,
+ IsStruct: !si.IsSlice,
+ IsSlice: si.IsSlice,
+ StructInfo: si,
+ })
+ }
+ }
+ }
+ continue
}
params = append(params, buildParamData(p))
}
@@ -1242,6 +1250,7 @@ func buildMethodData(f *Function, cPrefix string) MethodData {
Params: params,
ReturnType: returnType,
ReturnCType: returnCType,
+ OutParams: outParams,
}
}
diff --git a/cmd/ego-gen/templates/class.go.tmpl b/cmd/ego-gen/templates/class.go.tmpl
index f40cfa9..539d704 100644
--- a/cmd/ego-gen/templates/class.go.tmpl
+++ b/cmd/ego-gen/templates/class.go.tmpl
@@ -159,6 +159,30 @@ static void _ego_get_struct_ip_{{$ip.CGetName}}(const Eo *obj{{range $ip.Keys}},
}
{{- end}}
{{- end}}
+{{- range $m := .Methods}}
+{{- range $op := $m.OutParams}}
+{{- if $op.IsStruct}}
+// _ego_outparam_{{$m.CName}} wraps the method, decomposing the out-param struct
+// into individual pointer arguments for safe cgo consumption.
+static void _ego_outparam_{{$m.CName}}(Eo *obj{{range $i, $p := $m.Params}}, {{if needsStringConversion $p.CType}}const char *{{else if isBoolType $p.CType}}Eina_Bool{{else if $p.IsEo}}Eo *{{else if isPointerType $p.GoType}}void *{{else}}{{$p.CType}}{{end}} {{$p.GoName}}{{end}}{{range $i, $f := $op.StructInfo.CFields}}, {{index $op.StructInfo.CFieldTypes $i}} *out_{{$f}}{{end}}) {
+ {{$op.CType}} _r = {{$m.CName}}(obj{{range $m.Params}}, {{.GoName}}{{end}});
+{{- range $i, $f := $op.StructInfo.CFields}}
+ *out_{{$f}} = _r{{$op.StructInfo.CAccessPrefix}}.{{$f}};
+{{- end}}
+}
+{{- end}}
+{{- if $op.IsSlice}}
+// _ego_outparam_{{$m.CName}} wraps the method, decomposing the slice out-param
+// into mem and len pointer arguments for safe cgo consumption.
+static void _ego_outparam_{{$m.CName}}(Eo *obj{{range $i, $p := $m.Params}}, {{if needsStringConversion $p.CType}}const char *{{else if isBoolType $p.CType}}Eina_Bool{{else if $p.IsEo}}Eo *{{else if isPointerType $p.GoType}}void *{{else}}{{$p.CType}}{{end}} {{$p.GoName}}{{end}}, void **out_mem, size_t *out_len) {
+ {{$op.CType}} _s = {0};
+ {{$m.CName}}(obj{{range $m.Params}}, {{.GoName}}{{end}}, &_s);
+ *out_mem = (void *)_s.mem;
+ *out_len = _s.len;
+}
+{{- end}}
+{{- end}}
+{{- end}}
{{- range .Events}}
// _ego_ev_{{.CEventName}} returns the event description pointer using the EFL
// macro, avoiding a conflicting extern declaration for the symbol name.
@@ -360,6 +384,54 @@ func (o *{{$.GoName}}) {{$prop.GoSetter}}(val {{if $prop.IsEo}}efl.Objecter{{els
{{- end}}
{{- range .Methods}}
+{{- if .OutParams}}
+{{- $m := .}}
+{{- range $op := .OutParams}}
+{{- if $op.IsStruct}}
+
+// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
+func (o *{{$.GoName}}) {{$m.GoName}}({{range $i, $p := $m.Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}) {{$op.GoType}} {
+{{- range $m.Params}}
+{{- if needsStringConversion .CType}}
+ c{{.GoName}} := C.CString({{.GoName}})
+ defer C.free(unsafe.Pointer(c{{.GoName}}))
+{{- else if isBoolType .CType}}
+ var c{{.GoName}} C.{{cTypeToCgo .CType}}
+ if {{.GoName}} { c{{.GoName}} = 1 }
+{{- end}}
+{{- end}}
+{{- range $i, $f := $op.StructInfo.CFields}}
+ var c{{$f}} C.{{index $op.StructInfo.CFieldTypes $i}}
+{{- end}}
+ C._ego_outparam_{{$m.CName}}((*C.Eo)(o.Eo()){{range $m.Params}}, {{template "paramArg" .}}{{end}}{{range $op.StructInfo.CFields}}, &c{{.}}{{end}})
+ return {{$op.GoType}}{ {{- range $i, $gf := $op.StructInfo.GoFields}}{{if $i}}, {{end}}{{$gf}}: {{if eq (index $op.StructInfo.CFieldTypes $i) "double"}}float64{{else}}int{{end}}(c{{index $op.StructInfo.CFields $i}}){{end -}} }
+}
+{{- end}}
+{{- if $op.IsSlice}}
+
+// {{$m.GoName}} calls the {{$m.CName}} method on {{$.GoName}}.
+func (o *{{$.GoName}}) {{$m.GoName}}({{range $i, $p := $m.Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}) []byte {
+{{- range $m.Params}}
+{{- if needsStringConversion .CType}}
+ c{{.GoName}} := C.CString({{.GoName}})
+ defer C.free(unsafe.Pointer(c{{.GoName}}))
+{{- else if isBoolType .CType}}
+ var c{{.GoName}} C.{{cTypeToCgo .CType}}
+ if {{.GoName}} { c{{.GoName}} = 1 }
+{{- end}}
+{{- end}}
+ var cMem unsafe.Pointer
+ var cLen C.size_t
+ C._ego_outparam_{{$m.CName}}((*C.Eo)(o.Eo()){{range $m.Params}}, {{template "paramArg" .}}{{end}}, &cMem, &cLen)
+ if cMem == nil || cLen == 0 {
+ return nil
+ }
+ return C.GoBytes(cMem, C.int(cLen))
+}
+{{- end}}
+{{- end}}
+{{- else}}
+
// {{.GoName}} calls the {{.CName}} method on {{$.GoName}}.
func (o *{{$.GoName}}) {{.GoName}}({{range $i, $p := .Params}}{{if $i}}, {{end}}{{$p.GoName}} {{if $p.IsEo}}efl.Objecter{{else}}{{$p.GoType}}{{end}}{{end}}) {{if .ReturnType}}{{.ReturnType}}{{end}} {
{{- range .Params}}
@@ -387,6 +459,7 @@ func (o *{{$.GoName}}) {{.GoName}}({{range $i, $p := .Params}}{{if $i}}, {{end}}
{{- end}}
}
{{- end}}
+{{- end}}
{{- range $ip := .IndexedProperties}}
type {{$ip.AccessorTypeName}} struct {
diff --git a/cmd/ego-gen/typemap.go b/cmd/ego-gen/typemap.go
index 64fd3e4..acaacf6 100644
--- a/cmd/ego-gen/typemap.go
+++ b/cmd/ego-gen/typemap.go
@@ -15,6 +15,8 @@ type StructInfo struct {
// CAccessPrefix is prepended to C field access for nested structs.
// e.g. ".rect" for Eina_Rect which is { Eina_Rectangle rect; }.
CAccessPrefix string
+ // IsSlice is true for Eina_Slice/Eina_Rw_Slice which map to []byte in Go.
+ IsSlice bool
}
// knownStructs maps C struct type names to their StructInfo.
@@ -40,6 +42,18 @@ var knownStructs = map[string]*StructInfo{
GoFields: []string{"X", "Y"}, CFields: []string{"x", "y"},
CFieldTypes: []string{"double", "double"},
},
+ "Eina_Slice": {
+ GoType: "[]byte", CType: "Eina_Slice",
+ GoFields: []string{"Mem", "Len"}, CFields: []string{"mem", "len"},
+ CFieldTypes: []string{"const void *", "size_t"},
+ IsSlice: true,
+ },
+ "Eina_Rw_Slice": {
+ GoType: "[]byte", CType: "Eina_Rw_Slice",
+ GoFields: []string{"Mem", "Len"}, CFields: []string{"mem", "len"},
+ CFieldTypes: []string{"void *", "size_t"},
+ IsSlice: true,
+ },
}
// IsKnownStruct reports whether a C type is a known struct type that can be
diff --git a/cmd/ego-gen/typemap_test.go b/cmd/ego-gen/typemap_test.go
index 38fb1c2..e5a75b2 100644
--- a/cmd/ego-gen/typemap_test.go
+++ b/cmd/ego-gen/typemap_test.go
@@ -177,7 +177,8 @@ func TestIsKnownStruct(t *testing.T) {
{"Eina_Position2D", true},
{"Eina_Rect", true},
{"Eina_Vector2", true},
- {"Eina_Slice", false},
+ {"Eina_Slice", true},
+ {"Eina_Rw_Slice", true},
{"int", false},
{"const char *", false},
{"", false},
@@ -226,9 +227,21 @@ func TestGetStructInfo(t *testing.T) {
t.Errorf("Eina_Rect GoFields has %d elements, want 4", len(rinfo.GoFields))
}
- // Unknown type returns nil
- if GetStructInfo("Eina_Slice") != nil {
- t.Error("GetStructInfo(\"Eina_Slice\") returned non-nil, want nil")
+ // Eina_Slice is a known slice type
+ sliceInfo := GetStructInfo("Eina_Slice")
+ if sliceInfo == nil {
+ t.Fatal("GetStructInfo(\"Eina_Slice\") returned nil, want non-nil")
+ }
+ if !sliceInfo.IsSlice {
+ t.Error("Eina_Slice IsSlice = false, want true")
+ }
+ if sliceInfo.GoType != "[]byte" {
+ t.Errorf("Eina_Slice GoType = %q, want \"[]byte\"", sliceInfo.GoType)
+ }
+
+ // Truly unknown type returns nil
+ if GetStructInfo("Eina_Unknown_Type") != nil {
+ t.Error("GetStructInfo(\"Eina_Unknown_Type\") returned non-nil, want nil")
}
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.