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:
#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 structurallyhash()- Computes a hash code from all public properties
Note
Built-in Derives
| Derive | Generated Method | Description |
|---|---|---|
Stringable | toString() | Human-readable representation |
Equatable | equals(T&) | Structural equality comparison |
Hashable | hash() | Hash code computation |
Sendable | Marker | Safe to move across thread boundaries |
Sharable | Marker | Safe to share across threads |
JSON | toJSON(), fromJSON() | JSON serialization |
Derive<Stringable>
Generates a toString() method that returns a human-readable representation in the format ClassName{prop1=value1, prop2=value2}.
#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
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.
#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:
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.
@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>
#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
// 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:
@Derive(trait = "Equatable")
[ Class <Account> Final Extends None
[ Private <>
Property <internalId> Types Integer^; // Ignored
]
[ Public <>
Property <accountNumber> Types String^; // Used
Constructor = default;
]
]Complete Example
#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 namegetNamespaceName()- Get the containing namespacegetSourceFile()- Get the source file pathgetBaseClassName()- Get the base class nameisClassFinal()- Check if the class is final
Property Inspection
getPropertyCount()- Count public propertiesgetPropertyNameAt(index)- Get property name by indexgetPropertyTypeAt(index)- Get property typegetPropertyOwnershipAt(index)- Get ownership marker (^,&,%, or empty)hasProperty(name)- Check if property exists
Type System Queries
typeHasMethod(type, method)- Check if type has a methodtypeImplementsTrait(type, trait)- Check trait implementationisBuiltinType(type)- Check if type is built-in
Code Generation
addMethod(name, returnType, params, body)- Add an instance methodaddStaticMethod(name, returnType, params, body)- Add a static methodaddProperty(name, type, ownership, default)- Add a property
Diagnostics
error(message)- Emit a compiler errorwarning(message)- Emit a compiler warninghasErrors()- Check if errors were emitted
Custom Derive Structure
A custom derive must implement two methods:
// 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:
[ 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.