3.32. Annotations
Annotations are metadata decorators attached to functions, structures, classes, enumerations, and variables. They control compiler behavior — export, initialization, safety, optimization, and macro registration.
An annotation is written in square brackets before the declaration it applies to:
[export]
def main {
print("hello\n")
}
Multiple annotations can be combined with commas:
[export, no_aot]
def main {
print("hello\n")
}
Some annotations accept arguments:
[init(tag="db")]
def init_database {
pass
}
[unused_argument(x, y)]
def foo(x, y : int) {
pass
}
3.32.1. Function Annotations
3.32.1.1. Lifecycle
[export]Marks a function as callable from the host application. The host invokes exported functions by name through the context API:
[export] def main { print("hello\n") }
[init]Marks a function to run automatically during context initialization. The function must take no arguments and return
void:[init] def setup { pass }
Ordering can be controlled with attributes:
tag— defines a named initialization passbefore— runs before the named passafter— runs after the named pass
All ordering attributes imply late initialization:
[init(tag="db")] def init_database { pass } [init(after="db")] def init_cache { pass }
[finalize]Marks a function to run automatically during context shutdown. Same constraints as
[init]— no arguments, no return value:[finalize] def cleanup { pass }
Supports a
lateattribute for ordering.[run]Marks a function to run at compile time:
[run] def compile_time_check { print("compiling...\n") }
Disabled by the
disable_runoption (see Options).
(see Program Structure for the full initialization lifecycle).
3.32.1.2. Visibility
Visibility is controlled with the private prefix keyword, not an annotation. Place
private after def to make a function private to the current module:
def private helper {
pass
}
There is no [private] annotation form.
3.32.1.3. Safety
[unsafe_deref]Marks a function as allowing unsafe dereferences inside its body:
[unsafe_deref] def read_ptr(p : int?) { return *p }
[unsafe_operation]Marks a function as an unsafe operation. Calling it requires an
unsafeblock:[unsafe_operation] def dangerous_thing { pass } unsafe { dangerous_thing() }
[unsafe_outside_of_for]Marks a function as unsafe when called outside of a
forloop body.
3.32.1.4. Lint Control
[unused_argument]Suppresses “unused argument” warnings for specific arguments:
[unused_argument(x)] def handler(x : int) { pass }
Multiple arguments can be listed:
[unused_argument(x, y)].[nodiscard]Errors if the return value of the function is discarded:
[nodiscard] def compute : int { return 42 } compute() // error: return value discarded
[deprecated]Marks a function as deprecated. Produces a compile-time warning when called:
[deprecated(message="use new_func instead")] def old_func { pass }
[no_lint]Disables all lint checks for this function.
[sideeffects]Declares that the function has side effects, even if the compiler cannot detect any. Prevents the optimizer from removing calls to this function.
3.32.1.5. Generics and Contracts
[generic]Marks a function as generic (a template that is instantiated for each unique set of argument types):
[generic] def add(a, b : auto) { return a + b }
(see Generic Programming).
[expect_ref]Specialization contract: requires named arguments to be references:
[expect_ref(arr)] def process(var arr : auto) { pass }
[expect_dim]Specialization contract: requires named arguments to be fixed-size arrays.
[expect_any_vector]Specialization contract: requires named arguments to be vector template types.
[local_only]Verifies that specific arguments are passed as local constructors. The argument value indicates the expected state —
truemeans the argument must be a literal constructor,falsemeans it must not be:[local_only(data=true)] def process(data : Foo) { pass }
Additional contract annotations are available in daslib/contracts
(see Contract Annotations (daslib) below).
3.32.1.6. Optimization and AOT
[no_aot]Disables AOT (ahead-of-time) compilation for this function.
[no_jit]Disables JIT compilation for this function.
[jit]Requests JIT compilation for this function.
[inline]Splices the function body into every direct call site during compilation, in every execution tier (interpreter, JIT, AOT). This is a contract, not a hint: a function shape the inliner can’t splice (by-reference or temporary
#result, generators, lambdas, class methods, block/lambda literals orassumeexpressions in the body, recursion through other[inline]functions) is a compile-time error, as is taking the function’s address with@@or calling it from a global or field initializer:[inline] def clamp01(x : float) : float { return saturate(x); }
Leaf arguments (constants and variables) substitute textually; pure single-use arguments substitute in place; everything else binds a temporary at the call site, preserving call-order evaluation of side effects. Bodies may return from several places: every return becomes a store into the call site’s result temporary — a return after which nothing executes rewrites in place at zero cost, and a return under a loop rides a generated boolean flag that breaks out of the spliced loops (
finallysections and iterator cleanup run exactly as they did for the real return). The one refused mix is an early return inside afinally-carrying block with statements after it — the rewrite would separate the finally from declarations it references. Cross-module splices resolve exactly: a body that uses its home module’s private symbols (or generic instances the caller cannot see) splices under a generatedwith (module ...)resolution scope, with argument temporaries bound outside it so caller expressions keep resolving at the caller. What cannot cross is user-spelled_::dispatch — it binds the program module at the call site, so a cross-module[inline]body carrying it is a compile-time error. Operator overloads (+,==,+=, unary-, …) take[inline]too - their operator sites splice exactly like calls; punctuation functions dispatched through other node kinds ([],??, properties) are refused.options disable_inline(or the host-sideCodeOfPolicies::disable_inline) turns splicing off - calls stay regular calls - while the declaration-level contract checks (body shape, recursion,@@) still run; call-site splice checks do not apply, since nothing splices.Separately from the
[inline]contract, optimized builds also inline best-effort (silent declines, never errors): calls passing a block literal argument splice automatically, and so do plain calls and operator sites of small SAME-MODULE non-generic callees - loop-free bodies within theauto_inline_costnode budget (default 32), or private functions referenced exactly once, whose body moves rather than duplicates. The heuristic tier never crosses modules: a transplanted body is not context-free by language design (_::dispatch and generic-operator resolution consult the calling module), so cross-module inlining stays the author’s explicit[inline]contract. The plain-call tier is on by default;options auto_inline_functions = false(or the host-sideCodeOfPolicies::auto_inline_functions) turns just that tier off, andoptions disable_auto_inlineturns all best-effort splicing off.[never_inline]Keeps the function out of best-effort (automatic) inlining - block-literal call-site splicing and the heuristic
auto_inline_functionstier both skip it. Combining it with[inline]is a compile-time error: the two contracts are contradictory.[hybrid]Marks a function as an AOT hybrid — it can call interpreted code from AOT context.
[alias_cmres]Allows aliasing of the copy-on-move return value.
[never_alias_cmres]Prevents aliasing of the copy-on-move return value.
[pinvoke]Marks a function for platform invoke (external function call).
[type_function]Registers the function as usable in type expressions.
3.32.1.7. Macros
[_macro]Marks a function as macro initialization code (runs during macro compilation pass).
[macro_function]Marks a function as existing only in the macro context — excluded from the regular program.
[macro]Defined in
daslib/ast_boost. Like[_macro]but wraps the function body in a module-ready check. Requiresrequire daslib/ast_boost:require daslib/ast_boost [macro] def setup_macros { print("registering macros\n") }
[tag_function]Defined in
daslib/ast_boost. Tags a function with string tags for retrieval viafor_each_tag_function:require daslib/ast_boost [tag_function(my_tag)] def tagged_func { pass }
3.32.1.8. Markers and Hints
[hint]A dummy annotation that carries key-value arguments for optimization hints. Does not change behavior by itself.
[marker]A generic function marker annotation. Does not change behavior.
3.32.2. Structure and Class Annotations
[cpp_layout]Uses C++ memory layout (matching C++ struct alignment rules):
[cpp_layout] struct CppInterop { x : int y : float }
Pass
pod=falseto allow non-POD layouts:[cpp_layout(pod=false)].[safe_when_uninitialized]Marks the struct as safe even when fields are uninitialized (zero-filled memory is a valid state):
[safe_when_uninitialized] struct Vec2 { x : float y : float }
[persistent]Makes a structure persistent (survives context reset). All fields must be POD unless
non_pod=trueis specified:[persistent] struct Config { value : int }
[no_default_initializer]Suppresses generation of the default constructor for this structure.
[macro_interface]Marks a structure as a macro interface (for the macro system).
[comment]A dummy annotation for attaching comment metadata to a structure.
[tag_structure]Defined in
daslib/ast_boost. Tags a structure with string tags for later retrieval.
3.32.3. Macro Registration Annotations (daslib)
These annotations are defined in daslib/ast_boost and applied to class declarations that
inherit from the appropriate AST base class. They auto-register the class as a macro during
module compilation.
All accept an optional name argument. If omitted, the class name is used.
Annotation |
Base class |
Purpose |
|---|---|---|
|
|
Custom function decorator |
|
|
Custom block decorator |
|
|
Custom struct/class decorator |
|
|
Custom enum decorator |
|
|
Specialization constraint |
|
|
Custom literal/expression reader |
|
|
Custom comment reader |
|
|
Intercepts function-like calls |
|
|
Custom |
|
|
Custom variant type processing |
|
|
Custom for-loop behavior |
|
|
Custom closure capture handling |
|
|
Custom type expression processing |
|
|
Custom simulation node generation |
|
|
Runs during type inference |
|
|
Runs during dirty inference passes |
|
|
Runs during optimization |
|
|
Runs during linting |
|
|
Runs after all modules are compiled |
Example:
require daslib/ast_boost
[function_macro(name="my_decorator")]
class MyDecorator : AstFunctionAnnotation {
def override apply(var func : FunctionPtr; var group : ModuleGroup;
args : AnnotationArgumentList; var errors : das_string) : bool {
print("decorating {func.name}\n")
return true
}
}
(see Macros for details on writing macros).
3.32.4. Contract Annotations (daslib)
These annotations are defined in daslib/contracts and used as specialization constraints
on generic function arguments. Each accepts one or more argument names to constrain.
Requires require daslib/contracts.
Annotation |
Constraint |
|---|---|
|
Argument must be a dynamic array |
|
Argument must be an enum |
|
Argument must be a bitfield |
|
Argument must be a vector template type |
|
Argument must be a struct |
|
Argument must be a numeric type |
|
Argument must be a “workhorse” type (int, float, etc.) |
|
Argument must be a tuple |
|
Argument must be a variant |
|
Argument must be a function type |
|
Argument must be a lambda |
|
Argument must be a reference |
|
Argument must be a pointer |
|
Argument must be a class pointer |
|
Argument must be a value handle type |
Example:
require daslib/contracts
[expect_any_array(arr)]
def first_element(arr : auto) {
return arr[0]
}
3.32.5. Annotation Syntax Details
Annotations can be combined with logical operators for contract composition:
[expect_ref(a) && expect_dim(b)]
def process(var a : auto; b : auto) {
pass
}
Negation is also supported:
[!expect_ref(a)]
def no_ref(a : auto) {
pass
}
Annotations on struct/class fields use the @ metadata syntax and appear before the field
declaration:
class Foo {
@big
@min = 13
@max = 42
value : int
}
These @ decorators attach metadata to the field. They are accessible via typeinfo and
at compile time in macros.