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 988b1707300faae71eb2a72c545e2921f7d95774
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 1 19:34:28 2026 -0600

    feat(ego-gen): add part accessor template with proxy structs
    
    Generate typed part proxy structs and accessor methods for widget
    parts declared in Eolian. Each proxy method atomically calls
    efl_part + interface method via a C helper. Collision detection
    skips parts whose names match existing property bindings.
    
    Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
---
 cmd/ego-gen/generator.go            |   1 +
 cmd/ego-gen/generator_test.go       |  23 +++---
 cmd/ego-gen/main.go                 |  18 ++++-
 cmd/ego-gen/templates/class.go.tmpl | 151 ++++++++++++++++++++++++++++++++++++
 4 files changed, 180 insertions(+), 13 deletions(-)

diff --git a/cmd/ego-gen/generator.go b/cmd/ego-gen/generator.go
index de4a538..e31360f 100644
--- a/cmd/ego-gen/generator.go
+++ b/cmd/ego-gen/generator.go
@@ -322,6 +322,7 @@ func NewGenerator(templateDir, outputDir string) (*Generator, error) {
 		"isEinaError":       IsEinaError,
 		"isEinaFuture":      IsEinaFuture,
 		"isEinaContent":     IsEinaContent,
+		"trimPrefix":        strings.TrimPrefix,
 	}
 
 	pattern := filepath.Join(templateDir, "*.tmpl")
diff --git a/cmd/ego-gen/generator_test.go b/cmd/ego-gen/generator_test.go
index 0f1c20f..bf630c5 100644
--- a/cmd/ego-gen/generator_test.go
+++ b/cmd/ego-gen/generator_test.go
@@ -1540,20 +1540,19 @@ func TestGenerateClass_PartAccessor(t *testing.T) {
 
 	src := string(content)
 
-	// These will fail until Task 3 adds the template — that's expected.
-	// For now, just verify the file is generated without error.
-	t.Logf("Generated file length: %d bytes", len(content))
-
-	// Check that these are NOT present yet (template hasn't been updated):
-	partsPatterns := []string{
+	mustContain := []string{
 		"type defaultItemIconPart struct",
-		"func (o *DefaultItem) Icon()",
+		"parent unsafe.Pointer",
+		"// Icon returns the icon part of DefaultItem.",
+		"func (o *DefaultItem) Icon() defaultItemIconPart",
+		"func (p defaultItemIconPart) Content() unsafe.Pointer",
+		"func (p defaultItemIconPart) SetContent(val efl.Objecter)",
+		"efl_part",
 	}
-	for _, p := range partsPatterns {
-		if strings.Contains(src, p) {
-			t.Logf("Found part pattern (template already supports parts): %q", p)
-		} else {
-			t.Logf("Part pattern not yet present (expected, template not updated): %q", p)
+
+	for _, want := range mustContain {
+		if !strings.Contains(src, want) {
+			t.Errorf("missing %q\n%s", want, src)
 		}
 	}
 }
diff --git a/cmd/ego-gen/main.go b/cmd/ego-gen/main.go
index 6357864..3c3cd0d 100644
--- a/cmd/ego-gen/main.go
+++ b/cmd/ego-gen/main.go
@@ -612,6 +612,9 @@ func buildClassData(c *Class, pkgOptionNames map[string]map[string]bool) (ClassD
 		}
 	}
 
+	// Collect declared parts with collision detection.
+	parts := buildPartsData(c, goName, goNames)
+
 	// The class-specific .eo.h file name mirrors the C prefix.
 	// e.g. "Efl.Ui.Button" → cPrefix "efl_ui_button" → "efl_ui_button.eo.h"
 	eoHeaderFile := cPrefix + ".eo.h"
@@ -817,7 +820,8 @@ func buildClassData(c *Class, pkgOptionNames map[string]map[string]bool) (ClassD
 		GlobalFunctions:     globalFunctions,
 		CallbackTypes:       callbackTypes,
 		HasCallbacks:        len(callbackTypes) > 0,
-		HasColor:            hasColorProperty(properties) || hasColorMethod(methods),
+		Parts:               parts,
+		HasColor:            hasColorProperty(properties) || hasColorMethod(methods) || hasColorParts(parts),
 		HasImageTypes:       hasImageTypesProperty(properties) || hasImageTypesMethod(methods) || hasImageTypesIndexed(indexedProperties) || hasImageTypesIterator(iteratorMethods) || hasImageTypesConstructors(constructors) || hasImageTypesConstructors(propertyOptions),
 		HasTime:             hasTimeProperty(properties),
 	}, nil
@@ -844,6 +848,18 @@ func hasColorMethod(methods []MethodData) bool {
 	return false
 }
 
+// hasColorParts reports whether any part method uses color.RGBA.
+func hasColorParts(parts []PartData) bool {
+	for _, p := range parts {
+		for _, m := range p.PartMethods {
+			if m.IsColor {
+				return true
+			}
+		}
+	}
+	return false
+}
+
 // hasImageTypesProperty reports whether any property uses image.Rectangle or
 // image.Point (Eina_Rect or Eina_Position2D), used to decide whether to import
 // "image" in the generated file.
diff --git a/cmd/ego-gen/templates/class.go.tmpl b/cmd/ego-gen/templates/class.go.tmpl
index 5c88a34..9238186 100644
--- a/cmd/ego-gen/templates/class.go.tmpl
+++ b/cmd/ego-gen/templates/class.go.tmpl
@@ -125,6 +125,56 @@ extern void {{.GoTrampolineName}}(void *data{{range .Params}}, {{.CType}} {{.GoN
 {{- end}}
 {{- end}}
 
+{{- range .Parts}}
+{{- range .PartMethods}}
+{{- if .IsGetter}}
+{{- if .IsString}}
+static const char *{{.CHelper}}(const Eo *obj, const char *part_name) {
+    return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else if .IsBool}}
+static Eina_Bool {{.CHelper}}(const Eo *obj, const char *part_name) {
+    return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else if .IsColor}}
+static void {{.CHelper}}(const Eo *obj, const char *part_name, int *r, int *g, int *b, int *a) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), r, g, b, a);
+}
+{{- else if .IsEoParam}}
+static Eo *{{.CHelper}}(const Eo *obj, const char *part_name) {
+    return {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- else}}
+static void *{{.CHelper}}(const Eo *obj, const char *part_name) {
+    return (void *)(uintptr_t){{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name));
+}
+{{- end}}
+{{- else if .IsSetter}}
+{{- if .IsString}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, const char *val) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else if .IsBool}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, Eina_Bool val) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else if .IsColor}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, int r, int g, int b, int a) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), r, g, b, a);
+}
+{{- else if .IsEoParam}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, Eo *val) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- else}}
+static void {{.CHelper}}(Eo *obj, const char *part_name, void *val) {
+    {{trimPrefix .CHelper "_ego_part_"}}(efl_part(obj, part_name), val);
+}
+{{- end}}
+{{- end}}
+{{- end}}
+{{- end}}
+
 {{- range .Properties}}
 {{- if and .HasGet (isPointerType .GoType)}}
 // _ego_get_{{.CGetName}} wraps the getter, casting the return value through
