Skip to content
Beskid Platform specification

Beskid

Jump to a Beskid service

Beskid

Jump to a Beskid service

Language macros

Platform spec node

Language macros

Spec standingStandard

Owner
Piotr Mikstacki
Submitter
Piotr Mikstacki

Role

Language macros are a first-class module item in App, Lib, Test, and Mod projects. They provide Rust-style macro rules ergonomics (name!) with Beskid declaration style (macro name (fragment param, ...) { ... }).

Expansion is structural: the compiler substitutes captured syntax fragments for $param references in the macro body. v1 does not interpret arbitrary Beskid statements at compile time and does not emit source text.

Definition syntax

pub macro sayHi (block body, expression message)
{
Console.WriteLine($message);
$body
}
ElementRule
Keywordmacro introduces MacroDefinition as an InnerItem.
VisibilityOptional pub; obeys modules and visibility.
ParametersParenthesized, comma-separated fragmentKind name pairs (see fragment kinds).
BodySingle braced Block of normal Beskid statements and expressions.
Metavariables$name in the body references a parameter; legal only inside macro definitions.

Cross-module use: pub macro plus normal use / path qualification (see name-resolution hub).

Invocation syntax

Macros are invoked with a postfix ! on the macro name:

FormBinding
sayHi!()Zero arguments (unit / empty actual list).
sayHi!(expr)Positional arguments in definition order inside !(...).
sayHi! { stmts }Braced block actual (typical for block parameters).
sayHi!(a, b)Multiple positional arguments in one !(...).

MacroInvocation is an expression form: identifier + ! + optional parenthesized argument list + optional braced block.

Disambiguation! after an identifier is a macro invocation only when a macro binding resolves in scope. If no binding exists, the compiler emits E1901 (unknown macro). Future non-macro uses of ! must not collide with this rule.

Item-position macros — When a parameter is item, the invocation may appear at module item position (for example my_macro!(type Wrapper { pub i32 x; })), expanding to one or more InnerItem nodes spliced into the enclosing item list.

Fragment kinds (v1)

Closed vocabulary for parameter kinds:

KindAccepted syntax root
blockBlock
expressionExpression
statementStatement
typeType (surface BeskidType)
identifierIdentifier
literalLiteral or LiteralExpression
patternPattern
pathPath
itemany InnerItem
nodeany Node (escape hatch; discouraged in user code)

Mismatch between actual argument shape and declared kind → E1903 at the argument span.

Expansion model

  1. Resolve the invocation to a MacroDefinition in scope.
  2. Validate arity and fragment kinds for each actual argument.
  3. Deep-copy each captured fragment and substitute for every $param occurrence in the macro body (fresh node identities; diagnostic spans trace to expansion sites where required).
  4. Splice the resulting statements or expression in place of the MacroInvocation.
  5. Repeat until no ! invocations remain or maxMacroExpansionDepth is reached (E1905).

Depth cap — Default 32 per compilation unit generation; independent of maxGeneratorRounds on mod projects.

Well-formedness — Expanded trees must pass the same structural checks as authored syntax before semantic analysis proceeds.

Mod interaction — After mod.generate merges typed contributions and the host re-parses, macro.expand runs again inside the generator replay loop so mod-emitted code can contain macro invocations.

Relationship to Compiler Mod SDK

MechanismOwnerWhen
Language macro itemsCompiler intrinsic macro.expandAny project kind; no AOT mod artifact
Mod Collector / GeneratorMod hosttype: Mod packages; cross-cutting codegen

Mods may define pub macro items like any library. Mod contracts do not replace language macros, and language macros do not register in mod.descriptor.json.

See Compiler Mod SDK.

Non-goals (v1)

  • Repetition operators $(...)*, $(...)+, $(...)? (see Proposed roadmap only).
  • String formatting or source-text emission from macro bodies.
  • Compile-time execution of arbitrary Beskid statements (bodies are templates, not interpreted functions).
  • Mod-only or manifest-registered macro discovery.

Examples

Block wrapper

macro logged (block body)
{
Console.WriteLine("enter");
$body
Console.WriteLine("leave");
}
unit Main()
{
logged! {
Console.WriteLine("work");
}
}

Expression passthrough

macro identity (expression value)
{
$value
}
let x = identity!(1 + 2);