Metaprogramming
Source references link to 7e7307e2 (main) in the Logos repository.
Scope: compile-time metaprogramming surface — function-like macros (#[fn_macro], #[token_macro]), quote!/quote_expr!/quote_item!/quote_ty! AST quoting and antiquotation, explicit comptime metacalls, annotations (#[...]) and their cfg!/derive/trigger machinery, the format! family, and the AnyVal/WAny inline value-form + type/enum reflection intrinsics that back them. Sources: tools/peg_gen_cpp/grammars/logos.peg (grammar layer) and src/compiler/mlir_gen_expr.cpp / related codegen (lowering layer); extracted into tools/spec-extract/rules/**/*.json.
Note:
expr.macro.fn-style-callandmetaprog.macro.fn-style-callboth describe function-style macro-call grammar (general form vs. operand/no-struct-lit position) and are extracted from adjacent grammar productions; the first carries domainmetaprogdespite anexpr.*-shaped id. Both are kept verbatim below (grouped under Macro) rather than merged, per extraction-fidelity policy.
Function-style macro call syntax
metaprog.macro.raw-text-brace-escape — Raw-text macro-call capture honours backslash and backtick escapes
RAW_TEXT captured from a balanced brace-delimited group on a fn-macro call (RAW_GROUP_*) honours two escapes so literal braces don’t break brace balancing: (a) a backslash immediately preceding {, }, backtick, or \ passes that character through without counting it toward the balance (the backslash itself survives into the captured text, for the handler to de-escape); (b) a backtick-delimited region is skipped as a whole, with no brace counting performed inside it. Both are additive: a well-formed body without these constructs is captured byte-identically.
Divergence: A3
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L80
expr.macro.fn-style-call — Function-style macro invocation
Function-style macros invoke as name!(…), name![…], or name!{…}; the contents between balanced delimiters are captured as raw source text and re-interpreted at sema time per the callee’s macro kind (#[fn_macro] re-parses as an expression list; #[token_macro] lexes as a TokenStream). In no-struct-lit (condition) position the brace form name!{…} is excluded.
Source: tools/peg_gen_cpp/grammars/logos.peg#L2743-L2754, tools/peg_gen_cpp/grammars/logos.peg#L2550-L2559
metaprog.macro.fn-style-call — Function-style macro invocation in operand position
Function-style macros invoke as name!(…) or name![…] in operand/expression position (if cfg!(unix) && … {}, while m!(x) {}); the ordinary (non-no-struct-lit) primary additionally admits the brace form name!{…}, which is excluded from the no-struct-lit grammar since it would swallow the enclosing if/while body block. Contents between the balanced delimiters are captured as raw source text and re-interpreted at sema time per the callee’s macro kind.
Source: tools/peg_gen_cpp/grammars/logos.peg#L2616-L2619, tools/peg_gen_cpp/grammars/logos.peg#L2809-L2814
#[fn_macro] — expression-position function-like macros
metaprog.fn-macro.expr-and-item-forms — Function-style macros exist in expression and item forms
name!(args) / name![args] (FN_MACRO_CALL, expression position) resolves CALLEE against #[fn_macro]-annotated functions, passing each argument AST as an ExprBlob. name!{...} at module-item position (FN_MACRO_CALL_ITEM) has the same callee/args shape but routes through the metacall_item-style splice path (callee returns an ItemList / QuoteItemBlob); after splicing the node becomes FN_MACRO_CALL_ITEM_DONE, which sema skips.
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L294-L296
metaprog.fn-macro.item-position — Item-position fn-macro invocation
IDENT ! { ... } at item position invokes a function-like macro whose body is captured as raw text (RAW_GROUP_BRACE); brace delimiter only at item position (parens/brackets stay reserved for expression position). The callee resolves through the item-splice pipeline (returns an item list).
my_macro! { struct A; }
Related: metaprog.fn-macro.expr-and-item-forms
Source: tools/peg_gen_cpp/grammars/logos.peg#L579-L580
metaprog.fn-macro.callee-must-be-marked — name!(...) callee must be a #[fn_macro] or #[token_macro] fn
A name!(...) invocation resolves name against the function overload set; the callee selected must be a fn annotated #[fn_macro] or #[token_macro]. If name is unknown, or no overload bears such an annotation, the call is rejected. Only macro-annotated fns are callable via name!(...) syntax.
Uncertainty: Resolution is restricted to non-generic funcs_ (generic fn_macro out of scope for this slice).
Source: src/compiler/sema_expr.cpp#L18497-L18517
metaprog.fn-macro.signature-shapes — Accepted fn-macro/token-macro signatures
A #[fn_macro] callee must have exactly one parameter and signature (ExprBlob) -> ExprBlob (single-arg form) or (Vec<ExprBlob>) -> ExprBlob (N-arg packed form). A #[token_macro] callee must have signature (str) -> ExprBlob. Any other signature is rejected.
Source: src/compiler/sema_expr.cpp#L18525-L18555
metaprog.fn-macro.single-arg-arity — Single-arg fn-macro takes exactly one argument
For a callee with signature (ExprBlob) -> ExprBlob, the name!(...) invocation must supply exactly one argument; supplying any other count is an error.
Source: src/compiler/sema_expr.cpp#L18536-L18539, src/compiler/sema_expr.cpp#L19017-L19022
metaprog.fn-macro.args-are-expr-list — name!(...) arguments parse as a comma-separated expression list
The raw text between the parentheses of name!(...) is parsed as a comma-separated list of expressions (each becoming one macro ARG). If it does not parse as such, the invocation is rejected.
Source: src/compiler/sema_expr.cpp#L18566-L18681
metaprog.fn-macro.arg-passed-as-ast-blob — Each fn-macro argument is passed as its serialized AST subtree (ExprBlob)
Each argument expression of name!(...) is passed to the callee unevaluated as an ExprBlob referencing the serialized AST subtree of that argument; the callee receives the syntax tree, not a runtime value. For the Vec form, all argument ExprBlobs are packed into a Vec<ExprBlob> in source order.
Source: src/compiler/sema_expr.cpp#L19028-L19116
metaprog.fn-macro.result-is-exprblob-spliced — fn-macro call expands to the ExprBlob it returns
A name!(...) expression has the callee’s return type ExprBlob; at expansion the AST produced by the macro (the returned ExprBlob) is spliced in place of the call site before final sema.
Source: src/compiler/sema_expr.cpp#L19118-L19132
metaprog.fn-macro.cfg-builtin — cfg!(...) is a compile-time built-in predicate
The built-in macro cfg!(...) is evaluated at compile (sema) time to a bool literal, before user fn-macro resolution. It accepts built-in target keys, the boolean combinators all/any/not, and user feature flags supplied via --cfg.
Source: src/compiler/sema_expr.cpp#L18489-L18493
metaprog.fn-macro.callee-marker-required — name!(...) resolves only #[fn_macro]/#[token_macro] callees
A function-style macro call name!(...) resolves only callees marked #[fn_macro]; a #[token_macro] callee additionally receives its raw source text directly as a str argument.
Testability: transitive
Source: src/compiler/sema_impl.hpp#L2553-L2554
metaprog.fn-macro.builtin-macro-list — Fixed set of compiler-built-in function-style macros checked before user #[fn_macro]s
Function-style macro calls (name!(args) / name![args]) are first checked against a fixed compiler-built-in set — cfg!, line!, column!, file!, include!, include_str!, include_bytes!, env!, concat!, concat_bytes!, stringify!, compile_error! — handled by lower_builtin_macro; a callee name not in this set falls through to resolution against user #[fn_macro]-tagged functions.
Testability: behavioral
Source: src/compiler/sema_impl.hpp#L4018-L4029
metaprog.fn-macro.item-callee-resolution — Callee resolution and lowering of item-position fn-macro calls
A function-style macro invoked at module item position as name!{...} resolves its callee against #[fn_macro]-tagged functions (mirroring expression-position fn-macro calls) and routes its arguments through the per-site raw-capture arg-blob shim, mirroring metacall’s item-position lowering.
Related: metaprog.fn-macro.item-position, metaprog.metacall.item-position, metaprog.fn-macro.builtin-macro-list
Source: src/compiler/sema_impl.hpp#L4050-L4055
#[fn_macro] at item position
metaprog.fn-macro-item.callee-must-be-fn-macro — Item-position macro call callee must be #[fn_macro] or #[token_macro]
callee!{ ... } / callee!(...){...} at ITEM position resolves callee in the function-overload set; if no overload is found, or none of the overloads carries #[fn_macro] or #[token_macro], the item is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19483-L19508
metaprog.fn-macro-item.return-type — Item-macro callee must return ItemList or QuoteItemBlob
An item-position #[fn_macro]/#[token_macro] callee’s declared return type must be ItemList or QuoteItemBlob; any other return type is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19510-L19519
metaprog.fn-macro-item.param-signature — Item-macro callee parameter shape is fixed per attribute
An item-position #[fn_macro] callee must have parameter list (Vec<ExprBlob>) or (). An item-position #[token_macro] callee must have parameter list (str), (name: str, body: str), or (name: str, params: str, body: str). Any other arity/type combination for the given attribute is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19521-L19575
metaprog.fn-macro-item.raw-text-as-expr-list — fn_macro Vec<ExprBlob> item form re-parses RAW_TEXT as a comma-separated expr list
For an item-position #[fn_macro] callee of shape (Vec<ExprBlob>), the macro’s RAW_TEXT is wrapped as fn __f() { __c(<RAW_TEXT>); }, parsed as Logos source, and the resulting call’s ARGS elements are lowered one-for-one into ExprBlob values (each argument’s AST subtree is copied into its own document, tagged with its original AST node CODE) and passed to the callee as Vec<ExprBlob>.
Testability: behavioral
Source: src/compiler/sema_expr.cpp#L19769-L19825, src/compiler/sema_expr.cpp#L19838-L19876, src/compiler/sema_expr.cpp#L19887-L19901
metaprog.fn-macro-item.zero-arg-arity — Zero-arg fn_macro item form rejects nonzero arg count
An item-position #[fn_macro] callee declared with zero parameters is invoked with the RAW_TEXT re-parsed as a (possibly empty) comma-separated arg list; if that re-parse yields one or more arguments, the item is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19773-L19825, src/compiler/sema_expr.cpp#L19827-L19832
metaprog.fn-macro-item.arg-must-be-ast-node — Each re-parsed fn_macro item arg must be a valid AST node
Each element of the re-parsed RAW_TEXT arg list (for the Vec<ExprBlob> item form) must be a non-null pointer value whose root carries a nonzero AST CODE; otherwise the item is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19841-L19855
#[token_macro] — token-stream macros
metaprog.token-macro.raw-text-as-str — token-macro receives unparsed raw text as str
For a #[token_macro] callee, the raw bytes between the delimiters of name!(...) are forwarded verbatim as a single str argument, bypassing expression-list parsing and per-argument AST serialization.
Source: src/compiler/sema_expr.cpp#L18576-L18619
#[token_macro] at item position
metaprog.token-macro-item.name-slot-scope — Resource NAME slot required for 2/3-arg token_macro, optional+dropped for 1-arg
resource <name> = h!{...} / resource <name> = h!(<params>){...} supplies the LHS binding as a NAME field on the item node. Targeting the 2-arg (name: str, body: str) or 3-arg (name: str, params: str, body: str) #[token_macro] form without a NAME is rejected. Targeting the 1-arg (body: str) form, a supplied NAME is accepted but the value is discarded — never delivered to the callee.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19577-L19592, src/compiler/sema_expr.cpp#L19658-L19663
metaprog.token-macro-item.params-slot-scope — Resource PARAMS slot only valid for the 3-arg token_macro form
h!(<params>){...} supplies a PARAMS field carrying the raw params source text. Supplying PARAMS against any callee signature other than the 3-arg (name: str, params: str, body: str) #[token_macro] is rejected; conversely, targeting the 3-arg form without a supplied PARAMS is also rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19600-L19620
metaprog.token-macro-item.raw-text-verbatim — token_macro item form receives raw block text unparsed
For an item-position #[token_macro] callee of shape (str), (name: str, body: str), or (name: str, params: str, body: str), the full RAW_TEXT bytes of the macro’s block (and, where present, the NAME and PARAMS text) are passed to the callee as opaque str values byte-for-byte — the content is NOT re-parsed as Logos source before the call.
Testability: behavioral
Source: src/compiler/sema_expr.cpp#L19630-L19664, src/compiler/sema_expr.cpp#L19670-L19707
Antiquotation (#ident capture into quoted AST)
metaprog.antiquot.capture-forms — Writ antiquotation capture syntax
Within a quoted/Writ literal, an antiquotation captures a value either by identifier $name or by expression block ${expr}.
Divergence: Logos metaprogramming antiquotation; no Rust equivalent.
Testability: transitive
Source: src/compiler/sema_render.cpp#L532-L537
metaprog.antiquot.callee-skips-payload-arg — Antiquoted callee drops synthetic first arg
When a call’s callee is produced by antiquotation substitution, the grammar’s bulk $... capture inserts the antiquot payload as the first argument; semantically that first argument is not a real call argument and is skipped.
Uncertainty: Inferred from renderer mirroring lower_call; exact substitution mechanism defined elsewhere.
Testability: transitive
Source: src/compiler/sema_render.cpp#L141-L170
quote!/quote_item!/quote_ty! — overview
metaprog.quote.antiquote-placeholder — #ident antiquotation placeholder inside quote_*! bodies
Inside the body of a quote_*! macro, #ident is an antiquotation placeholder splicing an external value; its AST node carries a NAME_VAR field holding the placeholder’s identifier text. NAME_VAR exists only inside quote_*! bodies (where a where-clause never co-occurs).
Divergence: A3
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L78
metaprog.quote.typed-ast-literals — quote_item!/quote_expr!/quote_ty! are typed AST literals
quote_item! { item* } (QUOTE_ITEM), quote_expr! { expr } (QUOTE_EXPR), and quote_ty! { type } (QUOTE_TY) are typed AST-literal forms yielding, respectively, an item list, a single expression AST (root carries a schema_type_code), and a Type AST. Inside a quote body, #(body)sep* (REPEAT_GROUP) repeats body with separator sep in {none, comma, &&}. $ident in type position (ANTIQUOT_TYPE) is legal only inside quote_ty! and refers to a Type-valued binding; $ident... in a type-argument list (ANTIQUOT_PACK) is legal only inside quote_ty! and refers to an Array<Type>-valued binding.
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L269-L275
metaprog.quote.quote-item — quote_item! typed-AST item literal
quote_item! { item* } parses zero or more item declarations as its body and produces a QUOTE_ITEM node; sema deep-clones the parsed items into a fresh Writ module, serializes it, and emits a byte-slice (WritStatic) literal value.
Source: tools/peg_gen_cpp/grammars/logos.peg#L3105-L3112
metaprog.quote.quote-expr — quote_expr! typed-AST expression literal
quote_expr! { expr } parses a single expression as its body and produces a QUOTE_EXPR node; sema deep-clones the expr’s AnyVal as the root TOM of a fresh Writ doc (root schema_type_code set from CODE) and emits an ExprBlob value, spliced via metacall.
Source: tools/peg_gen_cpp/grammars/logos.peg#L3114-L3121
metaprog.quote.quote-ty — quote_ty! typed Type literal
quote_ty! { type_ref } parses a single type expression as its body and produces a QUOTE_TY node; sema resolves it to a TypeRef and emits the same Type{kind, name, size} struct literal that type_of::<T>() produces.
Source: tools/peg_gen_cpp/grammars/logos.peg#L3123-L3129
quote_expr! — expression-AST quoting
metaprog.quote-expr.reify-ast-to-exprblob — quote_expr! reifies an expression AST into an ExprBlob
quote_expr! { e } evaluates to a value of struct type ExprBlob carrying the serialized AST of e. With no antiquots, the AST is emitted as a static rodata blob and wrapped directly as ExprBlob { ptr }.
Divergence: A3/A6 (replaces Rust macro/quote layer)
Source: src/compiler/sema_expr.cpp#L16386-L16423, src/compiler/sema_expr.cpp#L16806-L16813
metaprog.quote-expr.antiquot-must-be-in-scope — Antiquot variable in quote_expr! must be a bound local
A #name antiquot inside quote_expr! is an error unless name is a variable in scope at the quote site (“#name — variable not in scope”).
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16510-L16514
metaprog.quote-expr.scalar-antiquot-type — Scalar antiquot must be Ident, or Ident/ExprBlob outside ident-only positions
A #name antiquot outside a repetition group, in a general expression position, must bind a value of type Ident or ExprBlob; in ident-only positions (field names, struct type name, field-read selector) it must bind an Ident. Otherwise it is rejected (“expected Ident” / “expected Ident or ExprBlob”).
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16549-L16560, src/compiler/sema_expr.cpp#L16618-L16621, src/compiler/sema_expr.cpp#L16661-L16685
metaprog.quote-expr.repeat-cursor-type — Repetition cursor must be [Ident;N], Vec<Ident>, or Vec<ExprBlob>
A #name antiquot inside a #(...)* repetition group (a cursor) must bind a value of type [Ident; N] (fixed count N), Vec<Ident>, or Vec<ExprBlob> (dynamic count); any other type is rejected (“expected [Ident; N], Vec<Ident>, or Vec<ExprBlob>”).
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16523-L16538, src/compiler/sema_expr.cpp#L16469-L16492
metaprog.quote-expr.repeat-cursor-length-agree — Fixed-length cursors in one repetition group must agree on length
Within a single #(...)* group, all fixed-size [Ident; N] cursors must share the same length N; a sibling cursor with a different N is rejected (“cursor length mismatches sibling cursor in same #(...)*”). A Vec-backed (dynamic) cursor makes the group dynamic and waives the fixed-length agreement check.
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16539-L16548
metaprog.quote-expr.repeat-needs-cursor — A repetition group must contain at least one cursor antiquot
A #(...)* repetition group body must contain at least one cursor antiquot #x of a cursor type; an empty-cursor body is rejected (“#(...)* body has no cursor #x”).
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16600-L16605
metaprog.quote-expr.no-nested-repeat — Nested repetition groups are not allowed
A #(...) repetition group may not be nested inside another #(...) group (“nested #(...) repetition not supported”).
Divergence: A3/A6
Testability: untestable
Source: src/compiler/sema_expr.cpp#L16589-L16597
metaprog.quote-expr.antiquot-carrier-positions — Antiquots are recognized only in defined AST carrier positions
Antiquots and repetition groups are recognized only within the supported carrier set: VAR_REF, BINOP (lhs/rhs), PAREN/UNARY/CAST/DEREF (value), FIELD_READ (selector + receiver), CALL/METHOD_CALL/STATIC_CALL (callee-name-var, receiver, args), STRUCT_LIT/FIELD_INIT/FIELD_SHORTHAND, ARR_LIT/TUPLE_LIT/BLOCK items, statement carriers (LET, LET_DESTRUCT, EXPR_STMT, TAIL_EXPR, RETURN), and control flow (IF, WHILE, FOR, LOOP, ASSIGN, COMPOUND_ASSIGN). Antiquots in unsupported shapes are not substituted.
Divergence: A3/A6
Uncertainty: The exact carrier set is an evolving implementation surface (slices noted in comments), not a frozen spec.
Source: src/compiler/sema_expr.cpp#L16586-L16747
metaprog.quote-expr.subst-runtime — quote_expr! with antiquots substitutes at runtime via logos_quote_expr_subst
quote_expr! containing N>0 antiquots lowers to a block that binds the static template blob and one IdentSpan { ptr, count, kind } per placeholder, then calls logos_quote_expr_subst(template_ptr, size, &spans[0], N) -> *const u8 and wraps the result as ExprBlob { ptr }. Span kind is 0 for Ident slots, 1 for ExprBlob slots, 2 for Vec<ExprBlob> cursors.
Divergence: A3/A6
Source: src/compiler/sema_expr.cpp#L16815-L16981, src/compiler/sema_expr.cpp#L16866-L16943
quote_item! — item-AST quoting
metaprog.quote-item.name-antiquot-forms — quote_item! accepts #name and #(expr) name antiquotations
Within quote_item! { ... }, a NAME_VAR placeholder accepts two forms: #name (shortcut) looks the variable up in the metafn scope and requires type Ident; #(expr) lowers the inner expression in the metafn scope and requires type Ident or ExprBlob. Any other pointee kind is an error.
Divergence: Logos metaprogramming construct; no Rust equivalent.
Source: src/compiler/sema_expr.cpp#L15569-L15625
metaprog.quote-item.repeat-cursor-depth — #(...)* repetition binds cursor placeholders by Vec nesting depth
Inside a #(...)* repetition group, a #name placeholder becomes a Cursor whose depth equals its Vec nesting (1 for Vec<Ident>, 2 for Vec<Vec<Ident>>); the variable’s cursor depth d must be non-zero and ≤ the current repeat depth, else an error. Outside any repetition group, #name must be a scalar Ident.
Source: src/compiler/sema_expr.cpp#L15582-L15606, src/compiler/sema_expr.cpp#L15523-L15533
metaprog.quote-item.repeat-nesting-limit — #(...) repetition nesting limited to 2 levels
#(...) repetition groups in quote_item! may nest at most 2 levels deep; deeper nesting is an error.
Source: src/compiler/sema_expr.cpp#L15554-L15568
metaprog.quote-item.placeholder-walk-order — Placeholder index is fixed by source-tree DFS order
Placeholder indices are assigned by a deterministic depth-first walk of the quoted item subtrees (recursing into all pointer-valued TOM keys and array elements except NAME_VAR), and the destination rewrite mirrors the same recursion so producer indices align with placeholder slots.
Testability: transitive
Source: src/compiler/sema_expr.cpp#L15541-L15661
metaprog.quote-item.synthetic-main-module — quote_item! produces a synthetic package main module
quote_item! { item* } constructs a synthetic AST module whose root is MODULE with NAME=“main”, empty PATH_PARTS, ITEMS = the deep-cloned quoted items, and SRC_LINE=1. The result is emitted as a serialized WritStatic blob carried by a QuoteItemBlob value.
Divergence: Logos metaprogramming addition (no Rust equivalent).
Testability: untestable
Source: src/compiler/sema_expr.cpp#L15859-L15894, src/compiler/sema_expr.cpp#L15805-L15819
metaprog.quote-item.inherit-import-scope — quote_item! inherits the metafn’s import scope
The synthetic module inherits the enclosing metafn’s wildcard use packages, plus a self-use of the metafn’s own package (if non-empty), so that unqualified names inside the quoted items resolve through the metafn’s use-list. Each inherited package becomes one USE node carrying the full dotted package name in NAME.
Divergence: Logos metaprogramming addition; controls hygiene/name resolution of quoted items.
Testability: transitive
Source: src/compiler/sema_expr.cpp#L15821-L15857
metaprog.quote-item.placeholder-walk-balance — Source and destination placeholder counts must match
The number of antiquot placeholders discovered while scanning the source items must equal the number of placeholder slots rewritten in the cloned destination tree; a mismatch is a compile error.
Divergence: Logos metaprogramming addition.
Uncertainty: This is an internal consistency invariant; user-observable only as a diagnostic.
Testability: untestable
Source: src/compiler/sema_expr.cpp#L15797-L15802
metaprog.quote-item.blob-result-type — quote_item! evaluates to a QuoteItemBlob value
quote_item! evaluates to a QuoteItemBlob struct value with fields { template_ptr, template_size, idents_blob, blobs_blob, cursors_blob }, where template_ptr/template_size address the serialized synthetic-module blob and the *_blob fields carry the packed antiquot substitution data (null when the corresponding placeholder kind has zero occurrences).
Divergence: Logos metaprogramming addition.
Testability: untestable
Source: src/compiler/sema_expr.cpp#L16133-L16144, src/compiler/sema_expr.cpp#L15907-L15910
metaprog.quote-item.ident-antiquot-packing — #name/#(expr) Ident antiquots are packed as Ident pointers
Each scalar Ident antiquot site (#name shortcut or #(expr) yielding Ident) contributes one *const Ident to the idents blob, in DFS placeholder order; a #(expr) form binds the lowered expression to a fresh local whose address is taken. When there are no Ident sites, idents_blob is null.
Divergence: Logos metaprogramming addition.
Testability: untestable
Related: metaprog.quote-item.blob-result-type
Source: src/compiler/sema_expr.cpp#L15953-L16003
metaprog.quote-item.exprblob-antiquot-packing — ExprBlob antiquots are packed by their .ptr field
Each #(expr) antiquot whose lowered expression has type ExprBlob contributes one *const u8 (the ExprBlob’s ptr field) to the blobs blob, in DFS placeholder order; the lowered ExprBlob is bound to a local that outlives the array. When there are no ExprBlob sites, blobs_blob is null.
Divergence: Logos metaprogramming addition.
Testability: untestable
Related: metaprog.quote-item.blob-result-type
Source: src/compiler/sema_expr.cpp#L16005-L16054
metaprog.quote-item.cursor-repetition-packing — Cursor (#(...)*) antiquots carry a per-site nesting depth
Each repetition-cursor antiquot site contributes a *const u8 (the address of a Vec cursor variable) plus a parallel per-site depth byte: depth 1 = Vec<Ident>, depth 2 = Vec<Vec<Ident>> (nested #(...)*). The element type is the neutral *const u8; pack reads each cursor according to its depth. When there are no cursor sites, cursors_blob is null.
Divergence: Logos metaprogramming addition.
Testability: untestable
Related: metaprog.quote-item.blob-result-type
Source: src/compiler/sema_expr.cpp#L16056-L16127, src/compiler/sema_expr.cpp#L15939-L15944
quote_ty! — type-AST quoting
metaprog.quote-ty.reify-type-to-struct — quote_ty! reifies a type into a runtime Type value
quote_ty! { T } evaluates to a value of struct type Type whose fields are { kind: u32 = __type_kind_of__::<T>(), name: &[u8] = __type_name_of__::<T>(), size: i64 = size_of::<T>(), align: i64 = align_of::<T>(), uid: u64 = __type_uid_of__::<T>() }.
Divergence: A6 (Logos addition; metaprog reflection intrinsic)
Related: intrinsic.type-reflect.kind, intrinsic.type-reflect.name, intrinsic.type-reflect.uid
Source: src/compiler/sema_expr.cpp#L16357-L16383, src/compiler/sema_expr.cpp#L16179
metaprog.quote-ty.antiquot-type-var — $ident antiquot inside quote_ty! refers to a bound Type value
An ANTIQUOT_TYPE $x inside quote_ty! lowers to a variable reference of type Type (the in-scope binding named x), instead of being reified from a static type.
Divergence: A6 (Logos addition)
Source: src/compiler/sema_expr.cpp#L16182-L16184, src/compiler/sema_expr.cpp#L16314-L16316
metaprog.quote-ty.tuple-antiquot — quote_ty! tuple with antiquot lowers to tuple_type_apply
quote_ty! { ($t1, $t2, ...) } where at least one element is an antiquot lowers to __tuple_type_apply__([p1, p2, ...]) where each pi is the per-element Type producer (var-ref for $x, reified Type literal otherwise); mixed literal/antiquot elements are permitted.
Divergence: A6 (Logos addition)
Source: src/compiler/sema_expr.cpp#L16209-L16234
metaprog.quote-ty.array-antiquot-literal-size — quote_ty! array with antiquot element requires literal integer size
quote_ty! { [$t; N] } lowers to __array_type_apply__(elem_producer, N); the size N MUST be a literal integer (a non-numeric/symbolic size is rejected with “array antiquot requires literal integer size”).
Divergence: A6 (Logos addition)
Source: src/compiler/sema_expr.cpp#L16238-L16263
metaprog.quote-ty.pack-splice — quote_ty! generic pack-splice lowers to type_apply with runtime array
quote_ty! { Foo<$ts...> }, where the sole generic argument is an ANTIQUOT_PACK $ts..., lowers to __type_apply__("Foo", ts) where ts is a var-ref to a runtime Array<Type>. A pack-splice mixed with any other generic argument (Foo<$t, $ts...>) is rejected (“mixed pack-splice with other args not yet supported”).
Divergence: A6 (Logos addition)
Uncertainty: Mixed-pack rejection is a current implementation limit, not a permanent language rule.
Source: src/compiler/sema_expr.cpp#L16271-L16294
metaprog.quote-ty.generic-inst-antiquot — quote_ty! generic instantiation with antiquot args lowers to type_apply
quote_ty! { Foo<args...> } with at least one $ident antiquot among the args lowers to __type_apply__("Foo", [elems]), where each elem is a var-ref (for $x) or a reified Type struct literal (for a concrete type arg). Lifetime args and pack-expand args in this position are rejected (“lifetime / pack args not yet supported”).
Divergence: A6 (Logos addition)
Source: src/compiler/sema_expr.cpp#L16299-L16355
Metacall — explicit comptime invocation
metaprog.metacall.explicit-comptime-call — metacall runs a compile-time callee and splices its result
metacall <call_expr> (METACALL, expression position) is an explicit compile-time invocation. At item position, metacall <call_expr>; (METACALL_ITEM) is JIT-executed by the driver; the callee must return a QuoteItemBlob, which is spliced into the surrounding item stream via logos_emit_item_blob_subst. After splicing, the node is rewritten to METACALL_ITEM_DONE, which sema’s item-dispatch silently skips (and which is never produced by parsing).
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L267, tools/peg_gen_cpp/grammars/logos.peg#L278-L279
metaprog.metacall.forms — metacall expression forms
metacall accepts exactly three operand shapes — a block (metacall { … }), a parenthesized expression (metacall (e)), or a call expression (metacall f(…), including generic f::<T>(…) and static Type::m(…)) — and evaluates its argument at compile time.
Divergence: A1/A6 — Logos addition: metacall is the explicit compile-time evaluation operator, the Logos replacement for const-eval (no implicit const-eval).
Related: metaprog.metacall.block-tail-required
Source: tools/peg_gen_cpp/grammars/logos.peg#L2599-L2604, tools/peg_gen_cpp/grammars/logos.peg#L2731-L2736, tools/peg_gen_cpp/grammars/logos.peg#L2791-L2796, src/compiler/sema_expr.cpp#L17084-L17088
metaprog.metacall.item-decl-syntax — Item-position metacall statement
metacall <call_expr> ; at item (module top-level) position invokes a metacall whose callee returns a QuoteItemBlob; the driver runs the JIT’d thunk, which splices the returned items into the enclosing program.
Related: metaprog.metacall.explicit-comptime-call
Source: tools/peg_gen_cpp/grammars/logos.peg#L604-L610
metaprog.metacall.item-position-splice — Item-position metacall splices synthesized items
A metacall foo(); in item position is evaluated during discovery and its result is spliced as synthesized top-level item(s) into the program.
Source: src/compiler/metaprog_dispatch.hpp#L1-L3, src/compiler/metaprog_dispatch.hpp#L97-L98
metaprog.metacall.no-nested-metacall — metacall may not be nested inside another metacall’s operand
A metacall operand (call args, or the inner subtree for the block/expr forms) must not contain another metacall node; metacall is a one-shot lift to compile time whose result is a runtime value and therefore cannot serve as a compile-time argument to an enclosing metacall. Violation is a compile error.
Divergence: A1/A6: metacall replaces Rust const-eval; rule has no Rust analogue.
Related: metaprog.metacall.forms
Source: src/compiler/sema_expr.cpp#L17090-L17178
metaprog.metacall.no-runtime-capture — metacall block/expr form cannot capture enclosing runtime locals
In the block and parenthesized-expr forms, every VAR_REF must resolve to a name introduced inside the operand (LET/FOR/FOR_EACH binding, or a match-arm pattern binding), a module-level const, or a known function (concrete or generic). A reference to an enclosing-scope runtime local is a compile error, since the metacall is evaluated at compile time with no access to surrounding locals.
Divergence: A1/A6: compile-time evaluation model specific to metacall.
Related: metaprog.metacall.const-resolver
Source: src/compiler/sema_expr.cpp#L17196-L17302
metaprog.metacall.args-ctfe-constant — Every argument of a metacall call form must be a compile-time constant
For the call form, each argument expression must be CTFE-evaluable to a constant literal; an argument that cannot be folded is a compile error. CALL stores arguments as a flat array, while GENERIC_CALL/STATIC_CALL wrap them as { ITEMS: [...] }.
Divergence: A1/A6: CTFE of metacall args; replaces Rust const-eval.
Related: metaprog.metacall.const-resolver
Source: src/compiler/sema_expr.cpp#L17334-L17359
metaprog.metacall.const-resolver — metacall argument CTFE resolves bare module-const idents
CTFE of metacall arguments and operands resolves a bare identifier naming a module-level const (collected into the module const-value map, including cross-package consts) to that const’s value, so expressions like metacall { THRESHOLD + 1 } fold.
Divergence: A1/A6: metacall const folding.
Related: metaprog.metacall.args-ctfe-constant, metaprog.metacall.no-runtime-capture
Source: src/compiler/sema_expr.cpp#L17311-L17332
metaprog.metacall.block-tail-required — metacall block must end in a tail expression
A metacall { ... } block must terminate with a tail expression (no trailing semicolon) so the metacall yields a value; a block lacking a tail expression is a compile error. The block’s value type is the type of that tail expression.
Divergence: A1/A6.
Related: metaprog.metacall.return-type
Source: src/compiler/sema_expr.cpp#L17366-L17389
metaprog.metacall.return-type — metacall result type must be primitive scalar, WritStatic, Writ, or ExprBlob
The type produced by a metacall operand must be a primitive scalar (bool; integer kinds i8/i16/i24/i32/i56/i64 and u8/u16/u24/u32/u56/u64; f32/f64; integer/float literal types), a &str / Slice<u8>, WritStatic, Writ (incl. Rc<Writ>), or ExprBlob. Any other result type is a compile error.
Divergence: A1/A6: WritStatic/Writ/ExprBlob returns are Logos additions.
Related: metaprog.metacall.exprblob-deferred-typing, metaprog.metacall.writ-autofreeze
Source: src/compiler/sema_expr.cpp#L17408-L17424
metaprog.metacall.exprblob-deferred-typing — ExprBlob-returning metacall defers result typing to the post-splice pass
When a metacall returns an ExprBlob (an AST-expression fragment marker), pass-1 typing is deferred: let X: T = metacall foo() accepts any annotated T over an ExprBlob RHS; the actual expression type is recovered after the driver splices the blob and pass-2 sema re-lowers it.
Divergence: A3/A6: ExprBlob is the Logos metaprog AST-fragment return.
Testability: untestable
Related: metaprog.writ-blob.ast-fragment-recurse
Source: src/compiler/sema_expr.cpp#L17400-L17407
metaprog.metacall.writ-autofreeze — Writ-returning metacall auto-freezes to WritStatic and is call-form only
A metacall whose operand returns a (mutable) Writ / Rc<Writ> is auto-frozen: user code observes the spliced value as WritStatic (the lowered expression is retyped to WritStatic). The Writ return type is supported only on the call form (metacall foo()); using it with the block or expr form is a compile error.
Divergence: A6: Writ/WritStatic is a Logos addition.
Related: metaprog.metacall.return-type
Source: src/compiler/sema_expr.cpp#L17537-L17568, src/compiler/sema_expr.cpp#L17597-L17603
metaprog.metacall.runtime-passthrough — metacall lowers as a runtime pass-through until driver-side splice
During sema iterations a metacall lowers to its operand’s lowered value (a pass-through), keeping the in-progress IR valid for borrow/type checks. The driver replaces the metacall AST node with the evaluated literal before the final non-metaprog sema pass, so this pass-through lowering never reaches code generation.
Divergence: A1/A6: compile-time splice model.
Testability: untestable
Related: metaprog.metacall.return-type
Source: src/compiler/sema_expr.cpp#L17606-L17610
metaprog.metacall.item-position — Item-position metacall
metacall <call-expr> ; is legal at module item position (top level). The named metafunction is invoked at compile time and must return an item blob; the items it produces are spliced into the program at the metacall’s position, replacing the metacall item itself.
metacall gen_items::<Foo>();
Source: tools/peg_gen_cpp/grammars/logos.peg#L581-L582, src/compiler/sema_impl.hpp#L4044-L4049
Metacall at item position
metaprog.metacall-item.callee-form — Item-position metacall callee must be a call expression
metacall <expr>; at item position requires <expr> to be a free-function call, a generic (turbofish) call, or a static-method call; any other expression form is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19980-L19989
metaprog.metacall-item.args-const-eval — Item-position metacall arguments must be compile-time constants
Every argument in an item-position metacall <callee>(args...) is evaluated via CTFE (with module-level named consts resolvable through lookup_const); an argument that fails to fold to a constant is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L19991-L20034
metaprog.metacall-item.return-type — Item-position metacall callee must return QuoteItemBlob or ItemList
The callee of an item-position metacall (after ordinary lowering/type-checking of the inner call) must have return type QuoteItemBlob or ItemList; any other return type is rejected.
Testability: diagnostic
Source: src/compiler/sema_expr.cpp#L20038-L20047
metaprog.metacall-item.turbofish-wstatic-source — WritStatic turbofish type-args re-rendered as source for the metacall thunk
When building the literal call text for an item-position metacall’s generic/static call, a turbofish type-argument that is a WritStatic literal (Foo::<@{...}>) is re-rendered from its parsed writ-literal AST back into @{...} source syntax, rather than through the ordinary type-to-string path (which would emit the non-reparseable internal form @hs_<hex>).
Uncertainty: Internal @hs_<hex> rendering form is inferred from the comment; the type_str() implementation itself is outside this slice.
Testability: behavioral
Source: src/compiler/sema_expr.cpp#L20050-L20126, src/compiler/sema_expr.cpp#L20140-L20151
Item emission from macro/metacall results
metaprog.item-emit.itemlist-iteration — ItemList-returning item macro/metacall emits each contained item
When an item-position fn_macro/token_macro call or item-position metacall resolves to a callee returning ItemList, the compiler synthesises a void thunk that iterates ItemList.blobs and, for each QuoteItemBlob element, emits it into the surrounding module (substituting captured identifiers) then releases its idents/blobs/cursors buffers.
Testability: behavioral
Source: src/compiler/sema_expr.cpp#L19709-L19736, src/compiler/sema_expr.cpp#L19904-L19930, src/compiler/sema_expr.cpp#L20196-L20222
metaprog.item-emit.quoteitemblob-single — QuoteItemBlob-returning item macro/metacall emits a single item
When an item-position fn_macro/token_macro call or item-position metacall resolves to a callee returning QuoteItemBlob (not ItemList), the compiler synthesises a void thunk that emits that single blob into the surrounding module (substituting captured identifiers) then releases its idents/blobs/cursors buffers.
Testability: behavioral
Source: src/compiler/sema_expr.cpp#L19737-L19757, src/compiler/sema_expr.cpp#L19931-L19950, src/compiler/sema_expr.cpp#L20223-L20241
Macro/handler discovery
metaprog.discovery.entry-body-skipped — Entry-file function bodies are skipped during discovery
During the metaprog discovery pass, function bodies of the entry file are not lowered (metaprog_mode); only signatures/items needed for trigger discovery are processed. Name mangling during discovery must stay consistent with the final pass.
Uncertainty: Exact set of skipped work is described only via comment.
Testability: untestable
Source: src/compiler/metaprog_dispatch.hpp#L94-L95, src/compiler/metaprog_dispatch.hpp#L82-L87
Macro expansion dispatch
metaprog.dispatch.fixpoint-iteration — Metaprog discovery iterates to fixpoint, capped at 16
Metaprogram item-generation runs as a discovery loop: each iteration re-lowers and fires triggers/metacalls that may emit new items; the loop repeats until an iteration emits nothing new (fixpoint), bounded by a hard cap of 16 iterations.
Uncertainty: Header doc comment; the loop body lives in the .cpp. Behavior of exceeding the cap (error vs silent stop) is not specified here.
Testability: transitive
Source: src/compiler/metaprog_dispatch.hpp#L94-L100
Macro handler registration
metaprog.handler.register — #[metaprog_handler(“trigger”)] registers a hook
#[metaprog_handler("trigger")] on a function registers (trigger, fn_name); the trigger is the annotation’s first positional string-literal argument. The host driver later scans user items for a matching #[trigger] annotation and invokes the registered fn on each. A missing/unresolvable trigger string is recorded as the sentinel <missing> so a later validation pass can surface the misuse as a diagnostic.
Divergence: A6: metaprog handler registration, Logos-only mechanism.
Source: src/compiler/sema_collect.cpp#L1837-L1873
Annotation-triggered expansion
metaprog.trigger.annotation-scan — top-level annotations matching a registered handler trigger are recorded as metaprog targets
After all modules complete field/variant/signature collection (so the metaprog-handler trigger registry is final), the compiler scans top-level ANNOTATION items in user (non-binary) ASTs; one or more consecutive annotations immediately preceding a non-annotation item, whose name matches a registered handler trigger, are each recorded as an (ast-index, item-byte-offset, trigger-name) metaprog target for the driver to dispatch against that following item.
Testability: transitive
Source: src/compiler/sema_collect.cpp#L577-L626
Derive-style annotation triggers
metaprog.derive.trigger-may-emit-items — Derive/handler hooks emit sibling items into the program
An annotated item bearing a #[derive_*] / metaprog-handler attribute invokes the corresponding handler during discovery; the handler may synthesize and splice additional top-level items into the program (alongside the annotated item).
Source: src/compiler/metaprog_dispatch.hpp#L94-L98, src/compiler/metaprog_dispatch.hpp#L1-L2
metaprog.derive.no-rust-derive-syntax — #[derive(...)] is rejected; use per-trait triggers
The Rust-style #[derive(Trait, ...)] attribute (a derive annotation carrying args) is not Logos surface syntax and is an error. Logos uses one trigger annotation per derive, #[derive_<trait>], paired with an in-scope #[metaprog_handler("derive_<trait>")] function.
Divergence: Logos replaces Rust #[derive(...)] with #[derive_<trait>] + #[metaprog_handler].
Source: src/compiler/sema_impl.hpp#L1762-L1774
Annotations (#[...]) — literal/argument forms and targets
metaprog.annot.literal-kinds — Annotation argument literal kinds
An annotation argument literal is one of: integer, float, bool, string, enum-variant (Name::Variant), or array of literals. Arrays are parsed recursively element-by-element.
#[A(42)]
#[A("s")]
#[A(Color::Red)]
#[A([1, 2, 3])]
Source: src/compiler/sema.cpp#L7165-L7229
metaprog.annot.float-suffix-strip — Float annotation literal: strip underscores and f32/f64 suffix
A float annotation literal has all ‘_’ separators removed and an optional trailing ‘f32’/‘f64’ suffix stripped before parsing to a double.
#[A(3.14f64)]
#[A(1_000.5)]
Source: src/compiler/sema.cpp#L7173-L7180
metaprog.annot.string-escapes — String annotation literal escape decoding
A quoted string annotation literal has surrounding quotes stripped and escapes \n \t \r \ " \0 decoded; an unrecognised escape is preserved verbatim (backslash + char). A raw string r"..." strips the r" prefix and trailing quote with no escape processing.
#[A("line\n")]
#[A(r"raw\n")]
Source: src/compiler/sema.cpp#L7186-L7217
metaprog.annot.no-args-bare — Bare annotation #[A] takes no arguments
An annotation written #[A] (no ARGS, no VALUE) produces an instance with an empty argument list.
#[A]
Source: src/compiler/sema.cpp#L7249-L7250
metaprog.annot.eq-value-first-field — #[A = lit] binds first field
An annotation #[A = lit] maps the single value to the annotation datatype’s first declared field; if the datatype has no fields it is an error (‘annotation takes no arguments’).
#[A = 5]
Source: src/compiler/sema.cpp#L7252-L7261
metaprog.annot.positional-by-order — Positional annotation args bind by declaration order
Positional arguments in #[A(arg, ...)] bind to the annotation datatype’s fields in declaration order; supplying more positional args than fields is an error (‘takes at most N positional args’).
#[A(1, 2)]
Source: src/compiler/sema.cpp#L7282-L7292
metaprog.annot.named-by-field-name — Named annotation args bind by field name
A named argument key=value in #[A(...)] binds to the field whose name equals the key; an unknown field name is an error (‘has no field’) but does not abort parsing of remaining args.
#[A(name = 3)]
Source: src/compiler/sema.cpp#L7271-L7281
metaprog.annot.user-annotation-requires-marker — User annotation NAME must be an #[annotation]-marked datatype
An annotation whose name is not a compiler-internal key is treated as a user annotation only if NAME resolves to a registered datatype carrying the IS_ANNOTATION_TYPE marker; otherwise it is silently ignored.
Related: metaprog.annot.struct-flags-set-only
Source: src/compiler/sema.cpp#L7327-L7336
metaprog.annot.accumulate-until-item — Annotations and doc-comments accumulate until next item
Annotation nodes accumulate in a pending buffer and are consumed by the next non-annotation item; doc-comment lines/blocks (///, /**, //!, /*!) similarly accumulate. Both buffers are cleared after each item.
Testability: transitive
Source: src/compiler/sema.cpp#L7304-L7305, src/compiler/sema.cpp#L7424-L7458, src/compiler/sema.cpp#L8055-L8058
metaprog.annot.type-code-on-struct — #[type_code=N] on struct/datatype sets type code
#[type_code=N] on a struct or datatype sets the type’s TYPE_CODE to N and registers N under the fully-qualified name (pkg::Name) in the explicit-type-code table so type_code_of::<T>() resolves cross-package.
#[type_code=42] datatype Foo { }
Source: src/compiler/sema.cpp#L7313-L7323
metaprog.annot.tag-dispatch-on-trait — #[tag_dispatch(system)] on trait sets dispatch system name
#[tag_dispatch(system_name)] on a trait records system_name as the trait’s TAG_DISPATCH_SYSTEM, identifying the tagged-dispatch system the trait participates in.
Source: src/compiler/sema.cpp#L7367-L7376
metaprog.annot.type-code-on-trait-genos — #[type_code=N] on trait marks it a genos
#[type_code=N] on a trait sets the trait’s TYPE_CODE: the code names the logical datatype family, and each impl Trait for Eidos propagates it to the target struct during lowering.
Source: src/compiler/sema.cpp#L7377-L7383
metaprog.annot.type-code-on-template-genos-forbidden — #[type_code] forbidden on parametric (template) genos
#[type_code=N] on a parametric genos (trait with non-empty type params) is an error; the code must be attached to a concrete specialization, else every specialization would collide in the same tag-system slot.
Source: src/compiler/sema.cpp#L8040-L8048
metaprog.annot.struct-flags-set-only — Structural struct flags are set-only (monotonic)
Structural attributes #[zone_mut], #[zoned2], #[rel_ptr], #[self_describing], #[borrow_carrying] each set the corresponding boolean struct flag when present; a present flag can only set, never clear, the base value.
Testability: untestable
Source: src/compiler/sema.cpp#L7344-L7360
metaprog.annot.value-int-or-enum-variant — Attribute = value must be an integer or enum variant
The right-hand value of an attribute #[name = V] read as an integer must be either an integer literal (#[name = 123]) or an enum variant path (#[name = Enum::Variant], resolving to that variant’s discriminant). Any other value form is an error; an unknown enum or unknown variant is also an error.
#[type_code = 42]
#[type_code = Tag::Foo]
Source: src/compiler/sema_impl.hpp#L1372-L1398
metaprog.annot.builtin-target-validity — Builtin attribute must match its allowed targets
A builtin attribute is valid only on item kinds in its allowed target set; applying a builtin attribute to a target outside that set is an error. Non-builtin attribute names are not diagnosed at this phase (may be user annotations, cross-module metaprog triggers, or deferred typo checks).
Source: src/compiler/sema_impl.hpp#L1775-L1790
metaprog.annot.type-code-not-on-generic — #[type_code] forbidden on generic items
#[type_code] may not be applied to an item that has type parameters; a type code must be assigned per concrete instantiation, not on the generic definition.
Source: src/compiler/sema_impl.hpp#L1792-L1797
metaprog.annot.type-code-reserved-range — type_code values 1..128 reserved for stdlib
Explicit #[type_code=N] values with 1 <= N <= 128 are reserved for stdlib primitive tags (TypeTagSystem); user code outside package std/std.* using a reserved value is warned and should use codes >= 129.
Testability: untestable
Source: src/compiler/sema_impl.hpp#L1798-L1811
cfg!/#[cfg(...)] — configuration predicates
metaprog.cfg.key-value-predicates — cfg key=value predicate resolution
A cfg key=value predicate matches against compile-target metadata: target_arch, target_os, target_endian, target_family, target_pointer_width resolve to the host/target platform values; feature = "name" matches iff name is in the active feature set. Any unknown key evaluates to false.
Divergence: Unknown-key-false matches Rust per inline comment.
Source: src/compiler/sema.cpp#L3507-L3517, src/compiler/sema.cpp#L3572-L3575
metaprog.cfg.bare-flag-predicates — cfg bare-identifier flag resolution
A cfg bare identifier matches as: unix/windows against target_family; test/debug_assertions against the feature set; otherwise as a feature-like flag checked against the active feature set.
Source: src/compiler/sema.cpp#L3519-L3528, src/compiler/sema.cpp#L3582
metaprog.cfg.combinators — cfg all/any/not combinators and boolean literals
cfg predicates compose: all(p...) is the AND of its children, any(p...) the OR, not(p) requires exactly one child and negates it (else error/false). The literals cfg(true)/cfg(false) evaluate to true/false directly. Unknown combinators evaluate to false / raise an error in attribute position.
Divergence: cfg(true)/cfg(false) per Rust 1.80 RFC 3695 (noted inline).
Source: src/compiler/sema.cpp#L3553-L3582, src/compiler/sema.cpp#L3692-L3708, src/compiler/sema.cpp#L3721-L3737
metaprog.cfg.attr-multi-arg-implicit-and — cfg attribute multi-arg implicit AND
In #[cfg(...)] attribute position, a top-level multi-argument list is an implicit AND of its arguments; #[cfg] with no args matches (true).
Divergence: Multi-arg implicit AND matches Rust (noted inline).
Source: src/compiler/sema.cpp#L3654-L3673
metaprog.cfg.cfg-attr-splice — cfg_attr predicate-gated attribute splicing and item drop
#[cfg_attr(pred, attrs...)] activates first: when pred is true its wrapped attrs are spliced into the item’s annotation list, when false the entry is dropped (wrapped attrs are NOT re-fed to cfg_attr). After activation, every plain #[cfg(...)] is evaluated; an item is dropped from compilation iff any of its cfg predicates is false. The drop gate applies uniformly to both the collection and lowering walks.
Source: src/compiler/sema.cpp#L3606-L3652
Built-in macros
metaprog.builtin-macro.vec-desugar — vec![...] desugars via a synthesized vec_from_arr call
The vec![...] built-in macro’s synthesized body is re-parsed as the tail expression of an ephemeral fn __f() -> i32 { <wrap_body> } and lowered in the current context, routing vec![e1, e2, ..] through a synthesized vec_from_arr([e1, e2, ..]) call; a re-parse failure produces error_expr() after a diagnostic.
Testability: behavioral
Source: src/compiler/sema_impl.hpp#L4032-L4039
Template declarations
metaprog.template.decl — Template declaration
template <item> wraps a struct/enum/datatype/trait/impl/fn declaration as inert data (an AST blob) rather than a real binding; the inner names are never registered by sema, so referencing the template as a type yields the ordinary unknown-type diagnostic. Templates are consumed later by metafunctions via apply/metacall.
template struct Pair<A,B> { a: A, b: B }
Divergence: No Rust equivalent.
Source: tools/peg_gen_cpp/grammars/logos.peg#L625-L640
Variadic tuple intrinsics
metaprog.variadic.tuple-all-eq — __tuple_all_eq__::<T>(a, b) expands to an &&-chain of elementwise eq
tuple_all_eq with type-arg T substituted to a concrete Tuple and exactly 2 args (a, b) expands to the left-associated &&-chain of per-element equality a.i.eq(&b.i): a Tuple-kind element inlines a recursive chain over the nested field refs (never re-emits tuple_all_eq, since mono does not re-process synthesized intrinsics); a Slice-kind element (canonicalized to element name “str” when it is Slice<u8>) is compared via a direct 2-arg by-value free-function call; any other element resolves a callee symbol by scanning out_.functions then in_.functions for a name containing <elem-type-str>__eq__f__ at a .-delimited-or-leading boundary (falling back to <elem-type-str>__eq) and calls it as a.eq(&b) via method_call. When T is missing/non-Tuple or fewer than 2 args are present, the intrinsic degenerates to the boolean literal true (also the result for an empty tuple).
Divergence: A6
Uncertainty: Symbol resolution by substring scan is an implementation detail; the observable language rule is the &&-chain elementwise-Eq semantics.
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2488-L2584
metaprog.variadic.tuple-each-field-debug — __tuple_each_field_debug__::<T>(self, f) expands variadic tuple Debug
tuple_each_field_debug with type-arg T substituted to a concrete Tuple and exactly 2 args (self, f: &mut Formatter) expands to a fmt_seq-chained Result: fmt_tuple_open(f), then per element i in order: fmt_tuple_sep(f) folded in when i>0, then either (Tuple-kind element) an inline recursive build over the nested field ref, or (otherwise) a Debug-fmt call resolved as a symbol containing <elem-type-str>__Debug__fmt__f__ else <elem-type-str>__fmt__f__ at a .-delimited-or-leading boundary, else literally <elem-type-str>__fmt (elem-type-str = type_str(elem), with Slice<u8> canonicalized to “str”), invoked as field.fmt(f); finally fmt_tuple_close1(f) if the tuple arity is 1, else fmt_tuple_close(f); each step is folded into the running chain via fmt_seq. Every reuse of the shared f argument after its first use goes through a reborrow wrapper (AddrOfTemp(Deref(f))), since &mut Formatter is a move-type under borrow-check and a bare reuse would consume it on the first call. When T is missing/non-Tuple or fewer than 2 args are present, the intrinsic degenerates to a single close-call.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2591-L2683
format!/println! family — macro expansion shape
metaprog.format.literal-string-checked — Format-family macros validate a literal format string at compile time
For the format family (format, print, println, eprint, eprintln, panic, format_args_str) and the write family (write, writeln), when the format-string argument is a string literal it is parsed and validated at sema time (brace balance, placeholder structure). A non-literal format-string argument skips the check (mirroring Rust format_args!). The format string is arg[0] for the format family and arg[1] for the write family.
Source: src/compiler/sema_expr.cpp#L18683-L18728, src/compiler/sema_expr.cpp#L18701-L18722
metaprog.format.arity-check — Format placeholder count must match the argument count
When no explicit {N} index appears, the number of value placeholders in the format string must equal the number of value arguments provided (args after the format string, and after the sink for write/writeln). When explicit indices are used, the number of value arguments must be at least max(explicit-index)+1. Otherwise the macro is rejected.
Source: src/compiler/sema_expr.cpp#L18754-L18792
metaprog.format.placeholder-trait-dispatch — Each format placeholder dispatches to its format-trait method
Each format placeholder lowers to a call dispatching to the format trait selected by the placeholder’s trait kind (e.g. {} -> Display, {:?} -> Debug, plus hex/oct/bin/exp variants), invoked on the argument value with a &mut Formatter. Per-placeholder format spec fields (fill, alignment, sign, alternate, zero-pad, width, precision) are applied to the Formatter before each dispatch and reset between placeholders.
Source: src/compiler/sema_expr.cpp#L18843-L18918
metaprog.format.expansion-shape — Format-family expansion builds a String/Formatter block
format!/format_args_str! expand to a block yielding a String built via a Formatter over a __buf. The print family (println/print/eprintln/eprint) appends a drain of the buffer to stdout/stderr; panic! drains to a panic. write!/writeln! build a Formatter directly over the sink (via (sink).as_formatter()), stream placeholders into it, and yield Result<(),Error> (always Ok — per-placeholder errors are discarded); writeln! additionally writes a trailing newline.
Source: src/compiler/sema_expr.cpp#L18828-L18941
metaprog.format.requires-format-arg — Format/write macros require a format-string argument
A format-family or write-family invocation must supply at least the format-string argument; an empty argument list is an error.
Testability: untestable
Source: src/compiler/sema_expr.cpp#L19011-L19014
Format-spec mini-language
metaprog.fmt.trait-spec-types — Format-spec type characters select a formatting trait
In a format!-family format string, a placeholder’s trailing spec type char selects a formatting trait: no type / absent = Display ({}), ? = Debug ({:?}), x = LowerHex, X = UpperHex, o = Octal, b = Binary, e = LowerExp, E = UpperExp. Each placeholder lowers to a call of that trait’s method on the argument rather than a variadic runtime path.
Uncertainty: LowerExp/UpperExp marked ‘(future)’ in source; presence in enum implies recognized but possibly not yet lowered.
Source: src/compiler/sema_fmt.hpp#L20-L29, src/compiler/sema_fmt.hpp#L52
metaprog.fmt.spec-alignment — Format-spec alignment characters
A format placeholder spec may carry an alignment: < = Left, > = Right, ^ = Center; when unspecified the default alignment is chosen per formatting trait.
Source: src/compiler/sema_fmt.hpp#L31-L36, src/compiler/sema_fmt.hpp#L46
metaprog.fmt.spec-fill-and-flags — Format-spec fill, sign, alternate, and zero flags
A format placeholder spec supports: a fill char (default space) used with alignment; a sign flag (+ = always show sign, default none); an alternate flag # (emit base prefix 0x/0o/0b); and a zero flag 0 (pad with zeros, overridden by an explicit fill char).
Source: src/compiler/sema_fmt.hpp#L38-L42, src/compiler/sema_fmt.hpp#L45-L49
metaprog.fmt.spec-width-precision — Format-spec width and precision
A format placeholder spec may set width and precision; both are unset by default (sentinel -1), and when set precision >= 0 denotes an exact precision.
Source: src/compiler/sema_fmt.hpp#L50-L51
metaprog.fmt.placeholder-arg-selection — Placeholder argument selection: positional, explicit index, or named
A format placeholder binds to an argument in one of three modes: positional auto-counter ({} consuming 0,1,2,… in order), explicit index ({N}), or named ({name}). Arity validation against positional args applies only when no placeholder uses the named or explicit-index form.
Source: src/compiler/sema_fmt.hpp#L55-L66, src/compiler/sema_fmt.hpp#L71-L75
metaprog.fmt.string-segmentation — Format string parses into literal text + typed placeholder segments
A format string body (without surrounding quotes) parses into an ordered sequence of segments, each either a literal-text run or a placeholder (carrying its argument selector and spec). Parsing is best-effort: a soft issue emits a diagnostic and continues, while a hard parse error halts at the broken placeholder and sets a failure flag.
Source: src/compiler/sema_fmt.hpp#L68-L89
Writ literal capture (rodata embedding)
metaprog.writ-lit.size-prefixed-rodata — Capture-free @-literal lowered to size-prefixed rodata
An @-literal (Writ literal) without runtime captures is serialized to a constant blob laid out as [u64 little-endian size][bytes]; the materialized handle points to the payload (8 bytes past the size prefix), so size() reads *(ptr - 8).
Source: src/compiler/mlir_gen_expr.cpp#L6151-L6196, src/compiler/mlir_gen_expr.cpp#L6234-L6267
metaprog.writ-lit.content-keyed-dedup — Identical @-literal blobs share one global (one address)
Two capture-free @-literals with byte-identical serialized (size-prefixed) content share a single rodata global, and therefore compare equal by address; capture-bearing @-literals are never deduplicated.
Source: src/compiler/mlir_gen_expr.cpp#L6199-L6202, src/compiler/mlir_gen_expr.cpp#L6247-L6249, src/compiler/mlir_gen_expr.cpp#L6270-L6271
metaprog.writ-lit.capture-zone-alloc-kinds — Capture kinds requiring zone allocation
An @-literal capture requires zone allocation (rather than an inline WAny word) iff its type is F64/F32/FloatLit, a raw pointer (treated as null-terminated C-string), a slice &[u8]/str (ptr+len), or struct StringView; all other (scalar/AnyVal) captures are stored as inline WAny value words.
Testability: untestable
Source: src/compiler/mlir_gen_expr.cpp#L6311-L6324, src/compiler/mlir_gen_expr.cpp#L6404-L6459
metaprog.writ-lit.float-capture-widens-to-f64 — Float captures widen to f64
A zone-allocated float capture is stored as f64: an f32 value is widened via float-extension, and a FloatLit (untyped float literal) defaults to f64.
Testability: untestable
Source: src/compiler/mlir_gen_expr.cpp#L6407-L6422
Writ AST-blob fragments
metaprog.writ-blob.ast-fragment-recurse — WRIT_BLOB carrying an AST-category root lowers as that expression
A WRIT_BLOB whose serialized root TinyMap has schema category CAT_AST and whose variant code is a supported expression node (BINOP, LIT_INT, LIT_BOOL, LIT_STR, VAR_REF, CALL, PAREN_EXPR, UNARY, FIELD_READ, METHOD_CALL, CAST, INDEX_READ, STRUCT_LIT, ARR_LIT, TUPLE_LIT, BLOCK, BLOCK_STMT, IF) is lowered by recursively type-checking that root node as an ordinary expression, yielding its recovered expression type. The blob’s arena is retained for the lifetime of sema.
Divergence: A6: Writ/metaprog is a Logos addition (ExprBlob AST fragments spliced from metafunctions).
Related: metaprog.writ-blob.opaque-static-fallback
Source: src/compiler/sema_expr.cpp#L17019-L17050
metaprog.writ-blob.opaque-static-fallback — Non-AST WRIT_BLOB lowers to an opaque WritStatic literal
A WRIT_BLOB whose root is null, non-TinyMap, or not of an AST expression category is lowered to an opaque data literal of type WritStatic carrying the raw blob bytes verbatim.
Divergence: A6: WritStatic is a Logos addition.
Testability: untestable
Related: metaprog.writ-blob.ast-fragment-recurse
Source: src/compiler/sema_expr.cpp#L17056-L17060
Writ type embedding
metaprog.writ-type.embed-as-first-class — <type:T> embeds a Type as a first-class Writ value
<type:T> inside a Writ @-literal (WRIT_TYPE_LIT, NAME = bare type identifier) is sema-embedded as a first-class Writ Type value, i.e. a type usable as ordinary literal data rather than only in type position.
Uncertainty: Comment marks this “slice 1: IDENT only” — the grammar accepted here may not cover the full type-expression surface for this position.
Testability: behavioral
Source: tools/peg_gen_cpp/grammars/logos.peg#L280
AnyVal inline value-form encoding
metaprog.anyval.bool-value-form — AnyVal bool value-form encoding
A bool coerced to an inline AnyVal word encodes as (b << 8) | 5 where b in {0,1} (low byte = (WA_BOOL=2)<<1 | 1 = 5).
Uncertainty: Bit-layout is a data-substrate (Writ/Memoria) encoding, not a Rust-language construct.
Source: src/compiler/mlir_gen_expr.cpp#L6002-L6010
metaprog.anyval.int-i24-value-form — AnyVal small-integer value-form (i24 niche)
Any integer type of width <=32 bits (i8/i16/i32/u8/u16/u32/i24/u24) coerced to an inline AnyVal word encodes as ((v & 0xFFFFFF) << 8) | 0x2F (24-bit payload, type tag 0x2F).
Source: src/compiler/mlir_gen_expr.cpp#L6011-L6022
metaprog.anyval.int64-truncates-to-i24 — AnyVal 64-bit integer truncates to 24 bits
An i64/u64 coerced to an inline AnyVal word is truncated to its low 24 bits and embedded as the i24 niche ((v & 0xFFFFFF) << 8) | 0x2F; values outside [-2^23, 2^23) lose their high bits in the inline form.
Uncertainty: Lossy truncation is the inline-AnyVal fallback; full-range values require the zone-alloc (C5) path elsewhere.
Source: src/compiler/mlir_gen_expr.cpp#L6023-L6032
metaprog.anyval.float-ptr-not-inline-capturable — Floats and pointers are not inline-capturable as AnyVal
F32/F64 and pointer/reference types (Ptr/Ref/MutRef) have no inline AnyVal value-form; coercing one yields a null (zero) AnyVal word, and such types must instead use zone-allocated capture encoding.
Testability: untestable
Source: src/compiler/mlir_gen_expr.cpp#L6033-L6040
metaprog.anyval.identity-passthrough — AnyVal value passes through as its first word
Coercing a value already of struct type AnyVal to an AnyVal word extracts its leading word (field 0) unchanged.
Testability: untestable
Source: src/compiler/mlir_gen_expr.cpp#L6041-L6048
WAny inline value-form encoding
metaprog.wany.bool-value-form — WAny bool value-form encoding
A bool coerced to an 8-byte WAny value word encodes as (b << 8) | 5 (low byte = (WA_BOOL=2)<<1 | 1).
Source: src/compiler/mlir_gen_expr.cpp#L6055-L6073
metaprog.wany.int-i56-value-form — WAny integer value-form (i56 niche)
Any integer type up to 64 bits (i8..i64/u8..u64/i24/u24) coerced to an 8-byte WAny word encodes as (v << 8) | 3 (payload in the high 56 bits, tag = (WA_I56=1)<<1 | 1 = 3).
Source: src/compiler/mlir_gen_expr.cpp#L6074-L6082
metaprog.wany.identity-passthrough — WAny passthrough and AnyVal zero-extension into WAny
A value already of enum type WAny passes its niche word through unchanged (extracted if struct-typed). A legacy struct AnyVal coerced to WAny is its leading 4-byte word zero-extended to 64 bits (the i24/bool Pod encodings coincide in the low 32 bits).
Testability: transitive
Source: src/compiler/mlir_gen_expr.cpp#L6083-L6104
Type-reflection intrinsics
metaprog.type-intrinsic.apply-generic — apply_generic instantiates a template struct type
For apply_generic(tmpl_name, arg0..argN), each arg expr is resolved to a compile-time TypeRef via the type-producer-recover protocol (unresolvable arg aborts compilation with a diagnostic); the intrinsic builds LogosType::Struct{name=tmpl_name, type_args=[recovered...]} (pkg inherited from the matching struct def in out_.structs), interns the type, and replaces the call with that type’s Type reflection value.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2191-L2239
metaprog.type-intrinsic.type-producer-recover — Type-producer recovery protocol for type-apply intrinsics
To resolve a LIR sub-expr to a compile-time TypeRef (shared by apply_generic/tuple_type_apply/array_type_apply): (1) chase through VarRef by looking up type_let_inits_, up to 8 hops; (2) if the result is a Call to typelist_nth or typelist_head, substitute its type-arg[0] (a type-list) through the current subst map and index into its type_args pack at the literal-int index given as typelist_nth’s value arg (typelist_head implicitly index 0); out-of-range index yields no type; (3) if the result is a StructLit with a field named “uid” whose initializer is a Call to type_uid_of, recover that call’s type-arg[0] substituted through the subst map; (4) otherwise recovery fails (empty TypeRef).
Divergence: A6
Testability: transitive
Source: src/compiler/mono_clone.cpp#L2152-L2189, src/compiler/mono_clone.cpp#L2255-L2315
metaprog.type-intrinsic.tuple-type-apply — tuple_type_apply([Type;N]) builds a tuple type
tuple_type_apply(arr) requires arg0 to chase to an ArrLit (a non-ArrLit is a compile-time abort); each element is resolved via type-producer-recover (unresolvable element aborts); the intrinsic yields LogosType::Tuple{tuple_elems=[recovered...]}'s Type reflection value.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2244-L2246, src/compiler/mono_clone.cpp#L2316-L2341
metaprog.type-intrinsic.array-type-apply — array_type_apply(Type, N) builds an array type
array_type_apply(elem, size) requires elem to resolve via type-producer-recover (unresolvable is a compile-time abort) and size (after chase) to be a LitInt literal (a non-literal is a compile-time abort); the intrinsic yields LogosType::Array{elem=recovered, arr_size=size}'s Type reflection value.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2244-L2246, src/compiler/mono_clone.cpp#L2342-L2361
metaprog.type-intrinsic.type-reflection-value — Type reflection value shape and uid
A reflected Type value (yielded by apply_generic/tuple_type_apply/array_type_apply) is the struct literal Type{kind:u32=T.kind(), name:&[u8]=type_str(T), size:i64=size_of(T), align:i64=align_of(T), uid:u64} where uid = type_hash_64bit(type_hash_23(type_id_canon(T))); T is registered into out_.type_pool and uid_to_type_[uid]=T so a later type_uid_of lookup on that uid recovers T.
Divergence: A6
Testability: transitive
Source: src/compiler/mono_clone.cpp#L2213-L2238, src/compiler/mono_clone.cpp#L2364-L2387
Enum-reflection intrinsics
metaprog.enum-intrinsic.variant-count-of — __variant_count_of__::<E>() yields variant count
variant_count_of with type-arg E evaluates to a literal i64 equal to the variant count of E’s enum definition when E resolves to a known LogosType::Enum with a matching def in in_.enums, else 0.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2395-L2417
metaprog.enum-intrinsic.variant-names-of — __variant_names_of__::<E>() yields variant names
variant_names_of with type-arg E evaluates to a fixed-size array literal of each variant’s name (string literal), in declaration order; empty array if E does not resolve to a known enum def.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2395-L2407, src/compiler/mono_clone.cpp#L2421-L2425, src/compiler/mono_clone.cpp#L2470-L2477
metaprog.enum-intrinsic.variant-payload-counts-of — __variant_payload_counts_of__::<E>() yields per-variant payload arities
variant_payload_counts_of with type-arg E evaluates to a fixed-size array literal of i64, one per variant in declaration order, each equal to that variant’s payload-field count; empty array if E does not resolve to a known enum def.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2426-L2431, src/compiler/mono_clone.cpp#L2470-L2477
metaprog.enum-intrinsic.variant-payload-types-flat-of — __variant_payload_types_flat_of__::<E>() yields flattened payload-type reflections
variant_payload_types_flat_of with type-arg E evaluates to a flat fixed-size array literal of Type reflection values (per metaprog.type-intrinsic.type-reflection-value), one per payload type across all variants in declaration order (variant-major, field-minor), each payload type substituted through the current instantiation’s subst map; empty array if E does not resolve to a known enum def.
Divergence: A6
Testability: behavioral
Source: src/compiler/mono_clone.cpp#L2432-L2469
Named resource item declarations
metaprog.resource.named-item-decl — Named resource item declaration
resource NAME = CALLEE!{ body }; desugars to the same FN_MACRO_CALL_ITEM node as an item-position fn-macro, but the left-hand NAME rides in the (otherwise-unused) NAME slot: sema passes it to the #[token_macro] callee as a second str arg so the emitted item is named by NAME, not by a fn <name> header inside body. A parameterized form resource NAME = CALLEE!(PARAMS){ body }; additionally captures the parenthesized list as balanced raw source text (PARAMS), passed as a third arg; the callee re-emits it verbatim as the generated fn’s real parameter list, so PARAMS is parsed and type-checked when that fn is compiled. The parameterized alternative is tried first (ordered choice; the extra paren group makes it strictly more specific, so plain resource N = h!{ ... } is unaffected).
resource conn = open_conn!{ ... };
resource h = handler!(req: Request){ ... };
Divergence: no Rust equivalent (ADR 0012 §6 named resource-macro item).
Source: tools/peg_gen_cpp/grammars/logos.peg#L582-L602
Statement-level repeat groups
metaprog.stmt.repeat-group — Statement repeat-group #(stmt)*
#(stmt)* at statement position is a metaprogram repeat-group over a statement, expanding to a compile-time-repeated sequence of statements (mirrors the analogous repeat-group forms in type-argument and pattern-group positions).
Divergence: A3
Testability: transitive
Source: tools/peg_gen_cpp/grammars/logos.peg#L1900-L1901
ABI interaction
metaprog.abi.macro-hook-excluded — fn_macro/token_macro hooks excluded from ABI surface
A function attributed #[fn_macro] or #[token_macro] is a compiler-invoked metaprogramming hook (discovered by attribute and invoked by the metaprog driver), not a linkable consumer API entry point; it is flagged as a macro hook so ABI-surface extraction (--emit-abi) excludes it from the public pub-allowlist, preventing its churning signature from tripping the ABI-compat gate.
Testability: transitive