Derives

The derive system allows automatic generation of common methods like equals(), hash(), and toString() based on class structure. Unlike macro systems in other languages, XXML derives use the language's own reflection system, maintaining full visibility into what code is generated.

Basic Usage

Apply one or more @Derive annotations to a class to automatically generate methods:

xxml
#import Language::Core;

@Derive(trait = "Stringable")
@Derive(trait = "Equatable")
@Derive(trait = "Hashable")
[ Class <Point> Final Extends None
    [ Public <>
        Property <x> Types Integer^;
        Property <y> Types Integer^;
        Constructor = default;
    ]
]

The compiler generates:

  • toString() - Returns "Point{x=<value>, y=<value>}"
  • equals(Point&) - Compares all public properties structurally
  • hash() - Computes a hash code from all public properties

Note

Derives only consider public properties. Private and protected properties are ignored during code generation.

Built-in Derives

DeriveGenerated MethodDescription
StringabletoString()Human-readable representation
Equatableequals(T&)Structural equality comparison
Hashablehash()Hash code computation
SendableMarkerSafe to move across thread boundaries
SharableMarkerSafe to share across threads
JSONtoJSON(), fromJSON()JSON serialization

Derive<Stringable>

Generates a toString() method that returns a human-readable representation in the format ClassName{prop1=value1, prop2=value2}.

xxml
#import Language::Core;

@Derive(trait = "Stringable")
[ Class <Person> Final Extends None
    [ Public <>
        Property <name> Types String^;
        Property <age> Types Integer^;

        Constructor = default;

        Method <init> Returns Person^ Parameters (
            Parameter <n> Types String^,
            Parameter <a> Types Integer^
        ) Do {
            Set name = n;
            Set age = a;
            Return this;
        }
    ]
]

[ Entrypoint {
    Instantiate Person^ As <person> = Person::Constructor();
    Run person.init(String::Constructor("Alice"), Integer::Constructor(30));

    // Prints: Person{name=Alice, age=30}
    Run System::Console::printLine(person.toString());

    Exit(0);
}]

Note

All public properties must have a toString() method available. String properties are used directly; other types call their toString() method. Empty classes return "ClassName{}".

Derive<Equatable>

Generates an equals() method that performs structural equality comparison of all public properties. Returns true if all properties match, false otherwise.

xxml
#import Language::Core;

@Derive(trait = "Equatable")
[ Class <Point> Final Extends None
    [ Public <>
        Property <x> Types Integer^;
        Property <y> Types Integer^;
        Constructor = default;
    ]
]

[ Entrypoint {
    Instantiate Point^ As <p1> = Point::Constructor();
    Set p1.x = Integer::Constructor(10);
    Set p1.y = Integer::Constructor(20);

    Instantiate Point^ As <p2> = Point::Constructor();
    Set p2.x = Integer::Constructor(10);
    Set p2.y = Integer::Constructor(20);

    If (p1.equals(p2)) -> {
        Run System::Console::printLine(String::Constructor("Points are equal"));
    }

    Exit(0);
}]

Derive<Hashable>

Generates a hash() method that computes a hash code from all public properties using the algorithm:

text
hash = 17
for each property:
    hash = hash * 31 + property.hash()

This ensures that equal objects (per Equatable) produce equal hash values, making the type suitable for use in hash-based collections.

xxml
@Derive(trait = "Equatable")
@Derive(trait = "Hashable")
[ Class <Point> Final Extends None
    [ Public <>
        Property <x> Types Integer^;
        Property <y> Types Integer^;
        Constructor = default;
    ]
]

// Equal points produce equal hashes
// p1.equals(p2) implies p1.hash() == p2.hash()

Derive<JSON>

xxml
#import Language::Format;

@Derive(trait = "JSON")
[ Class <Person> Final Extends None
    [ Public <>
        Property <name> Types String^;
        Property <age> Types Integer^;
        Constructor = default;
    ]
]

// Serialize to JSON
Instantiate Person^ As <p> = Person::Constructor();
Set p.name = String::Constructor("Alice");
Set p.age = Integer::Constructor(30);
Instantiate JSONObject^ As <json> = p.toJSON();
// json = {"name": "Alice", "age": 30}

// Deserialize from JSON
Instantiate JSONObject^ As <input> = JSONObject::parse(...);
Instantiate Person^ As <p2> = Person::fromJSON(input);

Thread Safety: Sendable & Sharable

xxml
// Sendable: safe to move across threads
@Derive(trait = "Sendable")
[ Class <Message> Final Extends None
    [ Public <>
        Property <id> Types Integer^;
        Property <content> Types String^;
        Constructor = default;
    ]
]

// Sharable: safe to share (reference) across threads
@Derive(trait = "Sharable")
[ Class <Config> Final Extends None
    [ Public <>
        Property <maxConnections> Types Integer^;

        // Immutable after construction
        Constructor Parameters (Parameter <max> Types Integer^) -> {
            Set maxConnections = max;
        }
    ]
]

Warning

Sendable types cannot have reference (&) fields. Sharable types should be immutable after construction.

Property Requirements

Derives only consider public properties. Private properties are ignored:

