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 6c474798586a19db304a27be8ba162b5cd2a6c9b
Author: [email protected] <[email protected]>
AuthorDate: Tue Mar 31 10:45:32 2026 -0600

    feat(efl): add Eina_Value Go wrapper with generic scalar API
    
    Add efl.Value type wrapping Eina_Value with NewValue[T] and
    ValueGet[T] generic constructors/getters for all scalar types.
    Includes Kind(), String(), Free(), WrapValue(), and Ptr().
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
---
 efl/value.go      | 517 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 efl/value_test.go | 388 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 905 insertions(+)

diff --git a/efl/value.go b/efl/value.go
new file mode 100644
index 0000000..4f1257a
--- /dev/null
+++ b/efl/value.go
@@ -0,0 +1,517 @@
+package efl
+
+/*
+#cgo pkg-config: eina
+
+#include <Eina.h>
+#include <stdlib.h>
+
+// _ego_value_new allocates a new Eina_Value of the given type.
+static Eina_Value *_ego_value_new(const Eina_Value_Type *type) {
+    return eina_value_new(type);
+}
+
+// _ego_value_free releases an Eina_Value allocated with eina_value_new.
+static void _ego_value_free(Eina_Value *v) {
+    eina_value_free(v);
+}
+
+// _ego_value_type_get returns the type descriptor of v.
+static const Eina_Value_Type *_ego_value_type_get(const Eina_Value *v) {
+    return eina_value_type_get(v);
+}
+
+// _ego_value_pset sets the scalar value of v by passing ptr (pointer to the
+// actual value). eina_value_pset is a static inline function; this thin
+// wrapper makes it reachable from cgo.
+static Eina_Bool _ego_value_pset(Eina_Value *v, const void *ptr) {
+    return eina_value_pset(v, ptr);
+}
+
+// _ego_value_pget reads the scalar value of v into the memory pointed to by
+// ptr. eina_value_pget is a static inline function; this thin wrapper makes
+// it reachable from cgo.
+static Eina_Bool _ego_value_pget(const Eina_Value *v, void *ptr) {
+    return eina_value_pget(v, ptr);
+}
+
+// _ego_value_to_string converts v to a human-readable C string. The caller
+// is responsible for freeing the returned pointer with free().
+static char *_ego_value_to_string(const Eina_Value *v) {
+    return eina_value_to_string(v);
+}
+
+// _ego_value_set_string sets the STRING value of v to s. The string type
+// requires pset(&s) — a pointer to the char pointer — not pset(s).
+static Eina_Bool _ego_value_set_string(Eina_Value *v, const char *s) {
+    return eina_value_pset(v, &s);
+}
+
+// _ego_value_get_string retrieves the STRING value of v. Returns the
+// internal string pointer owned by v; the caller must not free it.
+static const char *_ego_value_get_string(const Eina_Value *v) {
+    const char *s = NULL;
+    eina_value_pget(v, &s);
+    return s;
+}
+*/
+import "C"
+
+import (
+	"errors"
+	"fmt"
+	"unsafe"
+)
+
+// ValueKind identifies the broad category of scalar value stored in a Value.
+type ValueKind int
+
+const (
+	ValueKindUnknown ValueKind = iota
+	ValueKindInt
+	ValueKindUint
+	ValueKindInt64
+	ValueKindUint64
+	ValueKindInt16
+	ValueKindUint16
+	ValueKindFloat32
+	ValueKindFloat64
+	ValueKindString
+	ValueKindBool
+	ValueKindByte
+)
+
+// ScalarType is the constraint for types that can be stored in a Value.
+type ScalarType interface {
+	~int | ~uint | ~float64 | ~float32 | ~string | ~bool |
+		~int64 | ~uint64 | ~int32 | ~uint32 | ~int16 | ~uint16 | ~byte
+}
+
+// Value is a Go wrapper around a C Eina_Value scalar.
+// When owned is true, Free releases the underlying C allocation.
+type Value struct {
+	ptr   *C.Eina_Value
+	owned bool
+}
+
+// NewValue creates a new Eina_Value of the type inferred from T and sets it to
+// val. The returned Value is owned and must be released by calling Free when no
+// longer needed.
+func NewValue[T ScalarType](val T) (*Value, error) {
+	v := &Value{owned: true}
+
+	switch any(val).(type) {
+	case int, int32:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_INT)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(INT) returned nil")
+		}
+		cv := C.int(scalarToInt(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(INT) failed")
+		}
+
+	case uint, uint32:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_UINT)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(UINT) returned nil")
+		}
+		cv := C.uint(scalarToUint(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(UINT) failed")
+		}
+
+	case int64:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_INT64)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(INT64) returned nil")
+		}
+		cv := C.int64_t(scalarToInt64(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(INT64) failed")
+		}
+
+	case uint64:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_UINT64)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(UINT64) returned nil")
+		}
+		cv := C.uint64_t(scalarToUint64(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(UINT64) failed")
+		}
+
+	case int16:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_SHORT)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(SHORT) returned nil")
+		}
+		cv := C.short(scalarToInt16(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(SHORT) failed")
+		}
+
+	case uint16:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_USHORT)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(USHORT) returned nil")
+		}
+		cv := C.ushort(scalarToUint16(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(USHORT) failed")
+		}
+
+	case float32:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_FLOAT)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(FLOAT) returned nil")
+		}
+		cv := C.float(scalarToFloat32(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(FLOAT) failed")
+		}
+
+	case float64:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_DOUBLE)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(DOUBLE) returned nil")
+		}
+		cv := C.double(scalarToFloat64(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(DOUBLE) failed")
+		}
+
+	case string:
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_STRING)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(STRING) returned nil")
+		}
+		cs := C.CString(scalarToString(val))
+		defer C.free(unsafe.Pointer(cs))
+		if C._ego_value_set_string(v.ptr, cs) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(STRING) failed")
+		}
+
+	case bool:
+		// EINA_VALUE_TYPE_BOOL maps to an unsigned char (0/1).
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_BOOL)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(BOOL) returned nil")
+		}
+		var cv C.uchar
+		if scalarToBool(val) {
+			cv = 1
+		}
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(BOOL) failed")
+		}
+
+	case byte:
+		// byte == uint8. EINA_VALUE_TYPE_CHAR (signed char) has a distinct type
+		// pointer from EINA_VALUE_TYPE_BOOL/UCHAR, so Kind() can identify byte
+		// values unambiguously. The bit pattern round-trips correctly via a
+		// signed char; the cast to C.char reinterprets high-bit values as
+		// negative, but the byte value is recovered on the read side with the
+		// same cast back to byte.
+		v.ptr = C._ego_value_new(C.EINA_VALUE_TYPE_CHAR)
+		if v.ptr == nil {
+			return nil, errors.New("efl: eina_value_new(CHAR) returned nil")
+		}
+		cv := C.char(scalarToByte(val))
+		if C._ego_value_pset(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			C._ego_value_free(v.ptr)
+			return nil, fmt.Errorf("efl: eina_value_pset(CHAR) failed")
+		}
+
+	default:
+		return nil, fmt.Errorf("efl: unsupported scalar type %T", val)
+	}
+
+	return v, nil
+}
+
+// ValueGet retrieves the value stored in v as type T.
+// Returns an error if T does not match the stored type or if v is nil.
+func ValueGet[T ScalarType](v *Value) (T, error) {
+	var zero T
+	if v == nil || v.ptr == nil {
+		return zero, errors.New("efl: ValueGet called on nil Value")
+	}
+
+	switch any(zero).(type) {
+	case int, int32:
+		var cv C.int
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(INT) failed")
+		}
+		return fromInt[T](int(cv)), nil
+
+	case uint, uint32:
+		var cv C.uint
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(UINT) failed")
+		}
+		return fromUint[T](uint(cv)), nil
+
+	case int64:
+		var cv C.int64_t
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(INT64) failed")
+		}
+		return fromInt64[T](int64(cv)), nil
+
+	case uint64:
+		var cv C.uint64_t
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(UINT64) failed")
+		}
+		return fromUint64[T](uint64(cv)), nil
+
+	case int16:
+		var cv C.short
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(SHORT) failed")
+		}
+		return fromInt16[T](int16(cv)), nil
+
+	case uint16:
+		var cv C.ushort
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(USHORT) failed")
+		}
+		return fromUint16[T](uint16(cv)), nil
+
+	case float32:
+		var cv C.float
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(FLOAT) failed")
+		}
+		return fromFloat32[T](float32(cv)), nil
+
+	case float64:
+		var cv C.double
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(DOUBLE) failed")
+		}
+		return fromFloat64[T](float64(cv)), nil
+
+	case string:
+		cs := C._ego_value_get_string(v.ptr)
+		if cs == nil {
+			return zero, errors.New("efl: eina_value_pget(STRING) returned nil")
+		}
+		return fromString[T](C.GoString(cs)), nil
+
+	case bool:
+		var cv C.uchar
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(BOOL) failed")
+		}
+		return fromBool[T](cv != 0), nil
+
+	case byte:
+		var cv C.char
+		if C._ego_value_pget(v.ptr, unsafe.Pointer(&cv)) == 0 {
+			return zero, errors.New("efl: eina_value_pget(CHAR) failed")
+		}
+		return fromByte[T](byte(cv)), nil
+	}
+
+	return zero, fmt.Errorf("efl: unsupported scalar type %T", zero)
+}
+
+// WrapValue creates a non-owning Go wrapper around an existing C Eina_Value
+// pointer. The caller retains responsibility for the lifetime of the C object.
+// ptr must not be nil.
+func WrapValue(ptr unsafe.Pointer) *Value {
+	return &Value{
+		ptr:   (*C.Eina_Value)(ptr),
+		owned: false,
+	}
+}
+
+// Ptr returns the raw C pointer as an unsafe.Pointer so that callers can pass
+// the value to other C APIs without importing the cgo types directly.
+func (v *Value) Ptr() unsafe.Pointer {
+	if v == nil {
+		return nil
+	}
+	return unsafe.Pointer(v.ptr)
+}
+
+// Free releases the underlying C Eina_Value if this wrapper owns it. It is
+// safe to call Free on a nil *Value or to call it more than once; repeated
+// calls after the first are no-ops.
+func (v *Value) Free() {
+	if v == nil || v.ptr == nil || !v.owned {
+		return
+	}
+	C._ego_value_free(v.ptr)
+	v.ptr = nil
+}
+
+// IsNil reports whether the underlying C pointer is nil.
+func (v *Value) IsNil() bool {
+	return v == nil || v.ptr == nil
+}
+
+// Kind returns the ValueKind that describes the stored scalar type.
+// Returns ValueKindUnknown for unrecognised or compound types.
+func (v *Value) Kind() ValueKind {
+	if v == nil || v.ptr == nil {
+		return ValueKindUnknown
+	}
+	t := C._ego_value_type_get(v.ptr)
+	switch t {
+	case C.EINA_VALUE_TYPE_INT:
+		return ValueKindInt
+	case C.EINA_VALUE_TYPE_UINT:
+		return ValueKindUint
+	case C.EINA_VALUE_TYPE_INT64:
+		return ValueKindInt64
+	case C.EINA_VALUE_TYPE_UINT64:
+		return ValueKindUint64
+	case C.EINA_VALUE_TYPE_SHORT:
+		return ValueKindInt16
+	case C.EINA_VALUE_TYPE_USHORT:
+		return ValueKindUint16
+	case C.EINA_VALUE_TYPE_FLOAT:
+		return ValueKindFloat32
+	case C.EINA_VALUE_TYPE_DOUBLE:
+		return ValueKindFloat64
+	case C.EINA_VALUE_TYPE_STRING, C.EINA_VALUE_TYPE_STRINGSHARE:
+		return ValueKindString
+	case C.EINA_VALUE_TYPE_BOOL: // same pointer as UCHAR; represents bool
+		return ValueKindBool
+	case C.EINA_VALUE_TYPE_CHAR:
+		return ValueKindByte
+	default:
+		return ValueKindUnknown
+	}
+}
+
+// String implements fmt.Stringer by delegating to eina_value_to_string, which
+// produces a human-readable representation of the stored value. The returned
+// string is owned by Go after the call. Returns "<nil>" for a nil Value.
+func (v *Value) String() string {
+	if v == nil || v.ptr == nil {
+		return "<nil>"
+	}
+	cs := C._ego_value_to_string(v.ptr)
+	if cs == nil {
+		return ""
+	}
+	defer C.free(unsafe.Pointer(cs))
+	return C.GoString(cs)
+}
+
+// ---------------------------------------------------------------------------
+// Generic conversion helpers
+//
+// These bridge the gap between the type-switched concrete C types and the
+// generic T parameter. Because the calling switch statement has already
+// matched the concrete underlying type of T, the assertion from the
+// intermediate concrete type to T is always valid at runtime.
+// ---------------------------------------------------------------------------
+
+// scalarToInt extracts the int representation of v. v must have underlying
+// type int or int32 — the caller is responsible for the precondition.
+func scalarToInt[T ScalarType](v T) int {
+	switch u := any(v).(type) {
+	case int:
+		return u
+	case int32:
+		return int(u)
+	default:
+		return 0
+	}
+}
+
+func scalarToUint[T ScalarType](v T) uint {
+	switch u := any(v).(type) {
+	case uint:
+		return u
+	case uint32:
+		return uint(u)
+	default:
+		return 0
+	}
+}
+
+func scalarToInt64[T ScalarType](v T) int64 {
+	return any(v).(int64) //nolint:forcetypeassert
+}
+
+func scalarToUint64[T ScalarType](v T) uint64 {
+	return any(v).(uint64) //nolint:forcetypeassert
+}
+
+func scalarToInt16[T ScalarType](v T) int16 {
+	return any(v).(int16) //nolint:forcetypeassert
+}
+
+func scalarToUint16[T ScalarType](v T) uint16 {
+	return any(v).(uint16) //nolint:forcetypeassert
+}
+
+func scalarToFloat32[T ScalarType](v T) float32 {
+	return any(v).(float32) //nolint:forcetypeassert
+}
+
+func scalarToFloat64[T ScalarType](v T) float64 {
+	return any(v).(float64) //nolint:forcetypeassert
+}
+
+func scalarToString[T ScalarType](v T) string {
+	return any(v).(string) //nolint:forcetypeassert
+}
+
+func scalarToBool[T ScalarType](v T) bool {
+	return any(v).(bool) //nolint:forcetypeassert
+}
+
+func scalarToByte[T ScalarType](v T) byte {
+	return any(v).(byte) //nolint:forcetypeassert
+}
+
+// fromInt converts a plain int back to T. T must have underlying type int or
+// int32; the calling switch guarantees this.
+func fromInt[T ScalarType](n int) T {
+	switch any(*new(T)).(type) {
+	case int32:
+		return any(int32(n)).(T) //nolint:forcetypeassert
+	default:
+		return any(n).(T) //nolint:forcetypeassert
+	}
+}
+
+func fromUint[T ScalarType](n uint) T {
+	switch any(*new(T)).(type) {
+	case uint32:
+		return any(uint32(n)).(T) //nolint:forcetypeassert
+	default:
+		return any(n).(T) //nolint:forcetypeassert
+	}
+}
+
+func fromInt64[T ScalarType](n int64) T   { return any(n).(T) } //nolint:forcetypeassert
+func fromUint64[T ScalarType](n uint64) T { return any(n).(T) } //nolint:forcetypeassert
+func fromInt16[T ScalarType](n int16) T   { return any(n).(T) } //nolint:forcetypeassert
+func fromUint16[T ScalarType](n uint16) T { return any(n).(T) } //nolint:forcetypeassert
+func fromFloat32[T ScalarType](n float32) T {
+	return any(n).(T) //nolint:forcetypeassert
+}
+func fromFloat64[T ScalarType](n float64) T { return any(n).(T) } //nolint:forcetypeassert
+func fromString[T ScalarType](s string) T   { return any(s).(T) } //nolint:forcetypeassert
+func fromBool[T ScalarType](b bool) T       { return any(b).(T) } //nolint:forcetypeassert
+func fromByte[T ScalarType](b byte) T       { return any(b).(T) } //nolint:forcetypeassert
diff --git a/efl/value_test.go b/efl/value_test.go
new file mode 100644
index 0000000..2bf4b0e
--- /dev/null
+++ b/efl/value_test.go
@@ -0,0 +1,388 @@
+package efl
+
+import (
+	"math"
+	"strings"
+	"testing"
+)
+
+// TestNewValueIntRoundTrip verifies that an int value survives a NewValue /
+// ValueGet round-trip with the correct numeric value.
+func TestNewValueIntRoundTrip(t *testing.T) {
+	v, err := NewValue[int](42)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[int](v)
+	if err != nil {
+		t.Fatalf("ValueGet[int]: %v", err)
+	}
+	if got != 42 {
+		t.Errorf("want 42, got %d", got)
+	}
+}
+
+// TestNewValueStringRoundTrip verifies string storage and retrieval.
+func TestNewValueStringRoundTrip(t *testing.T) {
+	const want = "hello, eina"
+	v, err := NewValue[string](want)
+	if err != nil {
+		t.Fatalf("NewValue[string]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[string](v)
+	if err != nil {
+		t.Fatalf("ValueGet[string]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %q, got %q", want, got)
+	}
+}
+
+// TestNewValueFloat64RoundTrip verifies float64 storage and retrieval.
+func TestNewValueFloat64RoundTrip(t *testing.T) {
+	const want = 3.14159265358979
+	v, err := NewValue[float64](want)
+	if err != nil {
+		t.Fatalf("NewValue[float64]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[float64](v)
+	if err != nil {
+		t.Fatalf("ValueGet[float64]: %v", err)
+	}
+	if math.Abs(got-want) > 1e-10 {
+		t.Errorf("want ~%.15f, got %.15f", want, got)
+	}
+}
+
+// TestNewValueBoolRoundTrip verifies bool storage and retrieval for both true
+// and false.
+func TestNewValueBoolRoundTrip(t *testing.T) {
+	for _, want := range []bool{true, false} {
+		v, err := NewValue[bool](want)
+		if err != nil {
+			t.Fatalf("NewValue[bool](%v): %v", want, err)
+		}
+		got, err := ValueGet[bool](v)
+		v.Free()
+		if err != nil {
+			t.Fatalf("ValueGet[bool](%v): %v", want, err)
+		}
+		if got != want {
+			t.Errorf("want %v, got %v", want, got)
+		}
+	}
+}
+
+// TestNewValueInt64RoundTrip verifies int64 storage and retrieval using a
+// value that exceeds 32-bit range.
+func TestNewValueInt64RoundTrip(t *testing.T) {
+	const want = int64(math.MaxInt64)
+	v, err := NewValue[int64](want)
+	if err != nil {
+		t.Fatalf("NewValue[int64]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[int64](v)
+	if err != nil {
+		t.Fatalf("ValueGet[int64]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %d, got %d", want, got)
+	}
+}
+
+// TestNewValueUint64RoundTrip verifies uint64 storage and retrieval.
+func TestNewValueUint64RoundTrip(t *testing.T) {
+	const want = uint64(math.MaxUint64)
+	v, err := NewValue[uint64](want)
+	if err != nil {
+		t.Fatalf("NewValue[uint64]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[uint64](v)
+	if err != nil {
+		t.Fatalf("ValueGet[uint64]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %d, got %d", want, got)
+	}
+}
+
+// TestNewValueFloat32RoundTrip verifies float32 storage and retrieval.
+func TestNewValueFloat32RoundTrip(t *testing.T) {
+	const want = float32(1.5)
+	v, err := NewValue[float32](want)
+	if err != nil {
+		t.Fatalf("NewValue[float32]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[float32](v)
+	if err != nil {
+		t.Fatalf("ValueGet[float32]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %v, got %v", want, got)
+	}
+}
+
+// TestNewValueByteRoundTrip verifies byte (uint8) storage and retrieval.
+func TestNewValueByteRoundTrip(t *testing.T) {
+	const want = byte(0xAB)
+	v, err := NewValue[byte](want)
+	if err != nil {
+		t.Fatalf("NewValue[byte]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[byte](v)
+	if err != nil {
+		t.Fatalf("ValueGet[byte]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want 0x%02X, got 0x%02X", want, got)
+	}
+}
+
+// TestNewValueInt16RoundTrip verifies int16 storage and retrieval.
+func TestNewValueInt16RoundTrip(t *testing.T) {
+	const want = int16(-1000)
+	v, err := NewValue[int16](want)
+	if err != nil {
+		t.Fatalf("NewValue[int16]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[int16](v)
+	if err != nil {
+		t.Fatalf("ValueGet[int16]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %d, got %d", want, got)
+	}
+}
+
+// TestNewValueUint16RoundTrip verifies uint16 storage and retrieval.
+func TestNewValueUint16RoundTrip(t *testing.T) {
+	const want = uint16(60000)
+	v, err := NewValue[uint16](want)
+	if err != nil {
+		t.Fatalf("NewValue[uint16]: %v", err)
+	}
+	defer v.Free()
+
+	got, err := ValueGet[uint16](v)
+	if err != nil {
+		t.Fatalf("ValueGet[uint16]: %v", err)
+	}
+	if got != want {
+		t.Errorf("want %d, got %d", want, got)
+	}
+}
+
+// TestValueKind verifies that Kind returns the correct ValueKind for each
+// supported scalar type.
+func TestValueKind(t *testing.T) {
+	tests := []struct {
+		name string
+		kind ValueKind
+		make func() *Value
+	}{
+		{"int", ValueKindInt, func() *Value {
+			v, _ := NewValue[int](0)
+			return v
+		}},
+		{"uint", ValueKindUint, func() *Value {
+			v, _ := NewValue[uint](0)
+			return v
+		}},
+		{"int64", ValueKindInt64, func() *Value {
+			v, _ := NewValue[int64](0)
+			return v
+		}},
+		{"uint64", ValueKindUint64, func() *Value {
+			v, _ := NewValue[uint64](0)
+			return v
+		}},
+		{"int16", ValueKindInt16, func() *Value {
+			v, _ := NewValue[int16](0)
+			return v
+		}},
+		{"uint16", ValueKindUint16, func() *Value {
+			v, _ := NewValue[uint16](0)
+			return v
+		}},
+		{"float32", ValueKindFloat32, func() *Value {
+			v, _ := NewValue[float32](0)
+			return v
+		}},
+		{"float64", ValueKindFloat64, func() *Value {
+			v, _ := NewValue[float64](0)
+			return v
+		}},
+		{"string", ValueKindString, func() *Value {
+			v, _ := NewValue[string]("")
+			return v
+		}},
+		{"bool", ValueKindBool, func() *Value {
+			v, _ := NewValue[bool](false)
+			return v
+		}},
+		{"byte", ValueKindByte, func() *Value {
+			v, _ := NewValue[byte](0)
+			return v
+		}},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			v := tc.make()
+			if v == nil {
+				t.Fatal("NewValue returned nil")
+			}
+			defer v.Free()
+			if got := v.Kind(); got != tc.kind {
+				t.Errorf("want Kind %d, got %d", tc.kind, got)
+			}
+		})
+	}
+}
+
+// TestValueString verifies that String() produces a non-empty, readable
+// representation for common types.
+func TestValueString(t *testing.T) {
+	tests := []struct {
+		name     string
+		contains string
+		make     func() *Value
+	}{
+		{"int 99", "99", func() *Value {
+			v, _ := NewValue[int](99)
+			return v
+		}},
+		{"string hello", "hello", func() *Value {
+			v, _ := NewValue[string]("hello")
+			return v
+		}},
+		{"float64 2.5", "2.5", func() *Value {
+			v, _ := NewValue[float64](2.5)
+			return v
+		}},
+		{"bool true", "1", func() *Value { // eina renders bool as 0/1
+			v, _ := NewValue[bool](true)
+			return v
+		}},
+	}
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			v := tc.make()
+			if v == nil {
+				t.Fatal("NewValue returned nil")
+			}
+			defer v.Free()
+			s := v.String()
+			if !strings.Contains(s, tc.contains) {
+				t.Errorf("String() = %q, want it to contain %q", s, tc.contains)
+			}
+		})
+	}
+}
+
+// TestValueStringNil verifies that String() on a nil *Value returns "<nil>"
+// without panicking.
+func TestValueStringNil(t *testing.T) {
+	var v *Value
+	if got := v.String(); got != "<nil>" {
+		t.Errorf("want <nil>, got %q", got)
+	}
+}
+
+// TestValueFreeOwned verifies that Free releases an owned Value and that a
+// second call is a safe no-op (double-free guard).
+func TestValueFreeOwned(t *testing.T) {
+	v, err := NewValue[int](7)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	v.Free() // first Free: should release the C allocation
+	v.Free() // second Free: must not panic or double-free
+}
+
+// TestValueFreeNil verifies that calling Free on a nil *Value is a safe no-op.
+func TestValueFreeNil(t *testing.T) {
+	var v *Value
+	v.Free() // must not panic
+}
+
+// TestValueIsNilFalse verifies that IsNil returns false for a live Value.
+func TestValueIsNilFalse(t *testing.T) {
+	v, err := NewValue[int](1)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	defer v.Free()
+	if v.IsNil() {
+		t.Error("expected IsNil() == false for a live Value")
+	}
+}
+
+// TestValueIsNilTrue verifies that IsNil returns true for a nil *Value and for
+// a Value whose internal pointer has been cleared by Free.
+func TestValueIsNilTrue(t *testing.T) {
+	var v *Value
+	if !v.IsNil() {
+		t.Error("expected IsNil() == true for nil *Value")
+	}
+
+	v2, err := NewValue[int](0)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	v2.Free()
+	if !v2.IsNil() {
+		t.Error("expected IsNil() == true after Free()")
+	}
+}
+
+// TestValueGetNil verifies that ValueGet on a nil Value returns an error
+// rather than panicking.
+func TestValueGetNil(t *testing.T) {
+	var v *Value
+	_, err := ValueGet[int](v)
+	if err == nil {
+		t.Error("expected error from ValueGet on nil Value")
+	}
+}
+
+// TestWrapValue verifies that a non-owned wrapper over an existing Eina_Value
+// pointer can read back the stored value and that Free on it does not touch
+// the underlying allocation.
+func TestWrapValue(t *testing.T) {
+	owned, err := NewValue[int](123)
+	if err != nil {
+		t.Fatalf("NewValue[int]: %v", err)
+	}
+	defer owned.Free()
+
+	wrapped := WrapValue(owned.Ptr())
+	if wrapped.IsNil() {
+		t.Fatal("WrapValue returned a nil wrapper")
+	}
+	// Free on a non-owned wrapper must be a no-op.
+	wrapped.Free()
+	// The original owned value should still be readable.
+	got, err := ValueGet[int](owned)
+	if err != nil {
+		t.Fatalf("ValueGet after WrapValue.Free: %v", err)
+	}
+	if got != 123 {
+		t.Errorf("want 123, got %d", got)
+	}
+}

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

Reply via email to