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.

Reply via email to