xxml
@Derive(trait = "Equatable")
[ Class <Account> Final Extends None
    [ Private <>
        Property <internalId> Types Integer^;  // Ignored
    ]

    [ Public <>
        Property <accountNumber> Types String^;  // Used
        Constructor = default;
    ]
]

Complete Example

data-class.xxml
#import Language::Core;

@Derive(trait = "Equatable")
@Derive(trait = "Hashable")
@Derive(trait = "Stringable")
[ Class <Product> Final Extends None
    [ Public <>
        Property <id> Types Integer^;
        Property <name> Types String^;
        Property <price> Types Integer^;

        Constructor = default;
    ]
]

[ Entrypoint {
    Instantiate Product^ As <p1> = Product::Constructor();
    Set p1.id = Integer::Constructor(1);
    Set p1.name = String::Constructor("Widget");
    Set p1.price = Integer::Constructor(100);

    Instantiate Product^ As <p2> = Product::Constructor();
    Set p2.id = Integer::Constructor(1);
    Set p2.name = String::Constructor("Widget");
    Set p2.price = Integer::Constructor(100);

    // Test equality
    If (p1.equals(p2)) -> {
        Run Console::printLine(String::Constructor("Products are equal"));
    }

    // Test toString
    Run Console::printLine(p1.toString());
    // Output: Product{id=1, name=Widget, price=100}

    Exit(0);
}]

Writing Custom Derives

XXML supports writing derives in the language itself using the DeriveContext API. This provides full access to class introspection and code generation without leaving XXML.

DeriveContext API

The DeriveContext class provides methods for inspecting the target class and generating code:

Target Class Introspection

  • getClassName() - Get the target class name
  • getNamespaceName() - Get the containing namespace
  • getSourceFile() - Get the source file path
  • getBaseClassName() - Get the base class name
  • isClassFinal() - Check if the class is final

Property Inspection

  • getPropertyCount() - Count public properties
  • getPropertyNameAt(index) - Get property name by index
  • getPropertyTypeAt(index) - Get property type
  • getPropertyOwnershipAt(index) - Get ownership marker (^, &, %, or empty)
  • hasProperty(name) - Check if property exists

Type System Queries

  • typeHasMethod(type, method) - Check if type has a method
  • typeImplementsTrait(type, trait) - Check trait implementation
  • isBuiltinType(type) - Check if type is built-in

Code Generation

  • addMethod(name, returnType, params, body) - Add an instance method
  • addStaticMethod(name, returnType, params, body) - Add a static method
  • addProperty(name, type, ownership, default) - Add a property

Diagnostics

  • error(message) - Emit a compiler error
  • warning(message) - Emit a compiler warning
  • hasErrors() - Check if errors were emitted

Custom Derive Structure

A custom derive must implement two methods:

xxml
// Custom derive definition
[ Class <MyCustomDerive> Final Extends None
    [ Public <>
        // Validate that the derive can be applied
        Static Method <canDerive> Returns Bool^ Parameters (
            Parameter <ctx> Types DeriveContext^
        ) Do {
            // Check requirements
            // Return false and emit error() if invalid
            Return Bool::Constructor(true);
        }

        // Generate the derived code
        Static Method <generate> Returns Void Parameters (
            Parameter <ctx> Types DeriveContext^
        ) Do {
            // Use ctx to inspect class and generate methods
        }
    ]
]

Example: Custom ToString Derive

Here's a simplified example showing how the Stringable derive works internally:

xxml
[ Class <StringableDerive> Final Extends None
    [ Public <>
        Static Method <canDerive> Returns Bool^ Parameters (
            Parameter <ctx> Types DeriveContext^
        ) Do {
            // Check all properties can be converted to string
            Instantiate Integer^ As <count> = ctx.getPropertyCount();
            Instantiate Integer^ As <i> = Integer::Constructor(0);

            While (i.lessThan(count)) -> {
                Instantiate String^ As <propType> = ctx.getPropertyTypeAt(i);

                // Built-in types are OK
                If (ctx.isBuiltinType(propType).not()) -> {
                    // Custom types must have toString()
                    If (ctx.typeHasMethod(propType,
                        String::Constructor("toString")).not()) -> {
                        Run ctx.error(String::Constructor(
                            "Property type must have toString()"));
                        Return Bool::Constructor(false);
                    }
                }

                Set i = i.add(Integer::Constructor(1));
            }

            Return Bool::Constructor(true);
        }

        Static Method <generate> Returns Void Parameters (
            Parameter <ctx> Types DeriveContext^
        ) Do {
            // Build the toString method body
            Instantiate String^ As <className> = ctx.getClassName();
            Instantiate String^ As <body> = String::Constructor("");

            // Generate: return "ClassName{prop1=..., prop2=...}"
            // ... method body construction ...

            Run ctx.addMethod(
                String::Constructor("toString"),
                String::Constructor("String^"),
                String::Constructor(""),
                body
            );
        }
    ]
]

Limitations

  • Template classes are only partially supported
  • Only public properties are considered
  • Derived/computed properties are ignored
  • Custom types must already have required methods (e.g., toString(), equals()) defined
  • Derives cannot access private members of the target class

Next Steps

Learn about Annotations for custom metadata, or explore Threading to understand Sendable and Sharable constraints. For compile-time introspection beyond derives, see Reflection.