Răsfoiți Sursa

feat(expressions): add nullable type support and handlebars-style format function

Add comprehensive nullable type handling with C implementation for proper
value type detection and conversion. Introduce handlebars-style string
interpolation in format() replacing printf-style formatting, with support
for {{expression}} evaluation and escape sequences.

- Add Nullable.c/Nullable.vala for runtime nullable type detection
- Add Element.stringify() for consistent value-to-string conversion
- Add as_X_or_null() helper methods on Element interface
- Add stringify() global function for concatenating stringified values
- Refactor format() to use {{variable}} syntax with expression evaluation
- Fix NativeElement null tracking for value and reference types
- Update all expression evaluators to use new nullable-aware APIs
- Add LotPropertyAccessor for Lot<T> property access
- Register LotPropertyAccessor in TypeAccessorRegistry

BREAKING CHANGE: format() function changed from printf-style (%s, %i)
to handlebars-style ({{variable}}) interpolation
Billy Barrow 1 lună în urmă
părinte
comite
d7f39f81b5

+ 0 - 1226
plans/expression-system-design.md

@@ -1,1226 +0,0 @@
-# Expression System Design
-
-## Overview
-
-This document outlines the architecture for a string expression system in the Invercargill library that supports evaluation, translation, and serves as a foundation for future ORM-like functionality.
-
-## Goals
-
-- String expressions for various operations in Invercargill
-- Foundation for ORM-type library (similar to LINQ but with strings instead of expression trees)
-- Support for evaluation AND translation
-- Return value types, objects, and enumerables
-- Boolean expressions: `==`, `!=`, `>=`, `<=`, `<`, `>`, `&&`, `||`
-- Arithmetic expressions: `+`, `-`, `*`, `/`, `%`
-- Unary operators: `-` (negate), `!` (not)
-- Ternary conditional: `condition ? true_value : false_value`
-- Nested expressions with brackets
-- Property navigation via `PropertyAccessor`
-- Function accessors (initially for `Enumerable`)
-- Lambda expressions with closure support
-- Root names provided via `Properties` object
-
-## Example Expressions
-
-```
-a == b
-a.prop
-a.values.where(s => s.rank > a.min_rank)
-a.values.where(s => s.rank > a.min_rank).first().is_important
-```
-
----
-
-## Architecture
-
-### Core Components Overview
-
-```mermaid
-classDiagram
-    class Expression {
-        <<interface>>
-        +evaluate(EvaluationContext): Element
-        +accept(ExpressionVisitor): void
-        +get_type(): ExpressionType
-    }
-    
-    class EvaluationContext {
-        +Properties root_values
-        +EvaluationContext? parent
-        +Element? get_variable(string name)
-        +EvaluationContext create_child_scope(string param_name, Element value)
-    }
-    
-    class ExpressionVisitor {
-        <<interface>>
-        +visit_literal(LiteralExpression)
-        +visit_variable(VariableExpression)
-        +visit_binary(BinaryExpression)
-        +visit_unary(UnaryExpression)
-        +visit_ternary(TernaryExpression)
-        +visit_property(PropertyExpression)
-        +visit_function_call(FunctionCallExpression)
-        +visit_lambda(LambdaExpression)
-    }
-    
-    Expression <-- LiteralExpression
-    Expression <-- VariableExpression
-    Expression <-- BinaryExpression
-    Expression <-- UnaryExpression
-    Expression <-- TernaryExpression
-    Expression <-- PropertyExpression
-    Expression <-- FunctionCallExpression
-    Expression <-- LambdaExpression
-    Expression <-- BracketedExpression
-    
-    ExpressionVisitor --> Expression: visits
-    EvaluationContext --> Expression: used by
-```
-
----
-
-## Core Interfaces
-
-### Expression Interface
-
-The base interface for all expression types:
-
-```vala
-namespace Invercargill.Expressions {
-
-    public enum ExpressionType {
-        LITERAL,
-        VARIABLE,
-        BINARY,
-        UNARY,
-        TERNARY,
-        PROPERTY,
-        FUNCTION_CALL,
-        LAMBDA,
-        BRACKETED
-    }
-
-    public interface Expression : Object {
-        public abstract ExpressionType expression_type { get; }
-        
-        // Evaluate the expression with the given context
-        public abstract Element evaluate(EvaluationContext context) throws ExpressionError;
-        
-        // Visitor pattern for translation/traversal
-        public abstract void accept(ExpressionVisitor visitor);
-        
-        // Optional: Get the expected return type (for optimization/validation)
-        public virtual Type? get_return_type() { return null; }
-        
-        // Convenience method for string representation (useful for debugging)
-        public virtual string to_expression_string() { return expression_type.to_string(); }
-    }
-
-}
-```
-
-### EvaluationContext
-
-Manages variable scopes and closures:
-
-```vala
-namespace Invercargill.Expressions {
-
-    public class EvaluationContext : Object {
-        
-        // Root values available in the expression
-        public Properties root_values { get; construct set; }
-        
-        // Parent context for closure support
-        public EvaluationContext? parent { get; construct set; }
-        
-        // Lambda parameter name and value (only in child scopes)
-        private string? _parameter_name;
-        private Element? _parameter_value;
-        
-        public EvaluationContext(Properties root_values) {
-            Object(root_values: root_values);
-        }
-        
-        private EvaluationContext.with_parent(EvaluationContext parent, string param_name, Element param_value) {
-            Object(root_values: parent.root_values, parent: parent);
-            _parameter_name = param_name;
-            _parameter_value = param_value;
-        }
-        
-        // Get a variable by name - searches current scope then parent
-        public Element? get_variable(string name) throws ExpressionError {
-            // Check if it's the lambda parameter
-            if (_parameter_name != null && _parameter_name == name) {
-                return _parameter_value;
-            }
-            
-            // Check parent scope (for closures)
-            if (parent != null) {
-                return parent.get_variable(name);
-            }
-            
-            // Check root values
-            Element element;
-            if (root_values.try_get(name, out element)) {
-                return element;
-            }
-            
-            throw new ExpressionError.NON_EXISTANT_PROPERTY(
-                @"Variable '$name' not found in current scope"
-            );
-        }
-        
-        // Create a child scope for lambda execution
-        public EvaluationContext create_child_scope(string param_name, Element param_value) {
-            return new EvaluationContext.with_parent(this, param_name, param_value);
-        }
-    }
-
-}
-```
-
-### ExpressionVisitor Interface
-
-For translation and traversal:
-
-```vala
-namespace Invercargill.Expressions {
-
-    public interface ExpressionVisitor : Object {
-        public virtual void visit_literal(LiteralExpression expr) {}
-        public virtual void visit_variable(VariableExpression expr) {}
-        public virtual void visit_binary(BinaryExpression expr) {}
-        public virtual void visit_unary(UnaryExpression expr) {}
-        public virtual void visit_ternary(TernaryExpression expr) {}
-        public virtual void visit_property(PropertyExpression expr) {}
-        public virtual void visit_function_call(FunctionCallExpression expr) {}
-        public virtual void visit_lambda(LambdaExpression expr) {}
-        public virtual void visit_bracketed(BracketedExpression expr) {}
-    }
-
-}
-```
-
----
-
-## Expression Types
-
-### 1. LiteralExpression
-
-Wraps constant values:
-
-```vala
-public class LiteralExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.LITERAL; } }
-    
-    public Element value { get; construct set; }
-    
-    public LiteralExpression(Element value) {
-        Object(value: value);
-    }
-    
-    // Convenience constructors
-    public LiteralExpression.for_string(string val) {
-        this(new ValueElement(val));
-    }
-    
-    public LiteralExpression.for_int(int val) {
-        this(new ValueElement(val));
-    }
-    
-    public LiteralExpression.for_bool(bool val) {
-        this(new ValueElement(val));
-    }
-    
-    public LiteralExpression.for_null() {
-        this(new NullElement());
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        return value;
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_literal(this);
-    }
-}
-```
-
-### 2. VariableExpression
-
-References a named variable from the context:
-
-```vala
-public class VariableExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.VARIABLE; } }
-    
-    public string variable_name { get; construct set; }
-    
-    public VariableExpression(string name) {
-        Object(variable_name: name);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        return context.get_variable(variable_name);
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_variable(this);
-    }
-}
-```
-
-### 3. BinaryExpression
-
-Handles binary operators:
-
-```vala
-public enum BinaryOperator {
-    // Comparison
-    EQUAL,           // ==
-    NOT_EQUAL,       // !=
-    GREATER_THAN,    // >
-    LESS_THAN,       // <
-    GREATER_EQUAL,   // >=
-    LESS_EQUAL,      // <=
-    
-    // Logical
-    AND,             // &&
-    OR,              // ||
-    
-    // Arithmetic
-    ADD,             // +
-    SUBTRACT,        // -
-    MULTIPLY,        // *
-    DIVIDE,          // /
-    MODULO;          // %
-    
-    public string to_string() {
-        switch (this) {
-            case EQUAL: return "==";
-            case NOT_EQUAL: return "!=";
-            case GREATER_THAN: return ">";
-            case LESS_THAN: return "<";
-            case GREATER_EQUAL: return ">=";
-            case LESS_EQUAL: return "<=";
-            case AND: return "&&";
-            case OR: return "||";
-            case ADD: return "+";
-            case SUBTRACT: return "-";
-            case MULTIPLY: return "*";
-            case DIVIDE: return "/";
-            case MODULO: return "%";
-            default: return "";
-        }
-    }
-    
-    public bool is_comparison() {
-        return this in { EQUAL, NOT_EQUAL, GREATER_THAN, LESS_THAN, GREATER_EQUAL, LESS_EQUAL };
-    }
-    
-    public bool is_logical() {
-        return this in { AND, OR };
-    }
-    
-    public bool is_arithmetic() {
-        return this in { ADD, SUBTRACT, MULTIPLY, DIVIDE, MODULO };
-    }
-}
-
-public class BinaryExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.BINARY; } }
-    
-    public Expression left { get; construct set; }
-    public Expression right { get; construct set; }
-    public BinaryOperator operator { get; construct set; }
-    
-    public BinaryExpression(Expression left, BinaryOperator op, Expression right) {
-        Object(left: left, right: right, operator: op);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        var left_val = left.evaluate(context);
-        var right_val = right.evaluate(context);
-        
-        switch (operator) {
-            case BinaryOperator.AND:
-                return new ValueElement(evaluate_bool(left_val) && evaluate_bool(right_val));
-            case BinaryOperator.OR:
-                return new ValueElement(evaluate_bool(left_val) || evaluate_bool(right_val));
-            case BinaryOperator.EQUAL:
-                return new ValueElement(evaluate_equality(left_val, right_val));
-            case BinaryOperator.NOT_EQUAL:
-                return new ValueElement(!evaluate_equality(left_val, right_val));
-            case BinaryOperator.GREATER_THAN:
-            case BinaryOperator.LESS_THAN:
-            case BinaryOperator.GREATER_EQUAL:
-            case BinaryOperator.LESS_EQUAL:
-                return new ValueElement(evaluate_comparison(left_val, right_val, operator));
-            case BinaryOperator.ADD:
-                return evaluate_arithmetic(left_val, right_val, (a, b) => a + b, (a, b) => a + b);
-            case BinaryOperator.SUBTRACT:
-                return evaluate_arithmetic(left_val, right_val, (a, b) => a - b, (a, b) => a - b);
-            case BinaryOperator.MULTIPLY:
-                return evaluate_arithmetic(left_val, right_val, (a, b) => a * b, (a, b) => a * b);
-            case BinaryOperator.DIVIDE:
-                return evaluate_arithmetic(left_val, right_val, (a, b) => a / b, (a, b) => a / b);
-            case BinaryOperator.MODULO:
-                return evaluate_arithmetic_int(left_val, right_val, (a, b) => a % b);
-        }
-    }
-    
-    private Element evaluate_arithmetic(
-        Element left,
-        Element right,
-        owned ComputeIntDelegate int_op,
-        owned ComputeDoubleDelegate double_op
-    ) throws ExpressionError {
-        // Try double first for mixed arithmetic
-        double? left_d, right_d;
-        if (left.try_get_as(out left_d) && right.try_get_as(out right_d)) {
-            return new ValueElement(double_op(left_d, right_d));
-        }
-        
-        // Try int
-        int? left_i, right_i;
-        if (left.try_get_as(out left_i) && right.try_get_as(out right_i)) {
-            return new ValueElement(int_op(left_i, right_i));
-        }
-        
-        // String concatenation for + operator
-        if (operator == BinaryOperator.ADD) {
-            string? left_s, right_s;
-            if (left.try_get_as(out left_s) && right.try_get_as(out right_s)) {
-                return new ValueElement(left_s + right_s);
-            }
-        }
-        
-        throw new ExpressionError.INVALID_TYPE(
-            @"Cannot perform arithmetic on $(left.type_name()) and $(right.type_name())"
-        );
-    }
-    
-    private Element evaluate_arithmetic_int(
-        Element left,
-        Element right,
-        owned ComputeIntDelegate op
-    ) throws ExpressionError {
-        int? left_i, right_i;
-        if (left.try_get_as(out left_i) && right.try_get_as(out right_i)) {
-            return new ValueElement(op(left_i, right_i));
-        }
-        throw new ExpressionError.INVALID_TYPE("Modulo operator requires integer operands");
-    }
-    
-    private delegate int ComputeIntDelegate(int a, int b);
-    private delegate double ComputeDoubleDelegate(double a, double b);
-    
-    private bool evaluate_bool(Element element) throws ExpressionError {
-        if (element.is<bool>()) {
-            return element.as<bool>();
-        }
-        throw new ExpressionError.INVALID_TYPE("Expected boolean value");
-    }
-    
-    private bool evaluate_equality(Element left, Element right) {
-        // Use existing equality operators from Invercargill.Operators
-        // Handle null cases
-        if (left.is_null() && right.is_null()) return true;
-        if (left.is_null() || right.is_null()) return false;
-        
-        // Try to compare using Element's try_get_as
-        // ... implementation details
-    }
-    
-    private bool evaluate_comparison(Element left, Element right, BinaryOperator op) throws ExpressionError {
-        // Use existing comparison operators from Invercargill.Operators
-        // ... implementation details
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_binary(this);
-        left.accept(visitor);
-        right.accept(visitor);
-    }
-    
-    public override string to_expression_string() {
-        return @"($(_left.to_expression_string()) $(operator.to_string()) $(_right.to_expression_string()))";
-    }
-}
-```
-
-### 4. UnaryExpression
-
-Handles unary operators (negation, etc.):
-
-```vala
-public enum UnaryOperator {
-    NEGATE,    // - (arithmetic negation)
-    NOT,       // ! (logical negation)
-    INCREMENT, // ++ (prefix/postfix increment)
-    DECREMENT; // -- (prefix/postfix decrement)
-    
-    public string to_string() {
-        switch (this) {
-            case NEGATE: return "-";
-            case NOT: return "!";
-            case INCREMENT: return "++";
-            case DECREMENT: return "--";
-            default: return "";
-        }
-    }
-}
-
-public class UnaryExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.UNARY; } }
-    
-    public Expression operand { get; construct set; }
-    public UnaryOperator operator { get; construct set; }
-    public bool is_prefix { get; construct set; } // true for ++x, false for x++
-    
-    public UnaryExpression(UnaryOperator op, Expression operand, bool is_prefix = true) {
-        Object(operand: operand, operator: op, is_prefix: is_prefix);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        var operand_val = operand.evaluate(context);
-        
-        switch (operator) {
-            case UnaryOperator.NEGATE:
-                return evaluate_negate(operand_val);
-            case UnaryOperator.NOT:
-                return evaluate_not(operand_val);
-            case UnaryOperator.INCREMENT:
-            case UnaryOperator.DECREMENT:
-                throw new ExpressionError.INVALID_SYNTAX(
-                    "Increment/decrement operators not supported in expression evaluation"
-                );
-        }
-    }
-    
-    private Element evaluate_negate(Element element) throws ExpressionError {
-        // Try numeric types
-        int? int_val;
-        if (element.try_get_as(out int_val)) {
-            return new ValueElement(-int_val);
-        }
-        
-        double? double_val;
-        if (element.try_get_as(out double_val)) {
-            return new ValueElement(-double_val);
-        }
-        
-        int64? int64_val;
-        if (element.try_get_as(out int64_val)) {
-            return new ValueElement(-int64_val);
-        }
-        
-        throw new ExpressionError.INVALID_TYPE("Cannot negate non-numeric value");
-    }
-    
-    private Element evaluate_not(Element element) throws ExpressionError {
-        if (element.is<bool>()) {
-            return new ValueElement(!element.as<bool>());
-        }
-        throw new ExpressionError.INVALID_TYPE("Logical NOT requires boolean operand");
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_unary(this);
-        operand.accept(visitor);
-    }
-    
-    public override string to_expression_string() {
-        if (is_prefix) {
-            return @"$(operator.to_string())$(operand.to_expression_string())";
-        }
-        return @"$(operand.to_expression_string())$(operator.to_string())";
-    }
-}
-```
-
-### 5. TernaryExpression
-
-Handles conditional expressions (ternary operator):
-
-```vala
-public class TernaryExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.TERNARY; } }
-    
-    public Expression condition { get; construct set; }
-    public Expression true_expression { get; construct set; }
-    public Expression false_expression { get; construct set; }
-    
-    public TernaryExpression(Expression condition, Expression true_expr, Expression false_expr) {
-        Object(condition: condition, true_expression: true_expr, false_expression: false_expr);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        var cond_val = condition.evaluate(context);
-        
-        bool cond_bool;
-        if (cond_val.try_get_as(out cond_bool)) {
-            if (cond_bool) {
-                return true_expression.evaluate(context);
-            } else {
-                return false_expression.evaluate(context);
-            }
-        }
-        
-        throw new ExpressionError.INVALID_TYPE("Ternary condition must evaluate to boolean");
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_ternary(this);
-        condition.accept(visitor);
-        true_expression.accept(visitor);
-        false_expression.accept(visitor);
-    }
-    
-    public override string to_expression_string() {
-        return @"$(condition.to_expression_string()) ? $(true_expression.to_expression_string()) : $(false_expression.to_expression_string())";
-    }
-}
-```
-
-### 6. PropertyExpression
-
-Navigates properties using `PropertyAccessor`:
-
-```vala
-public class PropertyExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.PROPERTY; } }
-    
-    public Expression target { get; construct set; }
-    public string property_name { get; construct set; }
-    
-    public PropertyExpression(Expression target, string property_name) {
-        Object(target: target, property_name: property_name);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        var target_value = target.evaluate(context);
-        
-        // Get PropertyAccessor from the element
-        PropertyAccessor? accessor = null;
-        
-        // If the element itself is a PropertyAccessor
-        if (target_value.try_get_as(out accessor)) {
-            return accessor.read_property(property_name);
-        }
-        
-        // If it's an Object, wrap it
-        Object? obj;
-        if (target_value.try_get_as(out obj)) {
-            accessor = new ObjectPropertyAccessor(obj);
-            return accessor.read_property(property_name);
-        }
-        
-        // If it's a Properties instance
-        Properties? props;
-        if (target_value.try_get_as(out props)) {
-            accessor = new PropertiesPropertyAccessor(props);
-            return accessor.read_property(property_name);
-        }
-        
-        throw new ExpressionError.INVALID_TYPE(
-            @"Cannot access property '$property_name' on type $(target_value.type_name())"
-        );
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_property(this);
-        target.accept(visitor);
-    }
-}
-```
-
-### 7. FunctionCallExpression
-
-Calls functions via `FunctionAccessor`:
-
-```vala
-public class FunctionCallExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.FUNCTION_CALL; } }
-    
-    public Expression target { get; construct set; }
-    public string function_name { get; construct set; }
-    public Gee.List<Expression> arguments { get; construct set; }
-    
-    public FunctionCallExpression(Expression target, string function_name, Gee.List<Expression>? arguments = null) {
-        Object(target: target, function_name: function_name, arguments: arguments ?? new Gee.ArrayList<Expression>());
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        var target_value = target.evaluate(context);
-        
-        // Get FunctionAccessor from the element
-        FunctionAccessor? accessor = get_function_accessor(target_value);
-        
-        if (accessor == null) {
-            throw new ExpressionError.INVALID_TYPE(
-                @"Cannot call function '$function_name' on type $(target_value.type_name())"
-            );
-        }
-        
-        // Evaluate arguments
-        var evaluated_args = new Gee.ArrayList<Element>();
-        foreach (var arg in arguments) {
-            evaluated_args.add(arg.evaluate(context));
-        }
-        
-        return accessor.call_function(function_name, evaluated_args, context);
-    }
-    
-    private FunctionAccessor? get_function_accessor(Element element) {
-        // Check if element directly implements FunctionAccessor
-        FunctionAccessor? accessor;
-        if (element.try_get_as(out accessor)) {
-            return accessor;
-        }
-        
-        // Check for Elements (Enumerable wrapper)
-        Elements? elements;
-        if (element.try_get_as(out elements)) {
-            return new EnumerableFunctionAccessor(elements);
-        }
-        
-        return null;
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_function_call(this);
-        target.accept(visitor);
-        foreach (var arg in arguments) {
-            arg.accept(visitor);
-        }
-    }
-}
-```
-
-### 8. LambdaExpression
-
-Represents lambda functions with closure support:
-
-```vala
-public class LambdaExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.LAMBDA; } }
-    
-    public string parameter_name { get; construct set; }
-    public Expression body { get; construct set; }
-    
-    // Capture the context at creation time for closures
-    private EvaluationContext? _captured_context;
-    
-    public LambdaExpression(string parameter_name, Expression body) {
-        Object(parameter_name: parameter_name, body: body);
-    }
-    
-    // Called when the lambda is created in an expression
-    public void capture_context(EvaluationContext context) {
-        _captured_context = context;
-    }
-    
-    // Evaluate the lambda with a specific argument
-    public Element evaluate_with_argument(Element argument) throws ExpressionError {
-        if (_captured_context == null) {
-            throw new ExpressionError.INVALID_TYPE("Lambda has no captured context");
-        }
-        
-        // Create child scope with the parameter
-        var lambda_context = _captured_context.create_child_scope(parameter_name, argument);
-        
-        return body.evaluate(lambda_context);
-    }
-    
-    // Standard evaluate returns the lambda itself as a callable
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        capture_context(context);
-        return new LambdaElement(this);
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_lambda(this);
-        body.accept(visitor);
-    }
-}
-```
-
-### 9. BracketedExpression
-
-Handles parenthesized expressions for precedence:
-
-```vala
-public class BracketedExpression : Object, Expression {
-    public ExpressionType expression_type { get { return ExpressionType.BRACKETED; } }
-    
-    public Expression inner { get; construct set; }
-    
-    public BracketedExpression(Expression inner) {
-        Object(inner: inner);
-    }
-    
-    public Element evaluate(EvaluationContext context) throws ExpressionError {
-        return inner.evaluate(context);
-    }
-    
-    public void accept(ExpressionVisitor visitor) {
-        visitor.visit_bracketed(this);
-        inner.accept(visitor);
-    }
-}
-```
-
----
-
-## Supporting Types
-
-### LambdaElement
-
-Wraps a lambda for passing around:
-
-```vala
-public class LambdaElement : Object, Element {
-    private LambdaExpression _lambda;
-    
-    public LambdaElement(LambdaExpression lambda) {
-        _lambda = lambda;
-    }
-    
-    public LambdaExpression get_lambda() {
-        return _lambda;
-    }
-    
-    // Element interface implementation
-    public bool assignable_to_type(Type type) {
-        return type == typeof(LambdaElement) || type == typeof(LambdaExpression);
-    }
-    
-    public Type? type() {
-        return typeof(LambdaExpression);
-    }
-    
-    public bool is_null() {
-        return false;
-    }
-    
-    public bool try_get_as<T>(out T result) {
-        if (typeof(T) == typeof(LambdaElement)) {
-            result = this;
-            return true;
-        }
-        if (typeof(T) == typeof(LambdaExpression)) {
-            result = _lambda;
-            return true;
-        }
-        result = null;
-        return false;
-    }
-}
-```
-
----
-
-## FunctionAccessor Interface
-
-```vala
-namespace Invercargill.Expressions {
-
-    public interface FunctionAccessor : Object {
-        
-        // Check if a function is available
-        public abstract bool has_function(string function_name);
-        
-        // Get available function names
-        public abstract Enumerable<string> get_function_names();
-        
-        // Call a function with evaluated arguments
-        // The context is provided for lambdas that need to capture scope
-        public abstract Element call_function(
-            string function_name,
-            Gee.List<Element> arguments,
-            EvaluationContext context
-        ) throws ExpressionError;
-    }
-
-}
-```
-
-### EnumerableFunctionAccessor
-
-Implementation for Enumerable/Elements:
-
-```vala
-public class EnumerableFunctionAccessor : Object, FunctionAccessor {
-    
-    private Elements _target;
-    
-    public EnumerableFunctionAccessor(Elements target) {
-        _target = target;
-    }
-    
-    public bool has_function(string function_name) {
-        return get_supported_functions().contains(function_name);
-    }
-    
-    public Enumerable<string> get_function_names() {
-        return get_supported_functions();
-    }
-    
-    private Enumerable<string> get_supported_functions() {
-        return Iterate.from_array(new string[] {
-            "where", "select", "first", "last", "single",
-            "take", "skip", "count", "any", "all",
-            "order_by", "order_by_descending", "distinct",
-            "element_at", "concat", "reverse"
-        });
-    }
-    
-    public Element call_function(
-        string function_name,
-        Gee.List<Element> arguments,
-        EvaluationContext context
-    ) throws ExpressionError {
-        
-        switch (function_name.to_lower()) {
-            case "where":
-                return call_where(arguments, context);
-            case "first":
-                return call_first(arguments);
-            case "last":
-                return call_last(arguments);
-            case "count":
-                return call_count(arguments);
-            case "any":
-                return call_any(arguments, context);
-            // ... other methods
-            
-            default:
-                throw new ExpressionError.INVALID_TYPE(
-                    @"Unknown function '$function_name' for Enumerable"
-                );
-        }
-    }
-    
-    private Element call_where(Gee.List<Element> arguments, EvaluationContext context) throws ExpressionError {
-        if (arguments.size != 1) {
-            throw new ExpressionError.INVALID_SYNTAX("where() requires exactly 1 argument");
-        }
-        
-        var lambda_element = arguments[0];
-        LambdaExpression? lambda;
-        
-        if (!lambda_element.try_get_as(out lambda)) {
-            throw new ExpressionError.INVALID_TYPE("where() requires a lambda argument");
-        }
-        
-        // Capture the current context for closure support
-        lambda.capture_context(context);
-        
-        // Apply the where filter
-        var filtered = _target.where(item => {
-            try {
-                var result = lambda.evaluate_with_argument(new NativeElement<Object>(item));
-                return result.as<bool>();
-            } catch (Error e) {
-                return false;
-            }
-        });
-        
-        return new NativeElement<Elements>(filtered.to_elements());
-    }
-    
-    private Element call_first(Gee.List<Element> arguments) throws ExpressionError {
-        if (arguments.size > 1) {
-            throw new ExpressionError.INVALID_SYNTAX("first() takes at most 1 argument");
-        }
-        
-        try {
-            if (arguments.size == 0) {
-                return new NativeElement<Object?>(_target.first());
-            }
-            
-            // With predicate lambda
-            var lambda_element = arguments[0];
-            LambdaExpression? lambda;
-            if (!lambda_element.try_get_as(out lambda)) {
-                throw new ExpressionError.INVALID_TYPE("first() predicate must be a lambda");
-            }
-            
-            var result = _target.first(item => {
-                try {
-                    var eval_result = lambda.evaluate_with_argument(new NativeElement<Object>(item));
-                    return eval_result.as<bool>();
-                } catch (Error e) {
-                    return false;
-                }
-            });
-            
-            return new NativeElement<Object?>(result);
-        } catch (SequenceError e) {
-            return new NullElement();
-        }
-    }
-    
-    // ... other method implementations
-}
-```
-
----
-
-## Expression Evaluator
-
-A facade for evaluating expressions:
-
-```vala
-public class ExpressionEvaluator : Object {
-    
-    public Element evaluate(Expression expression, Properties root_values) throws ExpressionError {
-        var context = new EvaluationContext(root_values);
-        return expression.evaluate(context);
-    }
-    
-    public T evaluate_as<T>(Expression expression, Properties root_values) throws Error {
-        var result = evaluate(expression, root_values);
-        return result.as<T>();
-    }
-    
-    // Convenience method for creating and evaluating a simple property chain
-    public Element evaluate_property_chain(string[] properties, Properties root_values) throws ExpressionError {
-        if (properties.length == 0) {
-            throw new ExpressionError.INVALID_SYNTAX("Property chain cannot be empty");
-        }
-        
-        Expression current = new VariableExpression(properties[0]);
-        
-        for (int i = 1; i < properties.length; i++) {
-            current = new PropertyExpression(current, properties[i]);
-        }
-        
-        return evaluate(current, root_values);
-    }
-}
-```
-
----
-
-## Translation Pattern
-
-For ORM-like translation, implement `ExpressionVisitor`:
-
-```vala
-public class SqlExpressionTranslator : Object, ExpressionVisitor {
-    
-    private StringBuilder _builder = new StringBuilder();
-    private int _parameter_index = 0;
-    private Gee.ArrayList<Value> _parameters = new Gee.ArrayList<Value>();
-    
-    public string translate(Expression expression) {
-        _builder.truncate(0);
-        _parameters.clear();
-        _parameter_index = 0;
-        
-        expression.accept(this);
-        
-        return _builder.str;
-    }
-    
-    public Gee.List<Value> get_parameters() {
-        return _parameters.read_only_view;
-    }
-    
-    public override void visit_literal(LiteralExpression expr) {
-        // Add parameter placeholder
-        _parameters.add(extract_value(expr.value));
-        _builder.append(@"@p$(_parameter_index)");
-        _parameter_index++;
-    }
-    
-    public override void visit_variable(VariableExpression expr) {
-        // Translate variable to column reference
-        _builder.append(expr.variable_name);
-    }
-    
-    public override void visit_binary(BinaryExpression expr) {
-        _builder.append("(");
-        expr.left.accept(this);
-        _builder.append(" ");
-        _builder.append(expr.operator.to_string());
-        _builder.append(" ");
-        expr.right.accept(this);
-        _builder.append(")");
-    }
-    
-    public override void visit_unary(UnaryExpression expr) {
-        if (expr.is_prefix) {
-            _builder.append(expr.operator.to_string());
-            expr.operand.accept(this);
-        } else {
-            expr.operand.accept(this);
-            _builder.append(expr.operator.to_string());
-        }
-    }
-    
-    public override void visit_ternary(TernaryExpression expr) {
-        expr.condition.accept(this);
-        _builder.append(" CASE WHEN ");
-        // For SQL, we translate ternary to CASE WHEN ... THEN ... ELSE ... END
-        expr.true_expression.accept(this);
-        _builder.append(" ELSE ");
-        expr.false_expression.accept(this);
-        _builder.append(" END");
-    }
-    
-    public override void visit_property(PropertyExpression expr) {
-        expr.target.accept(this);
-        _builder.append(".");
-        _builder.append(expr.property_name);
-    }
-    
-    public override void visit_function_call(FunctionCallExpression expr) {
-        // Handle specific functions differently
-        // e.g., where() becomes part of WHERE clause
-        // ... implementation
-    }
-    
-    public override void visit_lambda(LambdaExpression expr) {
-        // Lambda body becomes part of the SQL expression
-        expr.body.accept(this);
-    }
-}
-```
-
----
-
-## File Structure
-
-```
-src/lib/Expressions/
-├── Expression.vala              # Main Expression interface
-├── ExpressionError.vala         # Error domain (existing)
-├── ExpressionType.vala          # ExpressionType enum
-├── EvaluationContext.vala       # Context for evaluation
-├── ExpressionVisitor.vala       # Visitor interface
-├── ExpressionEvaluator.vala     # Facade for evaluation
-│
-├── PropertyAccessor.vala        # Interface (existing)
-├── ObjectPropertyAccessor.vala  # Implementation (existing)
-├── PropertiesPropertyAccessor.vala # Implementation (existing)
-│
-├── FunctionAccessor.vala        # Interface for function calls
-├── EnumerableFunctionAccessor.vala # Implementation for Enumerable
-│
-├── Expressions/
-│   ├── LiteralExpression.vala
-│   ├── VariableExpression.vala
-│   ├── BinaryExpression.vala
-│   ├── BinaryOperator.vala
-│   ├── UnaryExpression.vala
-│   ├── UnaryOperator.vala
-│   ├── TernaryExpression.vala
-│   ├── PropertyExpression.vala
-│   ├── FunctionCallExpression.vala
-│   ├── LambdaExpression.vala
-│   └── BracketedExpression.vala
-│
-├── Elements/
-│   └── LambdaElement.vala       # Element wrapper for lambdas
-│
-└── Translators/
-    ├── SqlExpressionTranslator.vala
-    └── StructuredExpressionTranslator.vala
-```
-
----
-
-## Usage Examples
-
-### Basic Variable and Property Access
-
-```vala
-var props = new PropertiesDictionary();
-props.set_native("a", some_object);
-props.set_native("b", 42);
-
-var expr = new BinaryExpression(
-    new PropertyExpression(new VariableExpression("a"), "value"),
-    BinaryOperator.GREATER_THAN,
-    new VariableExpression("b")
-);
-
-var evaluator = new ExpressionEvaluator();
-var result = evaluator.evaluate_as<bool>(expr, props);
-```
-
-### Lambda with Closure
-
-```vala
-// Expression: a.values.where(s => s.rank > a.min_rank)
-var props = new PropertiesDictionary();
-props.set_native("a", container_with_values_and_min_rank);
-
-// Build: a.values
-var a_values = new PropertyExpression(
-    new VariableExpression("a"),
-    "values"
-);
-
-// Build: s.rank > a.min_rank
-var lambda_body = new BinaryExpression(
-    new PropertyExpression(new VariableExpression("s"), "rank"),
-    BinaryOperator.GREATER_THAN,
-    new PropertyExpression(new VariableExpression("a"), "min_rank")
-);
-
-// Build: s => s.rank > a.min_rank
-var lambda = new LambdaExpression("s", lambda_body);
-
-// Build: a.values.where(s => s.rank > a.min_rank)
-var where_call = new FunctionCallExpression(a_values, "where", 
-    new Gee.ArrayList<Expression>.wrap({ lambda })
-);
-
-var evaluator = new ExpressionEvaluator();
-var result = evaluator.evaluate(where_call, props);
-```
-
-### Chained Function Calls
-
-```vala
-// Expression: a.values.where(s => s.rank > a.min_rank).first().is_important
-// ... previous where_call ...
-
-var first_call = new FunctionCallExpression(where_call, "first");
-var is_important = new PropertyExpression(first_call, "is_important");
-
-var evaluator = new ExpressionEvaluator();
-var result = evaluator.evaluate_as<bool>(is_important, props);
-```
-
----
-
-## Design Decisions Summary
-
-1. **Dynamically Typed**: Uses `Element` system for flexible type handling
-2. **Closure Support**: `EvaluationContext` maintains parent chain for scope resolution
-3. **Visitor Pattern**: Enables translation without modifying expression classes
-4. **Interface-Based Accessors**: Both `PropertyAccessor` and `FunctionAccessor` are interfaces for extensibility
-5. **Separation of Concerns**: 
-   - Expressions define structure
-   - Evaluators handle execution
-   - Visitors handle translation
-6. **ORM Ready**: The visitor pattern allows for SQL or other query language generation
-
----
-
-## Next Steps
-
-1. Implement core interfaces (`Expression`, `EvaluationContext`, `ExpressionVisitor`)
-2. Implement expression types (start with `LiteralExpression`, `VariableExpression`, `BinaryExpression`)
-3. Implement `FunctionAccessor` interface and `EnumerableFunctionAccessor`
-4. Add `LambdaExpression` with closure capture
-5. Add `ExpressionEvaluator` facade
-6. Write unit tests
-7. Add parser (future task)
-8. Add SQL translator (future task)