@@ -1137,6 +1187,107 @@ func {{$gf.GoName}}({{range $i, $p := $gf.Params}}{{if $i}}, {{end}}{{$p.GoName}
 }
 {{- end}}
 
+{{- range $part := .Parts}}
+
+// {{$part.ProxyTypeName}} provides access to the "{{$part.PartName}}" part of {{$.GoName}}.
+type {{$part.ProxyTypeName}} struct {
+    parent unsafe.Pointer
+    name   string
+}
+
+// {{$part.GoAccessorName}} returns the {{$part.PartName}} part of {{$.GoName}}.
+func (o *{{$.GoName}}) {{$part.GoAccessorName}}() {{$part.ProxyTypeName}} {
+    return {{$part.ProxyTypeName}}{parent: o.Eo(), name: "{{$part.PartName}}"}
+}
+
+{{- range $m := $part.PartMethods}}
+{{- if $m.IsGetter}}
+{{- if $m.IsString}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() string {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    return C.GoString(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- else if $m.IsBool}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() bool {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    return C.{{$m.CHelper}}((*C.Eo)(p.parent), cName) != 0
+}
+{{- else if $m.IsColor}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() color.RGBA {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    var cr, cg, cb, ca C.int
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, &cr, &cg, &cb, &ca)
+    return color.RGBA{R: uint8(cr), G: uint8(cg), B: uint8(cb), A: uint8(ca)}
+}
+{{- else if $m.IsEoParam}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() unsafe.Pointer {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    return unsafe.Pointer(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- else}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}() {{$m.GoReturnType}} {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    return {{$m.GoReturnType}}(C.{{$m.CHelper}}((*C.Eo)(p.parent), cName))
+}
+{{- end}}
+{{- else if $m.IsSetter}}
+{{- if $m.IsString}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} string) {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    cVal := C.CString({{$m.GoParamName}})
+    defer C.free(unsafe.Pointer(cVal))
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else if $m.IsBool}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} bool) {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    var cVal C.Eina_Bool
+    if {{$m.GoParamName}} { cVal = 1 }
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else if $m.IsColor}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}(c color.RGBA) {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, C.int(c.R), C.int(c.G), C.int(c.B), C.int(c.A))
+}
+{{- else if $m.IsEoParam}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} efl.Objecter) {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    var cVal *C.Eo
+    if {{$m.GoParamName}} != nil { cVal = (*C.Eo)({{$m.GoParamName}}.Eo()) }
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, cVal)
+}
+{{- else}}
+
+func (p {{$part.ProxyTypeName}}) {{$m.GoName}}({{$m.GoParamName}} {{$m.GoParamType}}) {
+    cName := C.CString(p.name)
+    defer C.free(unsafe.Pointer(cName))
+    C.{{$m.CHelper}}((*C.Eo)(p.parent), cName, {{$m.GoParamName}})
+}
+{{- end}}
+{{- end}}
+{{- end}}
+
+{{- end}}
+
 {{- define "paramArg"}}
 {{- if .IsEo}}(*C.Eo)({{.GoName}}.Eo())
 {{- else if or (needsStringConversion .CType) (isBoolType .CType)}}c{{.GoName}}

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

Reply via email to