1. Abstract
This RFC defines Aura’s macro and compiler plugin story: hygienic token-tree expansion, attribute derives, and an out-of-process procedural macro protocol with sandboxing, lockfile checksums, and a versioned plugin ABI. The compiler keeps unsupported or unsandboxed plugin execution fail-closed.
2. Motivation
2.1 Problem statement
Users need derives (Equals, Debug), test registration, and boilerplate reduction. Unrestricted in-process plugins are a supply-chain nightmare; no macros at all force painful codegen external to the language.
2.2 Why now
Compiler expansion points (RFC-004) and attributes (RFC-009) need a concrete macro model.
2.3 Success metrics
| Metric | Target |
|---|---|
| MVP derives | Equals/Hash/Debug work |
| Hygiene | No accidental capture of user locals |
| Safety | Proc plugins cannot read arbitrary FS by default |
3. Goals
- Declarative macros + derive attributes in MVP.
- Hygienic expansion by default.
- Clear phase ordering with typecheck.
- Path to sandboxed procedural macros.
4. Non-goals
- Arbitrary compiler modification plugins in MVP.
- Unhygienic text-paste macros as the default.
- Full Template Haskell power day one.
5. Prior art & alternatives
| System | Notes | Take |
|---|---|---|
| Rust macros | decl + proc, hygiene | Primary inspiration |
| Lisp macros | Powerful | Too free-form |
| Java APT | Build-time processors | Phase inspiration |
| C macros | Textual | Reject |
6. Design
6.1 Overview
| Feature | MVP | Later |
|---|---|---|
Declarative macro! / macro_rules-style | Yes | — |
@derive(TraitLike) | Yes | — |
| Attribute macros (custom) | Limited builtins | User proc |
| Proc macros (Rust dylib / WASM sandbox) | No | Yes |
| In-process arbitrary plugins | No | Maybe never |
6.2 Declarative macros
macro! vec {
() => { Vec.new() };
($($x:expr),* $(,)?) => { /* ... */ };
}- Pattern-based; expands to tokens/AST.
- Hygienic identifiers by default; explicit
span/unhygienicopt-in rare. - Invoked as
vec!(1, 2, 3)or function-like form; exact grammar amended before implement (derives ship first). - Packaging: macros live in normal packages (no separate macro package kind); consumers depend like any library.
- Exported declarative macro names are unique within a resolved package graph;
the loader rejects duplicate names before expansion so dependency order cannot
change which definition runs. Plugin executable provenance remains tied to the
root package manifest and is never inherited implicitly from dependencies.
A dependency manifest that declares
[macro_plugins]is rejected during graph loading rather than silently dropping that executable declaration; root plugin paths must stay package-relative without parent traversal, and root plugin executables are pinned bymacro_plugin.<Name>lock entries with SHA-256 checksums. Dependency plugins require a future provenance policy. - Template-introduced bindings and item names receive an invocation-local gensym resolved through lexical token-tree scopes, including nested blocks and function parameters; shadowed bindings therefore remain distinct. Identifiers supplied through metavariables retain caller spelling. Explicit explicit unhygienic spans and expansion inspection remain future surface area; generated template tokens are attributed to the invocation span.
6.3 Derive macros
The alpha derive vocabulary is Debug, Equals, Hash, ToString, and
Json, with generated-member and ownership rules recorded in
docs/api/compiler-alpha.md. The names are locked
before expansion is implemented.
@derive(Debug, Equals)
class Point(val x: Int, val y: Int)- Built-in derives implemented in compiler or std plugins.
- Generate members:
equals,hashCode,toString/debugString. - User derives later via proc macros implementing a stable interface.
6.4 Expansion order
- Parse AST.
- Collect attributes & macro invocations.
- Expand outer-to-inner with recursion limit.
- Re-resolve names after expansion.
- Typecheck (some derives may need partial types—phased expansion allowed for advanced derives later).
6.5 Procedural macros (phase 2)
- Implemented as a separate sandboxed process receiving serialized AST and returning generated items. Unsupported hosts, malformed responses, timeouts, output-limit violations, and non-zero exits fail closed with diagnostics.
- Host language for authoring plugins: Rust first (matches toolchain).
- Capabilities: no network; FS limited to crate source root if needed; CPU/time limits.
- Distributed as versioned packages (RFC-005) with checksums.
The current ABI is versioned and validates request/response framing. Root plugins are checksum-pinned in the lockfile; dependency-owned plugin declarations are rejected until a provenance policy exists.
6.6 Error model
| Case | Behavior |
|---|---|
| Expand error | Span at invocation + macro definition notes |
| Infinite recursion | Hard limit error |
| Plugin crash | Compile error, not host ICE when sandboxed |
| Unresolved after expand | Normal name resolution errors |
6.7 Examples
@derive(Debug, Equals)
class User(val id: Long, val name: String)
fun demo() {
val u = User(1, "a")
println(debug(u))
}6.8 Compatibility & migration
- Built-in derive names reserved.
- Proc macro ABI versioned; old plugins rejected with upgrade message.
- Editions may change hygiene edge cases carefully.
7. Open questions
| # | Question | Options | Owner | Status |
|---|---|---|---|---|
| 1 | Declarative syntax exact form | Lang | Resolved — derives first; decl form = hygienic pattern macros (syntax amend pre-impl) | |
| 2 | Proc sandbox: WASM vs process | separate process first | Toolchain | Resolved |
| 3 | Macro packaging unit | Pkg | Resolved — normal packages export macros; no separate macro package kind |
8. Rationale & trade-offs
MVP without full proc macros reduces security and stability risk while covering 80% of boilerplate via derives and decl macros. Sandboxing later enables ecosystem power without making the compiler a plugin malware host. Cost: fewer magical frameworks early—aligned with core-only scope.
9. Unresolved / future work
- Full macro debugging tools (
cargo expandequivalent) - IDE expansion preview
- Official derive set list
10. Security & safety considerations
- Treat proc macros as untrusted code execution at compile time.
- Lockfile + checksums for plugin packages and root procedural executables.
- Capability denial by default (network/FS).
- No ambient
unsafeinjection into user code withoutunsafein expansion output still gated by user’s allowance—expandedunsafeshould be visible (macro expandaudit).
11. Implementation plan (optional)
| Phase | Scope | Exit criteria |
|---|---|---|
| P0 | Built-in derives | Equals/Debug |
| P1 | Declarative macros | vec!-like |
| P2 | Sandboxed proc | Custom derive demo |
12. References
12.1 Implemented host derive boundary
The Rust compiler exposes aura_sema::UserDerive/aura_sema::UserMacro and
check_file_with_derives/check_file_with_macros. Registered implementations
receive an AST class or file and expand before typecheck; duplicate members,
ownership rules, diagnostic phase markers, and expansion-origin metadata are
applied uniformly with built-in derives. These host APIs are deliberately not
a package-level proc-macro ABI: source-level token macros and untrusted
procedural derives still require the RFC-010 process sandbox and versioned
plugin protocol.
The process protocol is exposed as encode_plugin_request,
decode_plugin_request, and encode_plugin_response. On Linux the host uses
bubblewrap with network isolation, read-only source/plugin/runtime mounts,
and a private temporary directory; hosts without a supported sandbox fail
closed. Every UTF-8 field is capped at 16 MiB and oversized fields are
rejected before plugin execution or decoder allocation.
Changelog
| Date | Author | Change |
|---|---|---|
| 2026-08-02 | Add fail-closed 16 MiB field limits to the stable plugin protocol | |
| 2026-08-01 | Implement Linux bubblewrap sandbox and export the versioned request encoder | |
| 2026-07-16 | Lock derives-first + package-export macros; Status → Accepted | |
| 2026-07-16 | Status → In Review — Review: derives MVP + process sandbox locked; declarative syntax open | |
| 2026-07-15 | Initial skeleton | |
| 2026-07-15 | Solid draft: derives MVP, sandboxed proc later | |
| 2026-07-15 | Lock process sandbox for proc macros |