+ 222 - 58
src/lib/Element.vala

@@ -28,10 +28,10 @@ namespace Invercargill {
         }
 
         public virtual string type_name() {
-            return type().name();
+            return type()?.name() ?? "UNKNOWN";
         }
 
-        public virtual T? @as<T>() throws ElementError {
+        public virtual T @as<T>() throws ElementError {
             T result;
             if(try_get_as<T>(out result)) {
                 return result;
@@ -39,14 +39,6 @@ namespace Invercargill {
             throw new ElementError.INVALID_CONVERSION(@"Could not convert from $(type_name()) to $(typeof(T).name()).");
         }
 
-        public virtual T? as_or_default<T>() {
-            T result;
-            if(try_get_as<T>(out result)) {
-                return result;
-            }
-            return null;
-        }
-
         public virtual T assert_as<T>() {
             T result;
             if(try_get_as<T>(out result)) {
@@ -57,55 +49,213 @@ namespace Invercargill {
         }
 
         public virtual Value to_value(Type requested_type) throws Error {
-            Value result = Value(requested_type);
-            
-            if(requested_type == typeof(bool)) {
-                result.set_boolean(@as<bool>());
-            } else if(requested_type == typeof(char)) {
-                result.set_schar((int8)@as<char>());
-            } else if(requested_type == typeof(uchar)) {
-                result.set_uchar(@as<uchar>());
-            } else if(requested_type == typeof(int)) {
-                result.set_int(@as<int>());
-            } else if(requested_type == typeof(uint)) {
-                result.set_uint(@as<uint>());
-            } else if(requested_type == typeof(long)) {
-                result.set_long(@as<long>());
-            } else if(requested_type == typeof(ulong)) {
-                result.set_ulong(@as<ulong>());
-            } else if(requested_type == typeof(int64)) {
-                result.set_int64(@as<int64?>());
-            } else if(requested_type == typeof(uint64)) {
-                result.set_uint64(@as<uint64?>());
-            } else if(requested_type == typeof(float)) {
-                result.set_float(@as<float?>());
-            } else if(requested_type == typeof(double)) {
-                result.set_double(@as<double?>());
-            } else if(requested_type == typeof(string)) {
-                result.set_string(@as<string>());
-            } else if(requested_type == typeof(Object)) {
-                result.set_object(@as<Object>());
-            } else {
-                // Fallback for any other type - try to get as Object
-                result.set_object(@as<Object>());
+            var value = Value(requested_type);
+            if(requested_type.is_a(Type.BOOLEAN)) {
+                value.set_boolean(as<bool>());
+                return value;
             }
-            
-            return result;
+            if(requested_type.is_a(Type.CHAR)) {
+                value.set_schar(as<int8>());
+                return value;
+            }
+            if(requested_type.is_a(Type.DOUBLE)) {
+                value.set_double(as<double?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.ENUM)) {
+                value.set_enum(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.FLAGS)) {
+                value.set_flags(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.FLOAT)) {
+                value.set_float(as<float?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.INT)) {
+                value.set_int(as<int>());
+                return value;
+            }
+            if(requested_type.is_a(Type.INT64)) {
+                value.set_int64(as<int64?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.LONG)) {
+                value.set_long(as<long>());
+                return value;
+            }
+            if(requested_type.is_a(Type.STRING)) {
+                value.set_string(as<string>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UCHAR)) {
+                value.set_uchar(as<uchar>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UINT)) {
+                value.set_uint(as<uint>());
+                return value;
+            }
+            if(requested_type.is_a(Type.UINT64)) {
+                value.set_uint64(as<uint64?>());
+                return value;
+            }
+            if(requested_type.is_a(Type.ULONG)) {
+                value.set_ulong(as<ulong>());
+                return value;
+            }
+
+            value.set_object(as<Object>());
+            return value;
         }
 
         public virtual string to_string() {
             return @"Element[$(type_name())]";
         }
 
+        public virtual string stringify() {
+            if (is_null()) {
+                return "";
+            }
+            
+            // Try string
+            string? str_val;
+            if (try_get_as<string>(out str_val)) {
+                return str_val ?? "";
+            }
+            
+            var type = type();
+            if(type.is_a(typeof(Stringifyable))) {
+                return assert_as<Stringifyable>().to_string();
+            }
+            if(type == typeof(bool)) {
+                return assert_as<bool>().to_string();
+            } else if(type == typeof(char)) {
+                return assert_as<char>().to_string();
+            } else if(type == typeof(uchar)) {
+                return assert_as<uchar>().to_string();
+            } else if(type == typeof(int)) {
+                return assert_as<int>().to_string();
+            } else if(type == typeof(uint)) {
+                return assert_as<uint>().to_string();
+            } else if(type == typeof(long)) {
+                return assert_as<long>().to_string();
+            } else if(type == typeof(ulong)) {
+                return assert_as<ulong>().to_string();
+            } else if(type == typeof(int64)) {
+                return assert_as<int64?>().to_string();
+            } else if(type == typeof(uint64)) {
+                return assert_as<uint64?>().to_string();
+            } else if(type == typeof(float)) {
+                return assert_as<float?>().to_string();
+            } else if(type == typeof(double)) {
+                return assert_as<double?>().to_string();
+            } else if(type == typeof(string)) {
+                return assert_as<string>().to_string();
+            }
+
+            // Fallback to default to_string()
+            return to_string();
+        }
+
+        public virtual string? as_string_or_null() {
+            string res;
+            if(!is_null() && assignable_to_type(typeof(string))){
+                if(try_get_as<string>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual bool? as_bool_or_null() {
+            bool res;
+            if(!is_null() && assignable_to_type(typeof(bool))){
+                if(try_get_as<bool>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual int? as_int_or_null() {
+            int res;
+            if(!is_null() && assignable_to_type(typeof(int))){
+                if(try_get_as<int>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual uint? as_uint_or_null() {
+            uint res;
+            if(!is_null() && assignable_to_type(typeof(uint))){
+                if(try_get_as<uint>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual Object? as_object_or_null() {
+            Object res;
+            if(!is_null() && assignable_to_type(typeof(Object))){
+                if(try_get_as<Object>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual float? as_float_or_null() {
+            float? res;
+            if(!is_null() && assignable_to_type(typeof(float?))){
+                if(try_get_as<float?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual double? as_double_or_null() {
+            double? res;
+            if(!is_null() && assignable_to_type(typeof(double?))){
+                if(try_get_as<double?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual int64? as_int64_or_null() {
+            int64? res;
+            if(!is_null() && assignable_to_type(typeof(int64?))){
+                if(try_get_as<int64?>(out res))
+                    return res;
+            }
+            return null;
+        }
+
+        public virtual long? as_long_or_null() {
+            long res;
+            if(!is_null() && assignable_to_type(typeof(long))){
+                if(try_get_as<long>(out res))
+                    return res;
+            }
+            return null;
+        }
+
     }
 
 
     public class NativeElement<T> : Object, Element {
 
         private T object;
+        private bool _is_null;
+        public bool is_reference_type { get; private set; }
+        public bool is_nullable { get; private set; }
         
         public NativeElement(T obj) {
             object = obj;
+            _is_null = Nullable.is_null<T>(obj);
+            is_reference_type = Nullable.is_reference<T>(obj);
+            is_nullable = Nullable.is_nullable<T>(obj);
         }
 
         public bool assignable_to_type(GLib.Type type) {
@@ -123,40 +273,43 @@ namespace Invercargill {
         }
 
         public bool is_null() {
-            return false;
+            return is_nullable && _is_null;
         }
 
         public bool try_get_as<TOut>(out TOut result) {
-            if(typeof(T).is_a(typeof(Enumerable)) && typeof(TOut) == typeof(Elements)) {
+            if(!is_null() && typeof(T).is_a(typeof(Enumerable)) && typeof(TOut) == typeof(Elements)) {
                 result = (TOut)((Enumerable)object).to_elements();
                 return true;
             }
             if(assignable_to<TOut>()) {
-                result = (TOut)object;
-                return true;
+                return Nullable.convert_nullability<T, TOut>(object, out result);
             }
             result = null;
             return false;
         }
+
+        public string type_name() {
+            return Nullable.get_type_name<T>(object);
+        }
     }
 
     public class NullElement : Object, Element {
 
         public bool assignable_to_type(GLib.Type type) {
-            return true;
+            return type.is_a(Type.NONE);
         }
         public GLib.Type? type() {
-            return null;
+            return Type.NONE;
         }
         public bool is_null() {
             return true;
         }
         public bool try_get_as<T>(out T result) {
             result = null;
-            return true;
+            return false;
         }
         public string to_string() {
-            return "Element[null]";
+            return "NullElement[null]";
         }
 
     }
@@ -176,6 +329,9 @@ namespace Invercargill {
             if(value.type().is_a(typeof(Enumerable)) && type == typeof(Elements)) {
                 return true;
             }
+            if(Value.type_transformable(value.type(), type)) {
+                return true;
+            }
             return false;
         }
         public GLib.Type? type() {
@@ -183,8 +339,17 @@ namespace Invercargill {
         }
         public bool is_null() {
             var value_type = value.type();
-            if(!value_type.is_value_type()) {
-                return value.get_pointer() == null;
+            if(value_type == Type.INVALID) {
+                return true;
+            }
+            if(value_type.is_a(Type.STRING)) {
+                return value.get_string() == null;
+            }
+            if(value_type.is_a(Type.BOXED)) {
+                return value.get_boxed() == null;
+            }
+            if(value_type.is_a(Type.OBJECT)) {
+                return value.get_object() == null;
             }
             return false;
         }
@@ -199,21 +364,20 @@ namespace Invercargill {
             }
 
             if(value_type.is_a(requested_type)) {
-                result = (T)value.get_object();
-                return true;
+                if(safely_unwrap_value<T>(value, out result))
+                    return true;
             }
 
             // Try value transformation for reference types
             var converted = Value(requested_type);
             if(value.transform(ref converted)) {
-                result = (T)converted.get_object();
-                return true;
+                if(safely_unwrap_value<T>(converted, out result))
+                    return true;
             }
 
             result = null;
             return false;
         }
-
         
     }
 

+ 19 - 10
src/lib/Expressions/EnumerableFunctionAccessor.vala

@@ -142,8 +142,11 @@ namespace Invercargill.Expressions {
             var filtered = _target.where(item => {
                 try {
                     var result = lambda.evaluate_with_argument(item);
-                    bool? bool_result = null;
-                    if (result.try_get_as(out bool_result) && bool_result != null) {
+                    if (result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (result.try_get_as(out bool_result)) {
                         return bool_result;
                     }
                     return false;
@@ -299,7 +302,7 @@ namespace Invercargill.Expressions {
 
             // Simple count without predicate
             var count = _target.count();
-            return new NativeElement<int?>((int)count);
+            return new NativeElement<int64?>((int64)count);
         }
 
         private Element call_any(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
@@ -309,7 +312,7 @@ namespace Invercargill.Expressions {
 
             if (arguments.length == 0) {
                 var result = _target.any();
-                return new NativeElement<bool?>(result);
+                return new NativeElement<bool>(result);
             }
 
             // With predicate
@@ -322,8 +325,11 @@ namespace Invercargill.Expressions {
             var result = _target.any(item => {
                 try {
                     var eval_result = lambda.evaluate_with_argument(item);
-                    bool? bool_result = null;
-                    if (eval_result.try_get_as(out bool_result) && bool_result != null) {
+                    if (eval_result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (eval_result.try_get_as(out bool_result)) {
                         return bool_result;
                     }
                     return false;
@@ -332,7 +338,7 @@ namespace Invercargill.Expressions {
                 }
             });
 
-            return new NativeElement<bool?>(result);
+            return new NativeElement<bool>(result);
         }
 
         private Element call_all(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
@@ -349,8 +355,11 @@ namespace Invercargill.Expressions {
             var result = _target.all(item => {
                 try {
                     var eval_result = lambda.evaluate_with_argument(item);
-                    bool? bool_result = null;
-                    if (eval_result.try_get_as(out bool_result) && bool_result != null) {
+                    if (eval_result.is_null()) {
+                        return false;
+                    }
+                    bool bool_result;
+                    if (eval_result.try_get_as(out bool_result)) {
                         return bool_result;
                     }
                     return false;
@@ -359,7 +368,7 @@ namespace Invercargill.Expressions {
                 }
             });
 
-            return new NativeElement<bool?>(result);
+            return new NativeElement<bool>(result);
         }
 
         private Element call_order_by(Series<Element> arguments, EvaluationContext context) throws ExpressionError {

+ 3 - 3
src/lib/Expressions/ExpressionParser.vala

@@ -341,15 +341,15 @@ namespace Invercargill.Expressions {
 
             if (match(TokenType.STRING)) {
                 var token = previous();
-                return new LiteralExpression(new NativeElement<string?>(token.value));
+                return new LiteralExpression(new NativeElement<string>(token.value));
             }
 
             if (match(TokenType.TRUE)) {
-                return new LiteralExpression(new NativeElement<bool?>(true));
+                return new LiteralExpression(new NativeElement<bool>(true));
             }
 
             if (match(TokenType.FALSE)) {
-                return new LiteralExpression(new NativeElement<bool?>(false));
+                return new LiteralExpression(new NativeElement<bool>(false));
             }
 
             if (match(TokenType.NULL_LITERAL)) {

+ 38 - 38
src/lib/Expressions/Expressions/BinaryExpression.vala

@@ -56,40 +56,40 @@ namespace Invercargill.Expressions {
             // Handle short-circuit evaluation for logical operators
             if (op == BinaryOperator.AND) {
                 var left_result = left.evaluate(context);
-                bool? left_bool_nullable = left_result.as_or_default<bool?>();
+                bool? left_bool_nullable = left_result.as_bool_or_null();
                 if (left_bool_nullable == null) {
                     throw new ExpressionError.TYPE_MISMATCH("Left operand of && must be boolean");
                 }
                 bool left_bool = left_bool_nullable;
                 if (!left_bool) {
-                    return new NativeElement<bool?>(false);
+                    return new NativeElement<bool>(false);
                 }
                 var right_result = right.evaluate(context);
-                bool? right_bool_nullable = right_result.as_or_default<bool?>();
+                bool? right_bool_nullable = right_result.as_bool_or_null();
                 if (right_bool_nullable == null) {
                     throw new ExpressionError.TYPE_MISMATCH("Right operand of && must be boolean");
                 }
                 bool right_bool = right_bool_nullable;
-                return new NativeElement<bool?>(right_bool);
+                return new NativeElement<bool>(right_bool);
             }
             
             if (op == BinaryOperator.OR) {
                 var left_result = left.evaluate(context);
-                bool? left_bool_nullable = left_result.as_or_default<bool?>();
+                bool? left_bool_nullable = left_result.as_bool_or_null();
                 if (left_bool_nullable == null) {
                     throw new ExpressionError.TYPE_MISMATCH("Left operand of || must be boolean");
                 }
                 bool left_bool = left_bool_nullable;
                 if (left_bool) {
-                    return new NativeElement<bool?>(true);
+                    return new NativeElement<bool>(true);
                 }
                 var right_result = right.evaluate(context);
-                bool? right_bool_nullable = right_result.as_or_default<bool?>();
+                bool? right_bool_nullable = right_result.as_bool_or_null();
                 if (right_bool_nullable == null) {
                     throw new ExpressionError.TYPE_MISMATCH("Right operand of || must be boolean");
                 }
                 bool right_bool = right_bool_nullable;
-                return new NativeElement<bool?>(right_bool);
+                return new NativeElement<bool>(right_bool);
             }
 
             // Evaluate both operands for non-short-circuit operators
@@ -119,23 +119,23 @@ namespace Invercargill.Expressions {
             
             if (op == BinaryOperator.EQUAL) {
                 if (left_is_null && right_is_null) {
-                    return new NativeElement<bool?>(true);
+                    return new NativeElement<bool>(true);
                 }
                 if (left_is_null || right_is_null) {
-                    return new NativeElement<bool?>(false);
+                    return new NativeElement<bool>(false);
                 }
                 // For non-null values, use value comparison
-                return new NativeElement<bool?>(elements_equal(left_val, right_val));
+                return new NativeElement<bool>(elements_equal(left_val, right_val));
             }
             
             if (op == BinaryOperator.NOT_EQUAL) {
                 if (left_is_null && right_is_null) {
-                    return new NativeElement<bool?>(false);
+                    return new NativeElement<bool>(false);
                 }
                 if (left_is_null || right_is_null) {
-                    return new NativeElement<bool?>(true);
+                    return new NativeElement<bool>(true);
                 }
-                return new NativeElement<bool?>(!elements_equal(left_val, right_val));
+                return new NativeElement<bool>(!elements_equal(left_val, right_val));
             }
 
             // For ordering comparisons, both must be non-null and comparable
@@ -147,24 +147,24 @@ namespace Invercargill.Expressions {
             int? cmp_result = try_numeric_compare(left_val, right_val);
             if (cmp_result != null) {
                 switch (op) {
-                    case GREATER_THAN: return new NativeElement<bool?>(cmp_result > 0);
-                    case LESS_THAN: return new NativeElement<bool?>(cmp_result < 0);
-                    case GREATER_EQUAL: return new NativeElement<bool?>(cmp_result >= 0);
-                    case LESS_EQUAL: return new NativeElement<bool?>(cmp_result <= 0);
+                    case GREATER_THAN: return new NativeElement<bool>(cmp_result > 0);
+                    case LESS_THAN: return new NativeElement<bool>(cmp_result < 0);
+                    case GREATER_EQUAL: return new NativeElement<bool>(cmp_result >= 0);
+                    case LESS_EQUAL: return new NativeElement<bool>(cmp_result <= 0);
                     default: break;
                 }
             }
 
             // Try string comparison
-            string? left_str = left_val.as_or_default<string?>();
-            string? right_str = right_val.as_or_default<string?>();
+            string? left_str = left_val.as_string_or_null();
+            string? right_str = right_val.as_string_or_null();
             if (left_str != null && right_str != null) {
                 int str_cmp = strcmp(left_str, right_str);
                 switch (op) {
-                    case GREATER_THAN: return new NativeElement<bool?>(str_cmp > 0);
-                    case LESS_THAN: return new NativeElement<bool?>(str_cmp < 0);
-                    case GREATER_EQUAL: return new NativeElement<bool?>(str_cmp >= 0);
-                    case LESS_EQUAL: return new NativeElement<bool?>(str_cmp <= 0);
+                    case GREATER_THAN: return new NativeElement<bool>(str_cmp > 0);
+                    case LESS_THAN: return new NativeElement<bool>(str_cmp < 0);
+                    case GREATER_EQUAL: return new NativeElement<bool>(str_cmp >= 0);
+                    case LESS_EQUAL: return new NativeElement<bool>(str_cmp <= 0);
                     default: break;
                 }
             }
@@ -177,15 +177,15 @@ namespace Invercargill.Expressions {
          */
         private bool elements_equal(Element left_val, Element right_val) {
             // Try bool
-            bool? left_bool = left_val.as_or_default<bool?>();
-            bool? right_bool = right_val.as_or_default<bool?>();
+            bool? left_bool = left_val.as_bool_or_null();
+            bool? right_bool = right_val.as_bool_or_null();
             if (left_bool != null && right_bool != null) {
                 return left_bool == right_bool;
             }
             
             // Try string
-            string? left_str = left_val.as_or_default<string?>();
-            string? right_str = right_val.as_or_default<string?>();
+            string? left_str = left_val.as_string_or_null();
+            string? right_str = right_val.as_string_or_null();
             if (left_str != null && right_str != null) {
                 return left_str == right_str;
             }
@@ -206,31 +206,31 @@ namespace Invercargill.Expressions {
          */
         private double? try_get_numeric(Element val) {
             // Try int
-            int? as_int = val.as_or_default<int?>();
+            int? as_int = val.as_int_or_null();
             if (as_int != null) {
                 return (double)as_int;
             }
             
             // Try int64
-            int64? as_int64 = val.as_or_default<int64?>();
+            int64? as_int64 = val.as_int64_or_null();
             if (as_int64 != null) {
                 return (double)as_int64;
             }
             
             // Try long
-            long? as_long = val.as_or_default<long?>();
+            long? as_long = val.as_long_or_null();
             if (as_long != null) {
                 return (double)as_long;
             }
             
             // Try double
-            double? as_double = val.as_or_default<double?>();
+            double? as_double = val.as_double_or_null();
             if (as_double != null) {
                 return as_double;
             }
             
             // Try float
-            float? as_float = val.as_or_default<float?>();
+            float? as_float = val.as_float_or_null();
             if (as_float != null) {
                 return (double)as_float;
             }
@@ -272,10 +272,10 @@ namespace Invercargill.Expressions {
             if (left_double == null || right_double == null) {
                 // Special case for + with strings (concatenation)
                 if (op == BinaryOperator.ADD) {
-                    string? left_str = left_val.as_or_default<string?>();
-                    string? right_str = right_val.as_or_default<string?>();
+                    string? left_str = left_val.as_string_or_null();
+                    string? right_str = right_val.as_string_or_null();
                     if (left_str != null && right_str != null) {
-                        return new NativeElement<string?>(left_str + right_str);
+                        return new NativeElement<string>(left_str + right_str);
                     }
                 }
                 throw new ExpressionError.TYPE_MISMATCH("Arithmetic operators require numeric operands");
@@ -304,8 +304,8 @@ namespace Invercargill.Expressions {
                         throw new ExpressionError.DIVISION_BY_ZERO("Modulo by zero");
                     }
                     // For modulo, prefer integer operation if both are integers
-                    int? left_int = left_val.as_or_default<int?>();
-                    int? right_int = right_val.as_or_default<int?>();
+                    int? left_int = left_val.as_int_or_null();
+                    int? right_int = right_val.as_int_or_null();
                     if (left_int != null && right_int != null) {
                         return new NativeElement<int64?>(left_int % right_int);
                     }

+ 86 - 72
src/lib/Expressions/Expressions/LotLiteralExpression.vala

@@ -44,7 +44,7 @@ namespace Invercargill.Expressions {
         public Element evaluate(EvaluationContext context) throws ExpressionError {
             // If empty lot, return an empty enumerable of objects
             if (_elements.length == 0) {
-                return new NativeElement<Enumerable<Object?>>(Iterate.nothing<Object?>());
+                return new NativeElement<Enumerable<Object>>(Iterate.nothing<Object>());
             }
 
             // Evaluate all elements first
@@ -102,48 +102,33 @@ namespace Invercargill.Expressions {
                 var elem = evaluated[i];
                 var elem_type = elem.type();
                 
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to int lot");
+                }
+                
                 // Handle based on the element's actual type
-                if (elem_type == typeof(int) || elem_type == typeof(int?)) {
-                    int? nullable_val;
-                    if (elem.try_get_as<int?>(out nullable_val)) {
-                        series.add(nullable_val);
-                        continue;
-                    }
+                if (elem_type == typeof(int)) {
                     int val;
                     if (elem.try_get_as<int>(out val)) {
                         series.add(val);
                         continue;
                     }
-                } else if (elem_type == typeof(long) || elem_type == typeof(long?)) {
-                    long? nullable_val;
-                    if (elem.try_get_as<long?>(out nullable_val)) {
-                        series.add((int)nullable_val);
-                        continue;
-                    }
+                } else if (elem_type == typeof(long)) {
                     long val;
                     if (elem.try_get_as<long>(out val)) {
                         series.add((int)val);
                         continue;
                     }
-                } else if (elem_type == typeof(int64) || elem_type == typeof(int64?)) {
-                    int64? nullable_val;
-                    if (elem.try_get_as<int64?>(out nullable_val)) {
-                        series.add((int)nullable_val);
-                        continue;
-                    }
-                    // int64 can't be used as generic type argument directly
-                    // Try getting as long instead
-                    long long_val;
-                    if (elem.try_get_as<long>(out long_val)) {
-                        series.add((int)long_val);
+                } else if (elem_type == typeof(int64)) {
+                    // int64 requires nullable for generic access
+                    int64? val;
+                    if (elem.try_get_as<int64?>(out val)) {
+                        series.add((int)val);
                         continue;
                     }
                 }
                 
-                if (elem.is_null()) {
-                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to int lot");
-                }
-                
                 throw new ExpressionError.TYPE_MISMATCH(
                     @"Element $(i) is not an int (got $(elem_type.name()))"
                 );
@@ -154,24 +139,37 @@ namespace Invercargill.Expressions {
         private Element create_double_lot(Element[] evaluated) throws ExpressionError {
             var series = new Series<double?>();
             for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to double lot");
+                }
+                
+                // double? is valid for generic access
                 double? dval;
-                int ival;
-                int? nullable_ival;
-                if (evaluated[i].try_get_as<double?>(out dval)) {
+                if (elem.try_get_as<double?>(out dval)) {
                     series.add(dval);
-                } else if (evaluated[i].try_get_as<int>(out ival)) {
-                    // Promote int to double
+                    continue;
+                }
+                
+                // Try int promotion
+                int ival;
+                if (elem.try_get_as<int>(out ival)) {
                     series.add((double)ival);
-                } else if (evaluated[i].try_get_as<int?>(out nullable_ival)) {
-                    // Promote int? to double
-                    series.add((double)nullable_ival);
-                } else if (evaluated[i].is_null()) {
-                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to double lot");
-                } else {
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"Element $(i) is not a double"
-                    );
+                    continue;
+                }
+                
+                // Try long promotion
+                long lval;
+                if (elem.try_get_as<long>(out lval)) {
+                    series.add((double)lval);
+                    continue;
                 }
+                
+                throw new ExpressionError.TYPE_MISMATCH(
+                    @"Element $(i) is not a double"
+                );
             }
             return new NativeElement<Enumerable<double?>>(series);
         }
@@ -179,14 +177,16 @@ namespace Invercargill.Expressions {
         private Element create_bool_lot(Element[] evaluated) throws ExpressionError {
             var series = new Series<bool>();
             for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to bool lot");
+                }
+                
                 bool val;
-                bool? nullable_val;
-                if (evaluated[i].try_get_as<bool>(out val)) {
+                if (elem.try_get_as<bool>(out val)) {
                     series.add(val);
-                } else if (evaluated[i].try_get_as<bool?>(out nullable_val)) {
-                    series.add(nullable_val);
-                } else if (evaluated[i].is_null()) {
-                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to bool lot");
                 } else {
                     throw new ExpressionError.TYPE_MISMATCH(
                         @"Element $(i) is not a bool"
@@ -199,11 +199,17 @@ namespace Invercargill.Expressions {
         private Element create_string_lot(Element[] evaluated) throws ExpressionError {
             var series = new Series<string?>();
             for (int i = 0; i < evaluated.length; i++) {
-                string? val;
-                if (evaluated[i].try_get_as<string?>(out val)) {
+                var elem = evaluated[i];
+                
+                // Handle null explicitly
+                if (elem.is_null()) {
+                    series.add(null);
+                    continue;
+                }
+                
+                string val;
+                if (elem.try_get_as<string>(out val)) {
                     series.add(val);
-                } else if (evaluated[i].is_null()) {
-                    series.add(null);  // Strings can be null
                 } else {
                     throw new ExpressionError.TYPE_MISMATCH(
                         @"Element $(i) is not a string"
@@ -216,27 +222,29 @@ namespace Invercargill.Expressions {
         private Element create_long_lot(Element[] evaluated) throws ExpressionError {
             var series = new Series<long>();
             for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                // Check for null first
+                if (elem.is_null()) {
+                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to long lot");
+                }
+                
                 long lval;
-                long? nullable_lval;
-                int ival;
-                int? nullable_ival;
-                if (evaluated[i].try_get_as<long>(out lval)) {
+                if (elem.try_get_as<long>(out lval)) {
                     series.add(lval);
-                } else if (evaluated[i].try_get_as<long?>(out nullable_lval)) {
-                    series.add(nullable_lval);
-                } else if (evaluated[i].try_get_as<int>(out ival)) {
-                    // Promote int to long
+                    continue;
+                }
+                
+                // Try int promotion
+                int ival;
+                if (elem.try_get_as<int>(out ival)) {
                     series.add((long)ival);
-                } else if (evaluated[i].try_get_as<int?>(out nullable_ival)) {
-                    // Promote int? to long
-                    series.add((long)nullable_ival);
-                } else if (evaluated[i].is_null()) {
-                    throw new ExpressionError.TYPE_MISMATCH("Cannot add null to long lot");
-                } else {
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"Element $(i) is not a long"
-                    );
+                    continue;
                 }
+                
+                throw new ExpressionError.TYPE_MISMATCH(
+                    @"Element $(i) is not a long"
+                );
             }
             return new NativeElement<Enumerable<long>>(series);
         }
@@ -244,12 +252,18 @@ namespace Invercargill.Expressions {
         private Element create_object_lot(Element[] evaluated) throws ExpressionError {
             var series = new Series<Object?>();
             for (int i = 0; i < evaluated.length; i++) {
+                var elem = evaluated[i];
+                
+                if (elem.is_null()) {
+                    series.add(null);
+                    continue;
+                }
+                
                 Object? val;
-                if (evaluated[i].try_get_as<Object?>(out val)) {
+                if (elem.try_get_as<Object?>(out val)) {
                     series.add(val);
-                } else if (evaluated[i].is_null()) {
-                    series.add(null);
                 } else {
+                    // For object lots, just add null for incompatible types
                     series.add(null);
                 }
             }

+ 1 - 1
src/lib/Expressions/Expressions/TernaryExpression.vala

@@ -40,7 +40,7 @@ namespace Invercargill.Expressions {
         public Element evaluate(EvaluationContext context) throws ExpressionError {
             var cond_val = condition.evaluate(context);
 
-            bool? cond_bool;
+            bool cond_bool;
             if (cond_val.try_get_as(out cond_bool)) {
                 if (cond_bool) {
                     return true_expression.evaluate(context);

+ 7 - 7
src/lib/Expressions/Expressions/UnaryExpression.vala

@@ -71,35 +71,35 @@ namespace Invercargill.Expressions {
 
         private Element evaluate_negate(Element element) throws ExpressionError {
             // Try int first
-            int? int_val_nullable = element.as_or_default<int?>();
+            int? int_val_nullable = element.as_int_or_null();
             if (int_val_nullable != null) {
                 int int_val = (!) int_val_nullable;
                 return new NativeElement<int64?>(-(int64)int_val);
             }
 
             // Try int64
-            int64? int64_val_nullable = element.as_or_default<int64?>();
+            int64? int64_val_nullable = element.as_int64_or_null();
             if (int64_val_nullable != null) {
                 int64 int64_val = (!) int64_val_nullable;
                 return new NativeElement<int64?>(-int64_val);
             }
 
             // Try long
-            long? long_val_nullable = element.as_or_default<long?>();
+            long? long_val_nullable = element.as_long_or_null();
             if (long_val_nullable != null) {
                 long long_val = (!) long_val_nullable;
                 return new NativeElement<int64?>(-(int64)long_val);
             }
 
             // Try double
-            double? double_val_nullable = element.as_or_default<double?>();
+            double? double_val_nullable = element.as_double_or_null();
             if (double_val_nullable != null) {
                 double double_val = (!) double_val_nullable;
                 return new NativeElement<double?>(-double_val);
             }
 
             // Try float
-            float? float_val_nullable = element.as_or_default<float?>();
+            float? float_val_nullable = element.as_float_or_null();
             if (float_val_nullable != null) {
                 float float_val = (!) float_val_nullable;
                 return new NativeElement<double?>(-(double)float_val);
@@ -111,10 +111,10 @@ namespace Invercargill.Expressions {
         }
 
         private Element evaluate_not(Element element) throws ExpressionError {
-            bool? bool_val_nullable = element.as_or_default<bool?>();
+            bool? bool_val_nullable = element.as_bool_or_null();
             if (bool_val_nullable != null) {
                 bool bool_val = (!) bool_val_nullable;
-                return new NativeElement<bool?>(!bool_val);
+                return new NativeElement<bool>(!bool_val);
             }
             throw new ExpressionError.INVALID_TYPE(
                 @"Logical NOT requires boolean operand, got $(element.type_name())"

+ 125 - 214
src/lib/Expressions/GlobalFunctionAccessor.vala

@@ -8,13 +8,18 @@ namespace Invercargill.Expressions {
      * These functions are always available in expressions without needing
      * to be explicitly registered. Currently supports:
      * 
-     * - `format(template, ...args)` - Printf-style string formatting
+     * - `format(template)` - Handlebars-style string interpolation
+     * - `stringify(...args)` - Stringify and concatenate values
      * 
      * Examples:
-     * - `format("Hello, %s!", name)` - String substitution
-     * - `format("You have %i items", count)` - Integer substitution
-     * - `format("Value: %.2f", price)` - Float formatting with precision
-     * - `format("%s #%i: %s", category, id, title)` - Multiple arguments
+     * - `format("Hello, {{name}}!")` - Variable interpolation
+     * - `format("Value: {{count}}")` - Number interpolation
+     * - `format("Sum: {{a + b}}")` - Expression evaluation
+     * - `format("User: {{user.name}}")` - Property access
+     * - `format("Literal: \\{{name}}")` - Escape sequence (outputs "{{name}}")
+     * - `stringify(42)` - Returns "42"
+     * - `stringify("Hello", " ", "World")` - Returns "Hello World"
+     * - `stringify(1, " + ", 2, " = ", 3)` - Returns "1 + 2 = 3"
      */
     public class GlobalFunctionAccessor : Object, FunctionAccessor {
 
@@ -27,6 +32,7 @@ namespace Invercargill.Expressions {
             if (_function_names == null) {
                 _function_names = new Series<string>();
                 _function_names.add("format");
+                _function_names.add("stringify");
             }
             return _function_names;
         }
@@ -35,7 +41,7 @@ namespace Invercargill.Expressions {
          * Checks if a global function exists.
          */
         public bool has_function(string name) {
-            return name == "format";
+            return name == "format" || name == "stringify";
         }
 
         /**
@@ -43,13 +49,17 @@ namespace Invercargill.Expressions {
          * 
          * @param function_name The function name
          * @param arguments The function arguments as a Series
-         * @param context The evaluation context
+         * @param context The evaluation context (required for format function)
          * @return The result of the function call
          * @throws ExpressionError if the function doesn't exist or arguments are invalid
          */
         public Element call_function(string function_name, Series<Element> arguments, EvaluationContext context) throws ExpressionError {
             if (function_name == "format") {
-                return call_format(arguments);
+                return call_format(arguments, context);
+            }
+            
+            if (function_name == "stringify") {
+                return call_stringify(arguments);
             }
             
             throw new ExpressionError.FUNCTION_NOT_FOUND(
@@ -60,120 +70,98 @@ namespace Invercargill.Expressions {
         /**
          * Calls the format function.
          * 
-         * Signature: format(template: string, ...args: any) -> string
+         * Signature: format(template: string) -> string
+         * 
+         * Parses the template string for `{{expression}}` blocks, evaluates each
+         * expression using the EvaluationContext, and interpolates the stringified
+         * results.
+         * 
+         * **Expression Delimiters:**
+         * - `{{` and `}}` delimit expressions to be evaluated
+         * - Expressions are trimmed of surrounding whitespace
+         * - Empty expressions `{{}}` produce empty strings
          * 
-         * Applies printf-style formatting to the template string using
-         * the provided arguments. Supports:
-         * - %s - String
-         * - %i, %d - Integer
-         * - %f - Float/double
-         * - %x - Hexadecimal (lowercase)
-         * - %X - Hexadecimal (uppercase)
-         * - %o - Octal
-         * - %% - Literal percent sign
+         * **Escape Sequences:**
+         * - `\{{` produces literal `{{`
+         * - `\}}` produces literal `}}`
+         * - `\\` produces literal `\`
          * 
-         * Format specifiers can include width and precision, e.g.:
-         * - %10s - Right-aligned string in 10 characters
-         * - %-10s - Left-aligned string in 10 characters
-         * - %.2f - Float with 2 decimal places
-         * - %05i - Integer padded with zeros to 5 digits
+         * **Error Handling:**
+         * - Unclosed `{{` without matching `}}` throws INVALID_SYNTAX
+         * - Invalid expressions throw INVALID_SYNTAX with details
+         * - Missing variables propagate NON_EXISTANT_PROPERTY
+         * 
+         * @param arguments The function arguments (first must be template string)
+         * @param context The evaluation context for variable resolution
+         * @return The formatted string as a NativeElement<string>
+         * @throws ExpressionError for syntax errors or evaluation errors
          */
-        private Element call_format(Series<Element> arguments) throws ExpressionError {
+        private Element call_format(Series<Element> arguments, EvaluationContext context) throws ExpressionError {
             var args = arguments.to_array();
             
             if (args.length == 0) {
                 throw new ExpressionError.INVALID_ARGUMENTS(
-                    "format() requires at least 1 argument (the format template)"
+                    "format() requires a template string argument"
                 );
             }
 
             // Get the format template
             string? template;
-            if (!args[0].try_get_as<string?>(out template)) {
+            if (!args[0].try_get_as<string>(out template)) {
                 throw new ExpressionError.TYPE_MISMATCH(
-                    "format() first argument must be a string template"
+                    "format() argument must be a string template"
                 );
             }
 
             if (template == null) {
-                return new NativeElement<string?>((string?)null);
+                return new NullElement();
             }
 
-            // Build the formatted string by processing format specifiers
-            // (even with no args, we need to handle %% escape sequences)
+            // Process template for handlebars-style interpolation
             var result = new StringBuilder();
-            int arg_index = 1;
             int i = 0;
             
             while (i < template.length) {
-                if (template[i] == '%' && i + 1 < template.length) {
-                    // Parse format specifier
-                    int start = i;
-                    i++; // Skip '%'
-                    
-                    // Handle literal %
-                    if (template[i] == '%') {
-                        result.append_c('%');
-                        i++;
+                // Check for escape sequences first
+                if (i + 1 < template.length && template[i] == '\\') {
+                    char next = template[i + 1];
+                    if (next == '{' || next == '}' || next == '\\') {
+                        result.append_c(next);
+                        i += 2;
                         continue;
                     }
+                }
+                
+                // Look for opening delimiter {{
+                if (i + 1 < template.length && template[i] == '{' && template[i + 1] == '{') {
+                    // Find closing delimiter }}
+                    int start = i + 2;
+                    int end = find_closing_delimiter(template, start);
                     
-                    // Parse flags, width, precision
-                    var spec = new StringBuilder();
-                    spec.append_c('%');
-                    
-                    // Flags: -, +, space, #, 0
-                    while (i < template.length && (template[i] == '-' || template[i] == '+' || 
-                           template[i] == ' ' || template[i] == '#' || template[i] == '0')) {
-                        spec.append_c(template[i]);
-                        i++;
-                    }
-                    
-                    // Width
-                    while (i < template.length && template[i].isdigit()) {
-                        spec.append_c(template[i]);
-                        i++;
+                    if (end == -1) {
+                        throw new ExpressionError.INVALID_SYNTAX(
+                            @"Unclosed expression at position $i: missing }}"
+                        );
                     }
                     
-                    // Precision
-                    if (i < template.length && template[i] == '.') {
-                        spec.append_c(template[i]);
-                        i++;
-                        while (i < template.length && template[i].isdigit()) {
-                            spec.append_c(template[i]);
-                            i++;
-                        }
-                    }
+                    // Extract and process expression
+                    string expr_text = template.substring(start, end - start).strip();
                     
-                    // Length modifier (l, ll, h, etc.) - skip for now
-                    while (i < template.length && (template[i] == 'l' || template[i] == 'h' || 
-                           template[i] == 'L' || template[i] == 'z' || template[i] == 'j')) {
-                        spec.append_c(template[i]);
-                        i++;
-                    }
-                    
-                    // Conversion specifier
-                    if (i < template.length) {
-                        char conv = template[i];
-                        spec.append_c(conv);
-                        i++;
-                        
-                        // Get the argument
-                        if (arg_index >= args.length) {
-                            throw new ExpressionError.INVALID_ARGUMENTS(
-                                @"format() has more format specifiers than arguments"
+                    if (expr_text.length > 0) {
+                        // Parse and evaluate the expression
+                        try {
+                            var expression = ExpressionParser.parse(expr_text);
+                            var value = expression.evaluate(context);
+                            result.append(value.stringify());
+                        } catch (ExpressionError e) {
+                            throw new ExpressionError.INVALID_SYNTAX(
+                                @"Error in expression '$expr_text': $(e.message)"
                             );
                         }
-                        
-                        var arg = args[arg_index];
-                        arg_index++;
-                        
-                        // Format based on conversion specifier
-                        string? formatted = format_arg(spec.str, conv, arg);
-                        if (formatted != null) {
-                            result.append(formatted);
-                        }
                     }
+                    // Empty expressions produce empty string (nothing to append)
+                    
+                    i = end + 2; // Move past }}
                 } else {
                     result.append_c(template[i]);
                     i++;
@@ -184,132 +172,55 @@ namespace Invercargill.Expressions {
         }
 
         /**
-         * Formats a single argument using the format specifier.
+         * Finds the closing }} delimiter in a template string.
+         * 
+         * Searches for the first occurrence of }} that is not part of a nested {{.
+         * Note: Nested expressions like {{a {{b}} c}} are not supported and will
+         * result in finding the first }}, which will likely cause a parse error.
+         * 
+         * @param template The template string to search
+         * @param start The starting position (after the opening {{)
+         * @return The position of the closing }}, or -1 if not found
          */
-        private string? format_arg(string spec, char conv, Element arg) throws ExpressionError {
-            switch (conv) {
-                case 's': // String
-                    string? str_val;
-                    if (arg.try_get_as<string?>(out str_val)) {
-                        return spec.printf(str_val ?? "(null)");
-                    }
-                    // Convert to string representation
-                    return spec.printf(arg.to_string());
-                    
-                case 'd':
-                case 'i': // Integer
-                    // Try int64 first (common in expression system)
-                    int64? int64_val;
-                    if (arg.try_get_as<int64?>(out int64_val) && int64_val != null) {
-                        return "%lli".printf((int64)int64_val);
-                    }
-                    int? int_val;
-                    if (arg.try_get_as<int?>(out int_val) && int_val != null) {
-                        return "%i".printf((int)int_val);
-                    }
-                    long? long_val;
-                    if (arg.try_get_as<long?>(out long_val) && long_val != null) {
-                        return "%li".printf((long)long_val);
-                    }
-                    // Try to convert double to int
-                    double? dbl_val;
-                    if (arg.try_get_as<double?>(out dbl_val) && dbl_val != null) {
-                        return "%i".printf((int)(double)dbl_val);
-                    }
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"format() specifier '%%i' requires an integer, got $(arg.type_name())"
-                    );
-                    
-                case 'f':
-                case 'e':
-                case 'E':
-                case 'g':
-                case 'G': // Float/double
-                    double? dbl_val;
-                    if (arg.try_get_as<double?>(out dbl_val) && dbl_val != null) {
-                        return spec.printf((double)dbl_val);
-                    }
-                    // Try integer as double
-                    int? int_val;
-                    if (arg.try_get_as<int?>(out int_val) && int_val != null) {
-                        return spec.printf((double)int_val);
-                    }
-                    int64? int64_val;
-                    if (arg.try_get_as<int64?>(out int64_val) && int64_val != null) {
-                        return spec.printf((double)int64_val);
-                    }
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"format() specifier '%%f' requires a number, got $(arg.type_name())"
-                    );
-                    
-                case 'x':
-                case 'X': // Hexadecimal
-                    int64? hex_int64;
-                    if (arg.try_get_as<int64?>(out hex_int64) && hex_int64 != null) {
-                        if (conv == 'x') {
-                            return "%llx".printf((int64)hex_int64);
-                        } else {
-                            return "%llX".printf((int64)hex_int64);
-                        }
-                    }
-                    int? hex_int;
-                    if (arg.try_get_as<int?>(out hex_int) && hex_int != null) {
-                        if (conv == 'x') {
-                            return "%x".printf((int)hex_int);
-                        } else {
-                            return "%X".printf((int)hex_int);
-                        }
-                    }
-                    long? hex_long;
-                    if (arg.try_get_as<long?>(out hex_long) && hex_long != null) {
-                        if (conv == 'x') {
-                            return "%lx".printf((long)hex_long);
-                        } else {
-                            return "%lX".printf((long)hex_long);
-                        }
-                    }
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"format() specifier '%%x' requires an integer, got $(arg.type_name())"
-                    );
-                    
-                case 'o': // Octal
-                    int64? oct_int64;
-                    if (arg.try_get_as<int64?>(out oct_int64) && oct_int64 != null) {
-                        return "%llo".printf((int64)oct_int64);
-                    }
-                    int? oct_int;
-                    if (arg.try_get_as<int?>(out oct_int) && oct_int != null) {
-                        return "%o".printf((int)oct_int);
-                    }
-                    long? oct_long;
-                    if (arg.try_get_as<long?>(out oct_long) && oct_long != null) {
-                        return "%lo".printf((long)oct_long);
-                    }
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        @"format() specifier '%%o' requires an integer, got $(arg.type_name())"
-                    );
-                    
-                case 'c': // Character
-                    int char_int;
-                    if (arg.try_get_as<int>(out char_int)) {
-                        return spec.printf(char_int);
-                    }
-                    string? char_str;
-                    if (arg.try_get_as<string?>(out char_str) && char_str != null && char_str.length > 0) {
-                        return spec.printf(char_str[0]);
-                    }
-                    throw new ExpressionError.TYPE_MISMATCH(
-                        "format() specifier '%%c' requires an integer or single-character string"
-                    );
-                    
-                case 'p': // Pointer (not really useful, but supported)
-                    return spec.printf((void*)null);
-                    
-                default:
-                    throw new ExpressionError.INVALID_ARGUMENTS(
-                        @"format() unsupported format specifier '%%$conv'"
-                    );
+        private int find_closing_delimiter(string template, int start) {
+            int i = start;
+            
+            while (i < template.length - 1) {
+                if (template[i] == '}' && template[i + 1] == '}') {
+                    return i;
+                }
+                i++;
             }
+            
+            return -1; // Not found
+        }
+
+        /**
+         * Calls the stringify function.
+         * 
+         * Signature: stringify(...args) -> string
+         * 
+         * Accepts any number of arguments, stringifies each using Element.stringify(),
+         * and concatenates all stringified results together.
+         * 
+         * Examples:
+         * - `stringify(42)` → "42"
+         * - `stringify("Hello", " ", "World")` → "Hello World"
+         * - `stringify(1, " + ", 2, " = ", 3)` → "1 + 2 = 3"
+         * - `stringify(null)` → "" (empty string)
+         * - `stringify(true, " or ", false)` → "true or false"
+         * 
+         * @param arguments The elements to stringify and concatenate
+         * @return The concatenated string as a NativeElement<string>
+         */
+        private Element call_stringify(Series<Element> arguments) {
+            var result = new StringBuilder();
+            
+            foreach (var arg in arguments) {
+                result.append(arg.stringify());
+            }
+            
+            return new NativeElement<string>(result.str);
         }
 
     }

+ 14 - 14
src/lib/Expressions/IterateFunctionAccessor.vala

@@ -125,7 +125,7 @@ namespace Invercargill.Expressions {
          * Signature: single<T>(value: T) -> Enumerable<T>
          * 
          * Note: Since we don't have full type information at runtime,
-         * this creates an Enumerable<Object?> and wraps the value.
+         * this creates an Enumerable<Object> and wraps the value.
          */
         private Element call_single(Element[] args) throws ExpressionError {
             if (args.length != 1) {
@@ -138,8 +138,8 @@ namespace Invercargill.Expressions {
 
             if (arg.is_null()) {
                 // Null value - create Object? enumerable
-                var single = Iterate.single<Object?>(null);
-                return new NativeElement<Enumerable<Object?>>(single);
+                var single = Iterate.single<Object>(null);
+                return new NativeElement<Enumerable<Object>>(single);
             }
 
             // Determine the type and create appropriate enumerable
@@ -162,16 +162,16 @@ namespace Invercargill.Expressions {
             } else if (arg.try_get_as<bool>(out bool_val)) {
                 var single = Iterate.single<bool>(bool_val);
                 return new NativeElement<Enumerable<bool>>(single);
-            } else if (arg.try_get_as<string?>(out string_val)) {
-                var single = Iterate.single<string?>(string_val);
-                return new NativeElement<Enumerable<string?>>(single);
-            } else if (arg.try_get_as<Object?>(out object_val)) {
-                var single = Iterate.single<Object?>(object_val);
-                return new NativeElement<Enumerable<Object?>>(single);
+            } else if (arg.try_get_as<string>(out string_val)) {
+                var single = Iterate.single<string>(string_val);
+                return new NativeElement<Enumerable<string>>(single);
+            } else if (arg.try_get_as<Object>(out object_val)) {
+                var single = Iterate.single<Object>(object_val);
+                return new NativeElement<Enumerable<Object>>(single);
             } else {
                 // Unknown type - wrap as null object
-                var single = Iterate.single<Object?>(null);
-                return new NativeElement<Enumerable<Object?>>(single);
+                var single = Iterate.single<Object>(null);
+                return new NativeElement<Enumerable<Object>>(single);
             }
         }
 
@@ -180,7 +180,7 @@ namespace Invercargill.Expressions {
          * 
          * Signature: nothing<T>() -> Enumerable<T>
          * 
-         * Note: Returns an empty Enumerable<Object?> since we don't have
+         * Note: Returns an empty Enumerable<Object> since we don't have
          * type parameters in expressions.
          */
         private Element call_nothing(Element[] args) throws ExpressionError {
@@ -190,8 +190,8 @@ namespace Invercargill.Expressions {
                 );
             }
 
-            var nothing = Iterate.nothing<Object?>();
-            return new NativeElement<Enumerable<Object?>>(nothing);
+            var nothing = Iterate.nothing<Object>();
+            return new NativeElement<Enumerable<Object>>(nothing);
         }
 
         /**

+ 101 - 0
src/lib/Expressions/LotPropertyAccessor.vala

@@ -0,0 +1,101 @@
+using Invercargill.DataStructures;
+
+namespace Invercargill.Expressions {
+
+    /**
+     * Property accessor for types implementing the Lot<T> interface.
+     * 
+     * Provides access to Lot properties:
+     * - `length`: The number of items in the Lot (uint)
+     * 
+     * Example usage in expressions:
+     * {{{
+     * items.length          // Returns the number of items in the Lot
+     * }}}
+     */
+    public class LotPropertyAccessor : Object, PropertyAccessor {
+
+        /**
+         * The Lot value being accessed.
+         */
+        private Lot<Object>? _lot;
+
+        /**
+         * Creates a new LotPropertyAccessor.
+         * 
+         * @param lot The Lot value to access properties on
+         */
+        public LotPropertyAccessor(Lot<Object>? lot) {
+            _lot = lot;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public bool has_property(string property) {
+            return property == "length";
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Enumerable<string> get_property_names() {
+            return new Wrappers.Array<string>(new string[]{"length"});
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Element read_property(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return new NativeElement<uint>(_lot != null ? _lot.length : 0);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown Lot property: $property"
+                    );
+            }
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public Type get_property_type(string property) throws ExpressionError {
+            switch (property) {
+                case "length":
+                    return typeof(uint);
+                
+                default:
+                    throw new ExpressionError.NON_EXISTANT_PROPERTY(
+                        @"Unknown Lot property: $property"
+                    );
+            }
+        }
+
+    }
+
+    /**
+     * Factory for creating LotPropertyAccessor instances.
+     * 
+     * This factory is registered with the TypeAccessorRegistry to provide
+     * property access for Lot<T> values.
+     */
+    public class LotPropertyAccessorFactory : Object, PropertyAccessorFactory {
+
+        /**
+         * {@inheritDoc}
+         */
+        public PropertyAccessor? create(Element element) {
+            // Check if the element's value implements Lot
+            // We need to check for Lot<Object> since we can't check for Lot<T> generically
+            Lot<Object>? lot;
+            if (element.try_get_as(out lot)) {
+                return new LotPropertyAccessor(lot);
+            }
+            return null;
+        }
+
+    }
+
+}

+ 31 - 31
src/lib/Expressions/ObjectPropertyAccessor.vala

@@ -30,37 +30,37 @@ namespace Invercargill.Expressions {
             var value = Value(prop_type);
             target.get_property (gobject_property, ref value);
             
-            // Handle common types with NativeElement for better type conversion
-            if (prop_type == typeof(string)) {
-                return new NativeElement<string?>(value.get_string());
-            }
-            if (prop_type == typeof(int)) {
-                return new NativeElement<int?>(value.get_int());
-            }
-            if (prop_type == typeof(int64)) {
-                return new NativeElement<int64?>(value.get_int64());
-            }
-            if (prop_type == typeof(long)) {
-                return new NativeElement<long?>(value.get_long());
-            }
-            if (prop_type == typeof(double)) {
-                return new NativeElement<double?>(value.get_double());
-            }
-            if (prop_type == typeof(float)) {
-                return new NativeElement<float?>(value.get_float());
-            }
-            if (prop_type == typeof(bool)) {
-                return new NativeElement<bool?>(value.get_boolean());
-            }
-            // Check if the type implements Enumerable interface
-            // This handles Enumerable<T> properties properly
-            if (prop_type.is_a(typeof(Enumerable<Object?>))) {
-                // Return as ValueElement which can hold the enumerable
-                return new ValueElement(value);
-            }
-            if (prop_type.is_a(typeof(Object))) {
-                return new NativeElement<Object?>(value.get_object());
-            }
+            //  // Handle common types with NativeElement for better type conversion
+            //  if (prop_type == typeof(string)) {
+            //      return new NativeElement<string?>(value.get_string());
+            //  }
+            //  if (prop_type == typeof(int)) {
+            //      return new NativeElement<int?>(value.get_int());
+            //  }
+            //  if (prop_type == typeof(int64)) {
+            //      return new NativeElement<int64?>(value.get_int64());
+            //  }
+            //  if (prop_type == typeof(long)) {
+            //      return new NativeElement<long?>(value.get_long());
+            //  }
+            //  if (prop_type == typeof(double)) {
+            //      return new NativeElement<double?>(value.get_double());
+            //  }
+            //  if (prop_type == typeof(float)) {
+            //      return new NativeElement<float?>(value.get_float());
+            //  }
+            //  if (prop_type == typeof(bool)) {
+            //      return new NativeElement<bool?>(value.get_boolean());
+            //  }
+            //  // Check if the type implements Enumerable interface
+            //  // This handles Enumerable<T> properties properly
+            //  if (prop_type.is_a(typeof(Enumerable<Object?>))) {
+            //      // Return as ValueElement which can hold the enumerable
+            //      return new ValueElement(value);
+            //  }
+            //  if (prop_type.is_a(typeof(Object))) {
+            //      return new NativeElement<Object?>(value.get_object());
+            //  }
             
             // Default to ValueElement for other types
             return new ValueElement(value);

+ 24 - 24
src/lib/Expressions/StringFunctionAccessor.vala

@@ -158,7 +158,7 @@ namespace Invercargill.Expressions {
                 
                 default:
                     // Most string functions return null when called on null
-                    return new NativeElement<string?>(null);
+                    return new NullElement();
             }
         }
 
@@ -167,7 +167,7 @@ namespace Invercargill.Expressions {
          */
         private Element call_chug(Series<Element> arguments) throws ExpressionError {
             expect_argument_count("chug", arguments, 0);
-            return new NativeElement<string?>(_value.chomp().chug());
+            return new NativeElement<string>(_value.chomp().chug());
         }
 
         /**
@@ -175,7 +175,7 @@ namespace Invercargill.Expressions {
          */
         private Element call_chomp(Series<Element> arguments) throws ExpressionError {
             expect_argument_count("chomp", arguments, 0);
-            return new NativeElement<string?>(_value.chomp());
+            return new NativeElement<string>(_value.chomp());
         }
 
         /**
@@ -183,7 +183,7 @@ namespace Invercargill.Expressions {
          */
         private Element call_upper(Series<Element> arguments) throws ExpressionError {
             expect_argument_count("upper", arguments, 0);
-            return new NativeElement<string?>(_value.up());
+            return new NativeElement<string>(_value.up());
         }
 
         /**
@@ -191,7 +191,7 @@ namespace Invercargill.Expressions {
          */
         private Element call_lower(Series<Element> arguments) throws ExpressionError {
             expect_argument_count("lower", arguments, 0);
-            return new NativeElement<string?>(_value.down());
+            return new NativeElement<string>(_value.down());
         }
 
         /**
@@ -199,7 +199,7 @@ namespace Invercargill.Expressions {
          */
         private Element call_reverse(Series<Element> arguments) throws ExpressionError {
             expect_argument_count("reverse", arguments, 0);
-            return new NativeElement<string?>(_value.reverse());
+            return new NativeElement<string>(_value.reverse());
         }
 
         /**
@@ -215,9 +215,9 @@ namespace Invercargill.Expressions {
                     start = _value.length + start;
                 }
                 if (start < 0 || start > _value.length) {
-                    return new NativeElement<string?>("");
+                    return new NativeElement<string>("");
                 }
-                return new NativeElement<string?>(_value.substring(start));
+                return new NativeElement<string>(_value.substring(start));
             } else if (args.length == 2) {
                 // substring(start, length)
                 int start = get_int_argument(args[0], "substring start");
@@ -226,9 +226,9 @@ namespace Invercargill.Expressions {
                     start = _value.length + start;
                 }
                 if (start < 0 || start > _value.length) {
-                    return new NativeElement<string?>("");
+                    return new NativeElement<string>("");
                 }
-                return new NativeElement<string?>(_value.substring(start, len));
+                return new NativeElement<string>(_value.substring(start, len));
             } else {
                 throw new ExpressionError.INVALID_ARGUMENTS(
                     @"substring expects 1 or 2 arguments, got $(args.length)"
@@ -273,12 +273,12 @@ namespace Invercargill.Expressions {
             var args = to_array(arguments);
             string? substring;
             if (!args[0].try_get_as(out substring)) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
             if (substring == null) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
-            return new NativeElement<bool?>(_value.contains(substring));
+            return new NativeElement<bool>(_value.contains(substring));
         }
 
         /**
@@ -289,12 +289,12 @@ namespace Invercargill.Expressions {
             var args = to_array(arguments);
             string? prefix;
             if (!args[0].try_get_as(out prefix)) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
             if (prefix == null) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
-            return new NativeElement<bool?>(_value.has_prefix(prefix));
+            return new NativeElement<bool>(_value.has_prefix(prefix));
         }
 
         /**
@@ -305,12 +305,12 @@ namespace Invercargill.Expressions {
             var args = to_array(arguments);
             string? suffix;
             if (!args[0].try_get_as(out suffix)) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
             if (suffix == null) {
-                return new NativeElement<bool?>(false);
+                return new NativeElement<bool>(false);
             }
-            return new NativeElement<bool?>(_value.has_suffix(suffix));
+            return new NativeElement<bool>(_value.has_suffix(suffix));
         }
 
         /**
@@ -322,15 +322,15 @@ namespace Invercargill.Expressions {
             string? old_str;
             string? new_str;
             if (!args[0].try_get_as(out old_str)) {
-                return new NativeElement<string?>(_value);
+                return new NativeElement<string>(_value);
             }
             if (!args[1].try_get_as(out new_str)) {
                 new_str = "";
             }
             if (old_str == null) {
-                return new NativeElement<string?>(_value);
+                return new NativeElement<string>(_value);
             }
-            return new NativeElement<string?>(_value.replace(old_str, new_str ?? ""));
+            return new NativeElement<string>(_value.replace(old_str, new_str ?? ""));
         }
 
         /**
@@ -370,10 +370,10 @@ namespace Invercargill.Expressions {
             }
             
             if (index < 0 || index >= _value.length) {
-                return new NativeElement<string?>(null);
+                return new NullElement();
             }
             
-            return new NativeElement<string?>(_value.substring(index, 1));
+            return new NativeElement<string>(_value.substring(index, 1));
         }
 
         /**

+ 4 - 4
src/lib/Expressions/StringPropertyAccessor.vala

@@ -59,16 +59,16 @@ namespace Invercargill.Expressions {
         public Element read_property(string property) throws ExpressionError {
             switch (property) {
                 case "length":
-                    return new NativeElement<int?>(_value == null ? 0 : _value.length);
+                    return new NativeElement<int>(_value == null ? 0 : _value.length);
                 
                 case "empty":
-                    return new NativeElement<bool?>(_value != null && _value.length == 0);
+                    return new NativeElement<bool>(_value != null && _value.length == 0);
                 
                 case "is_null":
-                    return new NativeElement<bool?>(_value == null);
+                    return new NativeElement<bool>(_value == null);
                 
                 case "is_empty_or_null":
-                    return new NativeElement<bool?>(_value == null || _value.length == 0);
+                    return new NativeElement<bool>(_value == null || _value.length == 0);
                 
                 default:
                     throw new ExpressionError.NON_EXISTANT_PROPERTY(

+ 2 - 0
src/lib/Expressions/TypeAccessorRegistry.vala

@@ -195,10 +195,12 @@ namespace Invercargill.Expressions {
          * This is called automatically when the singleton is created.
          * Currently registers:
          * - StringPropertyAccessorFactory for string properties
+         * - LotPropertyAccessorFactory for Lot<T> properties
          * - StringFunctionAccessorFactory for string functions
          */
         private void register_defaults() {
             register_property_accessor(new StringPropertyAccessorFactory());
+            register_property_accessor(new LotPropertyAccessorFactory());
             register_function_accessor(new StringFunctionAccessorFactory());
         }
 

+ 4 - 4
src/lib/Interfaces.vala

@@ -1,19 +1,19 @@
 namespace Invercargill {
 
 
-    public interface Equatable<T> {
+    public interface Equatable<T> : Object {
         public abstract bool equals(T other);
     }
 
-    public interface Hashable {
+    public interface Hashable : Object {
         public abstract uint hash_code();
     }
 
-    public interface Comparable<T> {
+    public interface Comparable<T> : Object {
         public abstract int compare(T other);
     }
 
-    public interface Stringifyable {
+    public interface Stringifyable : Object {
         public abstract string to_string();
     }
 

+ 6 - 6
src/lib/Interfaces/AddressableBytes.vala

@@ -71,7 +71,7 @@ namespace Invercargill {
         }
 
 
-        public virtual void set_all_int32(uint index, Enumerable<int32?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int32(uint index, Enumerable<int32> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int32(index, value, endianness);
             }
@@ -95,7 +95,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint32(uint index, Enumerable<uint32?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint32(uint index, Enumerable<uint32> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint32(index, value, endianness);
             }
@@ -119,7 +119,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_int16(uint index, Enumerable<int16?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int16(uint index, Enumerable<int16> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int16(index, value, endianness);
             }
@@ -143,7 +143,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint16(uint index, Enumerable<uint16?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint16(uint index, Enumerable<uint16> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint16(index, value, endianness);
             }
@@ -155,7 +155,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_int8(uint index, Enumerable<int8?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_int8(uint index, Enumerable<int8> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_int8(index, value, endianness);
             }
@@ -166,7 +166,7 @@ namespace Invercargill {
             set_data(index, chunk);
         }
 
-        public virtual void set_all_uint8(uint index, Enumerable<uint8?> values, Endianness endianness) throws IndexError {
+        public virtual void set_all_uint8(uint index, Enumerable<uint8> values, Endianness endianness) throws IndexError {
             foreach(var value in values) {
                 set_uint8(index, value, endianness);
             }

+ 426 - 0
src/lib/Nullable.c

@@ -0,0 +1,426 @@
+/* Nullable.c - Production-ready nullable type detection and manipulation for Vala
+ * 
+ * This file provides functions to:
+ * - Detect if a generic type T is nullable (boxed) at runtime
+ * - Check if a nullable value is null
+ * - Get the raw value from a nullable (with default for null)
+ * - Create a boxed copy of a value
+ * 
+ */
+
+#include <glib-object.h>
+#include <string.h>
+#include <stdio.h>
+
+/* ============================================================================
+ * Internal Helpers
+ * ============================================================================ */
+
+static gboolean
+_nullable_type_is_boxable (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT:
+        case G_TYPE_UINT:
+        case G_TYPE_LONG:
+        case G_TYPE_ULONG:
+        case G_TYPE_INT64:
+        case G_TYPE_UINT64:
+        case G_TYPE_FLOAT:
+        case G_TYPE_DOUBLE:
+        case G_TYPE_BOOLEAN:
+        case G_TYPE_CHAR:
+        case G_TYPE_UCHAR:
+        case G_TYPE_ENUM:
+        case G_TYPE_FLAGS:
+            return TRUE;
+        default:
+            return FALSE;
+    }
+}
+
+/* Check if a type is always boxed in Vala generics (int64, uint64, float, double)
+ * These types are ALWAYS boxed regardless of nullability, so no boxing/unboxing
+ * transformation is needed for safe_cast. */
+static gboolean
+_nullable_type_is_always_boxed (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT64:
+        case G_TYPE_UINT64:
+        case G_TYPE_FLOAT:
+        case G_TYPE_DOUBLE:
+            return TRUE;
+        default:
+            return FALSE;
+    }
+}
+
+/* Get the size of a boxable type for memcpy/memset operations.
+ * Note: int64, uint64, float, double are excluded as they are always boxed. */
+static gsize
+_nullable_type_size (GType t_type)
+{
+    switch (t_type) {
+        case G_TYPE_INT:
+        case G_TYPE_UINT:
+            return sizeof (gint);
+        case G_TYPE_LONG:
+        case G_TYPE_ULONG:
+            return sizeof (glong);
+        case G_TYPE_BOOLEAN:
+            return sizeof (gboolean);
+        case G_TYPE_CHAR:
+        case G_TYPE_UCHAR:
+            return sizeof (gchar);
+        case G_TYPE_ENUM:
+            return sizeof (gint);
+        case G_TYPE_FLAGS:
+            return sizeof (guint);
+        /* int64, uint64, float, double excluded - always boxed */
+        default:
+            return sizeof (gpointer);
+    }
+}
+
+/* ============================================================================
+ * Type Detection Functions
+ * ============================================================================ */
+
+gboolean
+is_boxed_nullable_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                        gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func == NULL) {
+        return FALSE;
+    }
+    
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return FALSE;
+    }
+    
+    return _nullable_type_is_boxable (t_type);
+}
+
+gboolean
+is_nullable_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                  gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    /* For reference types (non-fundamental like Object), always nullable */
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return TRUE;
+    }
+    
+    /* Strings are always nullable reference types */
+    if (t_type == G_TYPE_STRING) {
+        return TRUE;
+    }
+    
+    /* For boxable value types (int, bool, etc.):
+     * - If destroy_func is set, it's a nullable type (int?, bool?, etc.)
+     * - If destroy_func is NULL, it's a non-nullable type (int, bool, etc.)
+     */
+    if (_nullable_type_is_boxable (t_type)) {
+        return (destroy_func != NULL);
+    }
+    
+    /* For pointer types without a destroy func, not nullable */
+    if (t_type == G_TYPE_POINTER) {
+        return FALSE;
+    }
+    
+    /* For other types with destroy_func, consider them nullable reference types */
+    return (destroy_func != NULL);
+}
+
+gboolean
+is_reference_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                   gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (!G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        return TRUE;
+    }
+    
+    if (t_type == G_TYPE_STRING) {
+        return TRUE;
+    }
+    
+    if (_nullable_type_is_boxable (t_type)) {
+        return FALSE;
+    }
+    
+    if (t_type == G_TYPE_POINTER) {
+        return FALSE;
+    }
+    
+    return (destroy_func != NULL);
+}
+
+/* ============================================================================
+ * Null Check Functions
+ * ============================================================================ */
+
+gboolean
+is_null_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+              gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func == NULL && G_TYPE_IS_FUNDAMENTAL (t_type) && 
+        _nullable_type_is_boxable (t_type)) {
+        return FALSE;
+    }
+    
+    return (value == NULL);
+}
+
+/* ============================================================================
+ * Value Access Functions
+ * 
+ * IMPORTANT: For nullable types, these functions return the value directly.
+ * Vala will handle ownership appropriately based on the function signature.
+ * ============================================================================ */
+
+/**
+ * unbox_or_default_impl - Get value or default for null
+ * 
+ * For nullable types, if value is not null, returns value (ownership transferred).
+ * If value is null, returns a copy of default_value.
+ * 
+ * The caller owns the returned value.
+ */
+gpointer
+unbox_or_default_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                       gconstpointer value, gconstpointer default_value)
+{
+    GBoxedCopyFunc dup_func = (GBoxedCopyFunc) t_dup_func;
+    
+    /* If value is not null, return it (transfer ownership) */
+    if (value != NULL) {
+        return (gpointer) value;
+    }
+    
+    /* If default is null, return null */
+    if (default_value == NULL) {
+        return NULL;
+    }
+    
+    /* Return a copy of the default value if we have a dup function */
+    if (dup_func != NULL) {
+        return dup_func ((gpointer) default_value);
+    }
+    
+    /* No dup function - return the default as-is (for non-nullable types) */
+    return (gpointer) default_value;
+}
+
+/**
+ * box_copy_impl - Create a boxed copy of a value
+ */
+gpointer
+box_copy_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+               gconstpointer value)
+{
+    GBoxedCopyFunc dup_func = (GBoxedCopyFunc) t_dup_func;
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (value == NULL) {
+        return NULL;
+    }
+    
+    /* If there's a dup function, use it */
+    if (dup_func != NULL) {
+        return dup_func ((gpointer) value);
+    }
+    
+    /* For nullable value types (destroy_func set but no dup_func),
+     * we need to allocate and copy the value manually */
+    if (destroy_func != NULL &&
+        G_TYPE_IS_FUNDAMENTAL (t_type) &&
+        _nullable_type_is_boxable (t_type)) {
+        gsize size = _nullable_type_size (t_type);
+        gpointer boxed = g_malloc (size);
+        memcpy (boxed, value, size);
+        return boxed;
+    }
+    
+    /* For non-nullable types or reference types without dup_func,
+     * return as-is (the value IS the pointer for non-nullable value types) */
+    return (gpointer) value;
+}
+
+/**
+ * free_boxed_impl - Free a boxed value
+ */
+void
+free_boxed_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                 gpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    
+    if (destroy_func != NULL && value != NULL) {
+        destroy_func (value);
+    }
+}
+
+/* ============================================================================
+ * Type Information Helpers
+ * ============================================================================ */
+
+static __thread char _nullable_type_name_buffer[256];
+
+const char*
+get_type_name_impl (GType t_type, gpointer t_dup_func, gpointer t_destroy_func,
+                    gconstpointer value)
+{
+    GDestroyNotify destroy_func = (GDestroyNotify) t_destroy_func;
+    const char* base_name = g_type_name (t_type);
+    
+    if (base_name == NULL) {
+        base_name = "unknown";
+    }
+    
+    if (destroy_func == NULL) {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s (non-nullable)", base_name);
+    } else if (_nullable_type_is_boxable (t_type) && G_TYPE_IS_FUNDAMENTAL (t_type)) {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s? (nullable)", base_name);
+    } else {
+        snprintf (_nullable_type_name_buffer, sizeof (_nullable_type_name_buffer),
+                  "%s (reference)", base_name);
+    }
+    
+    return _nullable_type_name_buffer;
+}
+
+/* ============================================================================
+ * Convert Nullability Functions
+ *
+ * Safely convert between types with the same GType but different nullability.
+ * For example: int <-> int?, bool <-> bool?, etc.
+ * ============================================================================ */
+
+/**
+ * convert_nullability_impl - Convert between types with same GType but different nullability
+ *
+ * Note: Parameter order is determined by Vala's generic method compilation:
+ * Vala passes all type info for all generic types first, then values.
+ *
+ * @from_type: GType of the source value
+ * @from_dup_func: Dup function for source type (from Vala generic)
+ * @from_destroy_func: Destroy function for source type (from Vala generic)
+ * @to_type: GType of the target type
+ * @to_dup_func: Dup function for target type (from Vala generic)
+ * @to_destroy_func: Destroy function for target type (from Vala generic)
+ * @from_value: The source value to convert
+ * @out_output: Pointer to store the output value
+ *
+ * Returns: TRUE if conversion succeeded, FALSE if source was null and target is non-nullable
+ *
+ * When returning FALSE, the output is set to a zeroed default value.
+ *
+ * Note: int64, uint64, float, double are always boxed in Vala generics,
+ * so they are treated like reference types (pointer pass-through only).
+ */
+gboolean
+convert_nullability_impl (
+    GType from_type, gpointer from_dup_func, gpointer from_destroy_func,
+    GType to_type, gpointer to_dup_func, gpointer to_destroy_func,
+    gconstpointer from_value,
+    gpointer out_output
+)
+{
+    GDestroyNotify from_destroy = (GDestroyNotify) from_destroy_func;
+    GDestroyNotify to_destroy = (GDestroyNotify) to_destroy_func;
+    GBoxedCopyFunc to_dup = (GBoxedCopyFunc) to_dup_func;
+    
+    /* Determine if types are boxable value types */
+    gboolean from_is_boxable = (G_TYPE_IS_FUNDAMENTAL (from_type) &&
+                                 _nullable_type_is_boxable (from_type));
+    gboolean to_is_boxable = (G_TYPE_IS_FUNDAMENTAL (to_type) &&
+                               _nullable_type_is_boxable (to_type));
+    
+    /* Check for always-boxed types (int64, uint64, float, double)
+     * These are ALWAYS boxed in Vala generics, treat like reference types */
+    gboolean from_is_always_boxed = _nullable_type_is_always_boxed (from_type);
+    gboolean to_is_always_boxed = _nullable_type_is_always_boxed (to_type);
+    
+    /* For non-boxable types, always-boxed types, or reference types:
+     * Simple pointer pass-through with optional duplication.
+     * Return TRUE since no conversion is needed - the reference is just passed through. */
+    if (!from_is_boxable || !to_is_boxable ||
+        from_is_always_boxed || to_is_always_boxed) {
+        if (to_dup != NULL && from_value != NULL) {
+            *(gpointer*) out_output = to_dup ((gpointer) from_value);
+        } else {
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* For boxable value types, determine nullability from destroy_func */
+    gboolean from_is_nullable = (from_destroy != NULL);
+    gboolean to_is_nullable = (to_destroy != NULL);
+    
+    /* IMPORTANT: For non-nullable value types, Vala passes the value directly
+     * cast as a pointer (e.g., (gpointer)(gintptr)42), NOT a pointer to the value.
+     * For nullable types, it's a real pointer to heap memory. */
+    
+    /* Case: Both non-nullable - value is embedded in pointer, just copy it */
+    if (!from_is_nullable && !to_is_nullable) {
+        /* The value IS the pointer for non-nullable types */
+        *(gpointer*) out_output = (gpointer) from_value;
+        return TRUE;
+    }
+    
+    /* Case: Both nullable - copy pointer or null */
+    if (from_is_nullable && to_is_nullable) {
+        if (from_value == NULL) {
+            *(gpointer*) out_output = NULL;
+        } else if (to_dup != NULL) {
+            *(gpointer*) out_output = to_dup ((gpointer) from_value);
+        } else {
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* Case: Non-nullable to nullable - box the value (value is in pointer) */
+    if (!from_is_nullable && to_is_nullable) {
+        if (to_dup != NULL) {
+            /* The from_value is the actual value cast to pointer, need to box it */
+            gsize size = _nullable_type_size (from_type);
+            gpointer boxed = g_malloc (size);
+            memcpy (boxed, &from_value, size);
+            *(gpointer*) out_output = boxed;
+        } else {
+            /* Shouldn't happen for nullable types, but handle it */
+            *(gpointer*) out_output = (gpointer) from_value;
+        }
+        return TRUE;
+    }
+    
+    /* Case: Nullable to non-nullable - unbox or default */
+    if (from_is_nullable && !to_is_nullable) {
+        if (from_value == NULL) {
+            /* Set to zeroed default, return false */
+            *(gpointer*) out_output = NULL;
+            return FALSE;
+        } else {
+            /* Unbox: the from_value is a real pointer to heap memory,
+             * copy the value from heap into the output pointer */
+            *(gpointer*) out_output = *(gpointer*) from_value;
+            return TRUE;
+        }
+    }
+    
+    /* Should not reach here */
+    return FALSE;
+}

+ 181 - 0
src/lib/Nullable.vala

@@ -0,0 +1,181 @@
+/* Nullable.vala - Production-ready nullable type detection and manipulation
+ * 
+ * This library provides functions to:
+ * - Detect if a generic type T is nullable (boxed) at runtime
+ * - Check if a nullable value is null
+ * - Get the raw value from a nullable (with default for null)
+ * - Create a boxed copy of a value
+ * 
+ * Usage:
+ *   using Nullable;
+ *   
+ *   int x = 42;
+ *   int? y = null;
+ *   
+ *   is_nullable(x);     // false
+ *   is_nullable(y);     // true
+ *   is_null(y);         // true
+ *   unbox(y, 0);        // 0 (default value since y is null)
+ *   box(x);             // creates a boxed copy
+ */
+
+namespace Invercargill.Nullable {
+    
+    /**
+     * Check if T is a boxed nullable value type (like int?)
+     *
+     * Returns true for nullable value types: int?, bool?, long?, etc.
+     * Returns false for: int, bool, string, Object, etc.
+     *
+     * This checks specifically for boxed value types - types that are
+     * normally stored unboxed but are boxed when nullable.
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = 42;
+     *   is_boxed_nullable(x);  // false
+     *   is_boxed_nullable(y);  // true
+     */
+    [CCode (cname = "is_boxed_nullable_impl")]
+    public extern bool is_boxed_nullable<T> (T value);
+    
+    /**
+     * Check if a value is null or represents a null state
+     *
+     * For reference types: returns true if value is null
+     * For nullable value types (int?, bool?, etc.): returns true if value is null
+     * For non-nullable value types (int, bool, etc.): returns true (cannot hold null, so "is nullable" in type sense)
+     *
+     * This is the inverse of the old is_non_nullable - it tells you if the type
+     * itself represents a nullable/non-nullable state.
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = null;
+     *   string? s = null;
+     *   is_nullable(x);  // true (non-nullable type, but "is nullable" in type sense)
+     *   is_nullable(y);  // true (y is null)
+     *   is_nullable(s);  // true (s is null)
+     */
+    [CCode (cname = "is_nullable_impl")]
+    public extern bool is_nullable<T> (T value);
+    
+    /**
+     * Check if T is a reference type (like string, Object)
+     * 
+     * Returns true for: string, Object, arrays, etc.
+     * Returns false for: int, int?, bool, bool?, etc.
+     */
+    [CCode (cname = "is_reference_impl")]
+    public extern bool is_reference<T> (T value);
+    
+    /**
+     * Check if a nullable value is null
+     * 
+     * For non-nullable value types, always returns false.
+     * For nullable types and reference types, checks if the value is null.
+     * 
+     * Example:
+     *   int? x = null;
+     *   int? y = 42;
+     *   is_null(x);  // true
+     *   is_null(y);  // false
+     */
+    [CCode (cname = "is_null_impl")]
+    public extern bool is_null<T> (T value);
+    
+    /**
+     * Get the value or a default if null (returns unowned reference)
+     * 
+     * Returns the value if not null, otherwise returns the default.
+     * 
+     * IMPORTANT: The returned value is NOT a copy - it's either the original
+     * value or the original default. Do NOT free it.
+     * 
+     * Example:
+     *   int? x = null;
+     *   unbox(x, 0);  // returns 0
+     *   int? y = 42;
+     *   unbox(y, 0);  // returns 42
+     */
+    [CCode (cname = "unbox_or_default_impl")]
+    public extern unowned T unbox<T> (T value, T default_value);
+    
+    /**
+     * Get the value or a default if null (alias for unbox)
+     */
+    [CCode (cname = "unbox_or_default_impl")]
+    public extern unowned T get_value<T> (T value, T default_value);
+    
+    /**
+     * Create a boxed copy of a value
+     * 
+     * For nullable value types, this creates a heap-allocated copy.
+     * For reference types, this creates a copy or increments reference count.
+     * 
+     * The caller owns the returned value and should free it with free_boxed()
+     * or the appropriate free function.
+     * 
+     * Example:
+     *   int x = 42;
+     *   var boxed = box(x);  // creates a boxed int
+     *   free_boxed(boxed);   // free it when done
+     */
+    [CCode (cname = "box_copy_impl")]
+    public extern T box<T> (T value);
+    
+    /**
+     * Free a boxed value created with box()
+     * 
+     * Frees a value created with box(). For reference types, this
+     * decrements the reference count or calls the appropriate free function.
+     */
+    [CCode (cname = "free_boxed_impl")]
+    public extern void free_boxed<T> (T value);
+    
+    /**
+     * Get a human-readable type name with nullability info
+     *
+     * Example:
+     *   int x = 42;
+     *   int? y = 42;
+     *   get_type_name(x);  // "gint (non-nullable)"
+     *   get_type_name(y);  // "gint? (nullable)"
+     */
+    [CCode (cname = "get_type_name_impl")]
+    public extern unowned string get_type_name<T> (T value);
+    
+    /**
+     * Convert between types with same GType but different nullability
+     *
+     * This function handles all combinations of nullable/non-nullable conversions
+     * for value types that share the same GType (e.g., int <-> int?).
+     *
+     * Returns true if conversion succeeded, false if source was null and target
+     * is non-nullable (output is set to default value in this case).
+     *
+     * Behavior matrix:
+     *   int -> int:     Direct copy, always returns true
+     *   int -> int?:    Box the value, always returns true
+     *   int? -> int:    Unbox if not null, returns false if null (output=0)
+     *   int? -> int?:   Copy pointer or null, always returns true
+     *
+     * Note: int64, uint64, float, double are always boxed in Vala generics,
+     * so they behave like reference types (pointer pass-through only).
+     *
+     * Example:
+     *   int? x = 42;
+     *   int y;
+     *   if (convert_nullability(x, out y)) {
+     *     // y is now 42
+     *   }
+     *
+     *   int? z = null;
+     *   int w;
+     *   if (!convert_nullability(z, out w)) {
+     *     // w is now 0 (default), z was null
+     *   }
+     */
+    [CCode (cname = "convert_nullability_impl")]
+    public extern bool convert_nullability<TFrom, TTo> (TFrom input, out TTo output);
+}

+ 150 - 0
src/lib/Safety.vala

@@ -269,4 +269,154 @@ namespace Invercargill {
         return null;
     }
 
+    internal bool safely_unwrap_value<T>(Value value, out T result) {
+        var requested_type = typeof(T);
+
+        if(requested_type.is_a(Type.BOOLEAN)) {
+            result = (T)value.get_boolean();
+            return true;
+        }
+        if(requested_type.is_a(Type.BOXED)) {
+            result = (T)value.get_boxed();
+            return true;
+        }
+        if(requested_type.is_a(Type.CHAR)) {
+            result = (T)value.get_schar();
+            return true;
+        }
+        if(requested_type.is_a(Type.DOUBLE)) {
+            result = (T)((double?)value.get_double());
+            return true;
+        }
+        if(requested_type.is_a(Type.ENUM)) {
+            result = (T)value.get_enum();
+            return true;
+        }
+        if(requested_type.is_a(Type.FLAGS)) {
+            result = (T)value.get_flags();
+            return true;
+        }
+        if(requested_type.is_a(Type.FLOAT)) {
+            result = (T)((float?)value.get_float());
+            return true;
+        }
+        if(requested_type.is_a(Type.INT)) {
+            result = (T)value.get_int();
+            return true;
+        }
+        if(requested_type.is_a(Type.INT64)) {
+            result = (T)value.get_int64();
+            return true;
+        }
+        if(requested_type.is_a(Type.LONG)) {
+            result = (T)value.get_long();
+            return true;
+        }
+        if(requested_type.is_a(Type.OBJECT) || requested_type.is_a(Type.INTERFACE)) {
+            result = (T)value.get_object();
+            return true;
+        }
+        if(requested_type.is_a(Type.PARAM)) {
+            result = (T)value.get_param();
+            return true;
+        }
+        if(requested_type.is_a(Type.POINTER)) {
+            result = (T)value.get_pointer();
+            return true;
+        }
+        if(requested_type.is_a(Type.STRING)) {
+            result = (T)value.get_string();
+            return true;
+        }
+        if(requested_type.is_a(Type.UCHAR)) {
+            result = (T)value.get_uchar();
+            return true;
+        }
+        if(requested_type.is_a(Type.UINT)) {
+            result = (T)value.get_uint();
+            return true;
+        }
+        if(requested_type.is_a(Type.UINT64)) {
+            result = (T)value.get_uint64();
+            return true;
+        }
+        if(requested_type.is_a(Type.ULONG)) {
+            result = (T)value.get_ulong();
+            return true;
+        }
+
+        result = null;
+        return false;
+    }
+
+    internal bool safely_wrap_value<T>(T input, out Value value) {
+        var type = typeof(T);
+        value = Value(type);
+        if(type.is_a(Type.BOOLEAN)) {
+            value.set_boolean((bool)input);
+            return true;
+        }
+		if(type.is_a(Type.BOXED)) {
+            value.set_boxed(input);
+            return true;
+        }
+		if(type.is_a(Type.CHAR)) {
+            value.set_schar((int8)input);
+            return true;
+        }
+		if(type.is_a(Type.DOUBLE)) {
+            value.set_double((double?)input);
+            return true;
+        }
+		if(type.is_a(Type.ENUM)) {
+            value.set_enum((int)input);
+            return true;
+        }
+		if(type.is_a(Type.FLAGS)) {
+            value.set_flags((int)input);
+            return true;
+        }
+		if(type.is_a(Type.FLOAT)) {
+            value.set_float((float?)input);
+            return true;
+        }
+		if(type.is_a(Type.INT)) {
+            value.set_int((int)input);
+            return true;
+        }
+		if(type.is_a(Type.INT64)) {
+            value.set_int64((int64)input);
+            return true;
+        }
+		if(type.is_a(Type.LONG)) {
+            value.set_long((long)input);
+            return true;
+        }
+		if(type.is_a(Type.OBJECT) || type.is_a(Type.INTERFACE)) {
+            value.set_object((Object)input);
+            return true;
+        }
+		if(type.is_a(Type.STRING)) {
+            value.set_string((string)input);
+            return true;
+        }
+		if(type.is_a(Type.UCHAR)) {
+            value.set_uchar((uchar)input);
+            return true;
+        }
+		if(type.is_a(Type.UINT)) {
+            value.set_uint((uint)input);
+            return true;
+        }
+		if(type.is_a(Type.UINT64)) {
+            value.set_uint64((uint64)input);
+            return true;
+        }
+		if(type.is_a(Type.ULONG)) {
+            value.set_ulong((ulong)input);
+            return true;
+        }
+        return false;
+    }
+
 }

+ 8 - 2
src/lib/meson.build

@@ -94,6 +94,10 @@ sources += files('Promotions/BinaryData.vala')
 sources += files('Promotions/Elements.vala')
 sources += files('Promotions/Registration.c')
 
+# Nullable type support (C implementation + Vala bindings)
+sources += files('Nullable.c')
+sources += files('Nullable.vala')
+
 sources += files('Interfaces/Lot.vala')
 sources += files('Interfaces/ImmutableLot.vala')
 sources += files('Interfaces/ReadOnlyCollection.vala')
@@ -159,6 +163,7 @@ sources += files('Expressions/ExpressionParser.vala')
 sources += files('Expressions/AccessorFactory.vala')
 sources += files('Expressions/TypeAccessorRegistry.vala')
 sources += files('Expressions/StringPropertyAccessor.vala')
+sources += files('Expressions/LotPropertyAccessor.vala')
 sources += files('Expressions/StringFunctionAccessor.vala')
 
 sources += files('Expressions/Expressions/LiteralExpression.vala')
@@ -172,7 +177,6 @@ sources += files('Expressions/Expressions/LambdaExpression.vala')
 sources += files('Expressions/Expressions/BracketedExpression.vala')
 sources += files('Expressions/Expressions/LotLiteralExpression.vala')
 sources += files('Expressions/Expressions/GlobalFunctionCallExpression.vala')
-sources += files('Expressions/Expressions/GlobalFunctionCallExpression.vala')
 
 sources += files('Expressions/Elements/LambdaElement.vala')
 sources += files('Expressions/Elements/GlobalFunctionsElement.vala')
@@ -181,7 +185,9 @@ invercargill = shared_library('invercargill-@0@'.format(invercargill_major), sou
     dependencies: dependencies,
     install: true,
     vala_gir: 'invercargill-@0@.gir'.format(invercargill_major),
-    install_dir: [true, true, true, true]
+    install_dir: [true, true, true, true],
+    vala_vapi: 'invercargill-@0@.vapi'.format(invercargill_major),
+    vala_header: 'invercargill-@0@.h'.format(invercargill_major)
 )
 invercargill_dep = declare_dependency(link_with: invercargill, include_directories: include_directories('.'))
 

Fișier diff suprimat deoarece este prea mare
+ 204 - 203
src/tests/Integration/Expressions.vala


+ 993 - 0
src/tests/Integration/NativeElement.vala

@@ -0,0 +1,993 @@
+using Invercargill;
+using Invercargill.Nullable;
+
+// Test helper class for reference type testing
+private class NativeElementTestObject : Object {
+    public int value { get; set; }
+    public NativeElementTestObject(int value) {
+        this.value = value;
+    }
+}
+
+// Test helper class implementing Stringifyable
+private class NativeElementTestStringifyable : Object, Stringifyable {
+    public string data { get; set; }
+    public NativeElementTestStringifyable(string data) {
+        this.data = data;
+    }
+    public string to_string() {
+        return @"Stringifyable[$data]";
+    }
+}
+
+void native_element_tests() {
+
+    // ============================================================================
+    // NativeElement Construction Tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/construction/int_non_nullable", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/int_nullable_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/int_nullable_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_non_nullable", () => {
+        var elem = new NativeElement<bool>(true);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_nullable_with_value", () => {
+        bool? x = false;
+        var elem = new NativeElement<bool?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/bool_nullable_null", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_non_nullable", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);  // string is always nullable
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_nullable_with_value", () => {
+        string? s = "world";
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/string_nullable_null", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_non_nullable", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);  // Objects are always nullable
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_nullable_with_value", () => {
+        NativeElementTestObject? obj = new NativeElementTestObject(99);
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/construction/object_nullable_null", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        assert_true(elem != null);
+        assert_true(elem.is_reference_type == true);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+    });
+
+    // ============================================================================
+    // type() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/type/int_returns_int_type", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.type() == typeof(int));
+    });
+
+    Test.add_func("/invercargill/native_element/type/int_nullable_returns_int_type", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.type() == typeof(int?));
+    });
+
+    Test.add_func("/invercargill/native_element/type/string_returns_string_type", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem.type() == typeof(string));
+    });
+
+    Test.add_func("/invercargill/native_element/type/object_returns_object_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.type() == typeof(NativeElementTestObject));
+    });
+
+    // ============================================================================
+    // type_name() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/type_name/int_type_name", () => {
+        var elem = new NativeElement<int>(42);
+        var name = elem.type_name();
+        assert_true(name != null);
+        assert_true("int" in name || "gint" in name);
+    });
+
+    Test.add_func("/invercargill/native_element/type_name/string_type_name", () => {
+        var elem = new NativeElement<string>("hello");
+        var name = elem.type_name();
+        assert_true(name != null);
+    });
+
+    Test.add_func("/invercargill/native_element/type_name/object_type_name", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        var name = elem.type_name();
+        assert_true(name != null);
+        assert_true("NativeElementTestObject" in name);
+    });
+
+    // ============================================================================
+    // is_type() method tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_type/int_is_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.is_type(typeof(int)) == true);
+        assert_true(elem.is_type(typeof(string)) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_type/object_is_correct_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.is_type(typeof(NativeElementTestObject)) == true);
+        assert_true(elem.is_type(typeof(Object)) == true);  // Inheritance
+        assert_true(elem.is_type(typeof(string)) == false);
+    });
+
+    // ============================================================================
+    // assignable_to_type() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assignable_to_type/int_assignable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.assignable_to_type(typeof(int)) == true);
+        assert_true(elem.assignable_to_type(typeof(string)) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/assignable_to_type/object_assignable_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.assignable_to_type(typeof(NativeElementTestObject)) == true);
+        assert_true(elem.assignable_to_type(typeof(Object)) == true);
+    });
+
+    // ============================================================================
+    // assignable_to<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assignable_to/int_assignable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.assignable_to<int>() == true);
+        assert_true(elem.assignable_to<string>() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/assignable_to/object_assignable_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.assignable_to<NativeElementTestObject>() == true);
+        assert_true(elem.assignable_to<Object>() == true);
+    });
+
+    // ============================================================================
+    // @is<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is/int_is_int", () => {
+        var elem = new NativeElement<int>(42);
+        assert_true(elem.is<int>() == true);
+        assert_true(elem.is<string>() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is/object_is_correct_type", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        assert_true(elem.is<NativeElementTestObject>() == true);
+        assert_true(elem.is<Object>() == true);
+        assert_true(elem.is<string>() == false);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - non-nullable value types
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int_zero", () => {
+        var elem = new NativeElement<int>(0);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 0);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_int_negative", () => {
+        var elem = new NativeElement<int>(-42);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == -42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_to_bool_true", () => {
+        var elem = new NativeElement<bool>(true);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_to_bool_false", () => {
+        var elem = new NativeElement<bool>(false);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/long_to_long", () => {
+        var elem = new NativeElement<long>(123456789L);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == 123456789L);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - nullable value types with values
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_to_int", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_to_int_nullable", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_to_bool", () => {
+        bool? x = true;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_to_bool_nullable", () => {
+        bool? x = false;
+        var elem = new NativeElement<bool?>(x);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == false);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - nullable value types with null
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_null_to_int_fails", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        // Should fail because null cannot be converted to non-nullable int
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_nullable_null_to_int_nullable", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_null_to_bool_fails", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        // Should fail because null cannot be converted to non-nullable bool
+        assert_true(elem.try_get_as<bool>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/bool_nullable_null_to_bool_nullable", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - reference types
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_nullable_to_string", () => {
+        string? s = "world";
+        var elem = new NativeElement<string?>(s);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "world");
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_nullable_null_to_string_succeeds", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        string result;
+        // For reference types, convert_nullability returns TRUE even for null
+        // because no conversion is needed - the reference is just passed through
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        NativeElementTestObject result;
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result != null);
+        assert_true(result.value == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_to_object", () => {
+        NativeElementTestObject? obj = new NativeElementTestObject(99);
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject result;
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result != null);
+        assert_true(result.value == 99);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_null_to_object_succeeds", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject result;
+        // For reference types, convert_nullability returns TRUE even for null
+        // because no conversion is needed - the reference is just passed through
+        assert_true(elem.try_get_as<NativeElementTestObject>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_nullable_null_to_object_nullable", () => {
+        NativeElementTestObject? obj = null;
+        var elem = new NativeElement<NativeElementTestObject?>(obj);
+        NativeElementTestObject? result;
+        assert_true(elem.try_get_as<NativeElementTestObject?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // try_get_as<T>() tests - type conversion failures
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/try_get_as/int_to_string_fails", () => {
+        var elem = new NativeElement<int>(42);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/string_to_int_fails", () => {
+        var elem = new NativeElement<string>("hello");
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/try_get_as/object_to_wrong_type_fails", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == false);
+    });
+
+    // ============================================================================
+    // @as<T>() tests - successful conversions
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        try {
+            var result = elem.as<int>();
+            assert_true(result == 42);
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        try {
+            var result = elem.as<string>();
+            assert_true(result == "hello");
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        try {
+            var result = elem.as<NativeElementTestObject>();
+            assert_true(result != null);
+            assert_true(result.value == 42);
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    // ============================================================================
+    // @as<T>() tests - failed conversions (should throw)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/as/int_to_string_throws", () => {
+        var elem = new NativeElement<int>(42);
+        try {
+            var result = elem.as<string>();
+            assert_not_reached();  // Should have thrown
+        } catch (ElementError e) {
+            // Expected
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    Test.add_func("/invercargill/native_element/as/string_to_int_throws", () => {
+        var elem = new NativeElement<string>("hello");
+        try {
+            var result = elem.as<int>();
+            assert_not_reached();  // Should have thrown
+        } catch (ElementError e) {
+            // Expected
+        } catch (Error e) {
+            assert_not_reached();
+        }
+    });
+
+    // ============================================================================
+    // assert_as<T>() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/assert_as/int_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        var result = elem.assert_as<int>();
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/assert_as/string_to_string", () => {
+        var elem = new NativeElement<string>("hello");
+        var result = elem.assert_as<string>();
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/native_element/assert_as/object_to_object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        var result = elem.assert_as<NativeElementTestObject>();
+        assert_true(result != null);
+        assert_true(result.value == 42);
+    });
+
+    // Note: assert_as<T>() with wrong type will critical and abort,
+    // so we don't test failure cases here as they would crash the test suite
+
+    // ============================================================================
+    // to_string() tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/to_string/int", () => {
+        var elem = new NativeElement<int>(42);
+        var str = elem.to_string();
+        assert_true(str != null);
+        assert_true("NativeElement" in str);
+    });
+
+    Test.add_func("/invercargill/native_element/to_string/string", () => {
+        var elem = new NativeElement<string>("hello");
+        var str = elem.to_string();
+        assert_true(str != null);
+        assert_true("NativeElement" in str);
+    });
+
+    Test.add_func("/invercargill/native_element/to_string/object", () => {
+        var obj = new NativeElementTestObject(42);
+        var elem = new NativeElement<NativeElementTestObject>(obj);
+        var str = elem.to_string();
+        assert_true(str != null);
+        assert_true("NativeElement" in str);
+    });
+
+    // ============================================================================
+    // Nullability conversion tests (matching Nullable.vala tests)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/nullability/int_to_int_nullable", () => {
+        var elem = new NativeElement<int>(42);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<int>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_nullable_with_value", () => {
+        int? x = 42;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/int_nullable_to_int_nullable_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_to_bool_nullable", () => {
+        var elem = new NativeElement<bool>(true);
+        bool? result;
+        assert_true(elem.try_get_as<bool?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_nullable_to_bool_with_value", () => {
+        bool? x = true;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        assert_true(elem.try_get_as<bool>(out result) == true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/bool_nullable_to_bool_null", () => {
+        bool? x = null;
+        var elem = new NativeElement<bool?>(x);
+        bool result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<bool>(out result) == false);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_to_long_nullable", () => {
+        var elem = new NativeElement<long>(123456789L);
+        long? result;
+        assert_true(elem.try_get_as<long?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_nullable_to_long_with_value", () => {
+        long? x = 123456789L;
+        var elem = new NativeElement<long?>(x);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/native_element/nullability/long_nullable_to_long_null", () => {
+        long? x = null;
+        var elem = new NativeElement<long?>(x);
+        long result;
+        // Should fail because null cannot be converted to non-nullable
+        assert_true(elem.try_get_as<long>(out result) == false);
+    });
+
+    // ============================================================================
+    // Edge cases and boundary values
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/edge/int_min_value", () => {
+        var elem = new NativeElement<int>(int.MIN);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == int.MIN);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/int_max_value", () => {
+        var elem = new NativeElement<int>(int.MAX);
+        int result;
+        assert_true(elem.try_get_as<int>(out result) == true);
+        assert_true(result == int.MAX);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/long_min_value", () => {
+        var elem = new NativeElement<long>(long.MIN);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == long.MIN);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/long_max_value", () => {
+        var elem = new NativeElement<long>(long.MAX);
+        long result;
+        assert_true(elem.try_get_as<long>(out result) == true);
+        assert_true(result == long.MAX);
+    });
+
+    Test.add_func("/invercargill/native_element/edge/empty_string", () => {
+        var elem = new NativeElement<string>("");
+        assert_true(elem.is_null() == false);
+        string result;
+        assert_true(elem.try_get_as<string>(out result) == true);
+        assert_true(result == "");
+    });
+
+    Test.add_func("/invercargill/native_element/edge/string_nullable_empty", () => {
+        string? s = "";
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem.is_null() == false);
+        string? result;
+        assert_true(elem.try_get_as<string?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == "");
+    });
+
+    // ============================================================================
+    // Sequence tests (multiple conversions)
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/sequence/int_to_nullable_to_int", () => {
+        var elem = new NativeElement<int>(42);
+        
+        // First conversion: int -> int?
+        int? temp;
+        assert_true(elem.try_get_as<int?>(out temp) == true);
+        assert_true(temp != null);
+        assert_true(temp == 42);
+        
+        // Create new element from nullable
+        var elem2 = new NativeElement<int?>(temp);
+        
+        // Second conversion: int? -> int
+        int result;
+        assert_true(elem2.try_get_as<int>(out result) == true);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/native_element/sequence/null_nullable_to_int_to_nullable", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        
+        // First conversion: int? (null) -> int (should fail)
+        int temp;
+        assert_true(elem.try_get_as<int>(out temp) == false);
+        
+        // Second conversion: int? (null) -> int? (null)
+        int? result;
+        assert_true(elem.try_get_as<int?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // Interface implementation tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/interface/implements_element", () => {
+        Element elem = new NativeElement<int>(42);
+        assert_true(elem != null);
+        assert_true(elem.is_null() == false);
+        assert_true(elem.type() == typeof(int));
+    });
+
+    Test.add_func("/invercargill/native_element/interface/polymorphic_usage", () => {
+        Element e1 = new NativeElement<int>(42);
+        Element e2 = new NativeElement<string>("hello");
+        Element e3 = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(99));
+        
+        assert_true(e1.type() == typeof(int));
+        assert_true(e2.type() == typeof(string));
+        assert_true(e3.type() == typeof(NativeElementTestObject));
+    });
+
+    // ============================================================================
+    // is_null() consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_null/non_nullable_never_null", () => {
+        var elem = new NativeElement<int>(0);
+        // Non-nullable types should never report as null
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/nullable_with_value_not_null", () => {
+        int? x = 0;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/nullable_null_is_null", () => {
+        int? x = null;
+        var elem = new NativeElement<int?>(x);
+        assert_true(elem.is_null() == true);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/string_not_null", () => {
+        var elem = new NativeElement<string>("hello");
+        assert_true(elem.is_null() == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_null/string_null_is_null", () => {
+        string? s = null;
+        var elem = new NativeElement<string?>(s);
+        assert_true(elem.is_null() == true);
+    });
+
+    // ============================================================================
+    // is_reference_type consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_reference_type/value_types_not_reference", () => {
+        var elem_int = new NativeElement<int>(42);
+        var elem_bool = new NativeElement<bool>(true);
+        var elem_long = new NativeElement<long>(123L);
+        
+        assert_true(elem_int.is_reference_type == false);
+        assert_true(elem_bool.is_reference_type == false);
+        assert_true(elem_long.is_reference_type == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_reference_type/reference_types_are_reference", () => {
+        var elem_str = new NativeElement<string>("hello");
+        var elem_obj = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(42));
+        
+        assert_true(elem_str.is_reference_type == true);
+        assert_true(elem_obj.is_reference_type == true);
+    });
+
+    // ============================================================================
+    // is_nullable property consistency tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/is_nullable/non_nullable_value_types", () => {
+        var elem_int = new NativeElement<int>(42);
+        var elem_bool = new NativeElement<bool>(true);
+        var elem_long = new NativeElement<long>(123L);
+        
+        assert_true(elem_int.is_nullable == false);
+        assert_true(elem_bool.is_nullable == false);
+        assert_true(elem_long.is_nullable == false);
+    });
+
+    Test.add_func("/invercargill/native_element/is_nullable/nullable_value_types", () => {
+        int? xi = 42;
+        bool? xb = true;
+        long? xl = 123L;
+        
+        var elem_int = new NativeElement<int?>(xi);
+        var elem_bool = new NativeElement<bool?>(xb);
+        var elem_long = new NativeElement<long?>(xl);
+        
+        assert_true(elem_int.is_nullable == true);
+        assert_true(elem_bool.is_nullable == true);
+        assert_true(elem_long.is_nullable == true);
+    });
+
+    Test.add_func("/invercargill/native_element/is_nullable/reference_types_always_nullable", () => {
+        var elem_str = new NativeElement<string>("hello");
+        var elem_obj = new NativeElement<NativeElementTestObject>(new NativeElementTestObject(42));
+        
+        // Reference types are always nullable
+        assert_true(elem_str.is_nullable == true);
+        assert_true(elem_obj.is_nullable == true);
+    });
+
+    // ============================================================================
+    // char and uchar tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/char/non_nullable", () => {
+        var elem = new NativeElement<char>('a');
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+        
+        char result;
+        assert_true(elem.try_get_as<char>(out result) == true);
+        assert_true(result == 'a');
+    });
+
+    Test.add_func("/invercargill/native_element/char/nullable_with_value", () => {
+        char? c = 'z';
+        var elem = new NativeElement<char?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+        
+        char? result;
+        assert_true(elem.try_get_as<char?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 'z');
+    });
+
+    Test.add_func("/invercargill/native_element/char/nullable_null", () => {
+        char? c = null;
+        var elem = new NativeElement<char?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+        
+        char? result;
+        assert_true(elem.try_get_as<char?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/non_nullable", () => {
+        var elem = new NativeElement<uchar>(255);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        assert_true(elem.is_null() == false);
+        
+        uchar result;
+        assert_true(elem.try_get_as<uchar>(out result) == true);
+        assert_true(result == 255);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/nullable_with_value", () => {
+        uchar? c = 128;
+        var elem = new NativeElement<uchar?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == false);
+        
+        uchar? result;
+        assert_true(elem.try_get_as<uchar?>(out result) == true);
+        assert_true(result != null);
+        assert_true(result == 128);
+    });
+
+    Test.add_func("/invercargill/native_element/uchar/nullable_null", () => {
+        uchar? c = null;
+        var elem = new NativeElement<uchar?>(c);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        assert_true(elem.is_null() == true);
+        
+        uchar? result;
+        assert_true(elem.try_get_as<uchar?>(out result) == true);
+        assert_true(result == null);
+    });
+
+    // ============================================================================
+    // uint and ulong tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/native_element/uint/non_nullable", () => {
+        var elem = new NativeElement<uint>(42u);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        
+        uint result;
+        assert_true(elem.try_get_as<uint>(out result) == true);
+        assert_true(result == 42u);
+    });
+
+    Test.add_func("/invercargill/native_element/uint/nullable_with_value", () => {
+        uint? x = 99u;
+        var elem = new NativeElement<uint?>(x);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        
+        uint? result;
+        assert_true(elem.try_get_as<uint?>(out result) == true);
+        assert_true(result == 99u);
+    });
+
+    Test.add_func("/invercargill/native_element/ulong/non_nullable", () => {
+        var elem = new NativeElement<ulong>(123456789UL);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == false);
+        
+        ulong result;
+        assert_true(elem.try_get_as<ulong>(out result) == true);
+        assert_true(result == 123456789UL);
+    });
+
+    Test.add_func("/invercargill/native_element/ulong/nullable_with_value", () => {
+        ulong? x = 123456789UL;
+        var elem = new NativeElement<ulong?>(x);
+        assert_true(elem.is_reference_type == false);
+        assert_true(elem.is_nullable == true);
+        
+        ulong? result;
+        assert_true(elem.try_get_as<ulong?>(out result) == true);
+        assert_true(result == 123456789UL);
+    });
+}

+ 578 - 0
src/tests/Integration/Nullable.vala

@@ -0,0 +1,578 @@
+using Invercargill;
+using Invercargill.Nullable;
+
+void nullable_tests() {
+
+    // ============================================================================
+    // is_boxed_nullable tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_non_nullable", () => {
+        int x = 42;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_nullable_with_value", () => {
+        int? x = 42;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/int_nullable_null", () => {
+        int? x = null;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/bool_non_nullable", () => {
+        bool x = true;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/bool_nullable", () => {
+        bool? x = true;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/long_non_nullable", () => {
+        long x = 123456789L;
+        assert_true(!is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/long_nullable", () => {
+        long? x = 123456789L;
+        assert_true(is_boxed_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/string_not_boxed", () => {
+        string s = "hello";
+        assert_true(!is_boxed_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/string_nullable_not_boxed", () => {
+        string? s = "hello";
+        assert_true(!is_boxed_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_boxed_nullable/object_not_boxed", () => {
+        var obj = new Object();
+        assert_true(!is_boxed_nullable(obj));
+    });
+
+    // ============================================================================
+    // is_nullable tests
+    // ============================================================================
+
+    // is_nullable checks if the TYPE is nullable (can hold null), not if the value IS null
+    // - Non-nullable value types (int, bool, etc.) -> FALSE
+    // - Nullable value types (int?, bool?, etc.) -> TRUE
+    // - Reference types (string, Object, etc.) -> TRUE (always nullable)
+    Test.add_func("/invercargill/nullable/is_nullable/int_non_nullable", () => {
+        int x = 42;
+        // int is a non-nullable value type, so is_nullable returns FALSE
+        assert_true(!is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/int_nullable_with_value", () => {
+        int? x = 42;
+        // int? is a nullable value type, so is_nullable returns TRUE
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/int_nullable_null", () => {
+        int? x = null;
+        // int? is a nullable value type, so is_nullable returns TRUE
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_is_nullable", () => {
+        string s = "hello";
+        // string is a reference type, always nullable
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_nullable_is_nullable", () => {
+        string? s = "hello";
+        // string? is also a nullable type
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/string_nullable_null", () => {
+        string? s = null;
+        // string? is a nullable type
+        assert_true(is_nullable(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_non_nullable", () => {
+        bool x = true;
+        // bool is a non-nullable value type
+        assert_true(!is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_nullable_with_value", () => {
+        bool? x = false;
+        // bool? is a nullable value type
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/bool_nullable_null", () => {
+        bool? x = null;
+        // bool? is a nullable value type
+        assert_true(is_nullable(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/object_is_nullable", () => {
+        var obj = new Object();
+        // Object is a reference type, always nullable
+        assert_true(is_nullable(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_nullable/object_nullable_is_nullable", () => {
+        Object? obj = new Object();
+        // Object? is also a nullable type
+        assert_true(is_nullable(obj));
+    });
+
+    // ============================================================================
+    // is_null tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_null/int_non_nullable_never_null", () => {
+        int x = 42;
+        assert_true(!is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/int_nullable_with_value", () => {
+        int? x = 42;
+        assert_true(!is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/int_nullable_null", () => {
+        int? x = null;
+        assert_true(is_null(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/string_with_value", () => {
+        string s = "hello";
+        assert_true(!is_null(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/string_nullable_null", () => {
+        string? s = null;
+        assert_true(is_null(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/object_with_value", () => {
+        var obj = new Object();
+        assert_true(!is_null(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_null/object_null", () => {
+        Object? obj = null;
+        assert_true(is_null(obj));
+    });
+
+    // ============================================================================
+    // is_reference tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/is_reference/int_not_reference", () => {
+        int x = 42;
+        assert_true(!is_reference(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/int_nullable_not_reference", () => {
+        int? x = 42;
+        assert_true(!is_reference(x));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/string_is_reference", () => {
+        string s = "hello";
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/object_is_reference", () => {
+        var obj = new Object();
+        assert_true(is_reference(obj));
+    });
+
+    Test.add_func("/invercargill/nullable/is_reference/object_nullable_is_reference", () => {
+        Object? obj = new Object();
+        assert_true(is_reference(obj));
+    });
+
+    // ============================================================================
+    // unbox tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/unbox/nullable_with_value", () => {
+        int? x = 42;
+        var result = unbox(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/nullable_null_returns_default", () => {
+        int? x = null;
+        var result = unbox(x, 99);
+        assert_true(result == 99);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/non_nullable_returns_value", () => {
+        int x = 42;
+        var result = unbox(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/string_nullable_with_value", () => {
+        string? s = "hello";
+        var result = unbox(s, "default");
+        assert_true(result == "hello");
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/string_nullable_null", () => {
+        string? s = null;
+        var result = unbox(s, "default");
+        assert_true(result == "default");
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/bool_nullable_with_value", () => {
+        bool? x = true;
+        var result = unbox(x, false);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/bool_nullable_null", () => {
+        bool? x = null;
+        var result = unbox(x, true);
+        assert_true(result == true);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/long_nullable_with_value", () => {
+        long? x = 123456789L;
+        var result = unbox(x, 0L);
+        assert_true(result == 123456789L);
+    });
+
+    Test.add_func("/invercargill/nullable/unbox/long_nullable_null", () => {
+        long? x = null;
+        var result = unbox(x, -1L);
+        assert_true(result == -1L);
+    });
+
+    // ============================================================================
+    // box tests
+    // Note: Vala automatically manages memory for the return value of box<T>(),
+    // so we don't need to call free_boxed manually - it would cause double-free.
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/box/int_boxes_successfully", () => {
+        int x = 42;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/box/int_nullable_boxes_successfully", () => {
+        int? x = 42;
+        var boxed = box(x);
+        // For nullable value types, box creates a new boxed copy
+        assert_true(boxed != null);
+        assert_true(boxed == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/box/null_returns_null", () => {
+        int? x = null;
+        var boxed = box(x);
+        // Boxing null returns null
+        assert_true(boxed == null);
+    });
+
+    Test.add_func("/invercargill/nullable/box/string_boxes_successfully", () => {
+        string s = "hello";
+        var boxed = box(s);
+        assert_true(boxed != null);
+        assert_true(boxed == "hello");
+    });
+
+    Test.add_func("/invercargill/nullable/box/bool_boxes_successfully", () => {
+        bool x = true;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == true);
+    });
+
+    Test.add_func("/invercargill/nullable/box/long_boxes_successfully", () => {
+        long x = 123456789L;
+        var boxed = box(x);
+        // For non-nullable value types, box returns the value as-is
+        assert_true(boxed == 123456789L);
+    });
+
+    // ============================================================================
+    // get_type_name tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/get_type_name/int_non_nullable", () => {
+        int x = 42;
+        var name = get_type_name(x);
+        assert_true(name != null);
+        assert_true("gint" in name || "int" in name);
+    });
+
+    Test.add_func("/invercargill/nullable/get_type_name/int_nullable", () => {
+        int? x = 42;
+        var name = get_type_name(x);
+        assert_true(name != null);
+        assert_true("gint" in name || "int" in name);
+    });
+
+    Test.add_func("/invercargill/nullable/get_type_name/string", () => {
+        string s = "hello";
+        var name = get_type_name(s);
+        assert_true(name != null);
+        assert_true("gchar" in name || "str" in name);
+    });
+
+    // ============================================================================
+    // convert_nullability tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int", () => {
+        int x = 42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_zero", () => {
+        int x = 0;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 0);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_negative", () => {
+        int x = -42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == -42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_to_int_nullable", () => {
+        int x = 42;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y != null);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_with_value", () => {
+        int? x = 42;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_null", () => {
+        int? x = null;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == 0); // Default value
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_nullable_with_value", () => {
+        int? x = 42;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y != null);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/int_nullable_to_int_nullable_null", () => {
+        int? x = null;
+        int? y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == null);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_to_bool", () => {
+        bool x = true;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == true);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_to_bool_false", () => {
+        bool x = false;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == false);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_nullable_to_bool_with_value", () => {
+        bool? x = true;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == true);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/bool_nullable_to_bool_null", () => {
+        bool? x = null;
+        bool y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == false); // Default value
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_to_long", () => {
+        long x = 123456789L;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == 123456789L);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_nullable_to_long_with_value", () => {
+        long? x = (long)9876543210;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == (long)9876543210);
+    });
+
+    Test.add_func("/invercargill/nullable/convert_nullability/long_nullable_to_long_null", () => {
+        long? x = null;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == false);
+        assert_true(y == 0L); // Default value
+    });
+
+    // ============================================================================
+    // Edge cases and corner cases
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/edge/int_min_value", () => {
+        int x = int.MIN;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == int.MIN);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/int_max_value", () => {
+        int x = int.MAX;
+        int y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == int.MAX);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/long_min_value", () => {
+        long x = long.MIN;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == long.MIN);
+    });
+
+    Test.add_func("/invercargill/nullable/edge/long_max_value", () => {
+        long x = long.MAX;
+        long y;
+        var result = convert_nullability(x, out y);
+        assert_true(result == true);
+        assert_true(y == long.MAX);
+    });
+
+
+    Test.add_func("/invercargill/nullable/edge/empty_string", () => {
+        string s = "";
+        assert_true(!is_null(s));
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/string_nullable_empty", () => {
+        string? s = "";
+        assert_true(!is_null(s));
+        assert_true(is_reference(s));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/char_non_nullable", () => {
+        char c = 'a';
+        assert_true(!is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/char_nullable", () => {
+        char? c = 'a';
+        assert_true(is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/uchar_non_nullable", () => {
+        uchar c = 255;
+        assert_true(!is_boxed_nullable(c));
+    });
+
+    Test.add_func("/invercargill/nullable/edge/uchar_nullable", () => {
+        uchar? c = 255;
+        assert_true(is_boxed_nullable(c));
+    });
+
+    // ============================================================================
+    // Multiple conversions in sequence
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/sequence/int_to_nullable_to_int", () => {
+        int x = 42;
+        int? temp;
+        int y;
+        
+        var result1 = convert_nullability(x, out temp);
+        assert_true(result1 == true);
+        assert_true(temp != null);
+        assert_true(temp == 42);
+        
+        var result2 = convert_nullability(temp, out y);
+        assert_true(result2 == true);
+        assert_true(y == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/sequence/null_nullable_to_int_to_nullable", () => {
+        int? x = null;
+        int temp;
+        int? y;
+        
+        var result1 = convert_nullability(x, out temp);
+        assert_true(result1 == false);
+        assert_true(temp == 0);
+        
+        var result2 = convert_nullability(temp, out y);
+        assert_true(result2 == true);
+        assert_true(y != null);
+        assert_true(y == 0);
+    });
+
+    // ============================================================================
+    // get_value alias tests
+    // ============================================================================
+
+    Test.add_func("/invercargill/nullable/get_value/with_value", () => {
+        int? x = 42;
+        var result = get_value(x, 0);
+        assert_true(result == 42);
+    });
+
+    Test.add_func("/invercargill/nullable/get_value/null_returns_default", () => {
+        int? x = null;
+        var result = get_value(x, 99);
+        assert_true(result == 99);
+    });
+}

+ 31 - 29
src/tests/TestRunner.vala

@@ -11,35 +11,37 @@ public static int main(string[] args) {
         }
     }
 
-    where_tests();
-    select_tests();
-    select_many_tests();
-    tracker_tests();
-    parallel_tests();
-    first_tests();
-    byte_composition_tests();
-    sort_tests();
-    vector_tests();
-    series_tests();
-    array_tests();
-    promotion_tests();
-    numbers_test();
-    dictionary_tests();
-    catalogue_tests();
-    property_mapper_tests();
-    cache_tests();
-    sorted_vector_tests();
-    sorted_series_tests();
-    order_by_tests();
-    set_tests();
-    fifo_tests();
-    lifo_tests();
-    priority_queue_tests();
-    enumerable_methods_tests();
-    modifiers_tests();
-    data_structures_tests();
-    remaining_components_tests();
-    expression_tests();
+    //  where_tests();
+    //  select_tests();
+    //  select_many_tests();
+    //  tracker_tests();
+    //  parallel_tests();
+    //  first_tests();
+    //  byte_composition_tests();
+    //  sort_tests();
+    //  vector_tests();
+    //  series_tests();
+    //  array_tests();
+    //  promotion_tests();
+    //  numbers_test();
+    //  dictionary_tests();
+    //  catalogue_tests();
+    //  property_mapper_tests();
+    //  cache_tests();
+    //  sorted_vector_tests();
+    //  sorted_series_tests();
+    //  order_by_tests();
+    //  set_tests();
+    //  fifo_tests();
+    //  lifo_tests();
+    //  priority_queue_tests();
+    //  enumerable_methods_tests();
+    //  modifiers_tests();
+    //  data_structures_tests();
+    //  remaining_components_tests();
+    //  expression_tests();
+    nullable_tests();
+    native_element_tests();
 
     var result = Test.run();
     

+ 2 - 0
src/tests/meson.build

@@ -31,6 +31,8 @@ sources += files('Integration/Modifiers.vala')
 sources += files('Integration/DataStructures.vala')
 sources += files('Integration/RemainingComponents.vala')
 sources += files('Integration/Expressions.vala')
+sources += files('Integration/Nullable.vala')
+sources += files('Integration/NativeElement.vala')
 
 sources += files('Speed/SpeedTest.vala')
 sources += files('Speed/Series.vala')

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff