Skip to main content

tatara_lisp/
macro_expand.rs

1//! Macro expander — rewrites `defmacro` / `defpoint-template` calls into
2//! their quasi-quoted templates.
3//!
4//! Semantics (v0, no evaluator):
5//!
6//! ```lisp
7//! (defmacro wrap (x) `(list ,x ,x))      ; or defpoint-template
8//! (wrap hello)                            ; expands to (list hello hello)
9//! ```
10//!
11//! Supported:
12//!   - Required params:      `(name a b c)`
13//!   - Optional params:      `(name a &optional b c)` — unsupplied bind to `()`
14//!   - Rest param:           `(name a &rest rest)`
15//!   - Quasi-quote body:     `` `(…) ``
16//!   - Unquote substitution: `,x`
17//!   - Splice substitution:  `,@x` (splices a bound list into the outer list)
18//!   - Recursive expansion: macro bodies may call other macros.
19//!
20//! Not yet supported (no evaluator):
21//!   - Arbitrary expressions under `,` — only bound symbol lookups.
22//!   - Nested quasi-quotes.
23//!   - Hygiene / gensym — param names capture aggressively.
24
25use std::cmp::Ordering;
26use std::collections::hash_map::DefaultHasher;
27use std::collections::HashMap;
28use std::hash::{Hash, Hasher};
29use std::sync::atomic::{AtomicU64, Ordering as AtomicOrdering};
30use std::sync::{Arc, Mutex};
31
32use crate::ast::Sexp;
33use crate::error::{LispError, MacroDefHead, Result, TemplateInvariantKind, UnquoteForm};
34
35/// Default ceiling on `Expander::expand`'s recursive re-entry into a
36/// macro-call form. A macro whose expansion contains another call to
37/// itself — the canonical runaway `(defmacro loop (x) `(loop ,x))` —
38/// pre-lift stack-overflowed the process (a below-floor abort with no
39/// `Result` witness). The ceiling turns that abort into a
40/// [`LispError::ExpansionDepthExceeded`] rejection at the expander
41/// boundary. `256` is large enough that no lawful hand-authored
42/// expansion nesting hits it (nested `when` / `let` / template
43/// compositions cap out in single digits in practice) and small
44/// enough that a runaway is rejected in microseconds instead of
45/// consuming a process stack. Consumers that need a tighter or looser
46/// ceiling call [`Expander::set_max_expansion_depth`].
47pub const DEFAULT_MAX_EXPANSION_DEPTH: usize = 256;
48
49/// Default ceiling on the expansion cache's entry count. Peer to
50/// [`DEFAULT_MAX_EXPANSION_DEPTH`] one RESOURCE axis over on the
51/// expander surface — where the depth ceiling bounds RECURSION (a
52/// runaway macro's stack), this ceiling bounds MEMORY (the memoized
53/// `apply(macro, args)` result table). Pre-lift the cache was an
54/// unbounded [`HashMap`] shared across [`Expander`] clones via
55/// [`std::sync::Arc`]; a long-running host (REPL, LSP,
56/// `tatara-check`, MCP server) that expanded many distinct
57/// `(macro, args)` pairs would accrete cache entries without bound —
58/// the same below-floor resource-drift the depth ceiling closed on
59/// the recursion axis. Post-lift the [`Expander::apply`] insert path
60/// consults this ceiling before growing the cache; once the entry
61/// count reaches the ceiling, new expansions still succeed (the
62/// cache remains a pure PERFORMANCE optimization — never a
63/// CORRECTNESS gate) but skip cache insertion until the operator
64/// clears the cache via [`Expander::clear_cache`]. Consumers that
65/// need a tighter ceiling (embedded / test harnesses) call
66/// [`Expander::set_max_cache_entries`] with a lower cap; consumers
67/// that want unbounded caching (batch compilation runs where memory
68/// is not the concern) call it with [`usize::MAX`].
69///
70/// `8192` is large enough that lawful macro authoring — a small
71/// closed set of macros, each expanded against a bounded set of
72/// argument shapes — never brushes the ceiling on a session-length
73/// workload (a typescape with 20 macros × 32 distinct arg-shapes ×
74/// several call sites still sits under 1K entries), and small
75/// enough that a pathological expansion loop (a macro emitting
76/// unique arg-shapes on every call) is rejected before it exhausts
77/// process memory. The default is deliberately generous — the
78/// ceiling exists to prevent unbounded drift, not to force operators
79/// to tune it. Consumers that want a tighter policy configure it
80/// explicitly.
81pub const DEFAULT_MAX_CACHE_ENTRIES: usize = 8192;
82
83/// Default ceiling on a single macro-expansion output's structural
84/// node count. Peer to [`DEFAULT_MAX_EXPANSION_DEPTH`] and
85/// [`DEFAULT_MAX_CACHE_ENTRIES`] one RESOURCE-DIMENSION axis over on
86/// the expander surface — where the depth ceiling bounds RECURSION
87/// LENGTH (a runaway macro's stack) and the cache ceiling bounds
88/// MEMOIZATION WIDTH (the memoized `apply(macro, args)` table's
89/// entry count), this ceiling bounds OUTPUT SIZE (the "expansion
90/// bomb" axis: a well-defined macro whose depth stays small while
91/// the produced tree grows past any reasonable working-set budget).
92/// Pre-lift the canonical
93/// `(defmacro bomb (x) `(list ,x ,x ,x ,x ,x ,x ,x ,x))` composed
94/// under itself a handful of times exhausted the process heap while
95/// the depth counter was still at low single digits and the cache
96/// entry count sat under any reasonable ceiling — a below-floor
97/// resource-drift the depth AND cache ceilings both admitted through.
98/// Post-lift the [`Expander::expand_with_depth`] site consults this
99/// ceiling on every freshly-applied macro output via
100/// [`crate::ast::Sexp::node_count`]; once an `apply` result exceeds
101/// the ceiling the expander returns
102/// [`crate::error::LispError::ExpansionSizeExceeded`] with
103/// `macro_name`, `size`, and `limit` populated. Consumers that need
104/// a tighter or looser ceiling call
105/// [`Expander::set_max_expansion_size`].
106///
107/// `65_536` is large enough that lawful macro-authored output — a
108/// domain-language body composed from a few dozen typed primitives,
109/// each expanded against a bounded set of arg-shapes — never brushes
110/// the ceiling on a session-length workload (a typical `defpoint` /
111/// `defmonitor` expansion sits under 100 nodes, and even a
112/// hand-authored `defcheck` cascade tops out in the low thousands),
113/// and small enough that a pathological "output bomb" (a macro
114/// producing a 2^N-node blob from a small input) is rejected in
115/// milliseconds instead of consuming multiple gigabytes of process
116/// memory in the worst case. The default is deliberately generous —
117/// the ceiling exists to prevent unbounded drift, not to force
118/// operators to tune it. Consumers that want a tighter policy
119/// configure it explicitly.
120pub const DEFAULT_MAX_EXPANSION_SIZE: usize = 65_536;
121
122/// Default ceiling on a registered macro's BODY structural node count.
123/// Peer to [`DEFAULT_MAX_EXPANSION_DEPTH`], [`DEFAULT_MAX_CACHE_ENTRIES`],
124/// and [`DEFAULT_MAX_EXPANSION_SIZE`] one PIPELINE-STAGE axis over on
125/// the expander surface — the three prior guards close the RESOURCE
126/// surface at EXPAND time (recursion length, memoization width,
127/// single-`apply` output size); this ceiling closes it at REGISTER time
128/// (the SIZE the body itself contributes to storage in
129/// [`Expander::macros`], to [`compile_template`]'s pre-expansion walk,
130/// and to every subsequent [`substitute`] walk that recurses through
131/// the body-shaped template). Pre-lift a pathological
132/// `(defmacro huge (x) `<template-of-N-million-nodes>)` would land the
133/// body in the macros table before any `apply` ran and inflate the
134/// per-registration cost proportional to `N` — a below-floor
135/// resource-drift the three expand-time ceilings all admitted through
136/// because none of them fires at the registration boundary. Post-lift
137/// the [`Expander::register_macro_def`] site consults this ceiling
138/// against `def.body.node_count()`; once the observed size exceeds
139/// the ceiling the expander returns
140/// [`crate::error::LispError::MacroBodySizeExceeded`] with
141/// `macro_name`, `size`, and `limit` populated, and BOTH the
142/// [`Expander::macros`] AND [`Expander::templates`] tables stay
143/// exactly as they were — no partial-write window in which the
144/// registration succeeded halfway.
145///
146/// `16_384` is large enough that lawful hand-authored macro bodies
147/// (an outer quasi-quote wrapping a few dozen nested lists with a
148/// handful of substitution slots) never brush the ceiling on any
149/// realistic authoring workload — even a heavily-composed
150/// `defalertpolicy` template with multi-page cascades tops out in the
151/// low thousands — and small enough that a pathological "authoring
152/// bomb" (a code-generator that unrolls a `Vec<TypedEntity>` into a
153/// giant literal body) is rejected at the registration boundary in
154/// microseconds instead of leaking a multi-megabyte template into the
155/// live macros table. The default is deliberately generous — the
156/// ceiling exists to prevent unbounded drift, not to force operators
157/// to tune it. Consumers that want a tighter or looser ceiling call
158/// [`Expander::set_max_macro_body_size`].
159pub const DEFAULT_MAX_MACRO_BODY_SIZE: usize = 16_384;
160
161/// Default ceiling on the [`Expander::macros`] table's entry count.
162/// Peer to [`DEFAULT_MAX_MACRO_BODY_SIZE`] one RESOURCE-DIMENSION axis
163/// over on the REGISTER-time surface — where the body-size ceiling
164/// bounds a single macro's BODY SIZE (the per-registration authoring
165/// bomb), this ceiling bounds TABLE ENTRY COUNT (the cumulative
166/// registration bomb: a code-generator emitting unbounded fresh
167/// `(defmacro N (x) `(list ,x))` heads whose bodies each sit
168/// comfortably under [`DEFAULT_MAX_MACRO_BODY_SIZE`] yet collectively
169/// saturate process memory). Also peer to [`DEFAULT_MAX_CACHE_ENTRIES`]
170/// one PIPELINE-STAGE axis over — where the cache-entries ceiling
171/// bounds EXPAND-time memoization width, this ceiling bounds
172/// REGISTER-time macro-table width. The two entry-count guards close
173/// the two pipeline stages symmetrically. Pre-lift the macros table
174/// was an unbounded [`HashMap`]; a long-running host (REPL, LSP,
175/// `tatara-check`, MCP server) that ingested a source stream of
176/// distinct `(defmacro …)` heads would accrete entries without
177/// bound — a below-floor resource-drift the four prior guards (depth,
178/// cache-entries, expansion-size, body-size) all admitted through
179/// because none of them fires against the CUMULATIVE registration
180/// count.
181///
182/// Overwrites (a re-registration of an already-registered key) do not
183/// grow the table and never trigger this ceiling — operators can
184/// redefine a macro at capacity without hitting the gate. Only FRESH
185/// keys are counted against the ceiling.
186///
187/// `4096` is large enough that lawful hand-authored typescapes never
188/// brush the ceiling on any realistic authoring workload — a
189/// typescape with 20-100 macros × several call sites sits well under
190/// 1K entries, and even a library-heavy project composing hundreds of
191/// domain macros stays under a fraction of the default — and small
192/// enough that a pathological registration bomb (a code-generator
193/// unrolling a `Vec<TypedEntity>` into a giant `(defmacro fresh-N …)`
194/// stream) is rejected at the registration boundary in microseconds
195/// instead of leaking a multi-megabyte macros table into the live
196/// expander. The default is deliberately generous — the ceiling
197/// exists to prevent unbounded drift, not to force operators to tune
198/// it. Consumers that want a tighter or looser ceiling call
199/// [`Expander::set_max_registered_macros`].
200pub const DEFAULT_MAX_REGISTERED_MACROS: usize = 4096;
201
202/// Default ceiling on a registered macro's lambda-list ARITY — the
203/// count of parameter slots (`required` + `optional` + `rest?`) the
204/// [`MacroDef`] declares. Peer to [`DEFAULT_MAX_MACRO_BODY_SIZE`] one
205/// RESOURCE-DIMENSION axis over on the REGISTER-time surface — where
206/// body-size bounds a per-registration BODY node count (the AST-nodes
207/// of the template a macro rewrites INTO), this ceiling bounds a
208/// per-registration PARAM slot count (the fresh symbols
209/// [`compile_template`]'s `,name`-index resolution AND
210/// [`MacroParams::bind`]'s per-index binder walk have to thread through
211/// on every call). Peer to [`DEFAULT_MAX_REGISTERED_MACROS`] one
212/// RESOURCE-DIMENSION axis over on the REGISTER-time surface — where
213/// the registered-macros ceiling bounds cumulative registration COUNT
214/// (the table-scoped resource), this ceiling bounds a per-registration
215/// PARAM-LIST WIDTH (the per-entry resource). Together the three
216/// REGISTER-time ceilings close the (per-body SIZE, per-body ARITY,
217/// per-table COUNT) three-corner surface — a `Vec<TypedEntity>`
218/// arity-bomb code-generator emitting a
219/// `(defmacro huge (a-1 a-2 … a-N-million) `,a-1)` head with a
220/// single-node body slips past `max_macro_body_size` and past
221/// `max_registered_macros` (one fresh key), yet each subsequent call
222/// pays O(N) walking the required-run binder — a below-floor
223/// resource-drift the two prior REGISTER-time guards admit through
224/// because neither of them fires against the PER-REGISTRATION
225/// PARAM-LIST WIDTH.
226///
227/// The default is deliberately generous — lawful hand-authored macros
228/// carry a handful of required params + a small optional run + at
229/// most a single `&rest` slot (`observability-stack`'s `defalertpolicy`
230/// carries under 8 slots; even keyword-heavy authoring surfaces sit
231/// well under 32 slots). `128` sits an order of magnitude past any
232/// realistic authoring workload yet an order of magnitude BELOW a
233/// pathological code-generator's arity-bomb output, so the ceiling
234/// gates the drift without brushing lawful traffic. Consumers that
235/// want a tighter or looser ceiling call
236/// [`Expander::set_max_macro_arity`].
237///
238/// Frontier inspiration: Common Lisp's `LAMBDA-PARAMETERS-LIMIT`
239/// standard variable (CLHS §3.4.1) — the runtime-reflectable
240/// "upper exclusive bound on the number of parameters that may
241/// appear in a lambda list" every conforming implementation carries.
242/// SBCL's is `4611686018427387903`, ECL's is `4096`, CLisp's is `4096`.
243/// `DEFAULT_MAX_MACRO_ARITY` is the substrate's typed-Rust peer at
244/// compile time, translated through pleme-io primitives: a
245/// `pub const usize` on the typed macro-expander algebra rather than
246/// a runtime-mutable global, wired into the substrate's typed
247/// [`crate::error::LispError`] Result algebra at
248/// [`Expander::register_macro_def`] rather than raised as an
249/// implementation-defined condition.
250pub const DEFAULT_MAX_MACRO_ARITY: usize = 128;
251
252/// Typed snapshot of the six `Expander` resource ceilings — the
253/// bundled peer of the six [`Expander::max_expansion_depth`] /
254/// [`Expander::max_cache_entries`] / [`Expander::max_expansion_size`] /
255/// [`Expander::max_macro_body_size`] / [`Expander::max_registered_macros`] /
256/// [`Expander::max_macro_arity`] individual getters. The six knobs form
257/// ONE resource surface (the (PIPELINE-STAGE × RESOURCE-DIMENSION)
258/// grid the last six commits closed corner-by-corner) yet the
259/// pre-lift `Expander` surface exposed them only as SIX independent
260/// projections — inspecting the full posture required six calls, and
261/// bulk-configuring the expander (a preset for CI, a preset for the
262/// REPL, a preset for a test harness) required six independent
263/// setter invocations whose ordering the type system did not gate.
264///
265/// Post-lift the typed bundle binds the six knobs at ONE typed value
266/// on the `Expander` surface. A `#[derive(Clone, Copy, Debug,
267/// PartialEq, Eq)]` posture — every ceiling is a `Copy` scalar —
268/// gives callers a cheap snapshot they can serialize, compare, or
269/// pass around; the `Copy` implementation guarantees the bundle
270/// never carries a hidden allocation. Adding a SEVENTH ceiling
271/// extends this struct AND [`DEFAULT_RESOURCE_LIMITS`] in lockstep
272/// via rustc's field-exhaustiveness on the `..Default::default()`-free
273/// literal construction — no independent-field-drift window.
274///
275/// Peer of the six `DEFAULT_MAX_*` module constants one AGGREGATION
276/// axis over: where each constant carries ONE ceiling, this struct
277/// carries the SIX-fold cross-product. [`DEFAULT_RESOURCE_LIMITS`]
278/// pins the (defaults × aggregation) corner so a consumer that wants
279/// the shipped posture in one value does not re-derive the six
280/// individual constants at its call site.
281///
282/// Theory grounding: THEORY.md §II.1 — typed entry / typed exit for
283/// the resource-limit configuration surface. The pre-lift six
284/// independent knobs were a set of typed scalars but their
285/// composition into an "Expander resource posture" was untyped —
286/// callers assembled the posture at their call site with no
287/// compile-time exhaustiveness check that all six were considered.
288/// Frontier inspiration: `tokio::runtime::Builder`'s bundled knob
289/// struct — a runtime's ceiling posture is ONE typed value rather
290/// than a chain of independent method calls; translation through
291/// pleme-io primitives is the plain `Copy` snapshot below, no
292/// builder-pattern indirection.
293#[derive(Clone, Copy, Debug, PartialEq, Eq)]
294pub struct ResourceLimits {
295    /// See [`Expander::max_expansion_depth`].
296    pub max_expansion_depth: usize,
297    /// See [`Expander::max_cache_entries`].
298    pub max_cache_entries: usize,
299    /// See [`Expander::max_expansion_size`].
300    pub max_expansion_size: usize,
301    /// See [`Expander::max_macro_body_size`].
302    pub max_macro_body_size: usize,
303    /// See [`Expander::max_registered_macros`].
304    pub max_registered_macros: usize,
305    /// See [`Expander::max_macro_arity`].
306    pub max_macro_arity: usize,
307}
308
309impl Default for ResourceLimits {
310    /// The shipped posture — every field seeded from its matching
311    /// `DEFAULT_MAX_*` module constant. Pinned equal to
312    /// [`DEFAULT_RESOURCE_LIMITS`] as a compile-time identity so a
313    /// future re-tuning of ONE default lands in ONE place and both
314    /// projections pick it up mechanically.
315    fn default() -> Self {
316        DEFAULT_RESOURCE_LIMITS
317    }
318}
319
320/// The shipped [`ResourceLimits`] posture — every field bound to its
321/// matching `DEFAULT_MAX_*` module constant at ONE typed value. This
322/// is the `const` peer of [`ResourceLimits::default`]; the two are
323/// pinned equal so a call-site that wants the shipped posture as a
324/// compile-time value binds through the `const` and a call-site that
325/// wants it as a runtime `Default::default()` value binds through the
326/// trait impl — both routes reach the SAME six numbers.
327///
328/// Adding a SEVENTH ceiling extends this constant AND the struct AND
329/// [`Expander`]'s constructors in lockstep — rustc's
330/// field-exhaustiveness on this `..Default::default()`-free literal
331/// forces the new field to appear here, and the trait impl above
332/// reuses this constant so no independent-default-drift window opens
333/// between the two projections.
334pub const DEFAULT_RESOURCE_LIMITS: ResourceLimits = ResourceLimits {
335    max_expansion_depth: DEFAULT_MAX_EXPANSION_DEPTH,
336    max_cache_entries: DEFAULT_MAX_CACHE_ENTRIES,
337    max_expansion_size: DEFAULT_MAX_EXPANSION_SIZE,
338    max_macro_body_size: DEFAULT_MAX_MACRO_BODY_SIZE,
339    max_registered_macros: DEFAULT_MAX_REGISTERED_MACROS,
340    max_macro_arity: DEFAULT_MAX_MACRO_ARITY,
341};
342
343/// The unbounded [`ResourceLimits`] posture — every ceiling seeded to
344/// [`usize::MAX`] at ONE typed value. This is the CEILING-LIFTED peer of
345/// [`DEFAULT_RESOURCE_LIMITS`] one PRESET-POSTURE axis over on the
346/// named-preset surface: where the default constant binds each field to
347/// the shipped `DEFAULT_MAX_*` module constant, this constant binds
348/// each field to the raw `usize::MAX` sentinel that every
349/// [`Expander::set_max_expansion_depth`] / [`Expander::set_max_cache_entries`] /
350/// [`Expander::set_max_expansion_size`] / [`Expander::set_max_macro_body_size`] /
351/// [`Expander::set_max_registered_macros`] / [`Expander::set_max_macro_arity`]
352/// setter's doc names as "the ceiling is effectively lifted" —
353/// admitting any lawful value the type system supports.
354///
355/// A test-harness fixture that wants "exercise the raw expander with no
356/// ceiling gating anything" pre-lift composed the posture at its call
357/// site as six independent [`usize::MAX`] literals (or six independent
358/// setter invocations post-`Expander::new`) whose exhaustiveness the
359/// type system did NOT gate — a copy-paste that dropped one of the six
360/// lifts left THAT ceiling at its default and gated the fixture
361/// silently. Post-lift the posture is ONE named typed constant every
362/// such fixture routes through, AND rustc's field-exhaustiveness on
363/// the `..Default::default()`-free literal here forces a SEVENTH
364/// ceiling extension to appear at this site in lockstep with
365/// [`DEFAULT_RESOURCE_LIMITS`] and the [`ResourceLimits`] struct
366/// itself — no independent-preset-drift window.
367///
368/// Peer to [`DEFAULT_RESOURCE_LIMITS`] on the (posture × aggregation)
369/// grid: both constants live at the AGGREGATION corner (six ceilings
370/// as ONE typed value); the posture axis distinguishes the SHIPPED
371/// default from the CEILING-LIFTED unbounded preset. Disagrees with
372/// [`DEFAULT_RESOURCE_LIMITS`] on every field (every default is a
373/// concrete non-[`usize::MAX`] positive constant, so the six-way
374/// disagreement is structurally exhaustive) — pinned as a typed
375/// theorem in the [`ResourceLimits`] test cohort.
376///
377/// Frontier inspiration: `tokio::runtime::Builder`'s pattern of
378/// exposing named preset thunks (`multi_thread`, `current_thread`)
379/// alongside the field-level configurators, so a caller that wants a
380/// standard posture binds through ONE named entry rather than
381/// composing the field literals at the call site. Translation
382/// through pleme-io primitives is the plain `const` snapshot below,
383/// no builder-pattern indirection — the `Copy` posture on
384/// [`ResourceLimits`] already gives callers the struct-update
385/// literal (`ResourceLimits { max_expansion_depth: 4,
386/// ..UNBOUNDED_RESOURCE_LIMITS }`) that isolates the ONE ceiling
387/// still gating from the five lifted.
388pub const UNBOUNDED_RESOURCE_LIMITS: ResourceLimits = ResourceLimits {
389    max_expansion_depth: usize::MAX,
390    max_cache_entries: usize::MAX,
391    max_expansion_size: usize::MAX,
392    max_macro_body_size: usize::MAX,
393    max_registered_macros: usize::MAX,
394    max_macro_arity: usize::MAX,
395};
396
397/// The zero [`ResourceLimits`] posture — every ceiling seeded to `0` at
398/// ONE typed value. This is the BOTTOM peer of
399/// [`UNBOUNDED_RESOURCE_LIMITS`] one LATTICE-POLE axis over on the
400/// (bottom, top) bounded-lattice preset-pair surface: where the
401/// ceiling-lifted preset carries every gate at [`usize::MAX`]
402/// (admitting every lawful `usize`-indexed value the type system
403/// supports), this preset carries every gate at `0` (rejecting every
404/// non-empty input past the first depth step, cache slot, macro-body
405/// node, macro registration, or arity slot). Together the two close
406/// the (BOTTOM, TOP) preset-posture pair on the bounded-lattice
407/// diagonal, and [`DEFAULT_RESOURCE_LIMITS`] sits strictly between
408/// them (`EMPTY.leq(DEFAULT) && DEFAULT.leq(UNBOUNDED)`).
409///
410/// **Bounded-lattice identity elements**: [`ResourceLimits`] under the
411/// pointwise `min` / `max` operations forms a BOUNDED lattice; each
412/// operation acquires its identity element from the OPPOSITE pole of
413/// the preset-pair diagonal, and its ANNIHILATOR from the SAME pole.
414///   * [`Self::strictest`] (meet, pointwise `min`) —
415///     [`UNBOUNDED_RESOURCE_LIMITS`] is the identity
416///     (`a.strictest(UNBOUNDED) == a` for every posture, since pointwise
417///     `min` against [`usize::MAX`] returns `a`); this constant is the
418///     ANNIHILATOR (`a.strictest(EMPTY) == EMPTY` for every posture,
419///     since pointwise `min` against `0` returns `0`).
420///   * [`Self::most_permissive`] (join, pointwise `max`) — this
421///     constant is the identity (`a.most_permissive(EMPTY) == a` for
422///     every posture, since pointwise `max` against `0` returns `a`);
423///     [`UNBOUNDED_RESOURCE_LIMITS`] is the ANNIHILATOR
424///     (`a.most_permissive(UNBOUNDED) == UNBOUNDED`).
425///   * [`Self::leq`] (partial order) — this constant is the MINIMUM
426///     (`EMPTY.leq(a) == true` for every posture, since `0 <= x` holds
427///     for every `usize x`); [`UNBOUNDED_RESOURCE_LIMITS`] is the
428///     MAXIMUM (`a.leq(UNBOUNDED) == true` for every posture, since
429///     `x <= usize::MAX` holds for every `usize x`).
430///
431/// **Fold-identity use case**: a caller aggregating a slice of
432/// postures through [`Self::most_permissive`] (computing the pointwise
433/// least upper bound across the slice) seeds the fold from this
434/// constant — the join-identity property guarantees the seed does not
435/// distort the fold's result:
436///
437/// ```rust,ignore
438/// let joined = postures.iter().copied()
439///     .fold(EMPTY_RESOURCE_LIMITS, ResourceLimits::most_permissive);
440/// ```
441///
442/// The dual pattern (folding through [`Self::strictest`] to compute
443/// the pointwise greatest lower bound) seeds from
444/// [`UNBOUNDED_RESOURCE_LIMITS`] — the meet-identity from the OPPOSITE
445/// pole. Pre-lift, an aggregator either (a) picked ONE arbitrary
446/// element of the slice as the seed and folded through the tail — a
447/// non-total fixup that panicked on an empty slice — or (b) seeded
448/// from `Some(first)`-style option wrapper — a runtime `Option` that
449/// carried a type-level admission the aggregate might be absent even
450/// though the algebra defines it exhaustively via its identity. Post-
451/// lift the fold takes ONE typed identity element and handles empty
452/// slices as the algebraic identity value — a boundary case which the
453/// bounded-lattice structure resolves at TYPE level rather than the
454/// caller resolving at CALL SITE with a wrapper type.
455///
456/// Adding a SEVENTH ceiling extends this constant AND the struct AND
457/// [`DEFAULT_RESOURCE_LIMITS`] AND [`UNBOUNDED_RESOURCE_LIMITS`] in
458/// lockstep — rustc's field-exhaustiveness on this
459/// `..Default::default()`-free literal forces the new field to appear
460/// at this site, and the identity-law tests pin `0` as the seventh
461/// field's value at compile time via the const-fn peer of the
462/// existing lattice-law const-fn pins.
463///
464/// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
465/// proofs; the identity element of a lattice operation is itself a
466/// typed named entry whose composition with any other element is the
467/// element (join) or the identity (meet). THEORY.md §V.1 — knowable
468/// platform; the bounded-lattice bottom binds at ONE typed constant
469/// rather than a six-primitive inline literal every consumer that
470/// needs the fold identity carries at its call site. Frontier
471/// inspiration: the algebraic-datatypes tradition of a `Monoid` typeclass
472/// (Haskell's `mempty`, Scala's `Monoid.empty`, Rust's various
473/// `Default::default` for `Sum`/`Product` newtypes) — a caller
474/// aggregating values through a binary combinator binds its identity
475/// element at ONE named entry rather than composing the identity
476/// literal at every fold site. Translation through pleme-io primitives
477/// is the plain `const` snapshot below, no typeclass indirection — the
478/// `Copy` posture on [`ResourceLimits`] already gives callers the
479/// struct-update literal (`ResourceLimits { max_macro_arity: 3,
480/// ..EMPTY_RESOURCE_LIMITS }`) that isolates the ONE ceiling admitting
481/// non-zero input from the five sealed at `0`.
482pub const EMPTY_RESOURCE_LIMITS: ResourceLimits = ResourceLimits {
483    max_expansion_depth: 0,
484    max_cache_entries: 0,
485    max_expansion_size: 0,
486    max_macro_body_size: 0,
487    max_registered_macros: 0,
488    max_macro_arity: 0,
489};
490
491/// Pointwise `min` / `max` primitives the [`ResourceLimits`] lattice binds
492/// its meet ([`ResourceLimits::strictest`]) and join
493/// ([`ResourceLimits::most_permissive`]) operations to. Named at module
494/// scope as `const fn` so both operations are themselves `const fn` on
495/// stable — composing two postures at compile time (a caller stitching a
496/// new `pub const` preset from two shipped presets) needs no runtime
497/// evaluation. Ternary form rather than [`usize::min`] / [`usize::max`]
498/// so the two `const fn` remain valid on any tatara MSRV whose stability
499/// on the primitive const-fn methods has not been established.
500const fn min_usize(a: usize, b: usize) -> usize {
501    if a <= b {
502        a
503    } else {
504        b
505    }
506}
507
508const fn max_usize(a: usize, b: usize) -> usize {
509    if a >= b {
510        a
511    } else {
512        b
513    }
514}
515
516impl ResourceLimits {
517    /// Pointwise-`min` across the six ceilings — the STRICTEST posture
518    /// whose admissible input set is the intersection of `self`'s and
519    /// `other`'s admissible input sets. `a.strictest(b)` admits an
520    /// input iff BOTH `a` AND `b` admit it: on every field the
521    /// per-axis ceiling is the smaller of the two, so a value that
522    /// clears the meet clears BOTH postures, and a value that any
523    /// posture rejects the meet also rejects.
524    ///
525    /// The meet of the `ResourceLimits` lattice under the pointwise
526    /// partial order `a ≤ b iff every field of a ≤ every field of b`
527    /// (tighter-first — a POSTURE admits FEWER inputs on that field's
528    /// axis when the ceiling is smaller). Peer of
529    /// [`Self::most_permissive`] one COMBINATOR axis over on the
530    /// lattice-algebra surface: where `most_permissive` is the join
531    /// (pointwise-`max`, LUB), this is the meet (pointwise-`min`, GLB).
532    /// Together the two operations close the (meet, join) lattice
533    /// operator pair on the [`ResourceLimits`] posture algebra.
534    ///
535    /// The concrete identity on the shipped preset pair holds
536    /// structurally: [`DEFAULT_RESOURCE_LIMITS`]`.strictest(`
537    /// [`UNBOUNDED_RESOURCE_LIMITS`]`) == `[`DEFAULT_RESOURCE_LIMITS`].
538    /// Every `DEFAULT_MAX_*` module constant is a concrete positive
539    /// value strictly less than [`usize::MAX`], so on every axis the
540    /// pointwise-`min` picks the DEFAULT side; the six-axis pin holds
541    /// as a typed theorem in the test cohort.
542    ///
543    /// Idempotent (`a.strictest(a) == a`), commutative
544    /// (`a.strictest(b) == b.strictest(a)`), associative
545    /// (`(a.strictest(b)).strictest(c) == a.strictest(b.strictest(c))`),
546    /// and satisfies the absorption identity with the join
547    /// (`a.strictest(a.most_permissive(b)) == a`) — all pinned as
548    /// typed theorems in the test cohort. Distributive over the join
549    /// (`a.strictest(b.most_permissive(c)) ==
550    /// a.strictest(b).most_permissive(a.strictest(c))`) because
551    /// pointwise-`min` and pointwise-`max` on [`usize`] each
552    /// distribute over the other on every axis.
553    ///
554    /// `const fn` so a caller can compose two named presets into a
555    /// third at compile time (`pub const CI_RESOURCE_LIMITS:
556    /// ResourceLimits = SOME_OPERATOR_PRESET.strictest(
557    /// SOME_CI_PRESET);`) rather than deferring the composition to
558    /// a runtime `Default::default()` chain.
559    ///
560    /// Pre-lift, a caller wanting the tightest common posture across
561    /// two shipped presets composed the intersection at its call site
562    /// as six independent `usize::min(a.max_X, b.max_X)` invocations
563    /// stitched into a fresh `ResourceLimits { ... }` literal — the
564    /// same six-inline-primitive shape the pre-`ResourceLimits`
565    /// bundled `Expander` posture required its callers to carry, and
566    /// the same exhaustiveness gap the `..Default::default()`-free
567    /// literal on the bundled struct exists to close: a copy-paste
568    /// that dropped ONE of the six `min` invocations left THAT
569    /// ceiling at whichever posture the caller happened to seed the
570    /// literal with, and the type system did not gate the drop.
571    /// Post-lift the composition binds at ONE typed method on the
572    /// posture algebra — a caller writes
573    /// `operator_preset.strictest(ci_preset)` and rustc's
574    /// field-exhaustiveness at the const's construction guarantees
575    /// every ceiling is threaded through the `min` primitive.
576    ///
577    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
578    /// preserves proofs; the tightest posture of two preset-carried
579    /// resource proofs is itself a `ResourceLimits` whose proof
580    /// content is the intersection of the two, and this method is
581    /// the typed named entry composing them. THEORY.md §V.1 —
582    /// knowable platform; the pointwise-`min` combinator becomes a
583    /// TYPE-level operation on the posture algebra rather than an
584    /// inline six-primitive cascade at every consumer that composes
585    /// two presets. `tatara-lattice`'s `meet` / `join` operators on
586    /// `Classification` (see `pleme-io/tatara` workspace crate
587    /// docstring: "Lattice algebra over Classification — `meet` /
588    /// `join` / `leq` / `Baseline`") close the SAME shape one layer
589    /// down: an entity's classification is a lattice; a resource
590    /// posture is a lattice; both algebras carry the (meet, join)
591    /// operator pair as their fundamental composition primitives.
592    #[must_use]
593    pub const fn strictest(self, other: Self) -> Self {
594        Self {
595            max_expansion_depth: min_usize(self.max_expansion_depth, other.max_expansion_depth),
596            max_cache_entries: min_usize(self.max_cache_entries, other.max_cache_entries),
597            max_expansion_size: min_usize(self.max_expansion_size, other.max_expansion_size),
598            max_macro_body_size: min_usize(self.max_macro_body_size, other.max_macro_body_size),
599            max_registered_macros: min_usize(
600                self.max_registered_macros,
601                other.max_registered_macros,
602            ),
603            max_macro_arity: min_usize(self.max_macro_arity, other.max_macro_arity),
604        }
605    }
606
607    /// Pointwise-`max` across the six ceilings — the MOST-PERMISSIVE
608    /// posture whose admissible input set is the union of `self`'s
609    /// and `other`'s admissible input sets. `a.most_permissive(b)`
610    /// admits an input iff EITHER `a` OR `b` admits it: on every
611    /// field the per-axis ceiling is the larger of the two, so a
612    /// value that either posture accepts the join accepts, and a
613    /// value the join rejects both postures reject.
614    ///
615    /// The join of the `ResourceLimits` lattice under the pointwise
616    /// partial order `a ≤ b iff every field of a ≤ every field of b`
617    /// (tighter-first). Peer of [`Self::strictest`] one COMBINATOR
618    /// axis over on the lattice-algebra surface: where `strictest` is
619    /// the meet (pointwise-`min`, GLB), this is the join
620    /// (pointwise-`max`, LUB). Together the two operations close the
621    /// (meet, join) lattice operator pair on the [`ResourceLimits`]
622    /// posture algebra.
623    ///
624    /// The concrete identity on the shipped preset pair holds
625    /// structurally: [`DEFAULT_RESOURCE_LIMITS`]`.most_permissive(`
626    /// [`UNBOUNDED_RESOURCE_LIMITS`]`) == `[`UNBOUNDED_RESOURCE_LIMITS`].
627    /// Every `DEFAULT_MAX_*` module constant is a concrete positive
628    /// value strictly less than [`usize::MAX`], so on every axis the
629    /// pointwise-`max` picks the UNBOUNDED side; the six-axis pin
630    /// holds as a typed theorem in the test cohort.
631    ///
632    /// Idempotent, commutative, associative, and satisfies the
633    /// absorption identity with the meet
634    /// (`a.most_permissive(a.strictest(b)) == a`) — all pinned as
635    /// typed theorems in the test cohort. Distributive over the meet
636    /// (`a.most_permissive(b.strictest(c)) ==
637    /// a.most_permissive(b).strictest(a.most_permissive(c))`).
638    ///
639    /// `const fn` so a caller can compose two named presets into a
640    /// third at compile time rather than deferring the composition
641    /// to a runtime `Default::default()` chain.
642    ///
643    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
644    /// preserves proofs; the most-permissive posture of two preset-
645    /// carried resource proofs is itself a `ResourceLimits` whose
646    /// proof content is the union of the two, and this method is the
647    /// typed named entry composing them. THEORY.md §V.1 — knowable
648    /// platform; the pointwise-`max` combinator becomes a TYPE-level
649    /// operation on the posture algebra rather than an inline
650    /// six-primitive cascade at every consumer that composes two
651    /// presets.
652    #[must_use]
653    pub const fn most_permissive(self, other: Self) -> Self {
654        Self {
655            max_expansion_depth: max_usize(self.max_expansion_depth, other.max_expansion_depth),
656            max_cache_entries: max_usize(self.max_cache_entries, other.max_cache_entries),
657            max_expansion_size: max_usize(self.max_expansion_size, other.max_expansion_size),
658            max_macro_body_size: max_usize(self.max_macro_body_size, other.max_macro_body_size),
659            max_registered_macros: max_usize(
660                self.max_registered_macros,
661                other.max_registered_macros,
662            ),
663            max_macro_arity: max_usize(self.max_macro_arity, other.max_macro_arity),
664        }
665    }
666
667    /// Pointwise partial-order relation across the six ceilings — `self`
668    /// is at least as tight as `other` iff every field of `self` is at
669    /// most the matching field of `other`. `a.leq(b)` holds iff every
670    /// input the `a` posture admits the `b` posture also admits: `a`'s
671    /// per-axis ceilings all sit at or below `b`'s per-axis ceilings, so
672    /// any value that clears `a`'s tighter gates also clears `b`'s
673    /// looser gates.
674    ///
675    /// The partial order underneath the [`ResourceLimits`] lattice.
676    /// Peer of [`Self::strictest`] (meet, GLB) and [`Self::most_permissive`]
677    /// (join, LUB) one PRIMITIVE-KIND axis over on the lattice-algebra
678    /// surface: where those two are the (meet, join) COMBINATORS, this
679    /// is the RELATION they are defined against. Together the three
680    /// close the (meet, join, leq) primitive triple on the
681    /// [`ResourceLimits`] posture algebra — the same triple
682    /// `tatara-lattice`'s [`Lattice`] trait exposes as its three
683    /// required methods for every classification lattice one crate over
684    /// (see `tatara-lattice/src/lib.rs`).
685    ///
686    /// **Meet-agreement** (`a.leq(b) ⇔ a.strictest(b) == a`): if `a` is
687    /// tighter on every axis than `b`, then the pointwise `min` picks
688    /// `a` on every axis, so `a.strictest(b) == a`; conversely, if
689    /// `a.strictest(b) == a`, every field's `min` picked `a`, so every
690    /// field of `a` is at most the matching field of `b`. Pinned as
691    /// `resource_limits_leq_agrees_with_meet` in the test cohort.
692    ///
693    /// **Join-agreement** (`a.leq(b) ⇔ a.most_permissive(b) == b`):
694    /// symmetric statement on the LUB side; pinned as
695    /// `resource_limits_leq_agrees_with_join`. Together the two
696    /// agreements are the canonical lattice cross-check axiom
697    /// `a ≤ b ⇔ a ⊓ b = a ⇔ a ⊔ b = b` documented in
698    /// `tatara-lattice/src/lib.rs`'s preamble.
699    ///
700    /// Reflexive (`a.leq(a) == true`), antisymmetric (`a.leq(b) &&
701    /// b.leq(a) → a == b`), transitive (`a.leq(b) && b.leq(c) →
702    /// a.leq(c)`) — all pinned as typed theorems in the test cohort.
703    /// NOT total: two postures with reversed per-axis orderings (some
704    /// axes where `a` is tighter, some where `b` is tighter) are
705    /// incomparable — `!a.leq(b) && !b.leq(a)` — pinned by
706    /// `resource_limits_leq_is_not_total_on_asymmetric_postures`.
707    ///
708    /// **Bounds pins in terms of leq**:
709    ///   * `a.strictest(b).leq(a) && a.strictest(b).leq(b)` (meet is
710    ///     the greatest lower bound) — pinned by
711    ///     `resource_limits_strictest_is_leq_both_operands`, the typed
712    ///     lattice-relation companion to the per-axis
713    ///     `resource_limits_strictest_is_dominated_by_both_operands_pointwise`.
714    ///   * `a.leq(a.most_permissive(b)) && b.leq(a.most_permissive(b))`
715    ///     (join is the least upper bound) — pinned by
716    ///     `resource_limits_most_permissive_is_geq_both_operands`, the
717    ///     typed lattice-relation companion to the per-axis
718    ///     `resource_limits_most_permissive_dominates_both_operands_pointwise`.
719    ///
720    /// **Concrete-preset pin**: [`DEFAULT_RESOURCE_LIMITS`]`.leq(`
721    /// [`UNBOUNDED_RESOURCE_LIMITS`]`) == true` (every
722    /// `DEFAULT_MAX_*` module constant is a concrete positive value at
723    /// most [`usize::MAX`]) AND
724    /// `!UNBOUNDED_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS)` (every
725    /// `DEFAULT_MAX_*` is strictly less than [`usize::MAX`], so on
726    /// every axis [`usize::MAX`] exceeds the matching default) — the
727    /// partial order is a STRICT order on the shipped preset pair.
728    /// Pinned by `resource_limits_leq_of_default_and_unbounded_is_a_strict_order`.
729    ///
730    /// `const fn` so a caller can pin a preset-relation identity at
731    /// compile time (`const _: () = assert!(DEFAULT_RESOURCE_LIMITS
732    /// .leq(UNBOUNDED_RESOURCE_LIMITS));`) — the typed lattice-relation
733    /// peer of the `const fn` composition ability on
734    /// [`Self::strictest`] / [`Self::most_permissive`].
735    ///
736    /// Pre-lift, a caller wanting to decide whether one posture was
737    /// tighter than another either (a) composed six independent
738    /// per-axis `<=` comparisons at the call site stitched into a
739    /// six-fold `&&` — the same six-inline-primitive shape the
740    /// pre-`ResourceLimits` bundled Expander posture required its
741    /// callers to carry, and the same exhaustiveness gap the
742    /// `..Default::default()`-free literal on the bundled struct exists
743    /// to close: a copy-paste that dropped ONE of the six comparisons
744    /// silently admitted a posture that violated tightness on THAT
745    /// axis — or (b) invoked [`Self::strictest`] and compared the result
746    /// to `self` — a correct but roundabout composition of the partial-
747    /// order primitive against its lattice-companion. Post-lift the
748    /// relation binds at ONE typed method on the posture algebra; a
749    /// caller writes `tighter.leq(looser)` and rustc's exhaustiveness
750    /// on the six-field conjunction below guarantees every ceiling is
751    /// threaded through the `<=` primitive.
752    ///
753    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
754    /// preserves proofs; the partial-order relation on two preset-
755    /// carried resource proofs is itself a typed named entry composing
756    /// their tightness ordering. THEORY.md §V.1 — knowable platform;
757    /// the pointwise `<=` combinator becomes a TYPE-level operation on
758    /// the posture algebra rather than an inline six-primitive
759    /// conjunction at every consumer that compares two presets.
760    /// `tatara-lattice`'s [`Lattice::leq`] method on `Classification`
761    /// closes the SAME shape one layer down — an entity's
762    /// classification is a lattice with a `leq` partial order; a
763    /// resource posture is a lattice with a `leq` partial order; both
764    /// algebras carry the (meet, join, leq) primitive triple as their
765    /// fundamental relation-and-combinator surface.
766    #[must_use]
767    pub const fn leq(self, other: Self) -> bool {
768        self.max_expansion_depth <= other.max_expansion_depth
769            && self.max_cache_entries <= other.max_cache_entries
770            && self.max_expansion_size <= other.max_expansion_size
771            && self.max_macro_body_size <= other.max_macro_body_size
772            && self.max_registered_macros <= other.max_registered_macros
773            && self.max_macro_arity <= other.max_macro_arity
774    }
775
776    /// Pointwise-`min` fold across a slice of postures — the STRICTEST
777    /// posture whose admissible input set is the intersection of every
778    /// operand's admissible input sets. `ResourceLimits::strictest_of(&[a,
779    /// b, c])` admits an input iff EVERY operand admits it: on every
780    /// field the per-axis ceiling is the smallest across the whole slice,
781    /// so a value that clears the aggregated meet clears EVERY operand,
782    /// and a value that ANY operand rejects the aggregated meet also
783    /// rejects.
784    ///
785    /// The N-ary MEET aggregation on the [`ResourceLimits`] lattice —
786    /// the extension of [`Self::strictest`] from a 2-input pairwise
787    /// combinator to an N-input fold across a slice. Seeded from
788    /// [`UNBOUNDED_RESOURCE_LIMITS`] (the meet-identity — its per-axis
789    /// [`usize::MAX`] never wins the pointwise `min` against a concrete
790    /// positive value, so the seed contributes nothing to the fold's
791    /// result), which makes the empty-slice case aggregate to the
792    /// identity element rather than requiring an `Option<Self>` wrapper
793    /// the caller inspects at every consumption site. Peer of
794    /// [`Self::most_permissive_of`] one COMBINATOR axis over on the
795    /// N-ary aggregation surface: where `most_permissive_of` is the
796    /// N-ary JOIN (LUB across the slice, seeded from the join-identity
797    /// [`EMPTY_RESOURCE_LIMITS`]), this is the N-ary MEET (GLB across
798    /// the slice, seeded from the meet-identity). Together the two
799    /// close the (N-ary meet, N-ary join) aggregation pair the pairwise
800    /// combinators [`Self::strictest`] / [`Self::most_permissive`]
801    /// extend from 2 to N.
802    ///
803    /// **Empty-slice identity**: `strictest_of(&[]) ==
804    /// UNBOUNDED_RESOURCE_LIMITS` — the null aggregate binds structurally
805    /// to the meet-identity element. Pre-lift a caller composing "the
806    /// tightest posture across THIS SLICE" from the pairwise `strictest`
807    /// primitive either (a) seeded the fold from a non-empty first
808    /// element and panicked on empty input, or (b) wrapped the aggregate
809    /// in `Option<Self>` — a runtime type-level admission the aggregate
810    /// might be absent even though the algebra's bounded-lattice
811    /// structure defines the null aggregate exhaustively via its
812    /// identity element. Post-lift the null aggregate is
813    /// [`UNBOUNDED_RESOURCE_LIMITS`] at the TYPE level, not a wrapper
814    /// the consumer inspects.
815    ///
816    /// **Single-element identity**: `strictest_of(&[a]) == a` — the
817    /// 1-input aggregate is the input verbatim, since
818    /// `UNBOUNDED.strictest(a) == a` for every posture `a` by the
819    /// meet-identity law.
820    ///
821    /// **Two-element identity**: `strictest_of(&[a, b]) ==
822    /// a.strictest(b)` — the 2-input aggregate reduces to the pairwise
823    /// combinator, since `UNBOUNDED.strictest(a).strictest(b) ==
824    /// a.strictest(b)`.
825    ///
826    /// **Order-independent**: `strictest_of(&[a, b, c]) ==
827    /// strictest_of(&[c, b, a])` — the fold inherits the commutativity
828    /// AND associativity of the pairwise `strictest`, so any permutation
829    /// of the slice yields the same aggregate.
830    ///
831    /// `const fn` so a caller can compose an arbitrary-length preset
832    /// slice into a third preset at compile time (`pub const
833    /// COMBINED_PRESET: ResourceLimits = ResourceLimits::strictest_of(
834    /// &[SOME_OPERATOR_PRESET, SOME_CI_PRESET, SOME_HOSTILE_PRESET]);`)
835    /// rather than deferring the composition to a runtime chain — the
836    /// N-ary const-fn peer of the pairwise const-fn on
837    /// [`Self::strictest`]. `Copy` on [`ResourceLimits`] lets the const-
838    /// fn loop index the slice by value without an explicit `.clone()`
839    /// (which const-fn would not permit anyway).
840    ///
841    /// Pre-lift, the SAME `.iter().copied().fold(UNBOUNDED, strictest)`
842    /// three-primitive cascade appeared verbatim in
843    /// `unbounded_resource_limits_seeds_strictest_fold_over_slice` AND at
844    /// every future consumer that would compose "the tightest across
845    /// this set of presets" — a PRIME DIRECTIVE ≥2 trigger, since the
846    /// fold docstring on [`UNBOUNDED_RESOURCE_LIMITS`] explicitly names
847    /// this pattern as the constant's fold-identity use case. Post-lift
848    /// the N-ary aggregation binds at ONE typed method the algebra
849    /// exposes, and every future consumer routes through ONE name whose
850    /// signature carries the identity-element choice INTO the method
851    /// (rather than the consumer having to remember which seed pairs
852    /// with which combinator at each call site).
853    ///
854    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
855    /// preserves proofs; the N-ary aggregation of N preset-carried
856    /// resource proofs is itself a `ResourceLimits` whose proof content
857    /// is the intersection of every operand's, and this method is the
858    /// typed named entry composing them. THEORY.md §V.1 — knowable
859    /// platform; the N-ary meet becomes a TYPE-level operation on the
860    /// posture algebra rather than an inline fold cascade at every
861    /// consumer that aggregates a slice of presets — with the
862    /// meet-identity seed baked in so the consumer cannot pair the join-
863    /// identity (`EMPTY`) with the meet combinator (a silent
864    /// distortion that would collapse every axis to `0`). Frontier
865    /// inspiration: the algebraic-datatypes tradition of a `Monoid`
866    /// typeclass exposing `mconcat` / `Monoid.combineAll` alongside the
867    /// pairwise `mappend` — the N-ary aggregation of a collection
868    /// through a binary combinator is a first-class named method the
869    /// typeclass carries. Translation through pleme-io primitives is
870    /// the plain `const fn` slice-fold below, no typeclass indirection
871    /// — the meet-identity element already exists as a named `pub const`
872    /// on the algebra so the fold picks it up structurally.
873    #[must_use]
874    pub const fn strictest_of(postures: &[Self]) -> Self {
875        let mut acc = UNBOUNDED_RESOURCE_LIMITS;
876        let mut i = 0;
877        while i < postures.len() {
878            acc = acc.strictest(postures[i]);
879            i += 1;
880        }
881        acc
882    }
883
884    /// Pointwise-`max` fold across a slice of postures — the MOST-
885    /// PERMISSIVE posture whose admissible input set is the union of
886    /// every operand's admissible input sets.
887    /// `ResourceLimits::most_permissive_of(&[a, b, c])` admits an input
888    /// iff ANY operand admits it: on every field the per-axis ceiling
889    /// is the largest across the whole slice.
890    ///
891    /// The N-ary JOIN aggregation on the [`ResourceLimits`] lattice —
892    /// the extension of [`Self::most_permissive`] from a 2-input
893    /// pairwise combinator to an N-input fold across a slice. Seeded
894    /// from [`EMPTY_RESOURCE_LIMITS`] (the join-identity — every field
895    /// at `0`, which never wins the pointwise `max` against any
896    /// non-zero value, so the seed contributes nothing to the fold's
897    /// result), which makes the empty-slice case aggregate to the
898    /// identity element. Peer of [`Self::strictest_of`] one COMBINATOR
899    /// axis over on the N-ary aggregation surface: where `strictest_of`
900    /// is the N-ary MEET (seeded from the meet-identity
901    /// [`UNBOUNDED_RESOURCE_LIMITS`]), this is the N-ary JOIN (seeded
902    /// from the join-identity). Together the two close the (N-ary meet,
903    /// N-ary join) aggregation pair.
904    ///
905    /// **Empty-slice identity**: `most_permissive_of(&[]) ==
906    /// EMPTY_RESOURCE_LIMITS` — the null aggregate binds structurally
907    /// to the join-identity element.
908    ///
909    /// **Single-element identity**: `most_permissive_of(&[a]) == a` —
910    /// the 1-input aggregate is the input verbatim, since
911    /// `EMPTY.most_permissive(a) == a` for every posture `a` by the
912    /// join-identity law.
913    ///
914    /// **Two-element identity**: `most_permissive_of(&[a, b]) ==
915    /// a.most_permissive(b)` — the 2-input aggregate reduces to the
916    /// pairwise combinator.
917    ///
918    /// **Order-independent**: `most_permissive_of(&[a, b, c]) ==
919    /// most_permissive_of(&[c, b, a])` — the fold inherits the
920    /// commutativity AND associativity of the pairwise
921    /// `most_permissive`.
922    ///
923    /// `const fn` so a caller can compose an arbitrary-length preset
924    /// slice at compile time — the N-ary const-fn peer of the pairwise
925    /// const-fn on [`Self::most_permissive`].
926    ///
927    /// Pre-lift, the SAME `.iter().copied().fold(EMPTY,
928    /// most_permissive)` three-primitive cascade appeared verbatim in
929    /// `empty_resource_limits_seeds_most_permissive_fold_over_slice`
930    /// AND at every future consumer that would compose "the loosest
931    /// across this set of presets" — a PRIME DIRECTIVE ≥2 trigger.
932    /// Post-lift the N-ary aggregation binds at ONE typed method with
933    /// the join-identity seed baked in so the consumer cannot pair the
934    /// meet-identity (`UNBOUNDED`) with the join combinator (a silent
935    /// distortion that would inflate every axis to [`usize::MAX`]).
936    ///
937    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
938    /// preserves proofs; the N-ary join of N preset-carried resource
939    /// proofs is itself a `ResourceLimits`. THEORY.md §V.1 — knowable
940    /// platform; the N-ary join becomes a TYPE-level operation on the
941    /// posture algebra rather than an inline fold cascade at every
942    /// consumer that aggregates a slice of presets.
943    #[must_use]
944    pub const fn most_permissive_of(postures: &[Self]) -> Self {
945        let mut acc = EMPTY_RESOURCE_LIMITS;
946        let mut i = 0;
947        while i < postures.len() {
948            acc = acc.most_permissive(postures[i]);
949            i += 1;
950        }
951        acc
952    }
953
954    /// Pointwise projection of `self` into the closed range `[lower, upper]`
955    /// — the bounded-lattice bracket combinator. `a.clamp(lower, upper)`
956    /// returns the posture whose per-axis ceiling is the input's own
957    /// ceiling raised to at least `lower`'s and then lowered to at most
958    /// `upper`'s: on every field the result is `min(max(a, lower), upper)`.
959    ///
960    /// The BRACKET operation on the [`ResourceLimits`] lattice — the
961    /// composition of [`Self::most_permissive`] with `lower` followed by
962    /// [`Self::strictest`] with `upper`. Peer of the (meet, join) pairwise
963    /// combinators one ARITY axis over (2 → 3 inputs) on the lattice-
964    /// algebra surface: where those two combine a `self` with ONE bound,
965    /// this combines a `self` with TWO bounds and closes the (unbounded-
966    /// side, bounded) row on the combinator-arity face. The bounded-lattice
967    /// analog of [`Ord::clamp`] on total orders, and the natural typed
968    /// entry a caller enforces "this posture must sit within the
969    /// `[ORG_MIN, ORG_MAX]` operator bracket" through.
970    ///
971    /// **In-range identity**: if `lower.leq(self) && self.leq(upper)` (the
972    /// input already sits within the bracket), then `a.clamp(lower, upper)
973    /// == a` — the pointwise `max(a, lower)` picks `a` on every axis (since
974    /// `lower ≤ a` per-axis), then pointwise `min(..., upper)` picks `a`
975    /// again (since `a ≤ upper` per-axis). Pinned as
976    /// `resource_limits_clamp_of_posture_already_in_range_returns_the_posture`.
977    ///
978    /// **Below-lower floor**: if `self.leq(lower)` and `lower.leq(upper)`
979    /// (input tighter than the bracket floor, and floor within ceiling),
980    /// then `a.clamp(lower, upper) == lower` — the pointwise `max` lifts
981    /// every axis to `lower`, then the pointwise `min` keeps `lower` (since
982    /// `lower ≤ upper` per-axis). Pinned as
983    /// `resource_limits_clamp_of_posture_below_lower_returns_lower`.
984    ///
985    /// **Above-upper ceiling**: if `upper.leq(self)` and `lower.leq(upper)`
986    /// (input looser than the bracket ceiling), then `a.clamp(lower, upper)
987    /// == upper` — the pointwise `max` keeps `self` on every axis (since
988    /// `lower ≤ self` per-axis via transitivity through `upper`), then the
989    /// pointwise `min` lowers every axis to `upper`. Pinned as
990    /// `resource_limits_clamp_of_posture_above_upper_returns_upper`.
991    ///
992    /// **Bracket-membership** (`lower.leq(a.clamp(lower, upper)) &&
993    /// a.clamp(lower, upper).leq(upper)` for every `a` when `lower.leq(upper)`):
994    /// the result always sits within the `[lower, upper]` bracket
995    /// regardless of the input's position relative to the bracket. This is
996    /// the DEFINING contract of the clamp primitive — every consumer that
997    /// invokes it does so precisely to guarantee the result sits within
998    /// the bracket, and the type system now carries that guarantee at ONE
999    /// named entry. Pinned as
1000    /// `resource_limits_clamp_result_sits_within_the_bracket`.
1001    ///
1002    /// **Extrema-bracket identity**: `a.clamp(EMPTY_RESOURCE_LIMITS,
1003    /// UNBOUNDED_RESOURCE_LIMITS) == a` — clamping against the bounded-
1004    /// lattice extrema is the identity function, since every posture
1005    /// already sits within the widest possible bracket. This is the
1006    /// bounded-lattice cross-check with the identity elements previously
1007    /// lifted (`EMPTY_RESOURCE_LIMITS` as the leq-minimum and
1008    /// `UNBOUNDED_RESOURCE_LIMITS` as the leq-maximum). Pinned as
1009    /// `resource_limits_clamp_with_lattice_extrema_returns_the_input`.
1010    ///
1011    /// **Degenerate-bracket collapse**: `a.clamp(x, x) == x` for every `a,
1012    /// x` — a zero-width bracket collapses every input to the single point
1013    /// `x`. Pinned as
1014    /// `resource_limits_clamp_with_equal_bounds_returns_the_bound`.
1015    ///
1016    /// **Idempotence**: `a.clamp(lower, upper).clamp(lower, upper) ==
1017    /// a.clamp(lower, upper)` — re-applying the same bracket to an
1018    /// already-clamped posture returns the same result, since the clamped
1019    /// value already sits within the bracket. Pinned as
1020    /// `resource_limits_clamp_is_idempotent`.
1021    ///
1022    /// `const fn` so a caller can pre-clamp a shipped preset against an
1023    /// operator-supplied policy bracket at compile time (`pub const
1024    /// POLICY_CLAMPED: ResourceLimits = SOME_PRESET.clamp(
1025    /// ORG_MIN_PRESET, ORG_MAX_PRESET);`) — the typed bracket-combinator
1026    /// peer of the const-fn composition ability on [`Self::strictest`] /
1027    /// [`Self::most_permissive`].
1028    ///
1029    /// Pre-lift, a caller wanting to bracket a posture within a
1030    /// `[lower, upper]` range composed the two-step cascade at its call
1031    /// site as `self.most_permissive(lower).strictest(upper)` — the same
1032    /// PRIME DIRECTIVE ≥2 pattern the pairwise combinators already lifted
1033    /// one arity down from the six-inline-primitive per-axis cascade.
1034    /// The `resource_limits_clamp_agrees_with_direct_two_step_cascade`
1035    /// pin below composes the pre-lift cascade at ONE explicit assertion
1036    /// site (its sole purpose is to pin the method-vs-cascade equivalence)
1037    /// — every future consumer that enforces a
1038    /// policy bracket (an operator-supplied `[ORG_MIN, ORG_MAX]`, a
1039    /// hostile-input `[EMPTY, HOSTILE_MAX]`, a CI-time `[TEST_MIN,
1040    /// TEST_MAX]`) would compose the same cascade at its call site with
1041    /// no exhaustiveness gate on the composition order. Post-lift the
1042    /// bracket binds at ONE typed method whose signature carries the
1043    /// bounds' roles (`lower` first, `upper` second) into the method
1044    /// rather than the consumer having to remember which side pairs with
1045    /// which combinator at each call site — a copy-paste that swapped the
1046    /// two combinators would clip the input to `min(max(a, upper), lower)`
1047    /// (the WRONG bracket order that returns `lower` for every input
1048    /// looser than `lower` regardless of `upper`'s position), a silent
1049    /// distortion the type system did not gate pre-lift and now does
1050    /// through the method's parameter order.
1051    ///
1052    /// **Non-lattice-bracket behaviour** (`!lower.leq(upper)`): the four
1053    /// documented invariants above are stated for the well-formed bracket
1054    /// case where `lower.leq(upper)`. When the two bounds are pointwise-
1055    /// incomparable OR strictly reversed (`upper.leq(lower) && lower !=
1056    /// upper`), the result is pointwise `min(max(a, lower), upper)` which
1057    /// is well-defined but no longer sits within a "bracket" in the
1058    /// intuitive sense — for a reversed bound the pointwise `max` pushes
1059    /// the value up to `lower`, then the pointwise `min` pushes it down to
1060    /// `upper`, producing `upper` at every axis (since `upper < lower`
1061    /// per-axis makes `min(anything ≥ lower, upper) == upper`). This
1062    /// matches [`Ord::clamp`]'s "min ≤ max is a precondition, otherwise
1063    /// behaviour is unspecified but well-defined" contract on total orders;
1064    /// callers routing preset pairs through `clamp` maintain the
1065    /// `lower.leq(upper)` invariant at their bounds' construction site,
1066    /// not at every clamp call site.
1067    ///
1068    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
1069    /// proofs; the bracket projection of a preset-carried resource proof
1070    /// against two bound preset-carried resource proofs is itself a
1071    /// `ResourceLimits` whose proof content is the input's proof intersected
1072    /// with the ceiling's and unioned with the floor's, and this method is
1073    /// the typed named entry composing them. THEORY.md §V.1 — knowable
1074    /// platform; the bracket combinator becomes a TYPE-level operation on
1075    /// the posture algebra rather than an inline two-step composition at
1076    /// every consumer that enforces a policy range — with the (lower,
1077    /// upper) parameter order baked in so the consumer cannot swap the
1078    /// two combinators (a silent distortion that would clip the input to
1079    /// the wrong bracket order). Frontier inspiration: [`Ord::clamp`] on
1080    /// total orders — the stdlib exposes a first-class named bracket
1081    /// combinator alongside the pairwise `min` / `max`, and the bounded-
1082    /// lattice extension is the same shape one dimension up (per-axis
1083    /// pointwise on a Cartesian product of total orders). Translation
1084    /// through pleme-io primitives is the plain `const fn` composition
1085    /// below, no trait indirection — the pairwise combinators already
1086    /// exist as `const fn` on the algebra so the bracket picks them up
1087    /// structurally.
1088    #[must_use]
1089    pub const fn clamp(self, lower: Self, upper: Self) -> Self {
1090        self.most_permissive(lower).strictest(upper)
1091    }
1092
1093    /// Pointwise bracket-membership predicate — `self` sits within the closed
1094    /// range `[lower, upper]` iff every axis's ceiling satisfies
1095    /// `lower ≤ self ≤ upper`. `a.within(lower, upper)` holds iff
1096    /// `lower.leq(self) && self.leq(upper)`: `self`'s per-axis ceilings all
1097    /// sit at or above `lower`'s AND at or below `upper`'s per-axis ceilings.
1098    ///
1099    /// The boolean PREDICATE peer of the BRACKET COMBINATOR [`Self::clamp`]
1100    /// one PRIMITIVE-KIND axis over on the bracket-primitive surface: where
1101    /// `clamp` PROJECTS an arbitrary input into the `[lower, upper]` bracket,
1102    /// this DECIDES whether the input already sits within it. Peer of
1103    /// [`Self::leq`] one ARITY axis over on the lattice-relation surface:
1104    /// where `leq` is the pairwise partial-order RELATION between two
1105    /// postures, this is the three-input CONTAINMENT RELATION deciding
1106    /// whether a posture sits between two bounds. Together the (leq, within)
1107    /// pair closes the (2-input, 3-input) row on the relation-arity face of
1108    /// the [`ResourceLimits`] lattice-algebra surface — the boolean twin of
1109    /// the (`strictest`+`most_permissive`, `clamp`) combinator-arity closure.
1110    ///
1111    /// **Clamp fixed-point theorem** (`a.within(lower, upper) ⇔
1112    /// a.clamp(lower, upper) == a`): the input sits within the bracket iff
1113    /// the clamp is the identity on it. The forward direction is the
1114    /// in-range identity arm of [`Self::clamp`] (already pinned as
1115    /// `resource_limits_clamp_of_posture_already_in_range_returns_the_posture`);
1116    /// the reverse follows from the bracket-membership contract on `clamp`
1117    /// (the clamp result always sits within the bracket, so if the clamp
1118    /// equals the input, the input also sits within the bracket). Pinned as
1119    /// `resource_limits_within_agrees_with_clamp_fixed_point` — the CANONICAL
1120    /// cross-check axiom binding the predicate to the combinator, analogous
1121    /// to the meet-agreement (`a.leq(b) ⇔ a.strictest(b) == a`) and
1122    /// join-agreement (`a.leq(b) ⇔ a.most_permissive(b) == b`) axioms binding
1123    /// the `leq` relation to its combinator peers.
1124    ///
1125    /// **In-range identity**: if `lower.leq(self) && self.leq(upper)`
1126    /// (the input already sits within the bracket), then `a.within(lower,
1127    /// upper) == true`. Pinned as
1128    /// `resource_limits_within_of_posture_in_range_is_true`.
1129    ///
1130    /// **Below-lower rejection**: if the input is strictly tighter than the
1131    /// bracket floor on any axis (`!lower.leq(self)`), then
1132    /// `a.within(lower, upper) == false` regardless of `self`'s relation to
1133    /// `upper` — the boolean conjunction rejects the input. Pinned as
1134    /// `resource_limits_within_of_posture_below_lower_is_false`.
1135    ///
1136    /// **Above-upper rejection**: if the input is strictly looser than the
1137    /// bracket ceiling on any axis (`!self.leq(upper)`), then
1138    /// `a.within(lower, upper) == false`. Pinned as
1139    /// `resource_limits_within_of_posture_above_upper_is_false`.
1140    ///
1141    /// **Extrema-bracket identity**: `a.within(EMPTY_RESOURCE_LIMITS,
1142    /// UNBOUNDED_RESOURCE_LIMITS) == true` for every posture, since every
1143    /// posture sits within the widest possible bracket — the bounded-lattice
1144    /// cross-check with the identity elements previously lifted. Pinned as
1145    /// `resource_limits_within_with_lattice_extrema_is_true`.
1146    ///
1147    /// **Degenerate-bracket collapse**: `a.within(x, x) ⇔ a == x` — a
1148    /// zero-width bracket admits only the single point `x`. Follows from
1149    /// antisymmetry of `leq`: `x.leq(a) && a.leq(x) → a == x`. Pinned as
1150    /// `resource_limits_within_of_equal_bounds_iff_equal_to_bound`.
1151    ///
1152    /// **Reflexive-bracket identity**: `a.within(a, a) == true` for every
1153    /// posture — every input trivially sits within the zero-width bracket
1154    /// at itself, since `a.leq(a)` holds by reflexivity of `leq`. Pinned as
1155    /// `resource_limits_within_of_self_is_reflexive`.
1156    ///
1157    /// `const fn` so a caller can pin a preset-bracket-membership identity
1158    /// at compile time (`const _: () = assert!(DEFAULT_RESOURCE_LIMITS
1159    /// .within(EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS));`) — the
1160    /// typed bracket-predicate peer of the const-fn evaluability on
1161    /// [`Self::leq`] and [`Self::clamp`].
1162    ///
1163    /// Pre-lift, a caller wanting to decide whether a posture sat within a
1164    /// `[lower, upper]` bracket composed the two-primitive scaffolding at its
1165    /// call site as `lower.leq(a) && a.leq(upper)` — the same PRIME DIRECTIVE
1166    /// ≥2 pattern the pairwise `strictest` / `most_permissive` / `leq`
1167    /// combinators already lifted one arity down from six-inline-primitive
1168    /// per-axis cascades. The clamp test cohort exercises this shape at
1169    /// every prerequisite pin (`FLOOR.leq(MID) && MID.leq(CEILING)`) AND at
1170    /// the bracket-membership contract's core assertion (`FLOOR.leq(clamped)
1171    /// && clamped.leq(CEILING)`); post-lift both bindings route through ONE
1172    /// named method whose signature carries the containment relation into the
1173    /// type system rather than the consumer composing the two-primitive
1174    /// conjunction at every call site — a copy-paste that swapped the
1175    /// bounds would test `upper.leq(a) && a.leq(lower)` (the WRONG
1176    /// containment direction returning `true` only for postures that
1177    /// simultaneously exceed the ceiling AND fall below the floor, i.e.
1178    /// only for the impossible `upper.leq(a) && a.leq(lower)` case when
1179    /// `lower.leq(upper)`), a silent distortion the type system did not gate
1180    /// pre-lift and now does through the method's parameter order.
1181    ///
1182    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
1183    /// proofs; the bracket-membership predicate on three preset-carried
1184    /// resource proofs is itself a typed named entry composing the input's
1185    /// containment ordering. THEORY.md §V.1 — knowable platform; the
1186    /// pointwise `≤`-conjunction combinator becomes a TYPE-level operation
1187    /// on the posture algebra rather than an inline two-primitive
1188    /// conjunction at every consumer that decides bracket membership — with
1189    /// the (lower, upper) parameter order baked in so the consumer cannot
1190    /// swap the two bounds. Frontier inspiration: the interval-containment
1191    /// predicate is the natural boolean twin of stdlib's [`Ord::clamp`] —
1192    /// stdlib lacks an `is_in_range(min, max)` method, but the bounded-
1193    /// lattice extension of the pattern is straightforward on the pointwise
1194    /// partial order and closes the (combinator, predicate) row on the
1195    /// bracket-primitive surface. Translation through pleme-io primitives is
1196    /// the plain `const fn` `leq`-conjunction below, no trait indirection —
1197    /// the partial-order relation already exists as a `const fn` on the
1198    /// algebra so the predicate picks it up structurally.
1199    #[must_use]
1200    pub const fn within(self, lower: Self, upper: Self) -> bool {
1201        lower.leq(self) && self.leq(upper)
1202    }
1203
1204    /// Boolean `leq`-conjunction across a slice of postures — `self` sits
1205    /// at-or-below every operand in `postures`. `a.is_lower_bound_of(&[b,
1206    /// c, d])` holds iff `a.leq(b) && a.leq(c) && a.leq(d)`: on every
1207    /// operand `self`'s per-axis ceilings sit at-or-below the operand's,
1208    /// so `self` is a common lower bound for the slice.
1209    ///
1210    /// The N-ary boolean PREDICATE peer of the pairwise partial-order
1211    /// relation [`Self::leq`] one ARITY axis over on the lattice-relation
1212    /// surface: where `leq` decides the two-input partial-order between
1213    /// two postures, this decides the (1 + N)-input containment-from-below
1214    /// between `self` and a slice of postures. Peer of
1215    /// [`Self::strictest_of`] one PRIMITIVE-KIND axis over on the N-ary
1216    /// aggregation surface: where `strictest_of` COMPUTES the meet (the
1217    /// greatest lower bound of the slice), this DECIDES whether `self` is
1218    /// a lower bound of the slice (member of the lower-bound set the meet
1219    /// is the largest element of). Together `strictest_of` and
1220    /// `is_lower_bound_of` close the (combinator, predicate) row on the
1221    /// N-ary-aggregation face, exactly the way (`strictest`,
1222    /// `most_permissive`) ↔ `leq` closes the pairwise row and (`clamp` ↔
1223    /// `within`) closes the bracket row on the (combinator, predicate) ×
1224    /// (pairwise, N-ary, bracket) primitive surface.
1225    ///
1226    /// **Empty-slice vacuous truth**: `a.is_lower_bound_of(&[]) == true`
1227    /// for every posture — the empty conjunction is vacuously true, since
1228    /// every posture is trivially a lower bound of the empty set. Peer of
1229    /// `strictest_of(&[]) == UNBOUNDED_RESOURCE_LIMITS`'s empty-slice
1230    /// identity: the empty slice aggregates to the identity element of
1231    /// the underlying operation (`true` for boolean conjunction,
1232    /// [`UNBOUNDED_RESOURCE_LIMITS`] for pointwise `min`), so the empty
1233    /// aggregate never rejects. Pinned as
1234    /// `resource_limits_is_lower_bound_of_empty_slice_is_vacuously_true`.
1235    ///
1236    /// **Single-element identity**: `a.is_lower_bound_of(&[b]) ==
1237    /// a.leq(b)` — the 1-input predicate reduces to the pairwise
1238    /// relation, the same way `strictest_of(&[a]) == a` reduces the
1239    /// 1-input N-ary combinator to the operand verbatim. Pinned as
1240    /// `resource_limits_is_lower_bound_of_single_element_reduces_to_leq`.
1241    ///
1242    /// **Meet witness**: `strictest_of(&postures).is_lower_bound_of(&
1243    /// postures) == true` for every slice — the N-ary meet is always a
1244    /// lower bound of the slice it aggregates. The definitional link
1245    /// between the N-ary COMBINATOR and the N-ary PREDICATE: the
1246    /// aggregate `strictest_of` produces is a member of the lower-bound
1247    /// set `is_lower_bound_of` characterizes. Pinned as
1248    /// `resource_limits_is_lower_bound_of_holds_for_the_meet_of_the_slice`.
1249    ///
1250    /// **Universal-bottom witness**: `EMPTY_RESOURCE_LIMITS
1251    /// .is_lower_bound_of(&postures) == true` for every slice — the
1252    /// lattice bottom is a common lower bound of every set, since
1253    /// `EMPTY.leq(a) == true` for every posture by the lattice-bottom
1254    /// axiom (`0 ≤ x` per-axis). Pinned as
1255    /// `resource_limits_empty_is_universal_lower_bound_of_every_slice`.
1256    ///
1257    /// **Any-operand rejection**: if `!self.leq(postures[i])` for any
1258    /// `i`, then `a.is_lower_bound_of(&postures) == false` — the
1259    /// conjunction short-circuits on the first violating operand and
1260    /// rejects. Pinned as
1261    /// `resource_limits_is_lower_bound_of_rejects_when_any_operand_is_below_self`.
1262    ///
1263    /// **Peer** — [`Self::is_upper_bound_of`] one COMBINATOR-DIRECTION
1264    /// axis over: the two close the (`is_lower_bound_of`,
1265    /// `is_upper_bound_of`) N-ary boolean-conjunction pair the pairwise
1266    /// `leq` extends from 2 to N, itself peer of the (`strictest_of`,
1267    /// `most_permissive_of`) N-ary combinator pair one PRIMITIVE-KIND
1268    /// axis over.
1269    ///
1270    /// `const fn` so a caller can pin an N-ary-bound-membership identity
1271    /// at compile time (`const _: () = assert!(EMPTY_RESOURCE_LIMITS
1272    /// .is_lower_bound_of(&[DEFAULT_RESOURCE_LIMITS,
1273    /// UNBOUNDED_RESOURCE_LIMITS]));`) — the N-ary predicate peer of the
1274    /// const-fn evaluability on [`Self::leq`] and [`Self::within`].
1275    /// `Copy` on [`ResourceLimits`] lets the const-fn loop index the
1276    /// slice by value without an explicit `.clone()` (which const-fn
1277    /// would not permit anyway).
1278    ///
1279    /// Pre-lift, a caller wanting to decide whether a posture was a
1280    /// common lower bound for a slice composed the
1281    /// `postures.iter().all(|p| self.leq(*p))` two-primitive scaffolding
1282    /// at every call site — the same PRIME DIRECTIVE ≥2 pattern the
1283    /// pairwise `leq` combinator already lifted one arity down from the
1284    /// six-inline-primitive per-axis cascade, and the same shape the
1285    /// pairwise `within` combinator already lifted one arity down from
1286    /// the two-primitive `lower.leq(a) && a.leq(upper)` conjunction.
1287    /// Post-lift the N-ary predicate binds at ONE typed method whose
1288    /// signature carries the containment direction (`self ≤ every
1289    /// operand`) into the type system rather than the consumer composing
1290    /// the `all()`-conjunction at every call site — a copy-paste that
1291    /// flipped the leq direction would test `postures[i].leq(self)` (the
1292    /// dual `is_upper_bound_of` question returning `true` only when
1293    /// `self` DOMINATES rather than UNDERLIES the slice), a silent
1294    /// distortion the method's parameter direction now gates.
1295    ///
1296    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
1297    /// preserves proofs; the N-ary containment-from-below predicate on
1298    /// (1 + N) preset-carried resource proofs is itself a typed named
1299    /// entry composing the pairwise partial-order relation. THEORY.md
1300    /// §V.1 — knowable platform; the N-ary `leq`-conjunction becomes a
1301    /// TYPE-level operation on the posture algebra rather than an inline
1302    /// two-primitive conjunction at every consumer that decides
1303    /// N-ary-bound membership. Frontier inspiration: the algebraic-
1304    /// datatypes tradition of a `Foldable` typeclass exposing `all` /
1305    /// `Data.Foldable.all` alongside the pairwise relation — the N-ary
1306    /// traversal of a collection through a boolean-conjunction seed is a
1307    /// first-class named method the typeclass carries. Translation
1308    /// through pleme-io primitives is the plain `const fn` slice-walk
1309    /// below, no typeclass indirection — the partial-order relation
1310    /// already exists as a `const fn` on the algebra so the predicate
1311    /// picks it up structurally.
1312    #[must_use]
1313    pub const fn is_lower_bound_of(self, postures: &[Self]) -> bool {
1314        let mut i = 0;
1315        while i < postures.len() {
1316            if !self.leq(postures[i]) {
1317                return false;
1318            }
1319            i += 1;
1320        }
1321        true
1322    }
1323
1324    /// Boolean `leq`-conjunction across a slice of postures — every operand
1325    /// in `postures` sits at-or-below `self`. `a.is_upper_bound_of(&[b, c,
1326    /// d])` holds iff `b.leq(a) && c.leq(a) && d.leq(a)`: on every operand
1327    /// the operand's per-axis ceilings sit at-or-below `self`'s, so `self`
1328    /// is a common upper bound for the slice.
1329    ///
1330    /// The DUAL N-ary predicate of [`Self::is_lower_bound_of`] one
1331    /// COMBINATOR-DIRECTION axis over: the two close the (lower, upper)
1332    /// N-ary boolean-conjunction pair the pairwise `leq` extends from 2
1333    /// to N, itself peer of the (`strictest_of`, `most_permissive_of`)
1334    /// N-ary combinator pair one PRIMITIVE-KIND axis over on the N-ary-
1335    /// aggregation face of the (combinator, predicate) × (pairwise,
1336    /// N-ary, bracket) primitive surface. Peer of
1337    /// [`Self::most_permissive_of`] one PRIMITIVE-KIND axis over on the
1338    /// N-ary aggregation surface: where `most_permissive_of` COMPUTES
1339    /// the join (the least upper bound of the slice), this DECIDES
1340    /// whether `self` is an upper bound of the slice (member of the
1341    /// upper-bound set the join is the smallest element of).
1342    ///
1343    /// **Empty-slice vacuous truth**: `a.is_upper_bound_of(&[]) == true`
1344    /// for every posture — the empty conjunction is vacuously true. Peer
1345    /// of `most_permissive_of(&[]) == EMPTY_RESOURCE_LIMITS`'s empty-
1346    /// slice identity: the empty aggregate binds structurally to the
1347    /// identity element of the underlying operation. Pinned as
1348    /// `resource_limits_is_upper_bound_of_empty_slice_is_vacuously_true`.
1349    ///
1350    /// **Single-element identity**: `a.is_upper_bound_of(&[b]) ==
1351    /// b.leq(a)` — the 1-input predicate reduces to the pairwise
1352    /// relation with the direction flipped (since `self` sits ABOVE the
1353    /// operand rather than BELOW it). Pinned as
1354    /// `resource_limits_is_upper_bound_of_single_element_reduces_to_leq`.
1355    ///
1356    /// **Join witness**: `most_permissive_of(&postures).is_upper_bound_of(&
1357    /// postures) == true` for every slice — the N-ary join is always an
1358    /// upper bound of the slice it aggregates. The dual definitional link
1359    /// between N-ary COMBINATOR and N-ary PREDICATE: the aggregate
1360    /// `most_permissive_of` produces is a member of the upper-bound set
1361    /// `is_upper_bound_of` characterizes. Pinned as
1362    /// `resource_limits_is_upper_bound_of_holds_for_the_join_of_the_slice`.
1363    ///
1364    /// **Universal-top witness**: `UNBOUNDED_RESOURCE_LIMITS
1365    /// .is_upper_bound_of(&postures) == true` for every slice — the
1366    /// lattice top is a common upper bound of every set, since
1367    /// `a.leq(UNBOUNDED) == true` for every posture by the lattice-top
1368    /// axiom (`x ≤ usize::MAX` per-axis). Pinned as
1369    /// `resource_limits_unbounded_is_universal_upper_bound_of_every_slice`.
1370    ///
1371    /// **Any-operand rejection**: if `!postures[i].leq(self)` for any
1372    /// `i`, then `a.is_upper_bound_of(&postures) == false` — the
1373    /// conjunction short-circuits and rejects. Pinned as
1374    /// `resource_limits_is_upper_bound_of_rejects_when_any_operand_is_above_self`.
1375    ///
1376    /// `const fn` so a caller can pin an N-ary-bound-membership identity
1377    /// at compile time. See [`Self::is_lower_bound_of`] for the shared
1378    /// pre-lift ≥2 pattern (`postures.iter().all(|p| p.leq(*self))`), the
1379    /// theory anchor (THEORY.md §II.1 invariant 5, §V.1), and the
1380    /// `Foldable::all` frontier inspiration — this method carries the
1381    /// dual `postures[i].leq(self)` direction rather than
1382    /// `self.leq(postures[i])`.
1383    #[must_use]
1384    pub const fn is_upper_bound_of(self, postures: &[Self]) -> bool {
1385        let mut i = 0;
1386        while i < postures.len() {
1387            if !postures[i].leq(self) {
1388                return false;
1389            }
1390            i += 1;
1391        }
1392        true
1393    }
1394
1395    /// Strict pointwise partial-order relation across the six ceilings —
1396    /// `self` is STRICTLY tighter than `other` iff `self.leq(other)` AND
1397    /// `self != other`. `a.lt(b)` holds iff every input the `a` posture
1398    /// admits the `b` posture also admits AND there is at least one axis
1399    /// on which `a`'s ceiling is strictly smaller than `b`'s: `a`'s per-
1400    /// axis ceilings all sit at-or-below `b`'s AND at least one field
1401    /// closes the inequality strictly, so `b` strictly extends `a`'s
1402    /// admissible input set on at least one axis.
1403    ///
1404    /// The STRICT peer of [`Self::leq`] one STRICTNESS axis over on the
1405    /// pairwise partial-order surface: where `leq` is the REFLEXIVE
1406    /// non-strict `≤` relation (`a.leq(a) == true`), this is the
1407    /// IRREFLEXIVE strict `<` relation (`a.lt(a) == false`). Together
1408    /// the two close the (non-strict, strict) partial-order pair on the
1409    /// pairwise-relation face of the (combinator, predicate) ×
1410    /// (strictness) primitive surface — itself peer of the
1411    /// (`strictest`, `most_permissive`) meet/join combinator pair one
1412    /// PRIMITIVE-KIND axis over (both bind against the same pointwise
1413    /// `≤` ordering).
1414    ///
1415    /// Encoded as `self.leq(other) && !other.leq(self)` — the two-
1416    /// primitive antisymmetric encoding of strict `<` on any partial
1417    /// order, pointwise or otherwise. Equivalent on the
1418    /// [`ResourceLimits`] pointwise partial order (which is
1419    /// antisymmetric) to `self.leq(other) && self != other`:
1420    /// antisymmetry says `self.leq(other) && other.leq(self) ⇒ self ==
1421    /// other`, so `self.leq(other) && !other.leq(self)` iff
1422    /// `self.leq(other) && self != other`. The antisymmetric-leq
1423    /// encoding is chosen because it does NOT rely on
1424    /// [`PartialEq::eq`] being `const fn` (which the derived impl is
1425    /// not on stable) — both [`Self::leq`] calls are already `const fn`
1426    /// on the algebra.
1427    ///
1428    /// **Irreflexivity**: `a.lt(a) == false` for every posture. A
1429    /// posture is never strictly tighter than itself, since
1430    /// `a.leq(a) && !a.leq(a)` short-circuits `false` on the second
1431    /// conjunct. Pinned as
1432    /// `resource_limits_lt_is_irreflexive`.
1433    ///
1434    /// **Asymmetry**: `a.lt(b) ⇒ !b.lt(a)` for every pair. The two
1435    /// directions of the strict relation cannot both hold, since
1436    /// `a.lt(b)` implies `!b.leq(a)`, and `b.lt(a)` requires
1437    /// `b.leq(a)` — the two are contradictory. Pinned as
1438    /// `resource_limits_lt_is_asymmetric`.
1439    ///
1440    /// **Transitivity**: `a.lt(b) && b.lt(c) ⇒ a.lt(c)` for every
1441    /// triple. Inherits from the transitivity of [`Self::leq`] on both
1442    /// conjuncts, with the strictness carried through by the outer
1443    /// antisymmetric leg (if `c.leq(a)` held, transitivity of `leq`
1444    /// would give `b.leq(a)` contradicting `a.lt(b)`). Pinned as
1445    /// `resource_limits_lt_is_transitive`.
1446    ///
1447    /// **Refines [`Self::leq`]**: `a.lt(b) ⇒ a.leq(b)` for every pair
1448    /// — the strict relation refines the non-strict one. Conversely
1449    /// `a.leq(b) && !a.lt(b) ⇒ a == b` (the reflexive tie is exactly
1450    /// the non-strict-minus-strict gap). Pinned as
1451    /// `resource_limits_lt_refines_leq` AND
1452    /// `resource_limits_lt_agrees_with_leq_minus_equality`.
1453    ///
1454    /// **Bounded-lattice diagonal**:
1455    /// `EMPTY_RESOURCE_LIMITS.lt(DEFAULT_RESOURCE_LIMITS)` AND
1456    /// `DEFAULT_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS)` AND
1457    /// `EMPTY_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS)` — the
1458    /// strict chain across the (bottom, middle, top) preset triple on
1459    /// the bounded-lattice diagonal. Every `DEFAULT_MAX_*` module
1460    /// constant is a concrete positive value strictly less than
1461    /// [`usize::MAX`] and strictly greater than `0`, so the two
1462    /// endpoints and the interior are strictly ordered on every axis.
1463    /// Pinned as `resource_limits_lt_of_bottom_diagonal_pinned`.
1464    ///
1465    /// **Incomparable rejection**: on the two hand-authored asymmetric
1466    /// postures (three axes smaller in each direction), both
1467    /// directions of the strict relation fail (`!a.lt(b) && !b.lt(a)`)
1468    /// because neither `leq` direction holds. The predicate does NOT
1469    /// promote incomparable pairs to a comparison verdict. Pinned as
1470    /// `resource_limits_lt_rejects_incomparable_postures`.
1471    ///
1472    /// **Antisymmetric-encoding cross-check**: `a.lt(b) == (a.leq(b)
1473    /// && !b.leq(a))` at every pair by definition, AND `a.lt(b) ==
1474    /// (a.leq(b) && a != b)` at every pair by antisymmetry. Both
1475    /// encodings are pinned equal to the shipped body across the
1476    /// (empty, default, unbounded, mid, other) 5×5 preset matrix.
1477    /// Pinned as
1478    /// `resource_limits_lt_agrees_with_direct_antisymmetric_encoding`.
1479    ///
1480    /// `const fn` so a caller can pin a strict-order identity at
1481    /// compile time (`const _: () = assert!(EMPTY_RESOURCE_LIMITS.lt(
1482    /// DEFAULT_RESOURCE_LIMITS));`) rather than deferring the
1483    /// composition to a runtime chain — sibling of the const-fn
1484    /// evaluability pin on [`Self::leq`] one STRICTNESS axis over.
1485    ///
1486    /// Pre-lift, a caller wanting "is this posture STRICTLY tighter
1487    /// than that one?" composed the two-primitive
1488    /// `a.leq(b) && !b.leq(a)` (or equivalently `a.leq(b) && a != b`)
1489    /// conjunction at its call site — a PRIME DIRECTIVE ≥2 pattern
1490    /// once two consumers need it, and one that couples the strictness
1491    /// interpretation to the caller's chosen encoding
1492    /// (antisymmetric-leq vs. leq-and-not-equal, which coincide on
1493    /// pointwise partial orders but diverge on non-antisymmetric
1494    /// preorders). Post-lift the strict relation binds at ONE typed
1495    /// method the algebra exposes, and the encoding is a private
1496    /// implementation detail no caller can drift from.
1497    ///
1498    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
1499    /// preserves proofs; the strict-tightening relation between two
1500    /// preset-carried resource proofs is itself a typed named `bool`
1501    /// predicate composing them via the underlying `leq`. THEORY.md
1502    /// §V.1 — knowable platform; the strict `<` relation becomes a
1503    /// TYPE-level primitive on the posture algebra rather than an
1504    /// inline two-primitive conjunction at every consumer that
1505    /// decides "did we strictly tighten?" `tatara-lattice`'s partial
1506    /// order on `Classification` closes the SAME shape one layer
1507    /// down: an entity's classification lattice carries a strict `<`
1508    /// peer of its `leq` for the same reason a resource posture does.
1509    /// Frontier inspiration: [`PartialOrd::lt`] on Rust's partial-
1510    /// order trait; [`Ord::lt`] on the total-order trait — the
1511    /// strict variant of `≤` is a first-class named method the
1512    /// standard library exposes rather than leaving each consumer to
1513    /// compose `partial_cmp(&other) == Some(Less)` at its call site.
1514    /// Translation through pleme-io primitives is the plain `const
1515    /// fn` antisymmetric encoding below, no trait indirection — the
1516    /// non-strict [`Self::leq`] already exists as a `const fn` on the
1517    /// algebra so the strict variant picks it up structurally.
1518    #[must_use]
1519    pub const fn lt(self, other: Self) -> bool {
1520        self.leq(other) && !other.leq(self)
1521    }
1522
1523    /// Strict pointwise partial-order relation dual of [`Self::lt`] —
1524    /// `a.gt(b) == b.lt(a)`. `self` is STRICTLY looser than `other`
1525    /// iff `other.leq(self)` AND `self != other`: on every axis
1526    /// `self`'s ceiling sits at-or-above `other`'s, and at least one
1527    /// axis closes the inequality strictly, so `self` strictly extends
1528    /// `other`'s admissible input set on at least one axis.
1529    ///
1530    /// The DIRECTION peer of [`Self::lt`] one DIRECTION axis over on
1531    /// the strict-pairwise-relation face — where `lt` is the strict
1532    /// "below" relation (`a < b`), this is the strict "above"
1533    /// relation (`a > b`). Together the two close the (below, above)
1534    /// direction pair on the strict pairwise relation, exactly as
1535    /// [`Self::is_lower_bound_of`] / [`Self::is_upper_bound_of`] close
1536    /// the (below, above) direction pair on the non-strict N-ary
1537    /// relation one ARITY axis over.
1538    ///
1539    /// Same laws as [`Self::lt`] with directions flipped:
1540    /// irreflexive (`a.gt(a) == false`), asymmetric
1541    /// (`a.gt(b) ⇒ !b.gt(a)`), transitive
1542    /// (`a.gt(b) && b.gt(c) ⇒ a.gt(c)`); refines a hypothetical `geq`
1543    /// (the reflexive `≥`) exactly as `lt` refines `leq`. Pinned on
1544    /// the shipped preset triangle: `UNBOUNDED_RESOURCE_LIMITS.gt(
1545    /// DEFAULT_RESOURCE_LIMITS) && DEFAULT_RESOURCE_LIMITS.gt(
1546    /// EMPTY_RESOURCE_LIMITS)`.
1547    ///
1548    /// Encoded as `other.lt(self)` — one primitive delegation to
1549    /// [`Self::lt`] so the strict-relation encoding lives at exactly
1550    /// one implementation site, and a future re-derivation of `lt`
1551    /// (e.g. to a different antisymmetry encoding) propagates to `gt`
1552    /// mechanically rather than requiring a per-method fix-up.
1553    ///
1554    /// `const fn` for the same compile-time-pin reasons as
1555    /// [`Self::lt`]. See [`Self::lt`] for the full docstring, the
1556    /// pre-lift `a.leq(b) && !b.leq(a)` two-primitive PRIME DIRECTIVE
1557    /// ≥2 pattern, the theory anchor (THEORY.md §II.1 invariant 5,
1558    /// §V.1), and the [`Ord::gt`] frontier inspiration.
1559    #[must_use]
1560    pub const fn gt(self, other: Self) -> bool {
1561        other.lt(self)
1562    }
1563
1564    /// Non-strict pointwise partial-order relation dual of
1565    /// [`Self::leq`] — `a.geq(b) == b.leq(a)`. `self` is AT-OR-LOOSER-
1566    /// THAN `other` iff on every axis `self`'s ceiling sits at-or-above
1567    /// `other`'s: every input the `other` posture admits the `self`
1568    /// posture also admits, so `self` dominates `other`'s admissible
1569    /// input set pointwise (possibly with equality on some or every
1570    /// axis).
1571    ///
1572    /// The DIRECTION peer of [`Self::leq`] one DIRECTION axis over on
1573    /// the non-strict pairwise-relation face — where `leq` is the
1574    /// non-strict "below" relation (`a ≤ b`), this is the non-strict
1575    /// "above" relation (`a ≥ b`). Together the two close the
1576    /// (below, above) direction pair on the non-strict pairwise
1577    /// relation, exactly as [`Self::lt`] / [`Self::gt`] close the
1578    /// (below, above) direction pair on the STRICT pairwise relation
1579    /// one STRICTNESS axis over. The STRICTNESS peer of [`Self::gt`]
1580    /// one STRICTNESS axis over on the "above" column of the
1581    /// (direction × strictness) 2×2 partial-order face — where `gt` is
1582    /// the IRREFLEXIVE strict `>` relation (`a.gt(a) == false`), this
1583    /// is the REFLEXIVE non-strict `≥` relation (`a.geq(a) == true`).
1584    /// The four methods `(leq, lt, gt, geq)` EXHAUSTIVELY close the
1585    /// (below, above) × (non-strict, strict) 2×2 pairwise partial-order
1586    /// face on [`ResourceLimits`], with each method the single-axis
1587    /// peer of two others (one DIRECTION axis over AND one STRICTNESS
1588    /// axis over).
1589    ///
1590    /// Same laws as [`Self::leq`] with directions flipped:
1591    /// reflexive (`a.geq(a) == true`), antisymmetric
1592    /// (`a.geq(b) && b.geq(a) ⇒ a == b`), transitive
1593    /// (`a.geq(b) && b.geq(c) ⇒ a.geq(c)`); refines [`Self::gt`]
1594    /// (`a.gt(b) ⇒ a.geq(b)`) exactly as `leq` is refined by `lt`.
1595    /// NOT total on incomparable postures: on the two hand-authored
1596    /// asymmetric postures both directions of `geq` fail — the
1597    /// non-strict relation does not promote incomparable pairs to a
1598    /// comparison verdict any more than the strict one does. Pinned on
1599    /// the shipped preset triangle:
1600    /// `UNBOUNDED_RESOURCE_LIMITS.geq(DEFAULT_RESOURCE_LIMITS) &&
1601    /// DEFAULT_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS)`.
1602    ///
1603    /// Encoded as `other.leq(self)` — one primitive delegation to
1604    /// [`Self::leq`] so the non-strict-relation encoding lives at
1605    /// exactly one implementation site, and a future re-derivation of
1606    /// `leq` (e.g. to a different pointwise conjunction) propagates to
1607    /// `geq` mechanically rather than requiring a per-method fix-up.
1608    /// Mirrors [`Self::gt`]'s `other.lt(self)` delegation one
1609    /// STRICTNESS axis over.
1610    ///
1611    /// **N-ary-predicate link**: `a.geq(b) == a.is_upper_bound_of(&[b])`
1612    /// — the pairwise "above" relation is exactly the 1-input
1613    /// specialization of the N-ary upper-bound predicate. The
1614    /// (`geq`, `is_upper_bound_of`) pair sits on the same ARITY axis
1615    /// as (`leq`, `is_lower_bound_of`) one DIRECTION axis over — every
1616    /// pairwise partial-order relation extends to an N-ary bound-
1617    /// membership predicate the substrate carries as a named entry.
1618    ///
1619    /// `const fn` for the same compile-time-pin reasons as
1620    /// [`Self::leq`] (`const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS
1621    /// .geq(DEFAULT_RESOURCE_LIMITS));`). See [`Self::leq`] for the
1622    /// full docstring, the pre-lift six-inline-`>=`-conjunction pattern
1623    /// callers wrote before the pairwise relation was named, the
1624    /// theory anchor (THEORY.md §II.1 invariant 5, §V.1), and the
1625    /// [`PartialOrd::ge`] frontier inspiration.
1626    #[must_use]
1627    pub const fn geq(self, other: Self) -> bool {
1628        other.leq(self)
1629    }
1630
1631    /// Antichain characterization on the [`ResourceLimits`] partial
1632    /// order — `true` iff `self` and `other` are INCOMPARABLE, i.e.,
1633    /// NEITHER `self.leq(other)` NOR `self.geq(other)` holds. The two
1634    /// postures then sit on distinct branches of the lattice: at least
1635    /// one axis has `self` strictly below `other`, and at least one
1636    /// axis has `self` strictly above `other`, so neither pointwise-
1637    /// domination direction closes and neither preset dominates the
1638    /// other on every field.
1639    ///
1640    /// The ANTICHAIN CORNER on the (leq, geq, lt, gt, incomparable)
1641    /// pairwise-relation surface — the corner that the four pairwise
1642    /// partial-order predicates leave open. On a TOTAL order every
1643    /// pair of elements sits at exactly one of `{Less, Equal,
1644    /// Greater}`, so `is_incomparable` would fold to `false`
1645    /// unconditionally; on the [`ResourceLimits`] pointwise partial
1646    /// order the antichain is non-empty (witnessed by
1647    /// `HAND_AUTHORED_MID_POSTURE` + `HAND_AUTHORED_OTHER_POSTURE`,
1648    /// which the just-lifted [`Self::leq`] / [`Self::geq`] / [`Self::lt`]
1649    /// / [`Self::gt`] tests already pin as neither-`leq`-nor-`geq`), so
1650    /// the corner carries a genuine bit distinct from
1651    /// `!self.leq(other) && !self.geq(other)` inlined at the callsite.
1652    /// The (leq, geq, lt, gt) 2×2 pairwise partial-order face the just-
1653    /// lifted [`Self::geq`] EXHAUSTIVELY closes at its (below, above) ×
1654    /// (non-strict, strict) grid pins the COMPARABLE cases; THIS
1655    /// projection pins the DISJOINT antichain complement one COVER
1656    /// axis over — the (comparable, incomparable) two-cell partition of
1657    /// the total (ordered pair × verdict) surface.
1658    ///
1659    /// Symmetric on its argument order: `a.is_incomparable(b) ==
1660    /// b.is_incomparable(a)` — the composition `!self.leq(other) &&
1661    /// !self.geq(other)` folds through `self.leq(other) ==
1662    /// other.geq(self)` (dual identity on [`Self::geq`]) and the
1663    /// conjunction's commutativity, so the projection is
1664    /// order-independent on its two inputs. This SYMMETRY POSTURE
1665    /// distinguishes it from [`Self::leq`] / [`Self::lt`] (both
1666    /// antisymmetric — swapping inputs FLIPS the verdict) and from
1667    /// [`Self::within`] (asymmetric — the first input is the target
1668    /// and the last two are the bracket bounds).
1669    ///
1670    /// Irreflexive on its own argument: `a.is_incomparable(a) ==
1671    /// false` — every posture is comparable to itself via
1672    /// [`Self::leq`]'s reflexivity (`a.leq(a) == true`), so the
1673    /// conjunction's `!self.leq(other)` leg forces the verdict to
1674    /// `false` at every diagonal input. Sibling posture to
1675    /// [`Self::lt`]'s irreflexivity one COVER axis over: `lt` is
1676    /// irreflexive because the strict relation rules out equality;
1677    /// THIS projection is irreflexive because the comparable
1678    /// relation already includes equality.
1679    ///
1680    /// **Bottom-pole absorption**: `a.is_incomparable(
1681    /// EMPTY_RESOURCE_LIMITS) == false` on every posture `a` —
1682    /// [`EMPTY_RESOURCE_LIMITS`] is the bounded-lattice bottom, so
1683    /// `EMPTY.leq(a)` holds on every axis (`0 <= a.max_*` on `usize`),
1684    /// which gives `a.geq(EMPTY)` through the [`Self::geq`] dual
1685    /// identity, which falsifies the `!self.geq(other)` leg. The
1686    /// bounded-lattice bottom is comparable to every posture. Pinned
1687    /// on the canonical preset roster.
1688    ///
1689    /// **Top-pole absorption**: `a.is_incomparable(
1690    /// UNBOUNDED_RESOURCE_LIMITS) == false` on every posture `a` —
1691    /// [`UNBOUNDED_RESOURCE_LIMITS`] is the bounded-lattice top, so
1692    /// `a.leq(UNBOUNDED)` holds on every axis (`a.max_* <=
1693    /// usize::MAX` unconditionally), which falsifies the
1694    /// `!self.leq(other)` leg. The bounded-lattice top is comparable
1695    /// to every posture. Pinned on the canonical preset roster.
1696    ///
1697    /// **Antichain load-bearing arm**:
1698    /// `HAND_AUTHORED_MID_POSTURE.is_incomparable(
1699    /// HAND_AUTHORED_OTHER_POSTURE) == true` — the two hand-authored
1700    /// asymmetric postures sit on distinct branches (each is smaller
1701    /// on three axes and larger on the other three), so neither
1702    /// pointwise-domination direction closes and the antichain
1703    /// verdict is `true`. Pairs with
1704    /// `resource_limits_geq_rejects_incomparable_postures` +
1705    /// `resource_limits_leq_rejects_incomparable_postures` one COVER
1706    /// axis over: those pin the two comparable-direction predicates
1707    /// FALSIFY on this pair; THIS projection pins the antichain
1708    /// verdict HOLDS on the SAME pair — the two are the negation-
1709    /// paired sides of the (comparable, incomparable) two-cell
1710    /// partition.
1711    ///
1712    /// **De Morgan duality**: `a.is_incomparable(b) == !(a.leq(b) ||
1713    /// a.geq(b))` — the antichain corner is the SET-COMPLEMENT of
1714    /// the union of the two pointwise-domination directions. Pinned
1715    /// by `resource_limits_is_incomparable_is_de_morgan_dual_of_comparable`
1716    /// which sweeps every ordered pair in the preset roster and
1717    /// confirms the equivalence at every cell.
1718    ///
1719    /// Encoded as `!self.leq(other) && !self.geq(other)` — one
1720    /// primitive delegation each to [`Self::leq`] and [`Self::geq`]
1721    /// (with [`Self::geq`] itself delegating to [`Self::leq`]) so the
1722    /// antichain characterization lives at exactly one implementation
1723    /// site, and a future re-derivation of [`Self::leq`] (e.g. to a
1724    /// different pointwise conjunction) propagates to
1725    /// `is_incomparable` mechanically rather than requiring a per-
1726    /// method fix-up. Mirrors [`Self::gt`]'s `other.lt(self)`
1727    /// delegation and [`Self::geq`]'s `other.leq(self)` delegation one
1728    /// COVER axis over on the pairwise-relation surface.
1729    ///
1730    /// `const fn` for the same compile-time-pin reasons as
1731    /// [`Self::leq`] (`const _: () =
1732    /// assert!(!DEFAULT_RESOURCE_LIMITS.is_incomparable(
1733    /// EMPTY_RESOURCE_LIMITS));`), so a caller can pin an antichain-
1734    /// disagreement identity at compile time — a build-break rather
1735    /// than a runtime `assert!` on the first execution.
1736    ///
1737    /// Pre-lift, a caller that wanted "these two postures are
1738    /// incomparable on the pointwise partial-order" composed
1739    /// `!a.leq(b) && !b.leq(a)` at the callsite (or the algebraically
1740    /// equivalent `!a.leq(b) && !a.geq(b)` after `geq` landed), a
1741    /// two-primitive composition that appeared verbatim at every
1742    /// prospective antichain-detection site pre-lift — a ≥2 PRIME
1743    /// DIRECTIVE trigger with the (comparable, incomparable) two-cell
1744    /// partition of the ordered pair × verdict surface as its
1745    /// substrate posture. Post-lift the antichain characterization is
1746    /// ONE named primitive on the [`ResourceLimits`] surface,
1747    /// `const fn`-composable into a compile-time bound, and the two-
1748    /// primitive `!leq && !geq` composition lives at ONE
1749    /// implementation site.
1750    ///
1751    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
1752    /// preserves proofs; the antichain characterization is the
1753    /// SET-COMPLEMENT of the union of the two comparable-direction
1754    /// predicates, and pinning the projection at ONE typed primitive
1755    /// makes the (comparable, incomparable) two-cell partition of the
1756    /// ordered-pair × verdict surface a substrate-level THEOREM
1757    /// rather than a per-consumer inline `!leq && !geq` sanity check.
1758    /// THEORY.md §V.1 — knowable platform; the antichain-detection
1759    /// corner was an unnamed two-primitive composition recurring at
1760    /// every prospective "these two postures sit on distinct
1761    /// branches?" callsite pre-lift.
1762    ///
1763    /// Frontier inspiration: Haskell's `Data.PartialOrd` typeclass'
1764    /// `compare :: PartialOrd a => a -> a -> Maybe Ordering` returning
1765    /// `Nothing` on incomparable inputs — the antichain corner is
1766    /// exactly the `isNothing` projection of that method; Julia's
1767    /// `Base.Order.NoOrder` sentinel on the same abstract; Rust's own
1768    /// `PartialOrd::partial_cmp` returning `Option<Ordering>` whose
1769    /// `None` arm is the antichain corner; Coq's `PreOrder` +
1770    /// classical LEM-derived `~ (leq a b) /\ ~ (leq b a)` proof
1771    /// obligation on the antichain arm. Translation through pleme-io
1772    /// primitives: the antichain characterization on the closed-set-
1773    /// paired [`ResourceLimits`] posture binds through the just-
1774    /// lifted [`Self::leq`] and [`Self::geq`] primitives (both const-
1775    /// evaluable, both delegating to [`Self::leq`]) with a two-arm
1776    /// conjunction on the negated forms — no new dep, no supertrait
1777    /// bound (`Sized + Copy + 'static`-plus-`Eq` stays untouched), no
1778    /// allocation, `const fn` throughout so the antichain verdict is
1779    /// a compile-time expression at every preset-tuple call site.
1780    #[must_use]
1781    pub const fn is_incomparable(self, other: Self) -> bool {
1782        !self.leq(other) && !self.geq(other)
1783    }
1784
1785    /// Pointwise partial-order COMPARABILITY relation across the six
1786    /// ceilings — `true` iff `self` and `other` sit on the SAME lattice
1787    /// branch, i.e., EITHER `self.leq(other)` OR `self.geq(other)`
1788    /// holds. The two postures then admit a pointwise-domination
1789    /// verdict in at least one direction: every axis has `self` at-or-
1790    /// below `other` (the `leq` branch) or every axis has `self`
1791    /// at-or-above `other` (the `geq` branch), so at least one
1792    /// direction closes and one preset dominates the other on every
1793    /// field.
1794    ///
1795    /// The COMPARABLE CORNER on the (leq, geq, lt, gt, incomparable,
1796    /// comparable) pairwise-relation surface — the DIRECT POSITIVE
1797    /// DUAL of [`Self::is_incomparable`] one COVER-COMPLEMENT axis
1798    /// over on the (comparable, incomparable) two-cell partition of
1799    /// the ordered pair × verdict surface. The just-lifted
1800    /// [`Self::is_incomparable`] projection pins the ANTICHAIN half of
1801    /// that partition (the pairs where NEITHER pointwise-domination
1802    /// direction closes); THIS projection pins the DUAL COMPARABLE
1803    /// half (the pairs where AT LEAST ONE direction closes) —
1804    /// EXHAUSTIVELY CLOSING the (comparable, incomparable) two-cell
1805    /// partition at its FINAL SECOND tile on the pairwise-relation
1806    /// surface. The (leq, geq, lt, gt) 2×2 pairwise partial-order face
1807    /// carries the four DIRECTIONAL comparable predicates one COVER
1808    /// axis over; THIS projection folds them into ONE POSITIVE
1809    /// COMPARABILITY verdict — the SET-UNION of the two pointwise-
1810    /// domination directions.
1811    ///
1812    /// Symmetric on its argument order: `a.is_comparable(b) ==
1813    /// b.is_comparable(a)` — the composition `self.leq(other) ||
1814    /// self.geq(other)` folds through `self.leq(other) ==
1815    /// other.geq(self)` (dual identity on [`Self::geq`]) and the
1816    /// disjunction's commutativity, so the projection is order-
1817    /// independent on its two inputs. This SYMMETRY POSTURE mirrors
1818    /// [`Self::is_incomparable`]'s symmetry one CELL axis over on the
1819    /// (comparable, incomparable) partition: both cells of an
1820    /// EXHAUSTIVE-AND-DISJOINT partition of a symmetric surface must
1821    /// themselves be symmetric — the two verdicts flip in lockstep on
1822    /// argument swap. Distinguishes it from [`Self::leq`] / [`Self::lt`]
1823    /// (both antisymmetric — swapping inputs FLIPS the verdict).
1824    ///
1825    /// Reflexive on its own argument: `a.is_comparable(a) == true` —
1826    /// every posture is comparable to itself via [`Self::leq`]'s
1827    /// reflexivity (`a.leq(a) == true`), so the disjunction's
1828    /// `self.leq(other)` leg holds at every diagonal input. Sibling
1829    /// posture to [`Self::leq`]'s reflexivity one COVER axis over:
1830    /// the diagonal COMPARABLE cell is a substrate-level THEOREM
1831    /// falling out of the partial-order axioms, and THIS projection
1832    /// pins it as ONE positive verdict rather than the caller re-
1833    /// deriving `a.leq(a) || a.geq(a) == true` at every diagonal
1834    /// callsite.
1835    ///
1836    /// **Bottom-pole absorption**: `a.is_comparable(
1837    /// EMPTY_RESOURCE_LIMITS) == true` on every posture `a` —
1838    /// [`EMPTY_RESOURCE_LIMITS`] is the bounded-lattice bottom, so
1839    /// `EMPTY.leq(a)` holds on every axis (`0 <= a.max_*` on `usize`),
1840    /// which gives `a.geq(EMPTY)` through the [`Self::geq`] dual
1841    /// identity, which satisfies the `self.geq(other)` leg. The
1842    /// bounded-lattice bottom is comparable to every posture. The
1843    /// COMPLEMENT of [`Self::is_incomparable`]'s bottom-pole
1844    /// absorption pin one CELL axis over — where the antichain
1845    /// verdict FOLDS FALSE at the bottom pole, the comparability
1846    /// verdict FOLDS TRUE. Pinned on the canonical preset roster.
1847    ///
1848    /// **Top-pole absorption**: `a.is_comparable(
1849    /// UNBOUNDED_RESOURCE_LIMITS) == true` on every posture `a` —
1850    /// [`UNBOUNDED_RESOURCE_LIMITS`] is the bounded-lattice top, so
1851    /// `a.leq(UNBOUNDED)` holds on every axis (`a.max_* <=
1852    /// usize::MAX` unconditionally), which satisfies the
1853    /// `self.leq(other)` leg. The bounded-lattice top is comparable
1854    /// to every posture. The COMPLEMENT of [`Self::is_incomparable`]'s
1855    /// top-pole absorption pin one CELL axis over; pinned on the
1856    /// canonical preset roster.
1857    ///
1858    /// **Antichain load-bearing arm**:
1859    /// `HAND_AUTHORED_MID_POSTURE.is_comparable(
1860    /// HAND_AUTHORED_OTHER_POSTURE) == false` — the two hand-authored
1861    /// asymmetric postures sit on distinct branches (each is smaller
1862    /// on three axes and larger on the other three), so NEITHER
1863    /// pointwise-domination direction closes and the comparability
1864    /// verdict is `false`. The DIRECT FALSIFICATION of
1865    /// [`Self::is_incomparable`]'s antichain-arm HOLDS pin one CELL
1866    /// axis over on the SAME pair — the two are the negation-paired
1867    /// sides of the (comparable, incomparable) two-cell partition,
1868    /// and pinning BOTH cells' verdicts on the SAME load-bearing
1869    /// antichain pair closes the partition as a substrate-level
1870    /// EXCLUSIVITY THEOREM rather than one direction's assertion.
1871    ///
1872    /// **De Morgan duality**: `a.is_comparable(b) == !a.is_incomparable(b)`
1873    /// — the comparable corner is the SET-COMPLEMENT of the antichain
1874    /// corner. Pinned by
1875    /// `resource_limits_is_comparable_is_de_morgan_dual_of_incomparable`
1876    /// which sweeps every ordered pair in the preset roster and
1877    /// confirms the equivalence at every cell. Together with
1878    /// `resource_limits_is_incomparable_is_de_morgan_dual_of_comparable`
1879    /// (which pins the OTHER direction on
1880    /// `a.leq(b) || a.geq(b)` composition), the two projections carry
1881    /// the (comparable, incomparable) two-cell partition as an
1882    /// EXHAUSTIVE-AND-DISJOINT decomposition of the ordered pair ×
1883    /// verdict surface.
1884    ///
1885    /// Encoded as `self.leq(other) || self.geq(other)` — one
1886    /// primitive delegation each to [`Self::leq`] and [`Self::geq`]
1887    /// (with [`Self::geq`] itself delegating to [`Self::leq`]) so the
1888    /// comparability characterization lives at exactly one
1889    /// implementation site, and a future re-derivation of [`Self::leq`]
1890    /// (e.g. to a different pointwise conjunction) propagates to
1891    /// `is_comparable` mechanically rather than requiring a per-
1892    /// method fix-up. Mirrors [`Self::is_incomparable`]'s
1893    /// `!self.leq(other) && !self.geq(other)` delegation one COVER
1894    /// axis over on the pairwise-relation surface — the two share
1895    /// the SAME two-primitive substrate and diverge only on the
1896    /// negation posture (`!… && !…` vs `… || …`), which De Morgan's
1897    /// law pins as the SAME projection.
1898    ///
1899    /// `const fn` for the same compile-time-pin reasons as
1900    /// [`Self::is_incomparable`] (`const _: () =
1901    /// assert!(DEFAULT_RESOURCE_LIMITS.is_comparable(
1902    /// EMPTY_RESOURCE_LIMITS));`), so a caller can pin a
1903    /// comparability-agreement identity at compile time — a build-
1904    /// break rather than a runtime `assert!` on the first execution.
1905    ///
1906    /// Pre-lift, a caller that wanted "these two postures are
1907    /// comparable on the pointwise partial-order" composed
1908    /// `a.leq(b) || a.geq(b)` at the callsite — a two-primitive
1909    /// composition that appeared verbatim at every prospective
1910    /// comparability-detection site pre-lift, and the NEGATION of the
1911    /// same two-primitive composition [`Self::is_incomparable`]
1912    /// lifted one COVER axis over — a ≥2 PRIME DIRECTIVE trigger with
1913    /// the (comparable, incomparable) two-cell partition of the
1914    /// ordered pair × verdict surface as its substrate posture. Post-
1915    /// lift the comparability characterization is ONE named primitive
1916    /// on the [`ResourceLimits`] surface, `const fn`-composable into
1917    /// a compile-time bound, and the two-primitive `leq || geq`
1918    /// composition lives at ONE implementation site.
1919    ///
1920    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
1921    /// preserves proofs; the comparability characterization is the
1922    /// SET-UNION of the two pointwise-domination directions, and
1923    /// pinning the projection at ONE typed primitive makes the
1924    /// (comparable, incomparable) two-cell partition of the ordered-
1925    /// pair × verdict surface a substrate-level THEOREM proved on
1926    /// BOTH SIDES rather than one side asserted and the other
1927    /// inferred at consumer sites. THEORY.md §V.1 — knowable
1928    /// platform; the comparability-detection corner was an unnamed
1929    /// two-primitive composition recurring at every prospective
1930    /// "these two postures sit on the same branch?" callsite pre-
1931    /// lift.
1932    ///
1933    /// Frontier inspiration: Haskell's `Data.PartialOrd` typeclass'
1934    /// `compare :: PartialOrd a => a -> a -> Maybe Ordering` — the
1935    /// comparable corner is exactly the `isJust` projection of that
1936    /// method (the DUAL of `is_incomparable`'s `isNothing` projection
1937    /// on the SAME method); Julia's `Base.Order.lt` + `Base.Order.gt`
1938    /// disjunction on the pairwise-comparability check; Rust's own
1939    /// `PartialOrd::partial_cmp` whose `Some(_)` arm is the
1940    /// comparable corner; Coq's `PreOrder` + classical LEM-derived
1941    /// `leq a b \/ leq b a` proof obligation on the comparable arm.
1942    /// Translation through pleme-io primitives: the comparability
1943    /// characterization on the closed-set-paired [`ResourceLimits`]
1944    /// posture binds through the just-lifted [`Self::leq`] and
1945    /// [`Self::geq`] primitives (both const-evaluable, both
1946    /// delegating to [`Self::leq`]) with a two-arm disjunction on the
1947    /// direct forms — no new dep, no supertrait bound (`Sized + Copy + 'static`-plus-`Eq` stays untouched), no allocation, `const fn`
1948    /// throughout so the comparability verdict is a compile-time
1949    /// expression at every preset-tuple call site.
1950    #[must_use]
1951    pub const fn is_comparable(self, other: Self) -> bool {
1952        self.leq(other) || self.geq(other)
1953    }
1954
1955    /// Pointwise partial-order VERDICT across the six ceilings — the
1956    /// `Option<Ordering>` corner on the pairwise-relation surface. Folds
1957    /// the four possible outcomes ([`Ordering::Less`], [`Ordering::Equal`],
1958    /// [`Ordering::Greater`], INCOMPARABLE) into ONE typed value, so a
1959    /// caller that wants "how do these two postures relate?" gets the
1960    /// FULL verdict in a single call rather than composing
1961    /// [`Self::leq`] / [`Self::geq`] / [`Self::lt`] / [`Self::gt`] /
1962    /// `PartialEq::eq` at its callsite.
1963    ///
1964    /// The `Option<Ordering>` PROJECTION on the pairwise-relation surface —
1965    /// the DIRECT LIFT of the frontier inspiration the just-lifted
1966    /// [`Self::is_incomparable`] + [`Self::is_comparable`] pair explicitly
1967    /// named (Rust's `PartialOrd::partial_cmp`, Haskell's
1968    /// `Data.PartialOrd.compare :: a -> a -> Maybe Ordering`, Julia's
1969    /// `Base.Order`, Coq's `PreOrder` decidability obligation). Those two
1970    /// projections pinned ONE ARM each of the surface — the antichain
1971    /// arm ([`Self::is_incomparable`] → `is_none()`) and the comparability
1972    /// arm ([`Self::is_comparable`] → `is_some()`); THIS lift closes the
1973    /// surface at its FULL VERDICT corner by returning the ordering
1974    /// direction on the comparable arm and `None` on the antichain arm —
1975    /// the (Less, Equal, Greater, None) 4-tile partition of the ordered
1976    /// pair × verdict surface at ONE named primitive rather than a
1977    /// four-way conditional composition at every consumer that wanted
1978    /// the full verdict.
1979    ///
1980    /// **Antichain-arm identity**: `a.partial_cmp(b).is_none() ==
1981    /// a.is_incomparable(b)` — the `None` arm of the return coincides
1982    /// EXACTLY with the antichain-corner projection [`Self::is_incomparable`]
1983    /// lifts. On the antichain the two-arm conjunction `!self.leq(other)
1984    /// && !self.geq(other)` holds; neither branch of THIS method's
1985    /// disjunction closes, so the fall-through arm binds. Pinned as a
1986    /// substrate-level identity in the test cohort.
1987    ///
1988    /// **Comparability-arm identity**: `a.partial_cmp(b).is_some() ==
1989    /// a.is_comparable(b)` — the `Some(_)` arm of the return coincides
1990    /// EXACTLY with the comparability-corner projection [`Self::is_comparable`]
1991    /// lifts. On every comparable pair at least one of the two
1992    /// pointwise-domination legs closes, so the method returns a
1993    /// concrete [`Ordering`] variant. Together with the antichain-arm
1994    /// identity, THIS method is the JOINT LIFT of the (comparable,
1995    /// incomparable) two-cell partition into ONE `Option<Ordering>`
1996    /// value.
1997    ///
1998    /// **Equality on the diagonal**: `a.partial_cmp(a) ==
1999    /// Some(Ordering::Equal)` on every posture — the `leq` reflexivity
2000    /// (`a.leq(a) == true`) AND the `geq` reflexivity (`a.geq(a) ==
2001    /// true`) both hold at the diagonal, so both branches of the
2002    /// conjunction close and the equality arm fires. The diagonal cell
2003    /// of the (Less, Equal, Greater, None) 4-tile partition on the
2004    /// ordered pair × verdict surface pins to the Equal tile.
2005    ///
2006    /// **Ordering-direction agreement**: `a.partial_cmp(b) ==
2007    /// Some(Ordering::Less)` iff `a.lt(b)` (strict less); `a.partial_cmp(b)
2008    /// == Some(Ordering::Greater)` iff `a.gt(b)` (strict greater);
2009    /// `a.partial_cmp(b) == Some(Ordering::Equal)` iff `a == b` (via
2010    /// antisymmetry `a.leq(b) && a.geq(b) → a == b`). The four-arm
2011    /// dispatch below binds each verdict tile to its named companion
2012    /// primitive at exactly one implementation site, so a caller that
2013    /// wants the strict-less verdict binds through either `a.lt(b)` OR
2014    /// `matches!(a.partial_cmp(b), Some(Ordering::Less))` and both route
2015    /// to the same substrate.
2016    ///
2017    /// **Antisymmetry under argument swap**: `a.partial_cmp(b) ==
2018    /// b.partial_cmp(a).map(Ordering::reverse)` — swapping the argument
2019    /// pair FLIPS the ordering-direction verdict (`Less <-> Greater`,
2020    /// `Equal` fixed, `None` fixed). Discriminates [`Self::is_incomparable`] /
2021    /// [`Self::is_comparable`] (both SYMMETRIC on argument swap) one
2022    /// POSTURE axis over: the projections carry no directional content
2023    /// and are argument-order-independent; the FULL verdict carries
2024    /// directional content and inverts under swap. Pinned in the test
2025    /// cohort.
2026    ///
2027    /// **Bottom-pole absorption**: `EMPTY_RESOURCE_LIMITS.partial_cmp(a)`
2028    /// returns `Some(Ordering::Less)` for every posture `a` strictly
2029    /// above [`EMPTY_RESOURCE_LIMITS`] on the pointwise partial order,
2030    /// AND `Some(Ordering::Equal)` at `a == EMPTY_RESOURCE_LIMITS`.
2031    /// [`EMPTY_RESOURCE_LIMITS`] is the bounded-lattice bottom, so on
2032    /// every posture the `EMPTY.leq(a)` leg closes; the antisymmetric
2033    /// leg discriminates the strict-less arm (concrete positive posture)
2034    /// from the equality arm (`a == EMPTY`). The DIRECT REFINEMENT of
2035    /// [`Self::is_comparable`]'s bottom-pole absorption pin one
2036    /// DIRECTION-CONTENT axis over — where the comparability projection
2037    /// folds TRUE at the bottom pole across every posture, THIS method
2038    /// discriminates the equality-vs-strict-less refinement on the
2039    /// SAME preset roster.
2040    ///
2041    /// **Top-pole absorption**: symmetric statement — `a.partial_cmp(
2042    /// UNBOUNDED_RESOURCE_LIMITS)` returns `Some(Ordering::Less)` for
2043    /// every posture `a` strictly below the top and
2044    /// `Some(Ordering::Equal)` at `a == UNBOUNDED`. Pinned on the
2045    /// canonical preset roster.
2046    ///
2047    /// **Antichain load-bearing arm**: `HAND_AUTHORED_MID_POSTURE
2048    /// .partial_cmp(HAND_AUTHORED_OTHER_POSTURE) == None` — the two
2049    /// hand-authored asymmetric postures sit on distinct branches
2050    /// (each is smaller on three axes and larger on the other three),
2051    /// so NEITHER pointwise-domination direction closes and the
2052    /// fall-through `None` arm fires. The DIRECT LIFT of
2053    /// [`Self::is_incomparable`]'s antichain-arm HOLDS pin AND
2054    /// [`Self::is_comparable`]'s antichain-arm FALSIFIES pin one
2055    /// PROJECTION axis over on the SAME hand-authored pair — the two
2056    /// projections pinned each ARM's cell verdict; THIS method pins
2057    /// the FULL `Option<Ordering>` return at the antichain corner
2058    /// (`None`).
2059    ///
2060    /// Encoded as `if self.leq(other) && other.leq(self) { Equal } else
2061    /// if self.leq(other) { Less } else if other.leq(self) { Greater }
2062    /// else { None }` — a four-arm dispatch on the (`self.leq(other)`,
2063    /// `other.leq(self)`) 2×2 boolean truth-table, with each arm binding
2064    /// to its named companion primitive at exactly one implementation
2065    /// site so a future re-derivation of [`Self::leq`] propagates to
2066    /// EVERY ordering-direction verdict mechanically rather than
2067    /// requiring a per-arm fix-up. The `self.leq(other) && other.leq(self)`
2068    /// conjunction is the pointwise antisymmetric-equality
2069    /// characterization — pinned as a substrate-level identity by the
2070    /// `resource_limits_leq_is_antisymmetric` test cohort, so the
2071    /// Equal-arm binding above is a substrate-level THEOREM rather than
2072    /// an ad-hoc composition.
2073    ///
2074    /// `const fn` for the same compile-time-pin reasons as [`Self::leq`]
2075    /// / [`Self::geq`] / [`Self::lt`] / [`Self::gt`] / [`Self::is_incomparable`]
2076    /// / [`Self::is_comparable`] one COVER axis over on the pairwise-
2077    /// relation surface (`const _: () = assert!(matches!(
2078    /// EMPTY_RESOURCE_LIMITS.partial_cmp(UNBOUNDED_RESOURCE_LIMITS),
2079    /// Some(Ordering::Less)));`) — a caller can pin a full-verdict
2080    /// identity at compile time as a build-break rather than a runtime
2081    /// `assert!` on the first execution. [`Ordering`] itself is
2082    /// `const`-constructible (the three variants are plain enum
2083    /// constructors), so the return-value cascade below stays inside
2084    /// stable const-fn scope with no runtime allocation, no supertrait
2085    /// bound, and no new dep.
2086    ///
2087    /// Pre-lift, a caller that wanted the FULL pointwise ordering
2088    /// verdict either (a) composed four separate calls
2089    /// (`a.lt(b)` / `a == b` / `a.gt(b)` / `a.is_incomparable(b)`) at
2090    /// its callsite and dispatched on the resulting 4-way conditional —
2091    /// a per-consumer FOUR-primitive composition whose exhaustiveness
2092    /// the type system did NOT gate (a dropped arm silently miscategorized
2093    /// the pair) — or (b) invoked [`Self::is_comparable`] to get the
2094    /// `is_some`/`is_none` distinction and THEN chose one of `lt` / `gt`
2095    /// / equality on the `is_some` arm — a two-stage composition that
2096    /// double-evaluates the `leq` / `geq` primitives on every consumer.
2097    /// Post-lift the full verdict is ONE named primitive returning
2098    /// `Option<Ordering>`, `const fn`-composable into a compile-time
2099    /// bound, and the two-primitive `leq(self, other)` + `leq(other, self)`
2100    /// substrate binds at ONE implementation site — a ≥2 PRIME
2101    /// DIRECTIVE trigger, since the four-way ordering dispatch appeared
2102    /// inline at every prospective full-verdict callsite pre-lift.
2103    ///
2104    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
2105    /// preserves proofs; the FULL pointwise partial-order verdict on
2106    /// two preset-carried resource proofs is itself a typed
2107    /// `Option<Ordering>` value whose four arms each carry a distinct
2108    /// substrate-level relation proof (`Less` ⇒ strict-less on every
2109    /// axis; `Equal` ⇒ pointwise equality on every axis; `Greater` ⇒
2110    /// strict-greater on every axis; `None` ⇒ antichain — asymmetric
2111    /// per-axis orderings). Pinning the projection at ONE typed
2112    /// primitive makes the (Less, Equal, Greater, None) 4-tile
2113    /// partition of the ordered pair × verdict surface a substrate-
2114    /// level THEOREM rather than a per-consumer inline four-way
2115    /// dispatch. THEORY.md §V.1 — knowable platform; the full-verdict
2116    /// projection was an unnamed four-primitive composition recurring
2117    /// at every prospective "how do these two postures relate?"
2118    /// callsite pre-lift.
2119    ///
2120    /// Frontier inspiration: Rust's own `PartialOrd::partial_cmp :: fn
2121    /// partial_cmp(&self, &Self) -> Option<Ordering>` — the CANONICAL
2122    /// `Option<Ordering>` corner on any partial-order-carrying type;
2123    /// Haskell's `Data.PartialOrd.compare :: PartialOrd a => a -> a
2124    /// -> Maybe Ordering`; Julia's `Base.Order.NoOrder` sentinel folded
2125    /// into `Option<Ordering>` on the `Base.Order`-compatible partial
2126    /// order; Coq's `PreOrder` + `Decidable` typeclass composition
2127    /// giving `forall a b, {leq a b} + {leq b a} + {~(leq a b) /\
2128    /// ~(leq b a)}` — the three-way decidable sum where the third
2129    /// disjunct is the antichain corner. Translation through pleme-io
2130    /// primitives: the full-verdict projection binds through the just-
2131    /// lifted [`Self::leq`] primitive (invoked twice — once in each
2132    /// argument order — to derive the (`self.leq(other)`,
2133    /// `other.leq(self)`) 2×2 truth-table the four-arm dispatch reads
2134    /// against), no new dep, no supertrait bound (`Sized + Copy +
2135    /// 'static`-plus-`Eq` stays untouched), no allocation, `const fn`
2136    /// throughout. This method does NOT wire an `impl PartialOrd for
2137    /// ResourceLimits` — the derive would give lexicographic ordering
2138    /// on the six fields (WRONG for the pointwise lattice), and manually
2139    /// implementing the trait would expose `<` / `<=` / `>` / `>=`
2140    /// operators that overlap the named `lt` / `leq` / `gt` / `geq`
2141    /// primitives already lifted one COVER axis over. Keeping the
2142    /// `Option<Ordering>` corner at an inherent method preserves the
2143    /// tight name-directed surface without introducing an operator
2144    /// alias for each of the four directional predicates.
2145    #[must_use]
2146    pub const fn partial_cmp(self, other: Self) -> Option<Ordering> {
2147        let self_leq = self.leq(other);
2148        let other_leq = other.leq(self);
2149        if self_leq && other_leq {
2150            Some(Ordering::Equal)
2151        } else if self_leq {
2152            Some(Ordering::Less)
2153        } else if other_leq {
2154            Some(Ordering::Greater)
2155        } else {
2156            None
2157        }
2158    }
2159
2160    /// Boolean `is_comparable`-conjunction across every distinct pair of a
2161    /// slice of postures — `postures` is a CHAIN (a totally-ordered subset of
2162    /// the pointwise partial-order lattice) iff every distinct pair
2163    /// `(postures[i], postures[j])` sits on the same lattice branch
2164    /// (`postures[i].is_comparable(postures[j])`).
2165    ///
2166    /// The SET-LEVEL N-ary peer of [`Self::is_comparable`] one CARDINALITY
2167    /// axis over on the (comparable, incomparable) × (pair-level, set-level)
2168    /// primitive surface. Where [`Self::is_comparable`] decides the pairwise
2169    /// question "do THESE TWO postures sit on the same branch?", THIS
2170    /// projection decides the set-level question "does EVERY PAIR in this
2171    /// slice sit on the same branch?" — the natural lift of the pair-level
2172    /// verdict to the collection level, matching the arity extension the
2173    /// [`Self::is_lower_bound_of`] / [`Self::is_upper_bound_of`] pair carries
2174    /// one PRIMITIVE-KIND axis over on the (leq, comparable) × (pairwise,
2175    /// N-ary) primitive surface.
2176    ///
2177    /// **Empty-slice vacuous truth**: `ResourceLimits::is_chain(&[]) == true`
2178    /// — the empty conjunction is vacuously true; the empty slice contains
2179    /// no distinct pairs to reject. Peer of
2180    /// `is_lower_bound_of(&[]) == true`'s empty-slice identity one
2181    /// PRIMITIVE-KIND axis over. Pinned as
2182    /// `resource_limits_is_chain_empty_slice_is_vacuously_true`.
2183    ///
2184    /// **Singleton vacuous truth**: `ResourceLimits::is_chain(&[a]) == true`
2185    /// for every posture `a` — a one-element slice contains no distinct
2186    /// pairs, so the outer-and-inner-index conjunction never enters the
2187    /// inner loop and returns `true` vacuously. Pinned as
2188    /// `resource_limits_is_chain_singleton_is_vacuously_true`.
2189    ///
2190    /// **Diagonal-duplicate identity**: `ResourceLimits::is_chain(&[a, a])
2191    /// == true` for every posture `a` — [`Self::is_comparable`] is
2192    /// REFLEXIVE (`a.is_comparable(a) == true`), so the single distinct
2193    /// index pair `(0, 1)` binds `a.is_comparable(a) == true` and the
2194    /// conjunction holds. Distinguishes it from [`Self::is_antichain`] one
2195    /// COVER-COMPLEMENT axis over, whose diagonal-duplicate verdict is
2196    /// `false` (via [`Self::is_incomparable`]'s IRREFLEXIVITY). Pinned as
2197    /// `resource_limits_is_chain_of_diagonal_duplicate_is_true`.
2198    ///
2199    /// **Ordered-chain closure**: `ResourceLimits::is_chain(
2200    /// &[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2201    /// UNBOUNDED_RESOURCE_LIMITS]) == true` — the shipped-preset triple on
2202    /// the bounded-lattice diagonal is an ascending chain (`EMPTY <=
2203    /// DEFAULT <= UNBOUNDED` pointwise), so every distinct pair among the
2204    /// three is comparable. Pinned as
2205    /// `resource_limits_is_chain_holds_on_the_shipped_preset_triple`.
2206    ///
2207    /// **Antichain rejection**: `ResourceLimits::is_chain(
2208    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) == false`
2209    /// — the two hand-authored asymmetric postures sit on distinct branches
2210    /// (each is smaller on three axes and larger on the other three), so
2211    /// neither pointwise-domination direction closes and the pair is
2212    /// INCOMPARABLE. The chain conjunction rejects at the first
2213    /// incomparable pair. Pinned as
2214    /// `resource_limits_is_chain_rejects_the_hand_authored_antichain_pair`.
2215    ///
2216    /// **Mixed-set rejection**: `ResourceLimits::is_chain(
2217    /// &[DEFAULT_RESOURCE_LIMITS, HAND_AUTHORED_MID_POSTURE,
2218    /// HAND_AUTHORED_OTHER_POSTURE]) == false` — even though the first two
2219    /// AND the first-and-third pairs are comparable in isolation, the
2220    /// second-and-third pair is incomparable, so the whole-set conjunction
2221    /// rejects. The set-level chain verdict is stricter than the union of
2222    /// its pair-level verdicts — one antichain pair anywhere in the slice
2223    /// falsifies the whole projection. Pinned as
2224    /// `resource_limits_is_chain_rejects_mixed_slice_with_one_antichain_pair`.
2225    ///
2226    /// **`most_permissive_of` closure witness**: on every slice `postures`
2227    /// where `ResourceLimits::is_chain(postures)` holds, the N-ary join
2228    /// `most_permissive_of(postures)` is a MEMBER of the slice (not just a
2229    /// bound above it) — the maximum element of a chain sits inside the
2230    /// chain. The set-level verdict thus underwrites the pointwise-`max`
2231    /// identification of the chain's top element. NOT pinned as a test in
2232    /// this cohort (the general witness needs a slice-membership predicate
2233    /// not yet lifted); recorded here as the future callsite the chain
2234    /// verdict unlocks.
2235    ///
2236    /// Encoded as a doubly-indexed const-`while` walk (`i in 0..n`, `j in
2237    /// i+1..n`) with one [`Self::is_comparable`] delegation per distinct
2238    /// pair. The `j = i + 1` inner starting index skips the diagonal AND
2239    /// the mirror pairs (`(j, i)` pairs the outer loop already visited from
2240    /// the `(i, j)` direction) — [`Self::is_comparable`] is SYMMETRIC on
2241    /// argument swap, so the mirror pair carries no additional information
2242    /// and the (n choose 2) unique-pair count is the tight iteration budget
2243    /// for the set-level verdict. `postures[i].is_comparable(postures[j])`
2244    /// (not `.leq(...) || .geq(...)` directly) so the primitive delegation
2245    /// lives at exactly one implementation site — a future re-derivation of
2246    /// [`Self::is_comparable`] (e.g. to a different comparability
2247    /// characterization) propagates to `is_chain` mechanically rather than
2248    /// requiring a per-callsite fix-up.
2249    ///
2250    /// `const fn` for the same compile-time-pin reasons as
2251    /// [`Self::is_comparable`] one CARDINALITY axis over on the (comparable,
2252    /// incomparable) × (pair-level, set-level) primitive surface (`const _:
2253    /// () = assert!(ResourceLimits::is_chain(&[EMPTY_RESOURCE_LIMITS,
2254    /// DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS]));`), so a
2255    /// caller can pin a chain-membership identity at compile time — a
2256    /// build-break rather than a runtime `assert!` on the first execution.
2257    /// `Copy` on [`ResourceLimits`] lets the const-fn loop index the slice
2258    /// by value without an explicit `.clone()` (which const-fn would not
2259    /// permit anyway).
2260    ///
2261    /// Pre-lift, a caller wanting "is this slice a chain?" composed the
2262    /// doubly-indexed `postures.iter().enumerate().all(|(i, a)|
2263    /// postures[i + 1..].iter().all(|b| a.is_comparable(*b)))` two-primitive
2264    /// scaffolding at every prospective callsite — a PRIME DIRECTIVE ≥2
2265    /// pattern once two consumers need the set-level chain verdict, and one
2266    /// whose exhaustiveness the type system did NOT gate (a slice iterator
2267    /// that stopped at the first `false` still needed the caller to bind
2268    /// the outer-and-inner-index conjunction correctly, and a copy-paste
2269    /// that swapped `postures[i]` with `postures[j]` would test the SAME
2270    /// pair via [`Self::is_comparable`]'s symmetry — silent no-op — but a
2271    /// copy-paste that inverted the primitive delegation to
2272    /// [`Self::is_incomparable`] would test the DUAL question and silently
2273    /// distort the verdict). Post-lift the chain verdict binds at ONE typed
2274    /// method the algebra exposes, and the doubly-indexed all-pairs
2275    /// scaffolding lives at ONE implementation site.
2276    ///
2277    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
2278    /// proofs; the set-level chain verdict on N preset-carried resource
2279    /// proofs is itself a typed named `bool` predicate composing them via
2280    /// the underlying pair-level [`Self::is_comparable`] projection.
2281    /// THEORY.md §V.1 — knowable platform; the doubly-indexed all-pairs
2282    /// comparability conjunction becomes a TYPE-level operation on the
2283    /// posture algebra rather than an inline two-primitive scaffolding at
2284    /// every consumer that decides "is this slice a totally-ordered
2285    /// subset?"
2286    ///
2287    /// Frontier inspiration: Haskell's `Data.List.isSorted`
2288    /// (adjacent-pair-comparability variant on `Ord a`); Coq's `Sorted`
2289    /// inductive on lists over a `TotalOrder`; Idris's `Sorted` predicate
2290    /// over `Ord`-carrying lists; the order-theoretic notion of a CHAIN
2291    /// (a totally-ordered subset of a poset) canonicalized in
2292    /// Birkhoff's *Lattice Theory* and every subsequent poset text.
2293    /// Translation through pleme-io primitives: on a `PartialOrd`-derived
2294    /// carrier the frontier `isSorted` variants reduce to adjacent-pair
2295    /// comparability because total-order transitivity closes the chain; on
2296    /// a PARTIAL order (the pointwise lattice) the adjacent-pair check is
2297    /// INSUFFICIENT (three postures `a, b, c` where `a` and `c` are
2298    /// comparable but `b` is incomparable to at least one of them satisfy
2299    /// adjacent-pair comparability without forming a chain), so this
2300    /// projection walks EVERY distinct pair rather than only the adjacent
2301    /// ones — the strict partial-order generalization of the frontier
2302    /// total-order predicate. No new dep, no supertrait bound, `const fn`
2303    /// throughout.
2304    #[must_use]
2305    pub const fn is_chain(postures: &[Self]) -> bool {
2306        let n = postures.len();
2307        let mut i = 0;
2308        while i < n {
2309            let mut j = i + 1;
2310            while j < n {
2311                if !postures[i].is_comparable(postures[j]) {
2312                    return false;
2313                }
2314                j += 1;
2315            }
2316            i += 1;
2317        }
2318        true
2319    }
2320
2321    /// Boolean `is_incomparable`-conjunction across every distinct pair of a
2322    /// slice of postures — `postures` is an ANTICHAIN (a pairwise-
2323    /// incomparable subset of the pointwise partial-order lattice) iff every
2324    /// distinct pair `(postures[i], postures[j])` sits on distinct branches
2325    /// (`postures[i].is_incomparable(postures[j])`).
2326    ///
2327    /// The SET-LEVEL N-ary peer of [`Self::is_incomparable`] one CARDINALITY
2328    /// axis over on the (comparable, incomparable) × (pair-level, set-level)
2329    /// primitive surface — the DIRECT SET-LEVEL DUAL of [`Self::is_chain`]
2330    /// one COVER-COMPLEMENT axis over on the SAME set-level cardinality
2331    /// slice. Together the two close the (chain, antichain) two-cell face
2332    /// on the set-level pairwise-relation surface, exactly ONE CARDINALITY
2333    /// axis over from the (comparable, incomparable) two-cell partition of
2334    /// the ordered-pair × verdict surface [`Self::is_comparable`] and
2335    /// [`Self::is_incomparable`] closed one arity down.
2336    ///
2337    /// The (chain, antichain) pair is NOT a partition of the set-level
2338    /// verdict surface: a slice of ≥3 postures whose pair-level verdicts
2339    /// mix comparable and incomparable pairs is neither a chain nor an
2340    /// antichain, so the two set-level projections carry a THREE-cell
2341    /// face (chain, antichain, mixed) at the set level whose ends the two
2342    /// projections lifted here pin. Distinguishes it from the pair-level
2343    /// (comparable, incomparable) partition [`Self::is_comparable`] +
2344    /// [`Self::is_incomparable`] EXHAUSTIVELY closed one arity down: at
2345    /// arity 2 every pair is exactly one of comparable-or-incomparable, and
2346    /// the two projections partition the ordered-pair × verdict surface; at
2347    /// arity ≥3 the mixed-set cell opens up between them and the two
2348    /// set-level projections carry the ends of that three-cell face.
2349    ///
2350    /// **Empty-slice vacuous truth**: `ResourceLimits::is_antichain(&[])
2351    /// == true` — the empty conjunction is vacuously true; the empty slice
2352    /// contains no distinct pairs to reject. Peer of [`Self::is_chain`]'s
2353    /// empty-slice identity one COVER-COMPLEMENT axis over: both cells of
2354    /// the (chain, antichain) set-level pair AGREE at the empty slice on
2355    /// the vacuous-truth verdict — the two cells coincide at the empty
2356    /// face of the set-level verdict surface. Pinned as
2357    /// `resource_limits_is_antichain_empty_slice_is_vacuously_true`.
2358    ///
2359    /// **Singleton vacuous truth**: `ResourceLimits::is_antichain(&[a])
2360    /// == true` for every posture `a` — a one-element slice contains no
2361    /// distinct pairs, so the outer-and-inner-index conjunction never
2362    /// enters the inner loop and returns `true` vacuously. Peer of
2363    /// [`Self::is_chain`]'s singleton-vacuous-truth identity: both cells
2364    /// AGREE at singleton slices too — the (chain, antichain) verdict
2365    /// surface coincides at every slice with strictly fewer than two
2366    /// distinct pairs. Pinned as
2367    /// `resource_limits_is_antichain_singleton_is_vacuously_true`.
2368    ///
2369    /// **Diagonal-duplicate rejection**: `ResourceLimits::is_antichain(
2370    /// &[a, a]) == false` for every posture `a` — [`Self::is_incomparable`]
2371    /// is IRREFLEXIVE (`a.is_incomparable(a) == false`), so the single
2372    /// distinct index pair `(0, 1)` binds `a.is_incomparable(a) == false`
2373    /// and the conjunction rejects. Distinguishes it from [`Self::is_chain`]
2374    /// one COVER-COMPLEMENT axis over, whose diagonal-duplicate verdict is
2375    /// `true` (via [`Self::is_comparable`]'s REFLEXIVITY). The reflexivity/
2376    /// irreflexivity divergence between the two pair-level projections
2377    /// PROPAGATES to the set-level projections at any slice with a
2378    /// duplicated element — the two set-level cells DIVERGE at every
2379    /// diagonal-duplicate slice, the mirror of their AGREEMENT at every
2380    /// empty-or-singleton slice one CARDINALITY axis over. Pinned as
2381    /// `resource_limits_is_antichain_of_diagonal_duplicate_is_false`.
2382    ///
2383    /// **Hand-authored antichain closure**: `ResourceLimits::is_antichain(
2384    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) == true`
2385    /// — the two hand-authored asymmetric postures sit on distinct
2386    /// branches (each is smaller on three axes and larger on the other
2387    /// three), so neither pointwise-domination direction closes and the
2388    /// pair is INCOMPARABLE. The antichain conjunction accepts every
2389    /// distinct pair of the slice. Pinned as
2390    /// `resource_limits_is_antichain_holds_on_the_hand_authored_antichain_pair`.
2391    ///
2392    /// **Chain rejection**: `ResourceLimits::is_antichain(
2393    /// &[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS]) == false` — the
2394    /// shipped-preset pair on the bounded-lattice diagonal is a strict
2395    /// chain (`EMPTY.lt(DEFAULT)`), so the single distinct pair is
2396    /// COMPARABLE and the antichain conjunction rejects. The DIRECT
2397    /// FALSIFICATION of [`Self::is_chain`]'s ordered-chain closure one
2398    /// CELL axis over on the SAME slice — the two set-level cells carry
2399    /// the negation-paired verdict at every strictly-chained slice, the
2400    /// mirror of their AGREEMENT at every empty-or-singleton slice one
2401    /// CARDINALITY axis over. Pinned as
2402    /// `resource_limits_is_antichain_rejects_the_shipped_preset_pair`.
2403    ///
2404    /// **Mixed-set rejection**: `ResourceLimits::is_antichain(
2405    /// &[DEFAULT_RESOURCE_LIMITS, HAND_AUTHORED_MID_POSTURE,
2406    /// HAND_AUTHORED_OTHER_POSTURE]) == false` — even though the
2407    /// second-and-third pair is incomparable, the first pair
2408    /// (`DEFAULT`-and-`MID`) is comparable, so the whole-set conjunction
2409    /// rejects. Pins the three-cell (chain, antichain, mixed) set-level
2410    /// face at the mixed-set cell: the same slice both [`Self::is_chain`]
2411    /// AND [`Self::is_antichain`] reject at, and the third cell of the
2412    /// three-cell face the two set-level projections carry the ends of.
2413    /// Pinned as
2414    /// `resource_limits_is_antichain_rejects_mixed_slice_with_one_comparable_pair`.
2415    ///
2416    /// **De Morgan mirror at arity 2**: on any two-element slice `[a, b]`
2417    /// (`a != b`), `ResourceLimits::is_antichain(&[a, b]) ==
2418    /// !ResourceLimits::is_chain(&[a, b])` — at exactly two DISTINCT
2419    /// elements the set-level (chain, antichain) two-cell face reduces to
2420    /// the pair-level (comparable, incomparable) two-cell partition and
2421    /// the two cells become MUTUALLY EXCLUSIVE (the mixed cell requires
2422    /// ≥3 elements to open). The set-level De Morgan mirror of the
2423    /// pair-level identity `a.is_comparable(b) == !a.is_incomparable(b)`
2424    /// [`Self::is_comparable`] pins one CARDINALITY axis over. Pinned as
2425    /// `resource_limits_is_antichain_and_is_chain_are_mutually_exclusive_on_distinct_pairs`.
2426    ///
2427    /// Encoded as a doubly-indexed const-`while` walk (`i in 0..n`, `j in
2428    /// i+1..n`) with one [`Self::is_incomparable`] delegation per distinct
2429    /// pair — the SAME iteration structure as [`Self::is_chain`] one
2430    /// PRIMITIVE-DELEGATION axis over, with only the inner primitive
2431    /// swapped between the two. The two set-level projections share the
2432    /// SAME two-primitive substrate (the doubly-indexed pair walk + one
2433    /// pair-level projection) and diverge only on the pair-level primitive
2434    /// they delegate to (`is_comparable` vs `is_incomparable`), which De
2435    /// Morgan's law pins as the pair-level negation-paired substrate.
2436    ///
2437    /// `const fn` for the same compile-time-pin reasons as
2438    /// [`Self::is_incomparable`] one CARDINALITY axis over on the
2439    /// (comparable, incomparable) × (pair-level, set-level) primitive
2440    /// surface (`const _: () = assert!(ResourceLimits::is_antichain(
2441    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]));`), so
2442    /// a caller can pin an antichain-membership identity at compile time
2443    /// — a build-break rather than a runtime `assert!` on the first
2444    /// execution.
2445    ///
2446    /// Pre-lift, a caller wanting "is this slice an antichain?" composed
2447    /// the doubly-indexed `postures.iter().enumerate().all(|(i, a)|
2448    /// postures[i + 1..].iter().all(|b| a.is_incomparable(*b)))` two-
2449    /// primitive scaffolding at every prospective callsite — the SAME
2450    /// PRIME DIRECTIVE ≥2 pattern [`Self::is_chain`]'s pre-lift shape
2451    /// carried, with only the pair-level primitive swapped. Post-lift the
2452    /// antichain verdict binds at ONE typed method the algebra exposes,
2453    /// and the doubly-indexed all-pairs scaffolding lives at ONE
2454    /// implementation site — the same site [`Self::is_chain`] delegates
2455    /// to, factored down to a per-projection choice of pair-level
2456    /// primitive.
2457    ///
2458    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
2459    /// proofs; the set-level antichain verdict on N preset-carried
2460    /// resource proofs is itself a typed named `bool` predicate composing
2461    /// them via the underlying pair-level [`Self::is_incomparable`]
2462    /// projection. THEORY.md §V.1 — knowable platform; the doubly-indexed
2463    /// all-pairs incomparability conjunction becomes a TYPE-level
2464    /// operation on the posture algebra rather than an inline two-
2465    /// primitive scaffolding at every consumer that decides "is this
2466    /// slice a pairwise-incomparable subset?"
2467    ///
2468    /// Frontier inspiration: the order-theoretic notion of an ANTICHAIN
2469    /// (a pairwise-incomparable subset of a poset) canonicalized in
2470    /// Dilworth's theorem (a poset's width — the maximum antichain size —
2471    /// equals the minimum number of chains covering the poset) and the
2472    /// Robinson–Schensted correspondence's Young-tableau chain/antichain
2473    /// duality; Haskell's `Data.List` idiom of
2474    /// `all (uncurry incomparable) . pairs` for a partial-order carrier;
2475    /// Coq's `Antichain` inductive on lists over a `PartialOrder`.
2476    /// Translation through pleme-io primitives: the doubly-indexed pair
2477    /// walk directly, with [`Self::is_incomparable`] as the pair-level
2478    /// primitive, no new dep, no supertrait bound, `const fn` throughout.
2479    /// The (chain, antichain) set-level pair opens the door to Dilworth's
2480    /// width invariant on the [`ResourceLimits`] lattice at a future
2481    /// callsite — the maximum antichain size is a substrate-level
2482    /// dimension the two set-level projections lift here jointly
2483    /// characterize.
2484    #[must_use]
2485    pub const fn is_antichain(postures: &[Self]) -> bool {
2486        let n = postures.len();
2487        let mut i = 0;
2488        while i < n {
2489            let mut j = i + 1;
2490            while j < n {
2491                if !postures[i].is_incomparable(postures[j]) {
2492                    return false;
2493                }
2494                j += 1;
2495            }
2496            i += 1;
2497        }
2498        true
2499    }
2500
2501    /// The MIDDLE-CELL corner of the (chain, antichain, mixed) three-cell
2502    /// face on the set-level pairwise-relation surface — `postures` is a
2503    /// MIXED slice iff it is NEITHER a chain NOR an antichain, computed
2504    /// as the boolean conjunction of the negations of the just-lifted
2505    /// [`Self::is_chain`] and [`Self::is_antichain`] projections.
2506    ///
2507    /// EXHAUSTIVELY CLOSES the (chain, antichain, mixed) three-cell face
2508    /// at its previously-open middle cell — the two set-level ENDS the
2509    /// just-lifted [`Self::is_chain`] / [`Self::is_antichain`] pair
2510    /// pinned already carried the (chain, antichain) two ends; this
2511    /// projection binds the third cell (F, F) as a NAMED typed `bool`.
2512    /// The three cells now sit at (T, F) — pure chain, (F, T) — pure
2513    /// antichain, and (F, F) — mixed. The fourth cell (T, T) is only
2514    /// achievable at the degenerate empty-or-singleton slice where BOTH
2515    /// projections agree on the vacuous-truth verdict — [`Self::is_mixed`]
2516    /// reports `false` there too, aligning with the intuition that a
2517    /// slice with strictly fewer than two distinct pairs is neither
2518    /// chain-mixed nor antichain-mixed.
2519    ///
2520    /// **Empty-slice contract**: `ResourceLimits::is_mixed(&[]) == false`
2521    /// — [`Self::is_chain`] and [`Self::is_antichain`] BOTH return `true`
2522    /// vacuously, so `!true && !true == false`. The empty slice is NOT
2523    /// mixed. Pinned as
2524    /// `resource_limits_is_mixed_empty_slice_is_false`.
2525    ///
2526    /// **Singleton contract**: `ResourceLimits::is_mixed(&[a]) == false`
2527    /// for every posture `a` — a one-element slice contains no distinct
2528    /// pairs; both [`Self::is_chain`] and [`Self::is_antichain`] return
2529    /// `true` vacuously. Pinned as
2530    /// `resource_limits_is_mixed_singleton_is_false`.
2531    ///
2532    /// **Diagonal-duplicate contract**: `ResourceLimits::is_mixed(&[a, a])
2533    /// == false` for every posture `a` — [`Self::is_chain`] returns
2534    /// `true` via [`Self::is_comparable`]'s REFLEXIVITY;
2535    /// [`Self::is_antichain`] returns `false` via
2536    /// [`Self::is_incomparable`]'s IRREFLEXIVITY; the conjunction is
2537    /// `!true && !false == false`. The diagonal-duplicate slice is a
2538    /// CHAIN, not mixed. Pinned as
2539    /// `resource_limits_is_mixed_of_diagonal_duplicate_is_false`.
2540    ///
2541    /// **Chain rejection**: `ResourceLimits::is_mixed(
2542    /// &[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2543    /// UNBOUNDED_RESOURCE_LIMITS]) == false` — the shipped-preset triple
2544    /// on the bounded-lattice diagonal is an ascending chain,
2545    /// [`Self::is_chain`] returns `true`, and `!true && …` short-
2546    /// circuits to `false`. Pinned as
2547    /// `resource_limits_is_mixed_rejects_the_shipped_preset_triple`.
2548    ///
2549    /// **Antichain rejection**: `ResourceLimits::is_mixed(
2550    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) ==
2551    /// false` — the hand-authored antichain pair binds
2552    /// [`Self::is_antichain`] `true`, and `!… && !true == false`. Pinned
2553    /// as
2554    /// `resource_limits_is_mixed_rejects_the_hand_authored_antichain_pair`.
2555    ///
2556    /// **Mixed-set closure (LOAD-BEARING `true`-arm catch)**:
2557    /// `ResourceLimits::is_mixed(&[DEFAULT_RESOURCE_LIMITS,
2558    /// HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) == true`
2559    /// — the (DEFAULT, MID) pair is comparable (falsifying
2560    /// [`Self::is_antichain`]) while the (MID, OTHER) pair is
2561    /// incomparable (falsifying [`Self::is_chain`]); the conjunction
2562    /// `!false && !false == true`. This is the SAME fixture the sibling
2563    /// [`Self::is_chain`] and [`Self::is_antichain`] BOTH reject at —
2564    /// pinning the DISCRIMINATING positive-arm catch for the (mixed)
2565    /// cell of the three-cell face. Pinned as
2566    /// `resource_limits_is_mixed_holds_on_the_mixed_slice_with_one_comparable_and_one_antichain_pair`.
2567    ///
2568    /// **Trichotomy exhaustiveness at arity ≥3 with distinct
2569    /// elements**: for every slice `postures` with `len() >= 2` and
2570    /// pairwise-distinct elements, EXACTLY ONE of [`Self::is_chain`],
2571    /// [`Self::is_antichain`], [`Self::is_mixed`] returns `true` — the
2572    /// three cells partition the set-level verdict surface at every
2573    /// non-degenerate slice. At arity < 2 (empty and singleton) the
2574    /// verdict surface degenerates to the vacuous-truth (T, T, F)
2575    /// cell; at arity ≥2 with any duplicated element the verdict
2576    /// surface degenerates to the chain-only (T, F, F) cell (chain
2577    /// wins via reflexivity, antichain drops via irreflexivity).
2578    /// Pinned as
2579    /// `resource_limits_is_chain_is_antichain_and_is_mixed_partition_the_verdict_surface_on_distinct_slices`.
2580    ///
2581    /// Encoded as the boolean conjunction of the negations of the two
2582    /// just-lifted set-level projections — one implementation site, ZERO
2583    /// new pair-level primitive delegations, `const fn` throughout via
2584    /// the underlying `const fn` bodies. The doubly-indexed all-pairs
2585    /// walk lives at ONE site (the [`Self::is_chain`] +
2586    /// [`Self::is_antichain`] implementations), and this projection
2587    /// factors THROUGH both without reopening it.
2588    ///
2589    /// Pre-lift, a caller wanting "is this slice a mixed subset (neither
2590    /// chain nor antichain)?" composed the doubly-indexed
2591    /// `!postures.iter().enumerate().all(|(i, a)|
2592    /// postures[i + 1..].iter().all(|b| a.is_comparable(*b))) &&
2593    /// !postures.iter().enumerate().all(|(i, a)|
2594    /// postures[i + 1..].iter().all(|b| a.is_incomparable(*b)))`
2595    /// two-scaffolding conjunction at every prospective callsite —
2596    /// the SAME PRIME DIRECTIVE ≥2 pattern the pair of just-lifted
2597    /// projections' pre-lift shapes carried, doubled. Post-lift the
2598    /// mixed-set verdict binds at ONE typed method the algebra exposes.
2599    ///
2600    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition
2601    /// preserves proofs; the set-level mixed-set verdict on N preset-
2602    /// carried resource proofs is itself a typed named `bool` predicate
2603    /// composing them via the underlying set-level [`Self::is_chain`] +
2604    /// [`Self::is_antichain`] projections. THEORY.md §V.1 — knowable
2605    /// platform; the doubly-negated set-level conjunction becomes a
2606    /// TYPE-level operation on the posture algebra rather than an inline
2607    /// negation-composition at every consumer that decides "is this
2608    /// slice a mixed subset?"
2609    ///
2610    /// Frontier inspiration: the order-theoretic notion of a MIXED
2611    /// subset (a subset that is neither a chain nor an antichain) sits
2612    /// at the interior of the (chain, antichain, mixed) trichotomy that
2613    /// canonicalizes the SET-LEVEL verdict surface of a poset — a slice
2614    /// whose Hasse-diagram induced subgraph carries both order-comparable
2615    /// AND order-incomparable edges. Dilworth's width invariant and the
2616    /// dual Mirsky theorem (a poset's height equals the minimum number of
2617    /// antichains covering it) both take mixed sets as their non-trivial
2618    /// inputs — a chain has width 1 and a mixed set of width ≥2 is
2619    /// exactly where the width invariant becomes informative. Haskell's
2620    /// `Data.List` idiom of `not . isChain && not . isAntichain`
2621    /// composition on a partial-order carrier. Translation through
2622    /// pleme-io primitives: the boolean conjunction of the negations of
2623    /// the just-lifted set-level projections, no new dep, no supertrait
2624    /// bound, `const fn` throughout.
2625    #[must_use]
2626    pub const fn is_mixed(postures: &[Self]) -> bool {
2627        !Self::is_chain(postures) && !Self::is_antichain(postures)
2628    }
2629
2630    /// Boolean `leq`-conjunction across every CONSECUTIVE pair of a slice
2631    /// of postures — `postures` is ASCENDING (a leq-monotone sequence on
2632    /// the pointwise partial order) iff every consecutive pair
2633    /// `(postures[i], postures[i + 1])` satisfies
2634    /// `postures[i].leq(postures[i + 1])`.
2635    ///
2636    /// The SEQUENCE-LEVEL N-ary peer of [`Self::leq`] one CARDINALITY axis
2637    /// over on the (pairwise, sequence-level) primitive surface — the
2638    /// DIRECT SEQUENCE-LEVEL LIFT of the pair-level partial-order primitive
2639    /// through a consecutive-index walk. Opens a NEW AXIS one CONSECUTIVE-
2640    /// VS-ALL-PAIRS step over from the just-lifted set-level projections
2641    /// [`Self::is_chain`] / [`Self::is_antichain`] / [`Self::is_mixed`]:
2642    /// where the set-level triple quantifies over EVERY distinct pair
2643    /// `(i, j)` with `j > i`, this projection quantifies over only the
2644    /// `n - 1` CONSECUTIVE pairs `(i, i + 1)`. The two axes lift the same
2645    /// pair-level primitive family through DIFFERENT quantification shapes
2646    /// — the set-level triple carries UNORDERED verdicts about the whole
2647    /// slice's pairwise-relation graph, while this projection carries an
2648    /// ORDER-SENSITIVE verdict about the slice's specific enumeration.
2649    ///
2650    /// **Transitivity theorem — `is_ascending ⇒ is_chain`**: for every
2651    /// slice `postures`, `Self::is_ascending(postures) == true` implies
2652    /// `Self::is_chain(postures) == true`. The pointwise partial order is
2653    /// TRANSITIVE (`a.leq(b) && b.leq(c) ⇒ a.leq(c)` — verified structurally
2654    /// by [`Self::leq`]'s six-axis pointwise `<=` conjunction, since each
2655    /// `usize <=` is transitive), so a consecutive-pair leq-chain closes
2656    /// under transitive composition into the all-pairs leq-chain; every
2657    /// (i, j) pair with j > i decomposes into the composition of
2658    /// consecutive-pair leqs (i, i+1), (i+1, i+2), ..., (j-1, j), which
2659    /// transitivity collapses into `postures[i].leq(postures[j])`, and
2660    /// `leq` implies `is_comparable`. This is a LOAD-BEARING substrate-
2661    /// level theorem: the sequence-level ascending verdict is strictly
2662    /// STRONGER than the set-level chain verdict (a chain admits any
2663    /// permutation of a leq-sorted enumeration; an ascending sequence
2664    /// admits exactly the leq-sorted one). Pinned as
2665    /// `resource_limits_is_ascending_implies_is_chain_on_every_shipped_slice`.
2666    ///
2667    /// **Empty-slice vacuous truth**: `ResourceLimits::is_ascending(&[])
2668    /// == true` — the empty conjunction is vacuously true; the empty
2669    /// slice contains no consecutive pairs to reject. Peer of
2670    /// [`Self::is_chain`]'s empty-slice identity one CONSECUTIVE-VS-ALL-
2671    /// PAIRS axis over: both cells of the (set-level, sequence-level)
2672    /// projection pair AGREE at the empty slice on the vacuous-truth
2673    /// verdict — the two cells coincide at the empty face of the N-ary
2674    /// verdict surface. Pinned as
2675    /// `resource_limits_is_ascending_empty_slice_is_vacuously_true`.
2676    ///
2677    /// **Singleton vacuous truth**: `ResourceLimits::is_ascending(&[a])
2678    /// == true` for every posture `a` — a one-element slice contains no
2679    /// consecutive pairs, so the walk never enters the loop and returns
2680    /// `true` vacuously. Peer of [`Self::is_chain`]'s singleton-vacuous-
2681    /// truth identity: both projections AGREE at singleton slices too.
2682    /// Pinned as
2683    /// `resource_limits_is_ascending_singleton_is_vacuously_true`.
2684    ///
2685    /// **Diagonal-duplicate identity**: `ResourceLimits::is_ascending(
2686    /// &[a, a]) == true` for every posture `a` — [`Self::leq`] is
2687    /// REFLEXIVE (`a.leq(a) == true` via each axis's `<=` reflexivity), so
2688    /// the single consecutive pair `(0, 1)` binds `a.leq(a) == true` and
2689    /// the conjunction holds. AGREES with [`Self::is_chain`]'s diagonal-
2690    /// duplicate verdict at every diagonal-duplicate slice — the two
2691    /// projections diverge only where the ORDER matters, and duplicated
2692    /// elements carry no ordering information to distinguish them.
2693    /// Pinned as `resource_limits_is_ascending_of_diagonal_duplicate_is_true`.
2694    ///
2695    /// **Ascending shipped-preset triple closure**: `ResourceLimits::
2696    /// is_ascending(&[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2697    /// UNBOUNDED_RESOURCE_LIMITS]) == true` — the shipped-preset triple
2698    /// on the bounded-lattice diagonal is a leq-monotone ascending
2699    /// sequence (`EMPTY.leq(DEFAULT).leq(UNBOUNDED)` pointwise), so every
2700    /// consecutive pair satisfies `leq` and the conjunction closes.
2701    /// Pinned as
2702    /// `resource_limits_is_ascending_holds_on_the_ascending_shipped_preset_triple`.
2703    ///
2704    /// **Descending shipped-preset triple rejection**: `ResourceLimits::
2705    /// is_ascending(&[UNBOUNDED_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2706    /// EMPTY_RESOURCE_LIMITS]) == false` — the reversed enumeration of the
2707    /// same shipped-preset chain is a leq-monotone DESCENDING sequence, so
2708    /// the first consecutive pair binds `UNBOUNDED.leq(DEFAULT) == false`
2709    /// and the walk rejects at index 0. The DIRECT ORDER-SENSITIVITY
2710    /// FALSIFICATION of the ascending closure one PERMUTATION axis over on
2711    /// the SAME chain — [`Self::is_chain`] accepts BOTH orderings (its
2712    /// verdict is order-INSENSITIVE), while this projection accepts only
2713    /// the leq-sorted one. Pinned as
2714    /// `resource_limits_is_ascending_rejects_the_descending_shipped_preset_triple`.
2715    ///
2716    /// **Non-monotone chain permutation rejection**: `ResourceLimits::
2717    /// is_ascending(&[DEFAULT_RESOURCE_LIMITS, EMPTY_RESOURCE_LIMITS,
2718    /// UNBOUNDED_RESOURCE_LIMITS]) == false` — the third permutation of
2719    /// the shipped-preset chain has `DEFAULT.leq(EMPTY) == false` at the
2720    /// first consecutive pair, so the walk rejects, EVEN THOUGH the slice
2721    /// is a chain ([`Self::is_chain`] accepts it). Pins the DISCRIMINATING
2722    /// arm between the sequence-level and set-level projections: a chain
2723    /// permutation that is neither ascending nor descending falsifies BOTH
2724    /// sequence-level projections while both set-level projections accept
2725    /// it. Pinned as
2726    /// `resource_limits_is_ascending_rejects_the_non_monotone_chain_permutation`.
2727    ///
2728    /// **Hand-authored antichain rejection**: `ResourceLimits::is_ascending(
2729    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) == false`
2730    /// — the two hand-authored asymmetric postures are incomparable
2731    /// (neither `MID.leq(OTHER)` nor `OTHER.leq(MID)`), so the single
2732    /// consecutive pair rejects. Pinned as
2733    /// `resource_limits_is_ascending_rejects_the_hand_authored_antichain_pair`.
2734    ///
2735    /// Encoded as a singly-indexed const-`while` walk (`i in 0..n-1`) with
2736    /// one [`Self::leq`] delegation per consecutive pair — the SAME walk
2737    /// structure the standard-library `<[T]>::is_sorted_by` idiom carries
2738    /// on a totally-ordered carrier, generalized to the partial-order
2739    /// [`Self::leq`] primitive here. `const fn` throughout via the
2740    /// underlying `const fn` [`Self::leq`] body — a caller can pin an
2741    /// ascending-sequence identity at compile time
2742    /// (`const _: () = assert!(ResourceLimits::is_ascending(&[
2743    /// EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2744    /// UNBOUNDED_RESOURCE_LIMITS]));`) — a build-break rather than a
2745    /// runtime `assert!` on the first execution.
2746    ///
2747    /// Pre-lift, a caller wanting "is this slice a leq-monotone ascending
2748    /// sequence?" composed the singly-indexed
2749    /// `postures.windows(2).all(|w| w[0].leq(w[1]))` two-primitive
2750    /// scaffolding at every prospective callsite — the SAME PRIME
2751    /// DIRECTIVE ≥2 pattern the set-level projections' pre-lift shapes
2752    /// carried, one CARDINALITY axis over. Post-lift the ascending
2753    /// verdict binds at ONE typed method the algebra exposes, and the
2754    /// consecutive-pair walk lives at ONE implementation site — the peer
2755    /// [`Self::is_descending`] shares the SAME walk structure and diverges
2756    /// only on the pair-level primitive it delegates to (`geq` in place of
2757    /// `leq`).
2758    ///
2759    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
2760    /// proofs; the sequence-level ascending verdict on N preset-carried
2761    /// resource proofs is itself a typed named `bool` predicate composing
2762    /// them via the underlying pair-level [`Self::leq`] projection.
2763    /// THEORY.md §V.1 — knowable platform; the consecutive-pair leq
2764    /// conjunction becomes a TYPE-level operation on the posture algebra
2765    /// rather than an inline windowed scaffolding at every consumer that
2766    /// decides "is this slice a leq-monotone ascending sequence?"
2767    ///
2768    /// Frontier inspiration: the order-theoretic notion of a MONOTONE
2769    /// SEQUENCE on a poset — the standard-library `Iterator::is_sorted`
2770    /// / `<[T]>::is_sorted_by` idioms carry the totally-ordered case;
2771    /// Haskell's `Data.List` idiom of `and . zipWith leq xs . tail xs`
2772    /// on a partial-order carrier; Coq's `Sorted` inductive on lists over
2773    /// a `Relation`. Translation through pleme-io primitives: the singly-
2774    /// indexed consecutive-pair walk directly, with [`Self::leq`] as the
2775    /// pair-level primitive, no new dep, no supertrait bound, `const fn`
2776    /// throughout. The (ascending, descending) sequence-level pair opens
2777    /// the door to `is_strictly_ascending` / `is_strictly_descending` via
2778    /// [`Self::lt`] / [`Self::gt`] one STRICTNESS axis over, and to
2779    /// `is_monotone` (`is_ascending || is_descending`) one DIRECTION-
2780    /// AGNOSTIC axis over, at future callsites.
2781    #[must_use]
2782    pub const fn is_ascending(postures: &[Self]) -> bool {
2783        let n = postures.len();
2784        let mut i = 0;
2785        while i + 1 < n {
2786            if !postures[i].leq(postures[i + 1]) {
2787                return false;
2788            }
2789            i += 1;
2790        }
2791        true
2792    }
2793
2794    /// Boolean `geq`-conjunction across every CONSECUTIVE pair of a slice
2795    /// of postures — `postures` is DESCENDING (a geq-monotone sequence on
2796    /// the pointwise partial order) iff every consecutive pair
2797    /// `(postures[i], postures[i + 1])` satisfies
2798    /// `postures[i].geq(postures[i + 1])`.
2799    ///
2800    /// The DIRECT SEQUENCE-LEVEL DUAL of [`Self::is_ascending`] one PAIR-
2801    /// LEVEL-PRIMITIVE axis over — where `is_ascending` delegates to
2802    /// [`Self::leq`], this projection delegates to [`Self::geq`]. The
2803    /// two together close the (ascending, descending) two-cell face on
2804    /// the sequence-level pairwise-relation surface, exactly one
2805    /// CONSECUTIVE-VS-ALL-PAIRS axis over from the set-level (chain,
2806    /// antichain) two-cell face [`Self::is_chain`] / [`Self::is_antichain`]
2807    /// pin, and exactly one PAIR-LEVEL-PRIMITIVE axis over from the
2808    /// (leq, geq) pair-level two-cell face [`Self::leq`] / [`Self::geq`]
2809    /// pin one CARDINALITY axis down.
2810    ///
2811    /// The (ascending, descending) pair is NOT a partition of the
2812    /// sequence-level verdict surface: a slice of ≥3 postures whose
2813    /// consecutive-pair verdicts mix leq and geq (or contain any
2814    /// incomparable consecutive pair) is neither ascending nor
2815    /// descending, so the two sequence-level projections carry a THREE-
2816    /// cell face (ascending, descending, non-monotone) at the sequence
2817    /// level whose ends the two projections lifted here pin. The
2818    /// (T, T) corner is achievable at every diagonal-duplicate slice
2819    /// AND at every empty-or-singleton slice — [`Self::leq`] and
2820    /// [`Self::geq`] are BOTH REFLEXIVE, so a repeated element passes
2821    /// both consecutive-pair conjunctions.
2822    ///
2823    /// **Transitivity theorem — `is_descending ⇒ is_chain`**: for every
2824    /// slice `postures`, `Self::is_descending(postures) == true` implies
2825    /// `Self::is_chain(postures) == true`, by the SAME transitive
2826    /// composition [`Self::is_ascending`] carries one PAIR-LEVEL-PRIMITIVE
2827    /// axis over — the pointwise partial order is transitive under `geq`
2828    /// (by [`Self::geq`]'s composition `other.leq(self)` and `leq`'s six-
2829    /// axis transitivity), so a consecutive-pair geq-chain closes under
2830    /// transitive composition into an all-pairs geq-chain, and `geq`
2831    /// implies `is_comparable`. Pinned as
2832    /// `resource_limits_is_descending_implies_is_chain_on_every_shipped_slice`.
2833    ///
2834    /// **Empty-slice vacuous truth**: `ResourceLimits::is_descending(&[])
2835    /// == true` — the empty conjunction is vacuously true. Peer of
2836    /// [`Self::is_ascending`]'s empty-slice identity one PAIR-LEVEL-
2837    /// PRIMITIVE axis over: both cells of the (ascending, descending)
2838    /// sequence-level pair AGREE at the empty slice. Pinned as
2839    /// `resource_limits_is_descending_empty_slice_is_vacuously_true`.
2840    ///
2841    /// **Singleton vacuous truth**: `ResourceLimits::is_descending(&[a])
2842    /// == true` for every posture `a`. Pinned as
2843    /// `resource_limits_is_descending_singleton_is_vacuously_true`.
2844    ///
2845    /// **Diagonal-duplicate identity**: `ResourceLimits::is_descending(
2846    /// &[a, a]) == true` for every posture `a` — [`Self::geq`] is
2847    /// REFLEXIVE, so the single consecutive pair binds `a.geq(a) == true`.
2848    /// AGREES with [`Self::is_ascending`]'s diagonal-duplicate verdict —
2849    /// both leq and geq carry reflexivity, so the sequence-level (T, T)
2850    /// corner opens at every diagonal-duplicate slice, distinct from the
2851    /// set-level (T, F) verdict [`Self::is_chain`] / [`Self::is_antichain`]
2852    /// carry on the same slice. Pinned as
2853    /// `resource_limits_is_descending_of_diagonal_duplicate_is_true`.
2854    ///
2855    /// **Descending shipped-preset triple closure**: `ResourceLimits::
2856    /// is_descending(&[UNBOUNDED_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2857    /// EMPTY_RESOURCE_LIMITS]) == true` — the reversed enumeration of the
2858    /// shipped-preset chain is a geq-monotone descending sequence. The
2859    /// DIRECT MIRROR of [`Self::is_ascending`]'s ascending closure one
2860    /// PERMUTATION axis over — pinning the two closures on the SAME chain
2861    /// in REVERSED orderings closes the (ascending, descending) sequence-
2862    /// level pair on the shipped chain. Pinned as
2863    /// `resource_limits_is_descending_holds_on_the_descending_shipped_preset_triple`.
2864    ///
2865    /// **Ascending shipped-preset triple rejection**: `ResourceLimits::
2866    /// is_descending(&[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,
2867    /// UNBOUNDED_RESOURCE_LIMITS]) == false` — the ascending-sorted
2868    /// enumeration binds `EMPTY.geq(DEFAULT) == false`. The DIRECT
2869    /// FALSIFICATION of [`Self::is_ascending`]'s ascending closure one
2870    /// CELL axis over on the SAME slice. Pinned as
2871    /// `resource_limits_is_descending_rejects_the_ascending_shipped_preset_triple`.
2872    ///
2873    /// **Hand-authored antichain rejection**: `ResourceLimits::is_descending(
2874    /// &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE]) == false`
2875    /// — the two hand-authored asymmetric postures are incomparable, so
2876    /// the single consecutive pair rejects. Pinned as
2877    /// `resource_limits_is_descending_rejects_the_hand_authored_antichain_pair`.
2878    ///
2879    /// Encoded as a singly-indexed const-`while` walk (`i in 0..n-1`)
2880    /// with one [`Self::geq`] delegation per consecutive pair — the SAME
2881    /// iteration structure as [`Self::is_ascending`] one PAIR-LEVEL-
2882    /// PRIMITIVE axis over, with only the inner primitive swapped between
2883    /// the two. `const fn` throughout via the underlying `const fn`
2884    /// [`Self::geq`] body.
2885    ///
2886    /// Pre-lift, a caller wanting "is this slice a geq-monotone descending
2887    /// sequence?" composed the singly-indexed
2888    /// `postures.windows(2).all(|w| w[0].geq(w[1]))` two-primitive
2889    /// scaffolding at every prospective callsite. Post-lift the
2890    /// descending verdict binds at ONE typed method the algebra exposes.
2891    ///
2892    /// Theory anchor: THEORY.md §II.1 invariant 5 — composition preserves
2893    /// proofs. THEORY.md §V.1 — knowable platform.
2894    ///
2895    /// Frontier inspiration: `Iterator::is_sorted_by` with a reversed
2896    /// comparator on a totally-ordered carrier; Coq's `StronglySorted`
2897    /// on a `<=` relation applied with the argument order flipped.
2898    /// Translation through pleme-io primitives: the singly-indexed
2899    /// consecutive-pair walk directly, with [`Self::geq`] as the pair-
2900    /// level primitive.
2901    #[must_use]
2902    pub const fn is_descending(postures: &[Self]) -> bool {
2903        let n = postures.len();
2904        let mut i = 0;
2905        while i + 1 < n {
2906            if !postures[i].geq(postures[i + 1]) {
2907                return false;
2908            }
2909            i += 1;
2910        }
2911        true
2912    }
2913}
2914
2915/// Cache key: (macro name, SipHash-2-4 of args). We hash `Sexp` directly via
2916/// its manual `Hash` impl — no serde_json round-trip per cache lookup.
2917type CacheKey = (String, u64);
2918
2919/// A registered macro definition.
2920#[derive(Debug, Clone)]
2921pub struct MacroDef {
2922    pub name: String,
2923    pub params: MacroParams,
2924    /// The template body (usually a Quasiquote).
2925    pub body: Sexp,
2926}
2927
2928impl MacroDef {
2929    /// Project the macro body to its substitution-walked form: the inner of
2930    /// the outer `Sexp::Quasiquote` when `(defmacro NAME (PARAMS) `(...))`
2931    /// authored the body through the canonical quasi-quote affordance, OR
2932    /// `&self.body` verbatim when authored without one. The two expansion
2933    /// strategies — bytecode (`compile_template`) and substitute (`apply`'s
2934    /// substitute fallback) — both walk this projection, never the raw
2935    /// `body`, because the outer quasi-quote is the syntactic "you're
2936    /// inside a template" marker and the substitution semantics operate on
2937    /// what's INSIDE it. Naming the projection lifts the inline
2938    /// `match &def.body { Sexp::Quasiquote(inner) => inner.as_ref(),
2939    /// other => other }` peel that appeared verbatim at BOTH sites — well
2940    /// past the ≥2 PRIME-DIRECTIVE trigger — into ONE function the two
2941    /// strategies share, so a regression that drifts ONE strategy's
2942    /// body-projection from the other (e.g. one path peels twice and the
2943    /// other peels once, or one path treats `Sexp::Quote(...)` as a
2944    /// template marker and the other doesn't) becomes structurally
2945    /// impossible: there is exactly one implementation both strategies
2946    /// call.
2947    ///
2948    /// Single-level peel by design: a nested `` ``form `` body unwraps to
2949    /// `` `form `` (the inner quasi-quote stays as-is), matching the v0
2950    /// "no nested quasi-quotes" scope the module preamble declares. A
2951    /// non-quasi-quote body — `(defmacro NAME (PARAMS) BODY)` where BODY
2952    /// is a plain `Sexp::List` / `Sexp::Atom` — returns `&self.body`
2953    /// verbatim, the "other" arm of the legacy match. The borrow is
2954    /// strictly `&'a Sexp` rooted in `&'a self.body` (no clone, no
2955    /// allocation); both `compile_node` (bytecode path) and `substitute`
2956    /// (substitute path) consume the projection immediately and never
2957    /// outlive the borrow.
2958    ///
2959    /// Theory anchor: THEORY.md §VI.1 — generation over composition; two
2960    /// inline copies of the body-peel match is the ≥2 trigger, and the
2961    /// substrate names the projection ONCE so authoring surfaces and
2962    /// future expansion strategies (a third interpreter? a JIT? a
2963    /// debugger that wants to render the body without the outer
2964    /// quasi-quote marker?) bind to ONE primitive. THEORY.md §II.1
2965    /// invariant 2 — free middle; the two expansion strategies emit
2966    /// IDENTICAL output for the same (macro, args) pair, and sharing one
2967    /// body-projection makes that per-strategy agreement structural at
2968    /// the entry to the walker, not a two-site discipline the
2969    /// `expansion_layers_agree_on_output_and_cache_wins` benchmark only
2970    /// observes after the fact.
2971    #[must_use]
2972    pub fn template_body(&self) -> &Sexp {
2973        match &self.body {
2974            Sexp::Quasiquote(inner) => inner.as_ref(),
2975            other => other,
2976        }
2977    }
2978}
2979
2980/// A macro's parameter list — structurally "zero or more required
2981/// positional params, then zero or more `&optional` params, then an OPTIONAL
2982/// single `&rest` param." This is the canonical Lisp lambda-list ordering
2983/// (Common Lisp `(req* &optional opt* &rest r)`), made a TYPE.
2984///
2985/// This shape promotes the invariants the reader ([`parse_params`])
2986/// previously upheld only by construction — `&rest` is LAST, there is AT MOST
2987/// ONE of it, and (now) `&optional` params sit strictly between the required
2988/// run and the rest — from *unobserved discipline* to *unrepresentable
2989/// state*. The prior representation `Vec<Param>` admitted `[Rest, Required]`
2990/// (a `&rest` in the middle) and `[Rest, Rest]` (two of them); both are
2991/// nonsense the binder cannot honor, yet the type permitted them. The flat
2992/// param INDEX that the bytecode references (`Subst(idx)` / `Splice(idx)`)
2993/// and the positional binder both walk would silently misalign on such a
2994/// `Vec` — a `Rest` at index 0 of `[Rest, Required]` makes the binder grab
2995/// every arg, then fail to bind the trailing `Required`, mapping the
2996/// template's index-1 substitution onto the wrong value. `MacroParams`
2997/// cannot express either shape: `rest` is exactly one `Option<String>`,
2998/// always conceptually after every `required` then every `optional` name,
2999/// and the three kinds live in distinct fields whose order is fixed by the
3000/// struct, not by a discipline the binder trusts a `Vec` to have upheld.
3001///
3002/// `optional` differs from `required` in the binder, not the index contract:
3003/// a required name with no arg at its position is a `MissingMacroArg`
3004/// rejection; an optional name with no arg binds to its declared default form
3005/// — `Sexp::Nil` when none was given, the parsed default literal when one was.
3006/// Both shapes — `&optional x` and `&optional (x 5)` — are now structural in
3007/// the typed [`OptionalParam`] entry rather than smeared across a flat
3008/// `Vec<String>` the binder would have had to discover the default for
3009/// elsewhere.
3010///
3011/// The flat-index contract the template bytecode depends on is preserved by
3012/// [`MacroParams::names`]: index `0..required.len()` are the required names
3013/// in order, the next `optional.len()` indices are the optional names, and
3014/// the final index (if present) is the rest name — the canonical lambda-list
3015/// order. [`MacroParams::bind`] produces the per-index bound values in that
3016/// same order, so the name-keyed (`bind_args` → `substitute`) and
3017/// index-keyed (`apply_compiled`) expansion strategies share ONE binder and
3018/// can never drift.
3019///
3020/// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid states
3021/// unrepresentable"; the lambda-list ordering (required → optional → rest,
3022/// rest-is-last, at-most-one-rest) becomes structural. THEORY.md §VI.1 —
3023/// generation over composition; the positional binding loop (verbatim in
3024/// both `bind_args` and `apply_compiled`, the ≥2 PRIME-DIRECTIVE trigger) is
3025/// lifted to ONE owner, `bind`, which the optional arm extends in one place.
3026#[derive(Debug, Clone, Default, PartialEq)]
3027pub struct MacroParams {
3028    pub required: Vec<String>,
3029    pub optional: Vec<OptionalParam>,
3030    pub rest: Option<String>,
3031}
3032
3033/// One entry in a macro's `&optional` section — a name plus an optional
3034/// default form. The two surface shapes the reader admits collapse into this
3035/// single typed shape:
3036///
3037///   * `&optional x`        ⇒ `OptionalParam { name: "x", default: None }`
3038///   * `&optional (x 5)`    ⇒ `OptionalParam { name: "x", default: Some(Int(5)) }`
3039///
3040/// The `default: Option<Sexp>` slot makes the per-param default-form a
3041/// FIELD on each optional entry, not a discipline a sibling `Vec<Sexp>` would
3042/// have had to maintain in lock-step with `Vec<String>`. Without this shape
3043/// the binder cannot tell "no arg supplied, no default declared → bind nil"
3044/// from "no arg supplied, default `5` declared → bind `5`": both would
3045/// collapse onto `Sexp::Nil`, the precise silent misalignment the typed
3046/// shape exists to forbid.
3047///
3048/// The default is the LITERAL `Sexp` — there is no evaluator in v0, so a
3049/// `(x (foo 1))` spec parks `(foo 1)` verbatim as the bound value when `x`'s
3050/// arg is absent. This is the no-evaluator floor of CL semantics: any
3051/// arbitrary form is admitted at the gate, what it MEANS is the next layer's
3052/// concern. The default is parsed exactly once at `defmacro`/
3053/// `defpoint-template`/`defcheck` time (inside `parse_params`); every call
3054/// to that macro consumes the same parsed `Sexp` via `Clone`, never re-
3055/// reading the source.
3056///
3057/// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid states
3058/// unrepresentable"; the (name, default?) pair is one entry rather than two
3059/// parallel `Vec`s a regression could desynchronize. THEORY.md §VI.1 —
3060/// generation over composition; the binder's optional arm consults
3061/// `param.default` in ONE place, so the substitute and bytecode strategies
3062/// inherit identical default-resolution semantics from the shared `bind`.
3063#[derive(Debug, Clone, PartialEq)]
3064pub struct OptionalParam {
3065    pub name: String,
3066    pub default: Option<Sexp>,
3067}
3068
3069impl OptionalParam {
3070    /// `&optional x` — a bare optional name with no default. An absent
3071    /// argument binds to `Sexp::Nil` (the no-default-form floor).
3072    #[must_use]
3073    pub fn bare(name: impl Into<String>) -> Self {
3074        Self {
3075            name: name.into(),
3076            default: None,
3077        }
3078    }
3079
3080    /// `&optional (x DEFAULT)` — an optional with a default form. An absent
3081    /// argument binds to `default.clone()`.
3082    #[must_use]
3083    pub fn with_default(name: impl Into<String>, default: Sexp) -> Self {
3084        Self {
3085            name: name.into(),
3086            default: Some(default),
3087        }
3088    }
3089
3090    /// The bound value when an absent call leaves this optional slot unfilled:
3091    /// the declared default form (cloned) when one was authored, OR the
3092    /// canonical `Sexp::Nil` floor when none was — the CL `&optional` no-
3093    /// default-form floor. ONE named primitive on the typed [`OptionalParam`]
3094    /// every absent-call binder consults; before this lift the same two-arm
3095    /// fallback `param.default.clone().unwrap_or(Sexp::Nil)` lived inline at
3096    /// [`MacroParams::bind`]'s optional arm, and a future absence-resolver
3097    /// (the kwarg-gate's typed-default fill? a future `&supplied-p` slot's
3098    /// "was this defaulted?" bit?) would have had to re-derive the same
3099    /// two-arm fallback at every site that walks the optional run.
3100    ///
3101    /// The projection IS the structural identity binding the typed
3102    /// `default: Option<Sexp>` slot to its bound-value contract:
3103    ///   * `bare(name).resolved_default()` is `Sexp::Nil` (the no-default
3104    ///     floor — `default.is_none()`).
3105    ///   * `with_default(name, d).resolved_default()` is `d.clone()` (the
3106    ///     declared default — `default = Some(d)` projected through Clone).
3107    ///
3108    /// `resolved_default()` is the typed accessor companion to the
3109    /// `bare` / `with_default` constructors: those two constructors define
3110    /// the ONLY admissible shapes of the typed `default` slot, and this
3111    /// accessor names the BOUND-VALUE projection both shapes yield at the
3112    /// binder's absence arm. Together the three close the `OptionalParam`'s
3113    /// self-contained typed surface — every authored shape lands through ONE
3114    /// of two constructors, and every absent-call binder reads through this
3115    /// ONE accessor.
3116    ///
3117    /// Returns an owned `Sexp` (not `&Sexp`) because the binder pushes the
3118    /// resolved default into a fresh `Vec<Sexp>` slot at every absent call;
3119    /// the `default.clone()` projection is the same allocation the pre-lift
3120    /// inline expression performed, just named at the typed boundary. The
3121    /// `Sexp::Nil` floor is a free per-call construction (a unit variant
3122    /// with no payload), so the no-default path is free of allocation
3123    /// beyond the function return slot.
3124    ///
3125    /// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid
3126    /// states unrepresentable"; the "no-default-form floor" structural
3127    /// concept becomes a NAMED projection on [`OptionalParam`] rather than
3128    /// re-derived `param.default.clone().unwrap_or(Sexp::Nil)` arithmetic
3129    /// at every site that walks the bound optional run. Authoring tools
3130    /// (REPL, LSP, `tatara-check`) that want to render "this optional
3131    /// binds to {default-form|nil} when absent" bind to ONE method on the
3132    /// typed param. THEORY.md §VI.1 — generation over composition; the
3133    /// constructor pair `bare` / `with_default` defines the typed shapes
3134    /// and the `resolved_default` accessor names the symmetric
3135    /// bound-value projection — the typed accessor companion. THEORY.md
3136    /// §II.1 invariant 2 — free middle; both expansion strategies route
3137    /// through the SHARED `MacroParams::bind`, so the new accessor is
3138    /// exposed to the bytecode and substitute paths uniformly via that
3139    /// shared binder.
3140    #[must_use]
3141    pub fn resolved_default(&self) -> Sexp {
3142        self.default.clone().unwrap_or(Sexp::Nil)
3143    }
3144}
3145
3146/// The value carrier a macro call binds its arguments in.
3147///
3148/// [`MacroParams::bind_carrier`] is the single positional binder every
3149/// expansion path routes through; this trait is the only thing that varies
3150/// between those paths. It names the two — and only two — places a binder
3151/// must SYNTHESIZE a value the caller never wrote:
3152///
3153///   * [`Self::lift_default`] — an `&optional` slot the call left unfilled
3154///     binds to the declared default form (or the `Sexp::Nil` floor). The
3155///     default is authored as plain `Sexp` at `defmacro` time, so each
3156///     carrier says how a template literal enters it.
3157///   * [`Self::collect_rest`] — the `&rest` slot collects the surplus args
3158///     into ONE value. `Sexp` wraps them in `Sexp::List`; a span-carrying
3159///     carrier additionally has to decide what span the synthesized list
3160///     wears.
3161///
3162/// Everything else in the binder — the required run, the arity rejections,
3163/// the required→optional→rest ordering — is carrier-independent and lives
3164/// exactly once.
3165///
3166/// `Site` is the per-call synthesis context. It is `()` for `Sexp`, whose
3167/// values carry no position; a span-carrying carrier sets it to the macro
3168/// CALL SITE, because a value the call never supplied has no source position
3169/// of its own and the call site is the honest place to point an error at.
3170///
3171/// Theory anchor: THEORY.md §V.1 — knowable platform; "which lambda-list
3172/// shapes does this expansion path accept?" stops being a per-path answer.
3173/// THEORY.md §VI.1 — generation over composition.
3174pub trait MacroArgCarrier: Clone {
3175    /// Per-call synthesis context — `()` when the carrier needs none.
3176    type Site: Copy;
3177
3178    /// Lift a template-authored default form into this carrier, for an
3179    /// `&optional` slot the call left unfilled.
3180    fn lift_default(default: &Sexp, site: Self::Site) -> Self;
3181
3182    /// Collect the `&rest` surplus into one carrier value.
3183    fn collect_rest(items: Vec<Self>, site: Self::Site) -> Self;
3184}
3185
3186impl MacroArgCarrier for Sexp {
3187    /// `Sexp` carries no position, so there is nothing to thread — the unit
3188    /// site is the structural statement that this carrier synthesizes values
3189    /// without context.
3190    type Site = ();
3191
3192    fn lift_default(default: &Sexp, (): ()) -> Self {
3193        default.clone()
3194    }
3195
3196    fn collect_rest(items: Vec<Self>, (): ()) -> Self {
3197        Sexp::List(items)
3198    }
3199}
3200
3201impl MacroParams {
3202    /// Canonical `&rest` marker — the ONE typed `&'static str` on
3203    /// [`MacroParams`] the parser's rest-slot dispatch specialises on.
3204    /// The Common-Lisp lambda-list `&rest` keyword names the position
3205    /// at which the parser stops collecting `required` / `optional`
3206    /// names and binds every subsequent arg into a `Sexp::List` at the
3207    /// [`Self::rest`] slot. Sibling posture to [`Self::OPTIONAL_MARKER`]
3208    /// (`"&optional"`) on the same lambda-list-keyword algebra layer:
3209    /// the two constants are the closed set of Common-Lisp
3210    /// lambda-list-keyword `&'static str` markers the parser's typed
3211    /// dispatch specialises on, sharing the canonical `char` lead byte
3212    /// [`Self::LAMBDA_LIST_KEYWORD_LEAD`] (`'&'`).
3213    ///
3214    /// Pre-lift the same `"&rest"` bytes lived inline as a single
3215    /// `s == "&rest"` comparison inside [`parse_params`], with the two
3216    /// typed `&'static str` markers the CL lambda-list algebra
3217    /// specialises on smeared across TWO parallel `s == "..."` inline
3218    /// comparisons at the SAME dispatch cascade. Post-lift the
3219    /// (`&rest` rest-slot marker, canonical `&'static str`) pairing
3220    /// binds at ONE constant on the typed [`MacroParams`] algebra that
3221    /// both the parser AND any future authoring / rendering surface
3222    /// route through; a refactor that swaps the marker (e.g. a
3223    /// Clojure-compat port to `&`  ONLY, an Elisp-compat port that
3224    /// keeps `&rest` as-is, a Scheme R7RS-style port to `.` for the
3225    /// dotted-pair rest slot) touches ONE constant rather than the
3226    /// two inline literals scattered across the parser AND the
3227    /// diagnostic message surfaces.
3228    ///
3229    /// Structural round-trip contract:
3230    /// `Self::REST_MARKER.starts_with(Self::LAMBDA_LIST_KEYWORD_LEAD)`
3231    /// — the projection law binding the `&'static str` marker to its
3232    /// canonical `char` lead byte on the typed algebra, pinned by
3233    /// `macro_params_rest_marker_prefixed_by_lambda_list_keyword_lead`.
3234    ///
3235    /// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
3236    /// (`&rest` rest-slot marker, canonical `&'static str`) pairing
3237    /// binds at ONE `pub const` on the typed [`MacroParams`] algebra
3238    /// regardless of which parser / authoring surface reaches in.
3239    /// THEORY.md §V.1 — knowable platform; the canonical CL lambda-
3240    /// list `&rest` keyword becomes a TYPE-level constant on the
3241    /// typed macro-params algebra rather than an inline `"&rest"`
3242    /// literal at the parser's rest-slot dispatch arm.
3243    pub const REST_MARKER: &'static str = "&rest";
3244
3245    /// Canonical `&optional` marker — the ONE typed `&'static str` on
3246    /// [`MacroParams`] the parser's optional-section dispatch
3247    /// specialises on. The Common-Lisp lambda-list `&optional` keyword
3248    /// names the position at which the parser switches subsequent
3249    /// bare-symbol names from the `required` bin to the `optional`
3250    /// bin, until either the end of the param list OR the
3251    /// [`Self::REST_MARKER`] terminal is reached. Sibling posture to
3252    /// [`Self::REST_MARKER`] (`"&rest"`) on the same lambda-list-
3253    /// keyword algebra layer: the two constants are the closed set of
3254    /// CL lambda-list-keyword `&'static str` markers the parser's
3255    /// typed dispatch specialises on, sharing the canonical `char`
3256    /// lead byte [`Self::LAMBDA_LIST_KEYWORD_LEAD`] (`'&'`).
3257    ///
3258    /// Structural round-trip contract:
3259    /// `Self::OPTIONAL_MARKER.starts_with(Self::LAMBDA_LIST_KEYWORD_LEAD)`
3260    /// — pinned by
3261    /// `macro_params_optional_marker_prefixed_by_lambda_list_keyword_lead`.
3262    ///
3263    /// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
3264    /// (`&optional` section-switch marker, canonical `&'static str`)
3265    /// pairing binds at ONE `pub const` on the typed [`MacroParams`]
3266    /// algebra regardless of which parser / authoring surface reaches
3267    /// in. THEORY.md §V.1 — knowable platform; the canonical CL
3268    /// lambda-list `&optional` keyword becomes a TYPE-level constant
3269    /// on the typed macro-params algebra rather than an inline
3270    /// `"&optional"` literal at the parser's optional-section
3271    /// dispatch arm.
3272    pub const OPTIONAL_MARKER: &'static str = "&optional";
3273
3274    /// Canonical `&` LEAD byte shared across [`Self::REST_MARKER`]
3275    /// (`"&rest"`) and [`Self::OPTIONAL_MARKER`] (`"&optional"`) — the
3276    /// ONE canonical `char` on the [`MacroParams`] algebra that the
3277    /// Common-Lisp lambda-list-keyword disjointness contract binds to.
3278    ///
3279    /// Sibling posture to the closed set of `pub const` per-role
3280    /// canonical bytes on the substrate's other closed-set outer
3281    /// algebras: [`crate::ast::Atom::STR_DELIMITER`] (`'"'`),
3282    /// [`crate::ast::Atom::STR_ESCAPE_LEAD`] (`'\\'`),
3283    /// [`crate::ast::Atom::KEYWORD_MARKER_LEAD`] (`':'`),
3284    /// [`crate::ast::Atom::BOOL_LITERAL_LEAD`] (`'#'`),
3285    /// [`crate::ast::Sexp::LIST_OPEN`] (`'('`),
3286    /// [`crate::ast::Sexp::LIST_CLOSE`] (`')'`),
3287    /// [`crate::ast::Sexp::COMMENT_LEAD`] (`';'`),
3288    /// [`crate::ast::Sexp::COMMENT_TERM`] (`'\n'`),
3289    /// [`crate::ast::QuoteForm::SPLICE_DISCRIMINATOR`] (`'@'`), and
3290    /// every `crate::ast::QuoteForm::lead_char` projection — every
3291    /// canonical per-role byte the substrate's typed algebras
3292    /// specialise on is now a `pub const` on its owning closed-set
3293    /// algebra. This constant closes the CL lambda-list-keyword
3294    /// family lead byte at the SAME algebra as its two `&'static str`
3295    /// marker projections so a delimiter swap (e.g. a Racket-compat
3296    /// port from CL `&rest` / `&optional` to `#!rest` / `#!optional`
3297    /// keyword-args, a Clojure-compat port to `&` ONLY as the rest
3298    /// marker) lands at ONE constant on the algebra.
3299    ///
3300    /// Structural round-trip contract:
3301    /// `Self::REST_MARKER.starts_with(Self::LAMBDA_LIST_KEYWORD_LEAD)`
3302    /// AND
3303    /// `Self::OPTIONAL_MARKER.starts_with(Self::LAMBDA_LIST_KEYWORD_LEAD)`
3304    /// — the projection law binding the two `&'static str` markers to
3305    /// their shared canonical `char` lead byte, pinned by
3306    /// `macro_params_rest_marker_prefixed_by_lambda_list_keyword_lead`
3307    /// and
3308    /// `macro_params_optional_marker_prefixed_by_lambda_list_keyword_lead`.
3309    ///
3310    /// Disjointness contract: `LAMBDA_LIST_KEYWORD_LEAD`'s byte MUST
3311    /// differ from every sibling outer-marker `char` the substrate's
3312    /// other closed-set algebras specialise on. A collision would
3313    /// silently break the reader's outer dispatch: an `&`-prefixed
3314    /// bare atom `&rest` / `&optional` would collide with whichever
3315    /// marker it aliased. Pinned structurally at
3316    /// `macro_params_lambda_list_keyword_lead_distinct_from_every_other_algebra_marker`.
3317    ///
3318    /// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
3319    /// (CL lambda-list-keyword LEAD byte, canonical `'&'`) pairing
3320    /// binds at ONE constant on the typed [`MacroParams`] algebra
3321    /// regardless of which of the two `&'static str` marker consumers
3322    /// reaches in. THEORY.md §V.1 — knowable platform; the canonical
3323    /// CL lambda-list-keyword LEAD byte becomes a TYPE-level constant
3324    /// on the substrate algebra rather than an inline `'&'` glyph at
3325    /// every docstring pinning the family disjointness contract.
3326    pub const LAMBDA_LIST_KEYWORD_LEAD: char = '&';
3327
3328    /// Closed-set forced-arity ALL array over the CL lambda-list-keyword
3329    /// `&'static str` markers the parser's typed dispatch specialises on
3330    /// — the [`Self::REST_MARKER`] (`"&rest"`) rest-slot marker followed
3331    /// by the [`Self::OPTIONAL_MARKER`] (`"&optional"`) optional-section
3332    /// marker, in the canonical CL lambda-list ordering (`&optional`
3333    /// binds AFTER `&required`, `&rest` binds AFTER `&optional`, so the
3334    /// declaration walk fires `REST_MARKER` FIRST as the terminal-arm
3335    /// early return; the ALL array's ordering keys on marker
3336    /// declaration order in this file, not on parse-time ordering).
3337    ///
3338    /// Sibling posture to the closed set of `pub const ALL: [Self; N]`
3339    /// forced-arity arrays on the substrate's other closed-set outer
3340    /// algebras: [`crate::ast::AtomKind::ALL`] (`[Self; 6]`),
3341    /// [`crate::ast::QuoteForm::ALL`] (`[Self; 4]`),
3342    /// [`crate::error::SexpShape::ALL`] (`[Self; 12]`),
3343    /// [`crate::error::UnquoteForm::ALL`] (`[Self; 2]`),
3344    /// [`crate::error::MacroDefHead::ALL`] (`[Self; 3]`) — every
3345    /// closed-set algebra the substrate carries pins its cardinality at
3346    /// the declaration site via a `pub const ALL` array whose forced
3347    /// arity fails compilation if a new variant lands without being
3348    /// added to the set. `LAMBDA_LIST_KEYWORDS` closes the same shape
3349    /// on the CL lambda-list-keyword family: a third marker (`&key`
3350    /// for keyword-args, `&aux` for auxiliaries, `&body` for the
3351    /// docstring-carrying tail of `defmacro` bodies) extends this ONE
3352    /// `pub const` + the matching per-role marker `pub const` above,
3353    /// AND rustc enforces every downstream consumer's closed-set
3354    /// sweep picks up the new marker at the SAME structural site.
3355    ///
3356    /// Pre-lift the two `&'static str` markers were named on the typed
3357    /// algebra but the family they close was NOT — the two structural
3358    /// round-trip pins (`_rest_marker_prefixed_by_lambda_list_keyword_
3359    /// lead` AND `_optional_marker_prefixed_by_lambda_list_keyword_
3360    /// lead`) each named ONE marker inline as a duplicate 3-line
3361    /// `assert!(A.starts_with(LEAD), ...)` shape, and the pairwise-
3362    /// disjointness pin (`_rest_and_optional_markers_pairwise_disjoint`)
3363    /// named the two markers as a hand-rolled `assert_ne!` pair.
3364    /// Post-lift the ALL array binds the family at the typed algebra so
3365    /// every family-wide contract (LEAD-byte round-trip, pairwise
3366    /// disjointness, membership gate) routes through the ONE array and
3367    /// a future `&key` / `&aux` extension automatically extends the
3368    /// sweep without re-deriving per-marker assertions.
3369    ///
3370    /// Structural round-trip contract: every element `m` of
3371    /// `Self::LAMBDA_LIST_KEYWORDS` satisfies
3372    /// `m.starts_with(Self::LAMBDA_LIST_KEYWORD_LEAD)` — pinned by
3373    /// `macro_params_every_lambda_list_keyword_prefixed_by_lambda_list_keyword_lead`.
3374    /// Cardinality contract: `Self::LAMBDA_LIST_KEYWORDS.len() == 2`
3375    /// pins the current family size at the type level; a future third
3376    /// marker extends the array's arity AND updates every downstream
3377    /// pin at ONE structural site. Pairwise-disjointness contract:
3378    /// every distinct index pair `(i, j)` in `Self::LAMBDA_LIST_KEYWORDS`
3379    /// satisfies `Self::LAMBDA_LIST_KEYWORDS[i] !=
3380    /// Self::LAMBDA_LIST_KEYWORDS[j]` — pinned by
3381    /// `macro_params_lambda_list_keywords_pairwise_distinct`.
3382    ///
3383    /// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
3384    /// CL lambda-list-keyword family closure binds at ONE `pub const`
3385    /// on the typed [`MacroParams`] algebra regardless of which
3386    /// consumer (parser dispatch, family-wide contract sweep,
3387    /// authoring / rendering surface, `is_lambda_list_keyword`
3388    /// membership gate) reaches in. THEORY.md §V.1 — knowable
3389    /// platform; the family's cardinality becomes a TYPE-level
3390    /// constant on the substrate algebra rather than a per-consumer
3391    /// hand-rolled enumeration of the two markers. THEORY.md §VI.1 —
3392    /// generation over composition; the family-wide contract sweeps
3393    /// (LEAD-byte round-trip, pairwise disjointness, membership gate)
3394    /// emerge from the composition of TWO substrate primitives (this
3395    /// `pub const` array + the per-role `pub const REST_MARKER` /
3396    /// `pub const OPTIONAL_MARKER`) rather than as per-marker inline
3397    /// assertions the current pins duplicate structurally.
3398    ///
3399    /// Frontier inspiration: Common Lisp's `LAMBDA-LIST-KEYWORDS`
3400    /// standard variable (CLHS §3.4.1) — the runtime-reflectable list
3401    /// of every reserved lambda-list keyword the implementation
3402    /// recognises; `LAMBDA_LIST_KEYWORDS` is the substrate's typed-Rust
3403    /// peer at compile time, with the closed-set ALL array standing in
3404    /// for CL's runtime-mutable list. Translation through pleme-io
3405    /// primitives: a `pub const [&'static str; N]` on the typed
3406    /// [`MacroParams`] algebra rather than a runtime-mutable list;
3407    /// forced-arity closure at the type layer with the same
3408    /// family-reflective read surface.
3409    pub const LAMBDA_LIST_KEYWORDS: [&'static str; 2] = [Self::REST_MARKER, Self::OPTIONAL_MARKER];
3410
3411    /// Typed membership gate over the closed set
3412    /// [`Self::LAMBDA_LIST_KEYWORDS`] — `true` iff `s` matches some
3413    /// element of the ALL array byte-for-byte, `false` for every other
3414    /// input.
3415    ///
3416    /// The typed "does this symbol name a CL lambda-list keyword?"
3417    /// projection on the [`MacroParams`] algebra. Sibling posture to the
3418    /// closed set of per-algebra membership / classifier gates the
3419    /// substrate carries:
3420    /// [`crate::ast::QuoteForm::from_lead_char`] (per-char quote-family
3421    /// dispatch — Some/None on a closed 3-char set),
3422    /// [`crate::ast::Sexp::is_bare_atom_boundary`] (six-fold
3423    /// bare-atom-boundary sweep), and
3424    /// [`crate::ast::Atom::decode_str_escape`] (Str-escape decode table
3425    /// on a closed 6-arm set) — every family-membership gate the
3426    /// substrate carries binds at ONE method on the closed-set
3427    /// algebra so a family extension lands at ONE place plus the
3428    /// per-role marker without re-deriving per-consumer inline
3429    /// enumerations.
3430    ///
3431    /// Pre-lift the "is this symbol a CL lambda-list keyword?" concept
3432    /// existed structurally but was NOT named on the algebra — the
3433    /// disjointness pin (`_rest_and_optional_markers_pairwise_disjoint`)
3434    /// AND the cross-axis disjointness sweep
3435    /// (`_lambda_list_keyword_lead_distinct_from_every_other_algebra_marker`)
3436    /// each named the concept implicitly as a hand-rolled per-marker
3437    /// `assert_ne!` sweep. Post-lift the membership gate binds at ONE
3438    /// typed method on the algebra a future consumer (a parser-time
3439    /// "this symbol looks like a lambda-list keyword but isn't
3440    /// recognised" hint, an authoring-surface completion bar that
3441    /// suggests `&rest` / `&optional` at param-list positions, a
3442    /// pretty-printer that colors CL lambda-list keywords distinctly)
3443    /// binds against without re-deriving the per-marker enumeration
3444    /// at every consumer site.
3445    ///
3446    /// Contract:
3447    ///   * Every element of [`Self::LAMBDA_LIST_KEYWORDS`] classifies
3448    ///     as `true` — pinned by
3449    ///     `macro_params_is_lambda_list_keyword_accepts_every_marker`.
3450    ///   * The bare CL lambda-list-keyword LEAD byte
3451    ///     ([`Self::LAMBDA_LIST_KEYWORD_LEAD`]) as a 1-char string
3452    ///     classifies as `false` — pinned by
3453    ///     `macro_params_is_lambda_list_keyword_rejects_bare_lead_byte`.
3454    ///   * Symbols starting with the LEAD byte but naming an
3455    ///     unrecognised keyword (`"&key"`, `"&aux"`, `"&body"`)
3456    ///     classify as `false` — pinned by
3457    ///     `macro_params_is_lambda_list_keyword_rejects_unrecognised_ampersand_prefixed_names`.
3458    ///
3459    /// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the
3460    /// closed-set membership gate binds at ONE typed method on the
3461    /// [`MacroParams`] algebra regardless of which consumer reaches
3462    /// in. THEORY.md §III — the typescape; the "is this a CL
3463    /// lambda-list keyword?" projection becomes a TYPE projection on
3464    /// the substrate algebra rather than per-consumer inline
3465    /// enumeration of the two markers. THEORY.md §V.1 — knowable
3466    /// platform; a future third marker (`&key`, `&aux`, `&body`)
3467    /// extends [`Self::LAMBDA_LIST_KEYWORDS`] ONCE and every
3468    /// membership-gate consumer picks up the new marker for free.
3469    ///
3470    /// Frontier inspiration: Common Lisp's `(find X LAMBDA-LIST-KEYWORDS)`
3471    /// pattern for testing lambda-list keyword membership at runtime;
3472    /// `is_lambda_list_keyword` is the substrate's typed-Rust peer at
3473    /// compile time. Translation through pleme-io primitives: a linear
3474    /// sweep over the [`Self::LAMBDA_LIST_KEYWORDS`] `pub const` array
3475    /// rather than a runtime `find` over a mutable list; ordinary
3476    /// slice `contains` on the `&'static str` array.
3477    #[must_use]
3478    pub fn is_lambda_list_keyword(s: &str) -> bool {
3479        Self::LAMBDA_LIST_KEYWORDS.contains(&s)
3480    }
3481
3482    /// The flat, ordered param-name list the template bytecode indexes into:
3483    /// every `required` name in order, then every `optional` name in order,
3484    /// then the `rest` name if present. `names()[i]` is the param `Subst(i)`
3485    /// / `Splice(i)` reference.
3486    #[must_use]
3487    pub fn names(&self) -> Vec<&str> {
3488        self.required
3489            .iter()
3490            .map(String::as_str)
3491            .chain(self.optional.iter().map(|p| p.name.as_str()))
3492            .chain(self.rest.as_deref())
3493            .collect()
3494    }
3495
3496    /// The rest-less maximum arity of this param list: `required.len() +
3497    /// optional.len()`. Two equivalent readings collapse into ONE primitive
3498    /// on the typed `MacroParams`:
3499    ///
3500    ///   * The **rest-start boundary**: when `self.rest` is `Some`, the
3501    ///     `&rest` slot collects `args[fixed_arity()..]` into a
3502    ///     `Sexp::List` (the empty slice when the call is exactly
3503    ///     saturated). `fixed_arity()` IS that slice's start index.
3504    ///   * The **rest-less maximum arity**: when `self.rest` is `None`,
3505    ///     `args.len() > fixed_arity()` is the surplus-args rejection
3506    ///     boundary [`bind`](Self::bind) checks before raising
3507    ///     `LispError::TooManyMacroArgs` (the call-site mirror of
3508    ///     `RestParamTrailingTokens`'s definition-site rejection).
3509    ///
3510    /// Both readings ARE the same arithmetic; [`bind`](Self::bind)
3511    /// previously inlined the same `self.required.len() +
3512    /// self.optional.len()` expression THREE times — once inside the
3513    /// `Vec::with_capacity(required + optional + rest?)` slot, once at
3514    /// the `rest_start` site (inside `if let Some(_rest_name) =
3515    /// self.rest`), and once at the `expected` site (inside the
3516    /// rest-less `else`). The latter two live in mutually-exclusive
3517    /// branches yet name ONE structural concept; lifting the arithmetic
3518    /// to a single named primitive makes that concept first-class on
3519    /// the typed param list.
3520    ///
3521    /// `fixed_arity()` IGNORES the `rest` slot by construction — a
3522    /// `&rest` param has no maximum and is not part of the fixed run.
3523    /// `names().len() == fixed_arity() + usize::from(self.rest.is_some())`
3524    /// is the structural identity binding this primitive to
3525    /// [`names`](Self::names) and to the `Vec::with_capacity` hint
3526    /// [`bind`](Self::bind) computes for the bound-values vec.
3527    ///
3528    /// Theory anchor: THEORY.md §V.1 — knowable platform; the structural
3529    /// "rest-start boundary / rest-less max arity" concept becomes a
3530    /// named `&MacroParams` projection rather than re-derived arithmetic
3531    /// at every site that walks the bound run. Authoring tools (REPL,
3532    /// LSP, `tatara-check`) that want to render "this macro takes
3533    /// between `required.len()` and `fixed_arity()` args (or unbounded
3534    /// if `rest.is_some()`)" bind to ONE method on the typed param
3535    /// list. THEORY.md §VI.1 — generation over composition; three
3536    /// inline copies of the same arithmetic in one function is past
3537    /// the ≥2 PRIME-DIRECTIVE trigger once the structural shape is
3538    /// named. THEORY.md §II.1 invariant 2 — free middle; both
3539    /// expansion strategies route through the SHARED `bind`, so the
3540    /// new primitive is exposed to the bytecode and substitute paths
3541    /// uniformly — no per-strategy drift in how the boundary is
3542    /// computed.
3543    #[must_use]
3544    pub fn fixed_arity(&self) -> usize {
3545        self.required.len() + self.optional.len()
3546    }
3547
3548    /// The TOTAL lambda-list arity of this param list — every named
3549    /// slot the reader accepted, in the canonical CL lambda-list
3550    /// ordering: `required.len() + optional.len() + rest.is_some() as
3551    /// usize`. Peer to [`Self::fixed_arity`] one REST-SLOT axis over
3552    /// on the `MacroParams` algebra — where [`Self::fixed_arity`]
3553    /// ignores the rest slot (the rest-less maximum-arity /
3554    /// rest-start-boundary reading, unbounded on the call-time surface
3555    /// when `self.rest.is_some()`), this projection INCLUDES the rest
3556    /// slot as one additional named param (the total-slots reading,
3557    /// bounded on the type-time surface regardless of `rest`).
3558    ///
3559    /// `Self::total_arity() == Self::names().len()` — a `Vec`-free
3560    /// projection of the [`Self::names`] flat-index list's length. Two
3561    /// equivalent readings collapse into ONE named primitive on the
3562    /// typed [`MacroParams`] algebra:
3563    ///
3564    ///   * The **`Vec::with_capacity` hint**: [`Self::bind`] previously
3565    ///     inlined `self.fixed_arity() + usize::from(self.rest.is_some())`
3566    ///     as the bound-vec capacity hint. That expression IS this
3567    ///     primitive.
3568    ///   * The **REGISTER-time arity ceiling**: on the arity-bomb
3569    ///     `(defmacro huge (a-1 a-2 … a-N) `,a-1)` [`Expander::register_macro_def`]
3570    ///     compares `def.params.total_arity()` against
3571    ///     [`Expander::max_macro_arity`] BEFORE the body-size gate walks
3572    ///     the body or any insert lands. `total_arity()` IS that gate's
3573    ///     axis.
3574    ///
3575    /// The structural identity
3576    /// `names().len() == fixed_arity() + usize::from(rest.is_some())
3577    ///                 == total_arity()`
3578    /// binds the primitive to [`Self::names`] and [`Self::fixed_arity`]
3579    /// on the [`MacroParams`] algebra — pinned by
3580    /// `macro_params_total_arity_matches_names_len` AND
3581    /// `macro_params_total_arity_equals_fixed_arity_plus_rest_bit`.
3582    ///
3583    /// Theory anchor: THEORY.md §V.1 — knowable platform; the
3584    /// "how many named slots does this macro declare?" projection
3585    /// becomes a NAMED `&MacroParams` accessor rather than re-derived
3586    /// arithmetic at [`Self::bind`]'s capacity hint AND at the
3587    /// [`Expander::register_macro_def`] arity-ceiling gate.
3588    /// THEORY.md §VI.1 — generation over composition; two inline copies
3589    /// of the same three-arm arithmetic across two consumers is the
3590    /// ≥2 PRIME-DIRECTIVE trigger once the structural shape is named.
3591    /// THEORY.md §II.1 invariant 2 — free middle; both the binder AND
3592    /// the arity-guard consult ONE primitive, so a regression that
3593    /// drifts the guard's arity computation from the binder's capacity
3594    /// hint (e.g. one counts `rest` as one slot, the other counts it
3595    /// as zero) becomes structurally impossible.
3596    #[must_use]
3597    pub fn total_arity(&self) -> usize {
3598        self.fixed_arity() + usize::from(self.rest.is_some())
3599    }
3600
3601    /// Bind call args to params positionally, returning the per-index bound
3602    /// values parallel to [`names`](Self::names): each required name takes
3603    /// the arg at its position (a missing one is
3604    /// [`missing_macro_arg`](self::missing_macro_arg)); each optional name
3605    /// takes the arg at its position, or — when the call ran out of args —
3606    /// its declared default form (`Sexp::Nil` when none was declared,
3607    /// matching CL's `&optional` floor); and a present `rest` collects every
3608    /// arg beyond the required+optional run into a `Sexp::List` (the empty
3609    /// list when none remain). Args beyond a REST-LESS param list have
3610    /// nowhere to bind and are rejected
3611    /// ([`too_many_macro_args`](self::too_many_macro_args)) rather than
3612    /// silently truncated. This is the single binding loop every expansion
3613    /// strategy shares — `apply_compiled` consumes the index vec directly,
3614    /// `bind_args` zips it against `names()` into the name-keyed map — and
3615    /// it is the `Sexp` instantiation of the carrier-generic
3616    /// [`Self::bind_carrier`].
3617    fn bind(&self, macro_name: &str, args: &[Sexp]) -> Result<Vec<Sexp>> {
3618        self.bind_carrier(macro_name, args, ())
3619    }
3620
3621    /// The ONE positional binder, generic over the value carrier a macro call
3622    /// binds its arguments in — [`Sexp`] for the plain `Sexp`→`Sexp`
3623    /// expander, `Spanned` for the span-preserving one, and any future
3624    /// carrier a consumer implements [`MacroArgCarrier`] for.
3625    ///
3626    /// Genericising this loop is what makes ONE lambda-list semantics
3627    /// (required run → `&optional` run with per-param defaults → at-most-one
3628    /// `&rest`, plus the too-few / too-many arity rejections) reachable from
3629    /// every expansion path, instead of each path restating the loop against
3630    /// its own value type. Before this lift the span-preserving expander
3631    /// carried its own `bind_spanned_args` that knew only `Required`/`Rest`
3632    /// — so `(defmacro f (a &optional b) …)` parsed on one path and was
3633    /// rejected on the other, and a surplus argument was a hard error on one
3634    /// path and silently dropped on the other. Both divergences are now
3635    /// structurally impossible: there is exactly one loop.
3636    ///
3637    /// `site` is the carrier's synthesis context — `()` for `Sexp` (a
3638    /// default form and a rest list need no context to construct), the
3639    /// macro CALL SITE span for `Spanned` (a value the CALL never supplied
3640    /// has no source position of its own, so it is stamped at the call).
3641    ///
3642    /// Theory anchor: THEORY.md §VI.1 — generation over composition; the
3643    /// binding loop had reached three copies (plain, spanned, and
3644    /// `tatara-lisp-eval`'s macro-time env binder), well past the ≥2
3645    /// PRIME-DIRECTIVE trigger.
3646    pub fn bind_carrier<V: MacroArgCarrier>(
3647        &self,
3648        macro_name: &str,
3649        args: &[V],
3650        site: V::Site,
3651    ) -> Result<Vec<V>> {
3652        let mut out = Vec::with_capacity(self.total_arity());
3653        for (i, name) in self.required.iter().enumerate() {
3654            let arg = args
3655                .get(i)
3656                .cloned()
3657                .ok_or_else(|| missing_macro_arg(macro_name, name))?;
3658            out.push(arg);
3659        }
3660        let opt_start = self.required.len();
3661        for (j, param) in self.optional.iter().enumerate() {
3662            // Absent optional slot binds to the typed `resolved_default()`
3663            // projection on `OptionalParam`: the declared default form when
3664            // one was authored, OR the `Sexp::Nil` no-default floor when
3665            // none was. The two-arm fallback `param.default.clone().
3666            // unwrap_or(Sexp::Nil)` previously inlined here is now ONE named
3667            // accessor on the typed param both expansion strategies share via
3668            // `MacroParams::bind_carrier`.
3669            let arg = match args.get(opt_start + j) {
3670                Some(supplied) => supplied.clone(),
3671                None => V::lift_default(&param.resolved_default(), site),
3672            };
3673            out.push(arg);
3674        }
3675        if let Some(_rest_name) = self.rest.as_ref() {
3676            // The `&rest` slot collects args[fixed_arity()..] (the empty
3677            // slice when the call is exactly saturated); the boundary is
3678            // the typed `fixed_arity()` primitive both branches share.
3679            let rest = args.get(self.fixed_arity()..).unwrap_or(&[]).to_vec();
3680            out.push(V::collect_rest(rest, site));
3681        } else {
3682            // No `&rest` slot — the param list has a FIXED maximum arity
3683            // of `fixed_arity()`. Surplus args have nowhere to bind;
3684            // reject rather than silently truncate. Closes the call-site
3685            // mirror of `RestParamTrailingTokens` (the definition-site
3686            // rejection lifted by the prior-run typed-promotion lineage),
3687            // so the typed-entry macro-call-gate is structurally complete
3688            // in both directions: too-few (`MissingMacroArg`) AND too-many
3689            // (`TooManyMacroArgs`).
3690            let expected = self.fixed_arity();
3691            if args.len() > expected {
3692                return Err(too_many_macro_args(macro_name, expected, args.len()));
3693            }
3694        }
3695        Ok(out)
3696    }
3697}
3698
3699/// How many macro applications each expansion layer answered (memo, bytecode,
3700/// substitute), read through [`Expander::expansion_counts`].
3701///
3702/// A count of work, not a timing: which layer ran is a deterministic fact of
3703/// the strategy and the workload, so a test can assert the fast path is wired
3704/// without measuring the machine it runs on.
3705#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
3706pub struct ExpansionCounts {
3707    /// Applications answered from the memo table.
3708    pub cache_hits: u64,
3709    /// Applications run through the compiled template bytecode.
3710    pub compiled: u64,
3711    /// Applications run through the name-keyed substitute walker.
3712    pub substituted: u64,
3713}
3714
3715/// The live counters behind [`ExpansionCounts`]. Atomic because `apply`
3716/// takes `&self`; a clone starts from the source's current values.
3717#[derive(Default)]
3718struct ApplyCounters {
3719    cache_hits: AtomicU64,
3720    compiled: AtomicU64,
3721    substituted: AtomicU64,
3722}
3723
3724impl ApplyCounters {
3725    fn snapshot(&self) -> ExpansionCounts {
3726        ExpansionCounts {
3727            cache_hits: self.cache_hits.load(AtomicOrdering::Relaxed),
3728            compiled: self.compiled.load(AtomicOrdering::Relaxed),
3729            substituted: self.substituted.load(AtomicOrdering::Relaxed),
3730        }
3731    }
3732}
3733
3734impl Clone for ApplyCounters {
3735    fn clone(&self) -> Self {
3736        let c = self.snapshot();
3737        Self {
3738            cache_hits: AtomicU64::new(c.cache_hits),
3739            compiled: AtomicU64::new(c.compiled),
3740            substituted: AtomicU64::new(c.substituted),
3741        }
3742    }
3743}
3744
3745/// Macro environment. Collects `defmacro` forms and rewrites callers.
3746///
3747/// Expansion strategy is tunable per-expander:
3748///   - **Compiled (default)** — every registered macro's template is walked once
3749///     and flattened into a linear `CompiledTemplate` (a tiny bytecode: Literal,
3750///     Subst(index), Splice(index), BeginList, EndList). Expansion of a call
3751///     is then a linear pass with no HashMap lookups and no recursion through
3752///     the template Sexp. Purely-literal subtrees compile to a single
3753///     `Literal(Sexp)` op — huge win for macros where most of the body is fixed.
3754///   - **Substitute-only** — runs the name-keyed `substitute` walker. Slower
3755///     but proves equivalence; used in the benchmark test to measure the
3756///     compiled-vs-substituted speedup.
3757#[derive(Clone, Default)]
3758pub struct Expander {
3759    macros: HashMap<String, MacroDef>,
3760    /// Pre-compiled template bytecodes, populated when `compile_templates`.
3761    templates: HashMap<String, CompiledTemplate>,
3762    /// When true, register a CompiledTemplate alongside each macro and dispatch
3763    /// expansion through the bytecode interpreter.
3764    compile_templates: bool,
3765    /// Memoization of `apply(macro, args)` — repeated calls with identical
3766    /// args skip expansion entirely. Shared across clones so realizations of
3767    /// the same `CompilerSpec` benefit across .compile() invocations.
3768    cache: Arc<Mutex<HashMap<CacheKey, Sexp>>>,
3769    /// Toggle caching. Default on — caching is the actual performance win
3770    /// the bytecode layer enables.
3771    cache_enabled: bool,
3772    /// The six resource ceilings, held as ONE typed [`ResourceLimits`] bundle
3773    /// rather than six sibling `max_*: usize` fields. Peer of the six
3774    /// getter/setter pairs one AGGREGATION axis over — where each pair
3775    /// projects ONE ceiling, this field carries the SIX-fold cross-product
3776    /// so [`Self::resource_limits`] returns `self.limits` verbatim and
3777    /// [`Self::set_resource_limits`] assigns through the bundle in ONE move.
3778    ///
3779    /// Adding a SEVENTH ceiling touches [`ResourceLimits`] + the matching
3780    /// `DEFAULT_MAX_*` module constant + the paired individual getter/setter
3781    /// — the [`Expander`] struct, its constructors, AND
3782    /// [`Self::resource_limits`] / [`Self::set_resource_limits`] stay
3783    /// exactly as they are. That's the compounding win of the
3784    /// consolidation: the (Expander, constructors, bulk getter, bulk
3785    /// setter) four-corner face is field-shape-agnostic — it composes with
3786    /// the bundle by value, not by field-by-field literal.
3787    ///
3788    /// Consumers on the read path (`register_macro_def`'s three REGISTER-time
3789    /// gates, `expand_with_depth`'s depth + output-size gates, `apply`'s
3790    /// cache-entries gate) dereference through `self.limits.max_*`; the
3791    /// projection is a `Copy usize` load — same shape as reading a bare
3792    /// field, no `Deref`, no `Arc`, no lock.
3793    limits: ResourceLimits,
3794    /// Per-layer application counts, read through [`Self::expansion_counts`].
3795    counters: ApplyCounters,
3796}
3797
3798impl Expander {
3799    /// Default-posture expander with an operator-supplied resource-ceiling
3800    /// bundle — the AGGREGATION-axis peer of
3801    /// [`Self::set_resource_limits`] one CONSTRUCTOR-STAGE axis over.
3802    /// Where [`Self::set_resource_limits`] applies the bundled six ceilings
3803    /// POST-construction on an already-built expander, this constructor
3804    /// binds them AT construction so a call-site that wants a fresh
3805    /// expander with a non-default posture composes ONE typed call
3806    /// (`Expander::with_limits(limits)`) rather than the two-step
3807    /// (`let mut e = Expander::new(); e.set_resource_limits(limits);`)
3808    /// dance the post-construction sibling still admits.
3809    ///
3810    /// The (compile_templates, cache_enabled) execution-strategy pair
3811    /// carries the [`Self::new`] default posture (`true, true`) — the
3812    /// bytecode-with-cache strategy — since callers that want the
3813    /// substitute-only strategy still route through
3814    /// [`Self::new_substitute_only`]. Post-lift both convenience
3815    /// constructors delegate their `macros` / `templates` / `cache` field
3816    /// literals through THIS primitive at the resource-posture axis: the
3817    /// four-out-of-six field literals (`HashMap::new()` × 2 +
3818    /// `Arc::new(Mutex::new(HashMap::new()))` + `limits`) lived
3819    /// byte-identical across [`Self::new`] and [`Self::new_substitute_only`]
3820    /// pre-lift; the two divergent flags (`compile_templates`,
3821    /// `cache_enabled`) still differ between the two constructors, and the
3822    /// substitute-only variant flips them AFTER threading through
3823    /// `with_limits` — same shape as [`Self::new_bytecode_no_cache`] does
3824    /// on the `cache_enabled` flag.
3825    ///
3826    /// Round-trip identity: `Expander::with_limits(limits).resource_limits()
3827    /// == limits` for any `ResourceLimits` — pinned as a typed theorem in
3828    /// the test cohort. The at-construction assignment is a direct `Copy`
3829    /// of the bundled field (no per-field cascade), so a mid-construction
3830    /// panic window on the six knobs is structurally impossible. Peer of
3831    /// the `Expander::with_limits(DEFAULT_RESOURCE_LIMITS) ==
3832    /// Expander::new()` delegation identity — pinned on the resource-
3833    /// posture projection so a future refactor that drifts the two
3834    /// constructors on the ceiling axis fails loudly.
3835    ///
3836    /// Frontier inspiration: Cranelift's `settings::Flags::new(builder)` —
3837    /// codegen ceilings (opt level, allocator strategy, feature enables)
3838    /// bind at CONSTRUCTION-time on a typed `Flags` bundle rather than
3839    /// composed through post-construction setters on the `Isa`. Same
3840    /// posture as this lift: the (getter, setter, at-construction)
3841    /// three-corner face on the resource-posture surface now binds at ONE
3842    /// typed bundle across ALL three surfaces.
3843    ///
3844    /// Theory grounding: THEORY.md §II.1 invariants 1 + 2 — typed entry +
3845    /// free middle. Pre-lift the "expander with a chosen resource posture"
3846    /// concept was untyped: consumers composed [`Self::new`] +
3847    /// [`Self::set_resource_limits`] at their call site with no
3848    /// compile-time gate that BOTH steps land atomically. Post-lift the
3849    /// concept has ONE named entry point on the `Expander` surface, and a
3850    /// future consumer (`RealizedCompiler` builder that ships a
3851    /// CI-tightened preset, an LSP session that ships a REPL-permissive
3852    /// preset, a test-harness fixture that ships an `UNBOUNDED`-style
3853    /// preset) binds through ONE typed constructor.
3854    pub fn with_limits(limits: ResourceLimits) -> Self {
3855        Self {
3856            macros: HashMap::new(),
3857            templates: HashMap::new(),
3858            compile_templates: true,
3859            cache: Arc::new(Mutex::new(HashMap::new())),
3860            cache_enabled: true,
3861            limits,
3862            counters: ApplyCounters::default(),
3863        }
3864    }
3865
3866    /// Default expander — compiled bytecode + expansion cache enabled.
3867    /// Delegates to [`Self::with_limits`] with [`DEFAULT_RESOURCE_LIMITS`]
3868    /// so the (macros, templates, cache, limits) field-shape agreement
3869    /// with every other constructor on this `Expander` surface lives at
3870    /// ONE literal.
3871    pub fn new() -> Self {
3872        Self::with_limits(DEFAULT_RESOURCE_LIMITS)
3873    }
3874
3875    /// Expander using the legacy substitute path (no template compilation,
3876    /// no cache). Kept for benchmarking + equivalence testing.
3877    /// Delegates to [`Self::with_limits`] with [`DEFAULT_RESOURCE_LIMITS`]
3878    /// and flips the two divergent execution-strategy flags — same
3879    /// composition shape as [`Self::new_bytecode_no_cache`] one flag over.
3880    pub fn new_substitute_only() -> Self {
3881        let mut e = Self::with_limits(DEFAULT_RESOURCE_LIMITS);
3882        e.compile_templates = false;
3883        e.cache_enabled = false;
3884        e
3885    }
3886
3887    /// Expander with bytecode on but expansion cache off — isolates the cache
3888    /// contribution from the bytecode infrastructure. Benchmark baseline.
3889    pub fn new_bytecode_no_cache() -> Self {
3890        let mut e = Self::new();
3891        e.cache_enabled = false;
3892        e
3893    }
3894
3895    /// Toggle the expansion cache at runtime.
3896    pub fn set_cache_enabled(&mut self, enabled: bool) {
3897        self.cache_enabled = enabled;
3898    }
3899
3900    /// How many entries are currently cached.
3901    pub fn cache_size(&self) -> usize {
3902        self.cache.lock().unwrap().len()
3903    }
3904
3905    /// How many macro applications each expansion layer has answered since
3906    /// this expander was built.
3907    pub fn expansion_counts(&self) -> ExpansionCounts {
3908        self.counters.snapshot()
3909    }
3910
3911    /// Clear the expansion cache (e.g., after redefining a macro).
3912    pub fn clear_cache(&self) {
3913        self.cache.lock().unwrap().clear();
3914    }
3915
3916    /// The configured ceiling on `expand`'s recursive re-entry into a
3917    /// macro-call form. Defaults to [`DEFAULT_MAX_EXPANSION_DEPTH`].
3918    #[must_use]
3919    pub fn max_expansion_depth(&self) -> usize {
3920        self.limits.max_expansion_depth
3921    }
3922
3923    /// Set the ceiling on `expand`'s recursive re-entry into a macro-call
3924    /// form. Tests bind low values (e.g. `4`) to pin the runaway-rejection
3925    /// shape without paying for a 256-round walk; consumers that compose
3926    /// deep proven macro cascades can raise it. Zero is admitted and
3927    /// makes every first macro-call return
3928    /// [`LispError::ExpansionDepthExceeded`] immediately — useful for
3929    /// contract-tests that assert "this reader path never expands a
3930    /// macro."
3931    pub fn set_max_expansion_depth(&mut self, depth: usize) {
3932        self.limits.max_expansion_depth = depth;
3933    }
3934
3935    /// The configured ceiling on the expansion cache's entry count.
3936    /// Defaults to [`DEFAULT_MAX_CACHE_ENTRIES`]. Peer to
3937    /// [`Self::max_expansion_depth`] one RESOURCE axis over.
3938    #[must_use]
3939    pub fn max_cache_entries(&self) -> usize {
3940        self.limits.max_cache_entries
3941    }
3942
3943    /// Set the ceiling on the expansion cache's entry count. Tests bind
3944    /// low values (e.g. `2`) to pin the bounded-cache contract without
3945    /// paying for an 8K-entry walk; consumers that expand many distinct
3946    /// argument shapes can raise it. Zero is admitted and effectively
3947    /// disables caching (every fresh `(name, args)` pair skips the
3948    /// insert path) — useful for contract-tests that assert "this
3949    /// expander realizes without ever caching." [`usize::MAX`] is the
3950    /// unbounded-cache mode for batch compilation runs where memory is
3951    /// not the concern.
3952    pub fn set_max_cache_entries(&mut self, cap: usize) {
3953        self.limits.max_cache_entries = cap;
3954    }
3955
3956    /// The configured ceiling on a single macro-`apply` output's
3957    /// [`crate::ast::Sexp::node_count`]. Defaults to
3958    /// [`DEFAULT_MAX_EXPANSION_SIZE`]. Peer to
3959    /// [`Self::max_expansion_depth`] and [`Self::max_cache_entries`]
3960    /// one RESOURCE-DIMENSION axis over.
3961    #[must_use]
3962    pub fn max_expansion_size(&self) -> usize {
3963        self.limits.max_expansion_size
3964    }
3965
3966    /// Set the ceiling on a single macro-`apply` output's node count.
3967    /// Tests bind low values (e.g. `8`) to pin the "expansion bomb"
3968    /// rejection shape without materializing a 64K-node blob;
3969    /// consumers that compose deep proven macro cascades can raise it.
3970    /// [`usize::MAX`] admits any lawful output (the ceiling is
3971    /// effectively lifted); operators that want strict "your macros
3972    /// must fit in N nodes" contract-tests set it exactly.
3973    pub fn set_max_expansion_size(&mut self, size: usize) {
3974        self.limits.max_expansion_size = size;
3975    }
3976
3977    /// The configured ceiling on a registered macro's BODY
3978    /// [`crate::ast::Sexp::node_count`]. Defaults to
3979    /// [`DEFAULT_MAX_MACRO_BODY_SIZE`]. Peer to
3980    /// [`Self::max_expansion_depth`], [`Self::max_cache_entries`],
3981    /// and [`Self::max_expansion_size`] one PIPELINE-STAGE axis over
3982    /// (the three prior guards fire at EXPAND time; this one fires
3983    /// at REGISTER time).
3984    #[must_use]
3985    pub fn max_macro_body_size(&self) -> usize {
3986        self.limits.max_macro_body_size
3987    }
3988
3989    /// Set the ceiling on a registered macro's BODY node count.
3990    /// Tests bind low values (e.g. `4`) to pin the authoring-bomb
3991    /// rejection shape without materializing a 16K-node blob;
3992    /// consumers that author deep proven macro-body templates can
3993    /// raise it. [`usize::MAX`] admits any lawful body (the ceiling
3994    /// is effectively lifted). Zero is admitted and rejects every
3995    /// non-empty body immediately (a `Nil` body is 1 node, so the
3996    /// smallest lawful body is still rejected under a zero
3997    /// ceiling) — useful for contract-tests that assert "this
3998    /// expander refuses every registration."
3999    pub fn set_max_macro_body_size(&mut self, size: usize) {
4000        self.limits.max_macro_body_size = size;
4001    }
4002
4003    /// The configured ceiling on the [`Self::macros`] table's entry
4004    /// count. Defaults to [`DEFAULT_MAX_REGISTERED_MACROS`]. Peer to
4005    /// [`Self::max_macro_body_size`] one RESOURCE-DIMENSION axis over
4006    /// on the REGISTER-time surface, and to [`Self::max_cache_entries`]
4007    /// one PIPELINE-STAGE axis over.
4008    #[must_use]
4009    pub fn max_registered_macros(&self) -> usize {
4010        self.limits.max_registered_macros
4011    }
4012
4013    /// Set the ceiling on the [`Self::macros`] table's entry count.
4014    /// Tests bind low values (e.g. `3`) to pin the registration-bomb
4015    /// rejection shape without materializing a 4K-entry macros table;
4016    /// consumers that compose deep proven typescapes can raise it.
4017    /// [`usize::MAX`] admits any lawful cumulative registration count
4018    /// (the ceiling is effectively lifted). Zero is admitted and
4019    /// rejects every FRESH registration immediately — useful for
4020    /// contract-tests that assert "this expander accepts no new
4021    /// macros" — while still admitting overwrites of any macro that
4022    /// was registered before the ceiling was set (a re-registration
4023    /// of an existing key never grows the table).
4024    pub fn set_max_registered_macros(&mut self, cap: usize) {
4025        self.limits.max_registered_macros = cap;
4026    }
4027
4028    /// The configured ceiling on a registered macro's lambda-list
4029    /// arity. Defaults to [`DEFAULT_MAX_MACRO_ARITY`]. Peer to
4030    /// [`Self::max_macro_body_size`] and [`Self::max_registered_macros`]
4031    /// one RESOURCE-DIMENSION axis over on the REGISTER-time surface.
4032    #[must_use]
4033    pub fn max_macro_arity(&self) -> usize {
4034        self.limits.max_macro_arity
4035    }
4036
4037    /// Set the ceiling on a registered macro's lambda-list arity —
4038    /// the [`MacroParams::total_arity`] projection of `def.params`.
4039    /// Tests bind low values (e.g. `2`) to pin the arity-bomb rejection
4040    /// shape without materializing a 128-slot authoring blob;
4041    /// consumers that author high-arity domain macros (a `defschema`
4042    /// with 40 typed slots) can raise it. [`usize::MAX`] admits any
4043    /// lawful arity (the ceiling is effectively lifted). Zero is
4044    /// admitted and rejects every non-nullary macro registration
4045    /// immediately — a `(defmacro nullary () BODY)` is still admitted
4046    /// (its `total_arity()` is `0`, and only strict overrun rejects) —
4047    /// useful for contract-tests that assert "this expander accepts
4048    /// only nullary macros."
4049    pub fn set_max_macro_arity(&mut self, cap: usize) {
4050        self.limits.max_macro_arity = cap;
4051    }
4052
4053    /// Snapshot the six configured resource ceilings as ONE typed
4054    /// [`ResourceLimits`] value — the bundled peer of the six
4055    /// [`Self::max_expansion_depth`] / [`Self::max_cache_entries`] /
4056    /// [`Self::max_expansion_size`] / [`Self::max_macro_body_size`] /
4057    /// [`Self::max_registered_macros`] / [`Self::max_macro_arity`]
4058    /// individual getters. Consumers that want to inspect the full
4059    /// resource posture (a test that compares a preset expander to a
4060    /// reference posture, a diagnostic that renders "the six ceilings"
4061    /// as one line, a caller that clones + selectively overrides one
4062    /// field via a struct-update literal) bind to this projection at
4063    /// ONE call site rather than composing the six individual getters.
4064    ///
4065    /// Post the [`Self::limits`]-field consolidation the projection is
4066    /// a direct `Copy` of the bundled field — a `usize`-sized fixed
4067    /// return with no per-field literal walk. Adding a SEVENTH ceiling
4068    /// extends [`ResourceLimits`] and the paired individual
4069    /// getter/setter; this bulk getter is field-shape-agnostic and
4070    /// stays exactly as it is.
4071    #[must_use]
4072    pub fn resource_limits(&self) -> ResourceLimits {
4073        self.limits
4074    }
4075
4076    /// Bulk-replace the six configured resource ceilings from ONE typed
4077    /// [`ResourceLimits`] value — the bundled peer of the six
4078    /// [`Self::set_max_expansion_depth`] /
4079    /// [`Self::set_max_cache_entries`] /
4080    /// [`Self::set_max_expansion_size`] /
4081    /// [`Self::set_max_macro_body_size`] /
4082    /// [`Self::set_max_registered_macros`] /
4083    /// [`Self::set_max_macro_arity`] individual setters. Presets
4084    /// (a hardened CI expander, a permissive REPL expander, a
4085    /// throttled test harness) compose the six ceilings ONCE at the
4086    /// preset call site and apply them ATOMICALLY here rather than
4087    /// threading six independent setter invocations whose ordering
4088    /// the type system does not gate.
4089    ///
4090    /// Round-trip identity: `let l = e.resource_limits(); e.set_resource_limits(l);`
4091    /// leaves `e` in an equivalent posture — pinned as a typed theorem
4092    /// in the [`ResourceLimits`] test cohort. Post the [`Self::limits`]-
4093    /// field consolidation the assignment is a direct `Copy` of the
4094    /// bundled field, structurally atomic — no per-field destructure +
4095    /// six-way write cascade in which a mid-cascade panic could leave
4096    /// the six knobs in a half-updated posture. Sibling of
4097    /// [`Self::resource_limits`]; the pair closes the (getter, setter)
4098    /// face on the aggregation axis.
4099    pub fn set_resource_limits(&mut self, limits: ResourceLimits) {
4100        self.limits = limits;
4101    }
4102
4103    pub fn with_macros<I: IntoIterator<Item = MacroDef>>(defs: I) -> Result<Self> {
4104        let mut e = Self::new();
4105        for d in defs {
4106            e.register_macro_def(d)?;
4107        }
4108        Ok(e)
4109    }
4110
4111    /// Register a parsed [`MacroDef`] into this expander's macro tables —
4112    /// the single named primitive on the `Expander` surface every
4113    /// macro-registration consumer routes through.
4114    ///
4115    /// The registration discipline is a two-step composition:
4116    ///   1. When [`Self::compile_templates`](Self::new) is on (the
4117    ///      `Self::new` default; flipped off by [`Self::new_substitute_only`]),
4118    ///      [`compile_template`] pre-compiles the macro body to a typed
4119    ///      [`CompiledTemplate`] bytecode and inserts it into `self.templates`
4120    ///      keyed by `def.name`.
4121    ///   2. The `MacroDef` is moved into `self.macros` keyed by `def.name` —
4122    ///      always, regardless of `compile_templates`, because the substitute
4123    ///      strategy reads `self.macros` exclusively while the bytecode
4124    ///      strategy consults `self.templates` first and falls back to
4125    ///      `self.macros` for the body and params.
4126    ///
4127    /// The order is structural: `compile_template` borrows `&def` while
4128    /// `self.macros.insert(def.name.clone(), def)` consumes `def` — the
4129    /// template pre-compile MUST precede the move into `self.macros`, and the
4130    /// `def.name.clone()` projection captures the key for the moved insert.
4131    /// `?`-routing through `compile_template` preserves the structural
4132    /// ordering of the rejection chain end-to-end: a template-compile error
4133    /// (`UnboundTemplateVar` for an unbound `,name`, `NonSymbolUnquoteTarget`
4134    /// for `,5` / `,(nested)`, et al.) short-circuits BEFORE `self.macros`
4135    /// is mutated, so a failed registration leaves both tables exactly as
4136    /// they were — no partial-write window in which `self.macros.has(name)`
4137    /// is true but `self.templates.has(name)` is missing (a regression that
4138    /// would silently coerce the bytecode strategy onto the substitute path
4139    /// for that one macro despite `compile_templates: true`).
4140    ///
4141    /// Before this lift the same two-step block —
4142    ///
4143    /// ```ignore
4144    /// if self.compile_templates {
4145    ///     self.templates.insert(def.name.clone(), compile_template(&def)?);
4146    /// }
4147    /// self.macros.insert(def.name.clone(), def);
4148    /// ```
4149    ///
4150    /// — lived byte-identical (modulo `self`/`e` and `def`/`d` substitutions)
4151    /// at TWO sites: [`Self::with_macros`] (the constructor that
4152    /// bulk-registers an `IntoIterator<Item = MacroDef>`, e.g. a curated
4153    /// preloaded set the caller assembled out-of-band) and
4154    /// [`Self::expand_program`]'s `(defmacro …)`-head arm (the program-level
4155    /// walker that recognizes a `defmacro` / `defpoint-template` / `defcheck`
4156    /// head via [`macro_def_from`] and registers it as a side-effect of
4157    /// walking the program). After this lift the registration block lives in
4158    /// ONE method on the `Expander`; both consumers and any future
4159    /// macro-registration surface bind to ONE primitive instead of
4160    /// re-deriving the two-step discipline at every call site.
4161    ///
4162    /// `pub` so authoring surfaces (an LSP that incrementally registers a
4163    /// `(defmacro …)` head as the user finishes typing it without a full
4164    /// program re-expand, a REPL `:define-macro` command that registers a
4165    /// pre-parsed `MacroDef` directly, a future "library merge" operation
4166    /// that absorbs another expander's macro set MacroDef-by-MacroDef) can
4167    /// register a typed `MacroDef` without round-tripping through source
4168    /// serialization first. Sibling of [`Self::with_macros`] (the
4169    /// bulk-from-iterator constructor — itself the
4170    /// `defs.into_iter().try_fold((), |_, d| self.register_macro_def(d))`
4171    /// shape on a fresh expander) and [`Self::expand_program`] (the
4172    /// source-level walker that recognizes `(defmacro …)` heads via
4173    /// [`macro_def_from`] — itself the program-level fold-over-defmacro-heads
4174    /// of this method). All three end up at this primitive.
4175    ///
4176    /// Returns `Result<()>` so the consumer's rejection chain composes with
4177    /// `?`-routing — `with_macros` short-circuits its bulk loop on the first
4178    /// `compile_template` failure; `expand_program` short-circuits its
4179    /// program walk at the offending `(defmacro …)` form. Infallibility on
4180    /// the `compile_templates: false` path is preserved (`compile_template`
4181    /// is gated behind the conditional), so a substitute-only expander never
4182    /// emits the `compile_template`-side rejection chain.
4183    ///
4184    /// Theory anchor: THEORY.md §VI.1 — generation over composition; two
4185    /// byte-identical inline copies of the registration block across
4186    /// `with_macros` and `expand_program` is past the ≥2 PRIME-DIRECTIVE
4187    /// trigger once the structural shape is named. THEORY.md §V.1 — knowable
4188    /// platform; the macro-registration discipline becomes a NAMED primitive
4189    /// on the substrate's `Expander` surface rather than a per-consumer
4190    /// inline duplication that future emitters (an LSP, a REPL, a library-
4191    /// merge operator) would have had to re-derive. THEORY.md §II.1
4192    /// invariant 2 — free middle; both consumers route through the SAME
4193    /// registration primitive, so a regression that drifts ONE consumer's
4194    /// discipline from the other (one path skips the template pre-compile,
4195    /// one path inserts the `self.macros` entry in a different order, a
4196    /// future third side-effect — logging, attestation, metrics — emitted
4197    /// at one site but not the other) cannot reach the substrate's runtime:
4198    /// there is exactly one implementation both consumers route through.
4199    ///
4200    /// Frontier inspiration: Racket's `(define-syntax name template)` at
4201    /// REPL is exactly this — register a typed macro into the live
4202    /// namespace, no source round-trip; the substrate's `register_macro_def`
4203    /// is the Rust-typed peer of that surface, lifted onto the `Expander`'s
4204    /// table-level algebra (`macros: HashMap<String, MacroDef>` +
4205    /// `templates: HashMap<String, CompiledTemplate>`). MLIR's
4206    /// `OpRegistry::registerOp<Op>()` — typed-op registration into a
4207    /// dialect's live table at construction-time AND at JIT-walk-time
4208    /// through ONE registration entry point; `register_macro_def` is the
4209    /// pleme-io peer of that registration entry point on the macro-table
4210    /// algebra.
4211    pub fn register_macro_def(&mut self, def: MacroDef) -> Result<()> {
4212        // Reject a registration-bomb (a code-generator emitting
4213        // unbounded fresh `(defmacro fresh-N (x) `(list ,x))` heads
4214        // whose bodies each sit comfortably under
4215        // `max_macro_body_size` yet collectively saturate process
4216        // memory) at the cheapest gate on the REGISTER-time surface
4217        // — a `HashMap::len` comparison, no body walk. Peer to the
4218        // body-size gate below one RESOURCE-DIMENSION axis over
4219        // (COUNT vs. SIZE) and to the expand-time cache-entries gate
4220        // in `apply` one PIPELINE-STAGE axis over (REGISTER-time
4221        // COUNT vs. EXPAND-time COUNT). Fires BEFORE the body-size
4222        // gate walks `def.body.node_count()` or any insert lands, so
4223        // a failed registration leaves BOTH tables exactly as they
4224        // were. Overwrites (a re-registration of an already-
4225        // registered key) are ADMITTED at any table size — a
4226        // re-`defmacro` never grows the table, so the ceiling cannot
4227        // gate operator redefinitions. Keyed on `>=` (a table
4228        // already at ceiling rejects a fresh key) so
4229        // `max_registered_macros` names the LARGEST admissible
4230        // `self.macros.len()` — same posture the expand-time
4231        // cache-entries gate holds on the memoization cache.
4232        let is_overwrite = self.macros.contains_key(&def.name);
4233        if !is_overwrite && self.macros.len() >= self.limits.max_registered_macros {
4234            return Err(LispError::RegisteredMacrosExceeded {
4235                macro_name: def.name.clone(),
4236                count: self.macros.len(),
4237                limit: self.limits.max_registered_macros,
4238            });
4239        }
4240        // Reject an arity-bomb `(defmacro huge (a-1 a-2 … a-N-million)
4241        // `,a-1)` — a code-generator whose macro body sits at ONE node
4242        // (well under `max_macro_body_size`) yet whose param list
4243        // carries millions of fresh names the per-index binder walks on
4244        // every call. Peer to the body-size gate below one RESOURCE-
4245        // DIMENSION axis over on the REGISTER-time surface (per-body
4246        // ARITY vs. per-body SIZE), AND peer to the table-count gate
4247        // above one RESOURCE-DIMENSION axis over on the REGISTER-time
4248        // surface (per-registration WIDTH vs. cumulative COUNT). Fires
4249        // BEFORE the body-size gate walks `def.body.node_count()` or
4250        // any insert lands, so a failed registration leaves BOTH tables
4251        // exactly as they were. Cheaper than the body-size gate — a
4252        // three-arm addition on `def.params` fields, no AST walk —
4253        // hence positioned BEFORE it on the O(1)-first ordering of the
4254        // REGISTER-time chain. Keyed on `>` (equality is admitted, only
4255        // strict overrun rejects) so `max_macro_arity` names the
4256        // LARGEST admissible `def.params.total_arity()` — same posture
4257        // the body-size gate holds on the body node count.
4258        let arity = def.params.total_arity();
4259        if arity > self.limits.max_macro_arity {
4260            return Err(LispError::MacroArityExceeded {
4261                macro_name: def.name.clone(),
4262                arity,
4263                limit: self.limits.max_macro_arity,
4264            });
4265        }
4266        // Reject an authoring-bomb `(defmacro huge (x) `<template-of-
4267        // N-million-nodes>)` at the REGISTRATION boundary — the
4268        // PIPELINE-STAGE peer of the EXPAND-time output-size gate in
4269        // `expand_with_depth`. Fires BEFORE `compile_template` walks
4270        // the body or `self.macros.insert` lands the entry, so a
4271        // failed registration leaves BOTH tables exactly as they
4272        // were (no partial-write window in which the body-size
4273        // rejection happened but the template pre-compile already
4274        // consumed cycles or the macros table already carries the
4275        // over-ceiling entry). Keyed on `>` rather than `>=` so
4276        // `max_macro_body_size` names the LARGEST admissible body —
4277        // equality is admitted, only strict overrun rejects — same
4278        // posture the expand-time output-size gate holds.
4279        let body_size = def.body.node_count();
4280        if body_size > self.limits.max_macro_body_size {
4281            return Err(LispError::MacroBodySizeExceeded {
4282                macro_name: def.name.clone(),
4283                size: body_size,
4284                limit: self.limits.max_macro_body_size,
4285            });
4286        }
4287        if self.compile_templates {
4288            self.templates
4289                .insert(def.name.clone(), compile_template(&def)?);
4290        }
4291        self.macros.insert(def.name.clone(), def);
4292        Ok(())
4293    }
4294
4295    /// Expand a whole program. Returns the list of top-level forms after
4296    /// `defmacro` definitions are registered and all macro calls expanded.
4297    pub fn expand_program(&mut self, forms: Vec<Sexp>) -> Result<Vec<Sexp>> {
4298        let mut out = Vec::new();
4299        for form in forms {
4300            if let Some(def) = macro_def_from(&form)? {
4301                self.register_macro_def(def)?;
4302                continue;
4303            }
4304            out.push(self.expand(&form)?);
4305        }
4306        Ok(out)
4307    }
4308
4309    /// Read a source string into top-level forms via [`crate::reader::read`],
4310    /// then route the forms through [`expand_program`](Self::expand_program) —
4311    /// the from-source posture of the yield-all-forms-after-expansion primitive,
4312    /// in ONE method on the `Expander` surface.
4313    ///
4314    /// Before this lift the same two-step chain — `let forms = read(src)?;
4315    /// <expander>.expand_program(forms)` — lived inline at two sites in
4316    /// [`crate::compiler_spec`]: [`RealizedCompiler::compile`](crate::compiler_spec::RealizedCompiler::compile)
4317    /// (the public from-source untyped-expansion entry on a realized compiler,
4318    /// returning the expanded `Vec<Sexp>` for untyped consumers like
4319    /// `tatara-check`'s per-form dispatcher) AND [`realize_in_memory`](crate::compiler_spec::realize_in_memory)'s
4320    /// `:macros` library load loop (the per-spec-macro source absorption that
4321    /// builds the preloaded expander's macro library through `expand_program`'s
4322    /// `defmacro` recognition side-effect). After this lift the read-then-expand
4323    /// composition lives in ONE method on the `Expander`; each of the two
4324    /// consumers binds it with the per-site expander posture that fits its call
4325    /// boundary — `self.preloaded.clone()` for `RealizedCompiler::compile`'s
4326    /// per-call clone, `&mut preloaded` for `realize_in_memory`'s shared
4327    /// build-up.
4328    ///
4329    /// Sibling of [`expand_source_and_collect_calls_to`](Self::expand_source_and_collect_calls_to)
4330    /// — that method stacks the typed-keyword projection on top of the
4331    /// from-source pipeline (`read + expand_program + iter_calls_to(_,
4332    /// keyword) + map + collect`); this method is the bare yield-all-forms
4333    /// from-source primitive (`read + expand_program`) the typed dispatchers
4334    /// stack their keyword projection atop. The two together close the
4335    /// from-source posture of the program-level walk family on the
4336    /// `Expander` surface: bare (this method) vs. typed-keyword-projected
4337    /// (the sibling).
4338    ///
4339    /// Closes the 2×2 program-level walk family on the `Expander` surface:
4340    /// from-forms × yield-all ([`expand_program`](Self::expand_program)),
4341    /// from-forms × keyword-projected ([`expand_and_collect_calls_to`](Self::expand_and_collect_calls_to)),
4342    /// from-source × keyword-projected ([`expand_source_and_collect_calls_to`](Self::expand_source_and_collect_calls_to)),
4343    /// AND now from-source × yield-all (this method). The four together name
4344    /// the canonical surfaces a dispatcher composes with to extract an
4345    /// expanded program from either a pre-parsed `Vec<Sexp>` (from-forms
4346    /// posture, for callers composing with another `Sexp`-producing surface)
4347    /// or a raw `&str` (from-source posture, for callers consuming
4348    /// authoring-surface source text directly), with or without a
4349    /// typed-keyword filter on the result.
4350    ///
4351    /// `?`-routing through `read` preserves the structural ordering of the
4352    /// rejection chain end-to-end: a reader error (lexer / parser /
4353    /// unbalanced-paren / unterminated-string) short-circuits BEFORE
4354    /// `expand_program` runs; an `expand_program` error
4355    /// (`defmacro`-NAME-not-a-symbol, `OptionalParamMalformed`,
4356    /// `RestParamMissingName`, et al.) short-circuits at the offending form.
4357    /// Each consumer's rejection chain remains exactly what it was pre-lift,
4358    /// now sourced from ONE composition point rather than two.
4359    ///
4360    /// `defmacro`-registration side-effects fire on `&mut self` exactly as
4361    /// they do for the from-forms primitive — `realize_in_memory`'s per-spec
4362    /// build-up depends on every `defmacro` in every `:macros` source
4363    /// landing in `self.macros` (and, when `compile_templates` is on, in
4364    /// `self.templates`); `RealizedCompiler::compile`'s per-call clone
4365    /// posture isolates absorption to the cloned expander, so a
4366    /// `defmacro` in the user's source does NOT leak into the persistent
4367    /// realized compiler. Both postures' absorption semantics are
4368    /// preserved by routing through this primitive instead of inlining
4369    /// the two-step chain.
4370    ///
4371    /// Theory anchor: THEORY.md §VI.1 — generation over composition; two
4372    /// inline copies of the `let forms = read(src)?; <expander>.expand_program(forms)`
4373    /// two-step chain across `RealizedCompiler::compile` and
4374    /// `realize_in_memory` is past the ≥2 PRIME-DIRECTIVE trigger once the
4375    /// structural shape is named. THEORY.md §V.1 — knowable platform; the
4376    /// read-then-expand composition becomes a NAMED primitive on the
4377    /// substrate's `Expander` surface rather than a re-derived two-step
4378    /// inline pipeline at every consumer. THEORY.md §II.1 invariant 2 —
4379    /// free middle; both consumers route through the SAME composition
4380    /// primitive, so a regression that drifts ONE consumer's pipeline
4381    /// shape from the other cannot reach the substrate's runtime.
4382    ///
4383    /// Frontier inspiration: Racket's `(eval-string str ns)` against a
4384    /// namespace — the from-source-string entry to namespace-level
4385    /// program evaluation is the Racket idiom; the substrate's
4386    /// `expand_source_program` is the Rust-typed peer of that, sourced
4387    /// from `&str` and yielding the post-macroexpansion `Vec<Sexp>`
4388    /// without a typed-keyword filter — exactly the shape an untyped
4389    /// consumer (`tatara-check`'s per-form dispatcher, a REPL's
4390    /// "expand this buffer" command, an LSP's "show me the expanded
4391    /// program" handler) binds to.
4392    pub fn expand_source_program(&mut self, src: &str) -> Result<Vec<Sexp>> {
4393        let forms = crate::reader::read(src)?;
4394        self.expand_program(forms)
4395    }
4396
4397    /// Compose the expander's program-level expansion with the substrate's
4398    /// slice-side typed-keyword projection ([`iter_calls_to`]) and a
4399    /// caller-supplied per-form projection — `expand_program(forms)?` followed
4400    /// by `iter_calls_to(&expanded, keyword).map(project).collect()`, in ONE
4401    /// method on the `Expander` surface. Both [`compile_typed`](crate::compile::compile_typed)
4402    /// and [`compile_named_from_forms`](crate::compile::compile_named_from_forms)
4403    /// route through this primitive — they differ only in the per-form
4404    /// projection `F`: `T::compile_from_args` for the bare-kwargs form
4405    /// (`compile_typed`), and the NAME-then-`T::compile_from_args` split
4406    /// (`compile_named_from_forms`).
4407    ///
4408    /// Before this lift each dispatcher opened the same three-step pipeline
4409    /// inline — `let mut exp = Expander::new(); let expanded =
4410    /// exp.expand_program(forms)?; iter_calls_to(&expanded, T::KEYWORD)
4411    /// .map(<per-site>).collect()` — past the ≥2 PRIME-DIRECTIVE trigger
4412    /// once the structural shape is named. After this lift the pipeline
4413    /// lives in ONE method on the `Expander` surface and the two
4414    /// dispatchers thread their per-site projection through `F`; a
4415    /// regression that drifts ONE dispatcher's pipeline from the other (a
4416    /// future emitter that re-derives `expand_program + iter_calls_to`
4417    /// inline rather than composing through this primitive, a future
4418    /// preloaded-expander consumer that wants the same walk but on its
4419    /// own `Expander` rather than a fresh one) is no longer a silent
4420    /// two-site divergence: the method binds the composition once and
4421    /// every consumer threads the per-site projection through `F`.
4422    ///
4423    /// Method on `Expander` (not a free function) so the primitive
4424    /// composes with the broader expander surface: a preloaded expander
4425    /// (e.g., one that has already absorbed a set of `defmacro` forms
4426    /// via `expand_program` or `with_macros`) can call this method on
4427    /// its own state to walk a follow-up program — the
4428    /// `compiler_spec`'s realize path is the natural future consumer for
4429    /// that shape. `compile_typed` and `compile_named_from_forms`
4430    /// instantiate a fresh `Expander::new()` and dispatch through this
4431    /// method on it; a future preloaded consumer dispatches through it
4432    /// on the preloaded expander directly. ONE primitive, two postures
4433    /// (fresh vs. preloaded), no per-posture duplication of the
4434    /// `expand_program + iter_calls_to + map + collect` pipeline.
4435    ///
4436    /// `F` is `FnMut(&[Sexp]) -> Result<R>` — the per-form projection
4437    /// that fits the consumer's call site. The standard-library
4438    /// `Iterator::map` bound is `FnMut`, so a closure that captures
4439    /// mutable state (a future dispatcher that threads a running index
4440    /// or a borrowed accumulator into the projection) composes
4441    /// naturally. The `Result<R>` projection short-circuits on the
4442    /// first error via `Iterator::collect::<Result<Vec<R>, _>>()`, so
4443    /// the per-form rejection chain (`compile_named_from_forms`'s
4444    /// `NamedFormMissingName` for the missing NAME slot,
4445    /// `NamedFormNonSymbolName` for the non-symbol NAME slot,
4446    /// `T::compile_from_args`'s typed-entry kwargs gate, AND
4447    /// `compile_typed`'s bare-kwargs `T::compile_from_args` rejection)
4448    /// fires in the same order under the new shape.
4449    ///
4450    /// `R` is owned by construction — the iterator's `&[Sexp]` items
4451    /// borrow from the local `expanded: Vec<Sexp>` and that borrow
4452    /// ends when the `.collect()` consumes the iterator, so a `R`
4453    /// that borrowed from `expanded` would fail to compile here. The
4454    /// two production consumers (`Vec<T>`, `Vec<NamedDefinition<T>>`)
4455    /// are both owned-`R` shapes, matching the borrow's structural
4456    /// constraint.
4457    ///
4458    /// Sibling of [`expand_program`](Self::expand_program) — that
4459    /// method names the FIRST step of the pipeline (program-level
4460    /// macroexpansion, yielding `Vec<Sexp>`); this method composes it
4461    /// with the SECOND step (slice-side typed-keyword projection +
4462    /// per-form mapper). The split lets a consumer that wants the
4463    /// expanded forms WITHOUT the keyword filter (`tatara-check`'s
4464    /// per-form dispatcher walks every form, not just matched
4465    /// keywords) bind to `expand_program` directly; a consumer that
4466    /// wants both halves composed binds to this method.
4467    ///
4468    /// Closes the substrate's program-level walk family on the
4469    /// `Expander` surface: `expand_program` (yield-all-forms-after-
4470    /// expansion), `expand` (per-form recursive expansion), `apply`
4471    /// (per-call substitution), AND now `expand_and_collect_calls_to`
4472    /// (typed-keyword projection of the expanded forms with a per-form
4473    /// mapper). Together the four name the canonical surfaces a
4474    /// dispatcher composes with to extract a typed program from a
4475    /// post-expansion form set.
4476    ///
4477    /// Theory anchor: THEORY.md §VI.1 — generation over composition;
4478    /// two inline copies of the `Expander::new() + expand_program +
4479    /// iter_calls_to(_, T::KEYWORD) + map + collect` pipeline across
4480    /// `compile_typed` and `compile_named_from_forms` is past the ≥2
4481    /// PRIME-DIRECTIVE trigger once the structural shape is named.
4482    /// THEORY.md §V.1 — knowable platform; the program-level walk's
4483    /// typed-keyword-projection composition becomes a NAMED primitive
4484    /// on the substrate's `Expander` surface rather than a re-derived
4485    /// three-step inline pipeline at every consumer. Authoring tools
4486    /// (REPL, LSP, `tatara-check`) that want to walk a program by
4487    /// typed keyword bind to ONE method on the `Expander` instead of
4488    /// re-implementing the composition. THEORY.md §II.1 invariant 1 —
4489    /// typed entry; the typed-keyword filter over an expanded program
4490    /// IS the rust-level typed-entry-batch gate, and naming its
4491    /// single shape lifts the gate from two-site duplication to one
4492    /// rust method the substrate's diagnostic promotions hang off of.
4493    /// THEORY.md §II.1 invariant 2 — free middle; both consumers
4494    /// route through the SAME composition primitive, each binding the
4495    /// per-form projection that fits its call site — a regression
4496    /// that drifts ONE dispatcher's pipeline shape from the other
4497    /// cannot reach the substrate's runtime.
4498    ///
4499    /// Frontier inspiration: MLIR's `Region::walk<Op>(callback)` —
4500    /// every typed rewriter that wants "for every Op of kind K in this
4501    /// region, run callback" binds to ONE typed walker that composes
4502    /// the kind filter with the per-op visitor; the substrate's
4503    /// `expand_and_collect_calls_to` is the unstructured-projection
4504    /// peer of that walker, lifted onto the post-expansion `&[Sexp]`
4505    /// algebra with the per-form projection as the visitor. Racket's
4506    /// `syntax-parse` `~seq (keyword args ...) ...` ellipsis-form —
4507    /// the program-level typed-keyword filter with per-match handler
4508    /// is the closed-form sibling of `~seq`'s repeated-pattern
4509    /// matcher, translated through pleme-io primitives as ONE method
4510    /// on `Expander` composing `expand_program` with `iter_calls_to`
4511    /// and a per-form mapper. GHC Core's `everything :: forall b. (b
4512    /// -> b -> b) -> GenericQ b -> GenericQ b` — the typed-IR rewriter's
4513    /// program-level fold over a typed selector is named ONCE and
4514    /// every consumer threads the per-node projection; the substrate's
4515    /// `expand_and_collect_calls_to` is the keyword-projected sibling
4516    /// of that fold on the `&[Sexp]` algebra.
4517    pub fn expand_and_collect_calls_to<R, F>(
4518        &mut self,
4519        forms: Vec<Sexp>,
4520        keyword: &str,
4521        mut project: F,
4522    ) -> Result<Vec<R>>
4523    where
4524        F: FnMut(&[Sexp]) -> Result<R>,
4525    {
4526        // Routes through the typed-decoded classifier sibling
4527        // [`Self::expand_and_collect_calls_to_any`] with a constant-
4528        // classifier decoder — the same constant-classifier composition
4529        // [`crate::ast::iter_calls_to`] uses to route through
4530        // [`crate::ast::iter_calls_to_any`] on the slice algebra. The
4531        // discarded `()` typed witness (`then_some(())`) is consumed by
4532        // the wrapper projection `|(), args| project(args)` so the
4533        // keyword consumer's per-form mapper sees only the args tail,
4534        // matching the pre-lift signature exactly. Both the keyword AND
4535        // classifier expander walks now share ONE pipeline implementation
4536        // (`expand_program + iter_calls_to_any + map + collect`); a
4537        // regression that drifts a future debug-mode logger, span-aware
4538        // borrow walker, or fused-iterator invariant from one expander
4539        // walk to the other becomes structurally impossible.
4540        self.expand_and_collect_calls_to_any(
4541            forms,
4542            |h| (h == keyword).then_some(()),
4543            move |(), args| project(args),
4544        )
4545    }
4546
4547    /// Compose the expander's program-level expansion with the substrate's
4548    /// slice-side typed-decoded classifier projection ([`iter_calls_to_any`])
4549    /// and a caller-supplied per-form projection — `expand_program(forms)?`
4550    /// followed by `iter_calls_to_any(&expanded, decode).map(|(decoded, args)|
4551    /// project(decoded, args)).collect()`, in ONE method on the `Expander`
4552    /// surface. The typed-decoded sibling of
4553    /// [`Self::expand_and_collect_calls_to`] — where that method filters by
4554    /// ONE constant keyword, this method filters AND TYPES by a caller-
4555    /// supplied classifier, yielding the typed witness alongside the per-form
4556    /// projection's args input.
4557    ///
4558    /// Closes the substrate's program-level walk family on the `Expander`
4559    /// surface at the typed-decoded corner the prior runs' slice-side
4560    /// `iter_calls_to_any` lift left open — the (keyword, classifier) 2×2
4561    /// of compose-on-iter projections:
4562    ///
4563    /// |                | per-form algebra            | slice algebra                | expander surface                    |
4564    /// |----------------|-----------------------------|------------------------------|-------------------------------------|
4565    /// | keyword        | [`Sexp::as_call_to`]        | [`iter_calls_to`]            | [`Self::expand_and_collect_calls_to`] |
4566    /// | classifier `F` | [`Sexp::as_call_to_any`]    | [`iter_calls_to_any`]        | `expand_and_collect_calls_to_any` (this) |
4567    ///
4568    /// The keyword corner now ROUTES through the classifier corner at every
4569    /// row: [`Sexp::as_call_to`] = constant-classifier projection of
4570    /// [`Sexp::as_call_to_any`]; [`iter_calls_to`] = constant-classifier
4571    /// projection of [`iter_calls_to_any`]; [`Self::expand_and_collect_calls_to`]
4572    /// = constant-classifier projection of THIS method. The substrate's
4573    /// soft-dispatch family is structurally complete at every algebra
4574    /// level — per-form, slice, AND expander — so a regression that drifts
4575    /// a future debug-mode logger, span-aware borrow walker, or
4576    /// fused-iterator invariant from one walk to its sibling at any algebra
4577    /// level becomes structurally impossible.
4578    ///
4579    /// Two plausible future consumer shapes the typed-decoded expander walk
4580    /// admits with no boilerplate:
4581    ///   * **Closed-set classifier** — `expand_and_collect_calls_to_any(forms,
4582    ///     MacroDefHead::from_keyword, |head, args| dispatch(head, args))`
4583    ///     macroexpands a program and walks each `(defmacro …)` /
4584    ///     `(defpoint-template …)` / `(defcheck …)` form decoded to the typed
4585    ///     `MacroDefHead` enum with its args tail. Future `tatara-check`
4586    ///     consumers that want "for every macro-definition form in this
4587    ///     buffer, dispatch by typed kind" bind to ONE method on the
4588    ///     `Expander` rather than the three-step `expand_program +
4589    ///     iter_calls_to_any + map + collect` pipeline at each consumer site.
4590    ///   * **Live-registry classifier** — `expand_and_collect_calls_to_any(forms,
4591    ///     |h| registry.lookup(h), |handler, args| handler.handle(args))`
4592    ///     macroexpands a program and walks each form whose head matches a
4593    ///     runtime registry, decoded to a handler reference. Future
4594    ///     `tatara-check`-shaped runtime dispatchers (the kind already named
4595    ///     by [`iter_calls_to_any`]'s docstring as "the natural future
4596    ///     consumer") bind to ONE method on the `Expander` rather than the
4597    ///     three-step pipeline.
4598    ///
4599    /// The closed-form composition binding the keyword sibling to this typed-
4600    /// decoded primitive is the structural identity every consumer can pin
4601    /// against:
4602    ///
4603    /// ```ignore
4604    /// expand_and_collect_calls_to(forms, k, p) ==
4605    ///     expand_and_collect_calls_to_any(forms,
4606    ///         |h| (h == k).then_some(()),
4607    ///         |(), args| p(args))
4608    /// ```
4609    ///
4610    /// `D` is `FnMut(&str) -> Option<T>` — the typed-decoded classifier the
4611    /// substrate's per-form algebra already shapes ([`Sexp::as_call_to_any`]
4612    /// uses `FnOnce`, the slice algebra and this method use `FnMut` for the
4613    /// same reason: the walk calls the decoder once per matching form, and a
4614    /// decoder that captures mutable state (a counter, a registry cache, a
4615    /// visited-set) maintains that state across the program-level walk).
4616    /// `F` is `FnMut(T, &[Sexp]) -> Result<R>` — the per-form projection that
4617    /// receives BOTH the typed witness AND the args tail; the keyword sibling
4618    /// discards the (unit) typed witness via `|(), args| project(args)`. The
4619    /// `Result<R>` projection short-circuits on the first error via
4620    /// `Iterator::collect::<Result<Vec<R>, _>>()` so the per-form rejection
4621    /// chain fires in source order at the first failing matched form.
4622    ///
4623    /// `R` is owned by construction — same constraint as the keyword
4624    /// sibling. `T` is owned because the underlying [`iter_calls_to_any`]
4625    /// requires `T: 'a` where `'a` is the post-expansion `Vec<Sexp>` borrow
4626    /// lifetime; consumers projecting to a typed `Copy` enum
4627    /// (`MacroDefHead`, a future `AuthoringDirective`) get the value
4628    /// directly per form, consumers projecting to a borrowed `&'static str`
4629    /// (a closed-set head) project to `&'static str` and inherit the static
4630    /// lifetime through the classifier, consumers projecting to a `&Handler`
4631    /// (a live-registry classifier) bind through the registry's owned
4632    /// borrow with `&self` outliving the walk.
4633    ///
4634    /// Theory anchor: THEORY.md §VI.1 — generation over composition; the
4635    /// keyword sibling [`Self::expand_and_collect_calls_to`] is now a
4636    /// CONSEQUENCE of this typed-decoded primitive + a constant-classifier
4637    /// decoder, parallel to how [`iter_calls_to`] is a consequence of
4638    /// [`iter_calls_to_any`] on the slice algebra. The substrate's
4639    /// program-level walk family is no longer two parallel implementations
4640    /// the type system fails to bind (a future regression that drifts the
4641    /// keyword pipeline's instrumentation from the classifier pipeline's
4642    /// instrumentation cannot reach the runtime — both compose through ONE
4643    /// `expand_program + iter_calls_to_any + map + collect` body, with the
4644    /// keyword filter expressed as a closed-form classifier). THEORY.md
4645    /// §V.1 — knowable platform; the typed-decoded expander walk becomes a
4646    /// NAMED primitive on the substrate's `Expander` surface rather than a
4647    /// future re-derived three-step inline pipeline per consumer.
4648    /// Authoring tools (REPL, LSP, `tatara-check`) that want to walk a
4649    /// program by typed-decoded classifier bind to ONE method on the
4650    /// `Expander` instead of re-implementing the composition. THEORY.md
4651    /// §II.1 invariant 2 — free middle; both expander-surface dispatchers
4652    /// (keyword + classifier) route through the SAME `expand_program +
4653    /// iter_calls_to_any + map + collect` composition primitive.
4654    ///
4655    /// Frontier inspiration: MLIR's `Region::walk<OpInterface,
4656    /// OpInterface2, …>([&](auto op) { … })` — the typed-IR walk over a
4657    /// region yielding ops decoded to their typed interface witness with
4658    /// per-op callback is the MLIR idiom; the substrate's
4659    /// `expand_and_collect_calls_to_any` is the unstructured-Rust peer on
4660    /// the post-expansion `&[Sexp]` algebra, with `decode: FnMut(&str) ->
4661    /// Option<T>` standing in for MLIR's typed-interface dyn-cast bag and
4662    /// `project: FnMut(T, &[Sexp]) -> Result<R>` standing in for the
4663    /// typed-op callback. Racket's `syntax-parse` `~or* (~datum defX)
4664    /// (~datum defY) (head args …)` over an ellipsis-form combined with a
4665    /// `#:with name:id` typed-witness binder — the program-level
4666    /// typed-decoded filter with per-match handler is the closed-form
4667    /// sibling of `syntax-parse`'s typed-choice repeater, translated
4668    /// through pleme-io primitives as ONE method on `Expander`. GHC Core's
4669    /// `everythingBut :: (a -> a -> a) -> GenericQ (a, Bool) -> GenericQ a`
4670    /// — the typed-IR rewriter's program-level fold over a typed
4671    /// kind-with-stop selector is named ONCE and every consumer threads
4672    /// the per-node typed projection; this method is the typed-decoded
4673    /// peer on the `Vec<Sexp>` algebra, with the classifier playing
4674    /// `everythingBut`'s typed-selector role.
4675    pub fn expand_and_collect_calls_to_any<R, F, D, T>(
4676        &mut self,
4677        forms: Vec<Sexp>,
4678        decode: D,
4679        mut project: F,
4680    ) -> Result<Vec<R>>
4681    where
4682        D: FnMut(&str) -> Option<T>,
4683        F: FnMut(T, &[Sexp]) -> Result<R>,
4684    {
4685        let expanded = self.expand_program(forms)?;
4686        crate::ast::iter_calls_to_any(&expanded, decode)
4687            .map(|(decoded, args)| project(decoded, args))
4688            .collect()
4689    }
4690
4691    /// Read a source string into top-level forms via [`crate::reader::read`],
4692    /// then route the forms through
4693    /// [`expand_and_collect_calls_to`](Self::expand_and_collect_calls_to) — the
4694    /// from-source posture of the program-level walk, in ONE method on the
4695    /// `Expander` surface.
4696    ///
4697    /// Before this lift the same two-step chain — `let forms = read(src)?;
4698    /// <expander>.expand_and_collect_calls_to(forms, keyword, project)` — lived
4699    /// inline at four sites: the two free-function typed dispatchers
4700    /// ([`compile_typed`](crate::compile::compile_typed) and
4701    /// [`compile_named`](crate::compile::compile_named) via
4702    /// [`compile_named_from_forms`](crate::compile::compile_named_from_forms))
4703    /// AND the two preloaded-expander methods on
4704    /// [`RealizedCompiler`](crate::compiler_spec::RealizedCompiler)
4705    /// (`compile_typed`, `compile_named`). After this lift the read-then-walk
4706    /// composition lives in ONE method on the `Expander`; each of the four
4707    /// dispatchers binds it with the per-site `(expander posture, projection)`
4708    /// pair that fits its call boundary — `Expander::new()` for the
4709    /// fresh-expander dispatchers, `self.preloaded.clone()` for the
4710    /// preloaded-expander dispatchers; `T::compile_from_args` for the
4711    /// bare-kwargs projection, `named_form_projection::<T>` for the
4712    /// NAME-then-kwargs projection.
4713    ///
4714    /// Sibling of [`expand_and_collect_calls_to`](Self::expand_and_collect_calls_to)
4715    /// — that method takes a pre-parsed `Vec<Sexp>` (the from-forms posture,
4716    /// for callers that have already read or that compose with another
4717    /// `Sexp`-producing surface like a macro-expanded subform); this method
4718    /// takes a `&str` (the from-source posture, for callers consuming
4719    /// authoring-surface source text directly). Both compose with the same
4720    /// `expand_program + iter_calls_to + map + collect` pipeline through ONE
4721    /// from-forms primitive — the from-source posture stacks one read step on
4722    /// top, projecting `crate::reader::read`'s `Result<Vec<Sexp>>` into the
4723    /// from-forms primitive via `?`.
4724    ///
4725    /// `?`-routing through `read` preserves the structural ordering of the
4726    /// rejection chain end-to-end: a reader error (lexer / parser /
4727    /// unbalanced-paren / unterminated-string) short-circuits BEFORE
4728    /// `expand_program` runs; an `expand_program` error
4729    /// (`defmacro`-NAME-not-a-symbol, `OptionalParamMalformed`,
4730    /// `RestParamMissingName`, et al.) short-circuits BEFORE the keyword
4731    /// filter walks anything; a per-form `project` error
4732    /// (`NamedFormMissingName`, `NamedFormNonSymbolName`,
4733    /// `T::compile_from_args`'s typed-entry kwargs gate) short-circuits at the
4734    /// first failing matched form via `Iterator::collect::<Result<Vec<R>, _>>()`.
4735    /// Each dispatcher's rejection chain remains exactly what it was pre-lift,
4736    /// now sourced from ONE composition point rather than four.
4737    ///
4738    /// Closes the program-level walk family on the `Expander` surface across
4739    /// BOTH the from-forms posture
4740    /// ([`expand_and_collect_calls_to`](Self::expand_and_collect_calls_to))
4741    /// AND the from-source posture (this method) — together with
4742    /// `expand_program` (yield-all-forms-after-expansion), `expand` (per-form
4743    /// recursive expansion), and `apply` (per-call substitution), they name
4744    /// the canonical surfaces a dispatcher composes with to extract a typed
4745    /// program from a post-expansion form set. A future dispatcher that wants
4746    /// "read this source, then walk every call to keyword K and project each
4747    /// to R" — a debug-mode REPL command, an LSP "find all typed-domain
4748    /// definitions in this buffer" handler, a `tatara-check` command that
4749    /// dispatches each typed `(defX …)` form in `checks.lisp` to its
4750    /// registered domain — binds to ONE method on the `Expander` instead of
4751    /// re-deriving the two-step `read + expand_and_collect_calls_to` chain.
4752    ///
4753    /// Theory anchor: THEORY.md §VI.1 — generation over composition; four
4754    /// inline copies of the `read(src)? + <expander>.expand_and_collect_calls_to`
4755    /// chain across the two fresh-expander dispatchers and the two
4756    /// preloaded-expander dispatchers is past the ≥2 PRIME-DIRECTIVE trigger
4757    /// once the structural shape is named. THEORY.md §V.1 — knowable
4758    /// platform; the read-then-walk composition becomes a NAMED primitive on
4759    /// the substrate's `Expander` surface rather than a re-derived two-step
4760    /// inline pipeline at every dispatcher. THEORY.md §II.1 invariant 2 —
4761    /// free middle; all four dispatchers route through the SAME composition
4762    /// primitive, so a regression that drifts ONE dispatcher's read-then-walk
4763    /// pipeline from the others cannot reach the substrate's runtime — the
4764    /// type system binds all consumers to the from-source primitive's single
4765    /// emission shape.
4766    ///
4767    /// Frontier inspiration: Racket's `(eval-string str ns)` against a
4768    /// namespace populated with the preloaded compiler's `require`d macros —
4769    /// the from-source-string entry to typed-program evaluation inside a
4770    /// namespace that carries the macro library is the Racket idiom; the
4771    /// substrate's `expand_source_and_collect_calls_to` is the Rust-typed
4772    /// peer of that, with the typed-keyword projection composed in.
4773    ///
4774    /// Post-lift the body routes through the typed-decoded sibling
4775    /// [`Self::expand_source_and_collect_calls_to_any`] with a
4776    /// constant-classifier decoder — the same constant-classifier
4777    /// composition [`Self::expand_and_collect_calls_to`] uses to route
4778    /// through [`Self::expand_and_collect_calls_to_any`] on the
4779    /// from-forms axis, and that [`crate::ast::iter_calls_to`] uses to
4780    /// route through [`crate::ast::iter_calls_to_any`] on the slice
4781    /// algebra. The discarded `()` typed witness (`then_some(())`) is
4782    /// consumed by the wrapper projection `|(), args| project(args)`
4783    /// so the keyword consumer's per-form mapper sees only the args
4784    /// tail. Both the keyword AND classifier from-source walks now
4785    /// share ONE pipeline implementation (`read + expand_program +
4786    /// iter_calls_to_any + map + collect`) — a regression that drifts
4787    /// a future debug-mode logger, span-aware borrow walker, or
4788    /// fused-iterator invariant from one from-source walk to the
4789    /// other becomes structurally impossible.
4790    pub fn expand_source_and_collect_calls_to<R, F>(
4791        &mut self,
4792        src: &str,
4793        keyword: &str,
4794        mut project: F,
4795    ) -> Result<Vec<R>>
4796    where
4797        F: FnMut(&[Sexp]) -> Result<R>,
4798    {
4799        self.expand_source_and_collect_calls_to_any(
4800            src,
4801            |h| (h == keyword).then_some(()),
4802            move |(), args| project(args),
4803        )
4804    }
4805
4806    /// Read a source string into top-level forms via [`crate::reader::read`],
4807    /// then route the forms through
4808    /// [`expand_and_collect_calls_to_any`](Self::expand_and_collect_calls_to_any) —
4809    /// the from-source posture of the typed-decoded classifier walk, in
4810    /// ONE method on the `Expander` surface. The typed-decoded sibling of
4811    /// [`Self::expand_source_and_collect_calls_to`] — where that method
4812    /// filters by ONE constant keyword, this method filters AND TYPES by
4813    /// a caller-supplied classifier, yielding the typed witness alongside
4814    /// the per-form projection's args input. Sourced from `&str` rather
4815    /// than a `Vec<Sexp>`.
4816    ///
4817    /// Closes the substrate's program-level walk family on the `Expander`
4818    /// surface across BOTH input postures × BOTH projection forms — the
4819    /// (from-forms, from-source) × (keyword, classifier) 2×2 of
4820    /// compose-on-iter projections:
4821    ///
4822    /// |                | from-forms (`Vec<Sexp>`)                                      | from-source (`&str`)                                                |
4823    /// |----------------|---------------------------------------------------------------|---------------------------------------------------------------------|
4824    /// | keyword        | [`Self::expand_and_collect_calls_to`]                         | [`Self::expand_source_and_collect_calls_to`]                        |
4825    /// | classifier `F` | [`Self::expand_and_collect_calls_to_any`]                     | `expand_source_and_collect_calls_to_any` (this)                     |
4826    ///
4827    /// Each row's keyword corner now ROUTES through the classifier corner
4828    /// at every column: [`Self::expand_source_and_collect_calls_to`]'s
4829    /// body composes this typed-decoded primitive with a
4830    /// `|h| (h == keyword).then_some(())` decoder and the wrapper
4831    /// projection `|(), args| project(args)`, parallel to how
4832    /// [`Self::expand_and_collect_calls_to`] routes through
4833    /// [`Self::expand_and_collect_calls_to_any`] on the from-forms axis
4834    /// and [`crate::ast::iter_calls_to`] routes through
4835    /// [`crate::ast::iter_calls_to_any`] on the slice algebra. The 2×2
4836    /// reduces to ONE pipeline implementation
4837    /// (`read + expand_program + iter_calls_to_any + map + collect`)
4838    /// every consumer threads its `(decoder, project)` pair through.
4839    ///
4840    /// Future consumer shapes the typed-decoded from-source primitive
4841    /// admits:
4842    ///   * **Closed-set classifier** — `expand_source_and_collect_calls_to_any(
4843    ///     src, MacroDefHead::from_keyword, |head, args| { … })` walks a
4844    ///     source buffer dispatching every `(defmacro …)` / `(defpoint-template …)` /
4845    ///     `(defcheck …)` form to its typed `MacroDefHead` arm — exactly the
4846    ///     shape a future LSP "find every macro-definition form in this
4847    ///     buffer, decode each by typed kind" handler reaches for, sourced
4848    ///     from authoring text directly rather than a pre-parsed
4849    ///     `Vec<Sexp>`.
4850    ///   * **Live-registry classifier** — `expand_source_and_collect_calls_to_any(
4851    ///     src, |h| registry.get(h), |handler, args| handler.compile(args))`
4852    ///     walks a source buffer dispatching every form whose head is
4853    ///     registered to its typed handler — exactly the shape
4854    ///     `tatara-check`'s "macroexpand checks.lisp and dispatch every
4855    ///     `(defX …)` form through the registered domain dispatcher"
4856    ///     walker reaches for, sourced from disk-loaded source text
4857    ///     rather than the round-tripped `Vec<Sexp>` the typed
4858    ///     dispatchers consume.
4859    ///
4860    /// `?`-routing through `read` preserves the structural ordering of
4861    /// the rejection chain end-to-end: a reader error (lexer / parser /
4862    /// unbalanced-paren / unterminated-string) short-circuits BEFORE
4863    /// `expand_program` runs; an `expand_program` error short-circuits
4864    /// BEFORE the classifier filter walks anything; a per-form `project`
4865    /// error short-circuits at the first failing matched form via
4866    /// `Iterator::collect::<Result<Vec<R>, _>>()`. Each consumer's
4867    /// rejection chain inherits the from-forms typed-decoded primitive's
4868    /// shape verbatim, now sourced from `&str` via ONE composition point.
4869    ///
4870    /// Theory anchor: THEORY.md §VI.1 — generation over composition;
4871    /// the (from-forms, from-source) × (keyword, classifier) 2×2 closes
4872    /// at the from-source classifier corner this method establishes —
4873    /// the prior runs' from-forms classifier sibling
4874    /// (`expand_and_collect_calls_to_any`, run e47d043) AND from-source
4875    /// keyword sibling (`expand_source_and_collect_calls_to`, run
4876    /// b477d81) AND slice-side classifier sibling (`iter_calls_to_any`,
4877    /// run 38625e3) named three of the four corners; this lift names
4878    /// the fourth so the family is structurally complete at every
4879    /// algebra level. THEORY.md §V.1 — knowable platform; the
4880    /// read-then-classifier-walk composition becomes a NAMED primitive
4881    /// on the substrate's `Expander` surface rather than a future
4882    /// re-derived `read(src)? + expand_and_collect_calls_to_any(…)`
4883    /// two-step chain at every consumer that wants to walk source text
4884    /// by typed-decoded classifier. THEORY.md §II.1 invariant 2 — free
4885    /// middle; the from-source keyword filter
4886    /// (`expand_source_and_collect_calls_to`) now routes through the
4887    /// from-source classifier filter via the constant-classifier
4888    /// composition, so a regression that drifts the keyword filter's
4889    /// instrumentation from the classifier filter's instrumentation
4890    /// becomes structurally impossible. The pre-lift inline
4891    /// `read(src)? + expand_and_collect_calls_to(forms, keyword, project)`
4892    /// chain is now a TYPED CONSEQUENCE of the typed-decoded primitive
4893    /// composed with a constant-classifier decoder, not a parallel
4894    /// implementation the type system happens to not catch.
4895    ///
4896    /// Frontier inspiration: Racket's `(eval-string str ns)` against a
4897    /// namespace + typed-syntax-class dispatch — the from-source-string
4898    /// entry to typed-program evaluation inside a namespace that carries
4899    /// the macro library, dispatching by typed syntax-class is the
4900    /// Racket idiom; the substrate's
4901    /// `expand_source_and_collect_calls_to_any` is the Rust-typed peer
4902    /// of that, with the typed-decoded classifier composed in. MLIR's
4903    /// `parseSourceFile<Op>(srcFile, ctx)` then `mod.walk<OpInterface>(
4904    /// callback)` — the parse-then-typed-walk over a source buffer
4905    /// dispatching by typed-interface witness is the MLIR idiom; this
4906    /// method is the substrate's typed `&[Sexp]`-algebra peer.
4907    pub fn expand_source_and_collect_calls_to_any<R, F, D, T>(
4908        &mut self,
4909        src: &str,
4910        decode: D,
4911        project: F,
4912    ) -> Result<Vec<R>>
4913    where
4914        D: FnMut(&str) -> Option<T>,
4915        F: FnMut(T, &[Sexp]) -> Result<R>,
4916    {
4917        let forms = crate::reader::read(src)?;
4918        self.expand_and_collect_calls_to_any(forms, decode, project)
4919    }
4920
4921    /// Compose the expander's program-level expansion with the substrate's
4922    /// typed-decoded classifier walk AND the named-form NAME-shape gate
4923    /// ([`crate::compile::split_name_slot`]) — the from-forms posture of
4924    /// the (named NAME-then-kwargs × typed-decoded classifier) cell of
4925    /// the typed-dispatcher matrix on the `Expander` surface, sibling of
4926    /// [`Self::expand_to_named`] (the constant-`T::KEYWORD` × named cell)
4927    /// and of [`Self::expand_and_collect_calls_to_any`] (the from-forms
4928    /// classifier × bare-kwargs cell).
4929    ///
4930    /// Routes through [`Self::expand_and_collect_calls_to_any`] with a
4931    /// wrapper projection that composes [`crate::compile::split_name_slot`]
4932    /// (the named-form arity + NAME-shape gate on the substrate's
4933    /// `&[Sexp]` algebra) with the caller-supplied per-form projection.
4934    /// The decoder yields `Option<(T, &'static str)>` — the typed witness
4935    /// PAIRED with the canonical static keyword the named-form structural
4936    /// rejection variants ([`crate::error::LispError::NamedFormMissingName`],
4937    /// [`crate::error::LispError::NamedFormNonSymbolName`]) carry as
4938    /// `&'static str` slots. Threading the `&'static` constraint through
4939    /// the decoder's return type pins the same compile-time discipline
4940    /// [`crate::compile::split_name_slot`]'s `keyword: &'static str`
4941    /// parameter pins at the gate boundary — a typo in the canonical
4942    /// keyword can never drift into the diagnostic at runtime, same posture
4943    /// as the constant-`T::KEYWORD` cell where `T::KEYWORD` is
4944    /// `&'static str` by trait construction.
4945    ///
4946    /// Closes the (named × typed-decoded classifier) corner of the
4947    /// typed-dispatcher matrix on the `Expander` surface that the prior
4948    /// run's [`crate::compile::split_name_slot`] lift (dd50801) named as
4949    /// the future change the slice-side gate's named lift enables. The
4950    /// 2×2 of compose-on-iter projections on the named-form axis becomes
4951    /// structurally complete on the `Expander` surface:
4952    ///
4953    /// |                | constant `T::KEYWORD`               | typed-decoded classifier                     |
4954    /// |----------------|-------------------------------------|----------------------------------------------|
4955    /// | from-forms     | [`Self::expand_to_named`]           | `expand_and_collect_named_calls_to_any` (this) |
4956    /// | from-source    | [`Self::expand_source_to_named`]    | [`Self::expand_source_and_collect_named_calls_to_any`] |
4957    ///
4958    /// The constant-`T::KEYWORD` column is the typed CONSEQUENCE of the
4959    /// classifier column: an `expand_to_named::<T>(forms)` call composes
4960    ///
4961    /// ```ignore
4962    /// expand_and_collect_named_calls_to_any(forms,
4963    ///     |h| (h == T::KEYWORD).then_some(((), T::KEYWORD)),
4964    ///     |(), name, spec_args| {
4965    ///         let spec = T::compile_from_args(spec_args)?;
4966    ///         Ok(NamedDefinition { name: name.to_string(), spec })
4967    ///     })
4968    /// ```
4969    ///
4970    /// Both columns route through ONE composition point on the
4971    /// `Expander` surface (the typed-decoded classifier walk inside
4972    /// [`Self::expand_and_collect_calls_to_any`]) and ONE gate body on
4973    /// the `&[Sexp]` algebra ([`crate::compile::split_name_slot`]). A
4974    /// regression that drifts ONE cell's NAME-slot rejection chain from
4975    /// the others is structurally impossible — every cell binds to the
4976    /// SAME `split_name_slot` body, the SAME `expand_program +
4977    /// iter_calls_to_any + map + collect` pipeline. The
4978    /// pre-lift `NamedFormMissingName` / `NamedFormNonSymbolName`
4979    /// rejection chain inside `compile::named_form_projection` (the
4980    /// `pub(crate)` typed-domain SPECIALIZATION that
4981    /// [`Self::expand_to_named`] routes through) fires identically
4982    /// through this classifier primitive — both consumers compose
4983    /// [`crate::compile::split_name_slot`] with their per-site typed
4984    /// continuation.
4985    ///
4986    /// Future consumer shapes the typed-decoded named-form primitive
4987    /// admits with no boilerplate:
4988    ///   * **Closed-set classifier** — a `tatara-check` runner that
4989    ///     dispatches every `(defmonitor NAME …)` / `(defnotify NAME …)`
4990    ///     / `(defalertpolicy NAME …)` form in `checks.lisp` through ONE
4991    ///     classifier (a closed-set enum `Domain::{Monitor, Notify,
4992    ///     AlertPolicy}` with `Domain::from_keyword`) decoding each
4993    ///     head to its typed kind PAIRED with its canonical static
4994    ///     keyword, then dispatches in the per-form projection — binds
4995    ///     to ONE method rather than two-step `expand_and_collect_calls_to_any
4996    ///     + split_name_slot` composition at each consumer site.
4997    ///   * **Live-registry classifier** — a future `tatara-check`-shaped
4998    ///     runtime dispatcher: `expand_and_collect_named_calls_to_any(forms,
4999    ///     |h| registry.get(h).map(|d| (d, d.keyword())), |dispatcher,
5000    ///     name, args| dispatcher.compile(name, args))` walks a program
5001    ///     dispatching every named form whose head is in a runtime
5002    ///     registry, decoded to a handler reference AND its canonical
5003    ///     keyword (sourced from the dispatcher itself, NOT from the
5004    ///     matched head text). The `&'static str` discipline on the
5005    ///     keyword slot is preserved through the dispatcher's own
5006    ///     `keyword() -> &'static str` accessor.
5007    ///   * **`compile_named_any` free function family** — the natural
5008    ///     fresh-expander free-function + preloaded-`RealizedCompiler`
5009    ///     consumer pair, sibling of the existing `compile_typed_any`
5010    ///     / `RealizedCompiler::compile_typed_any` pair. A future run
5011    ///     lands those as 3-line composes on top of this primitive.
5012    ///
5013    /// `D` is `FnMut(&str) -> Option<(T, &'static str)>` — the typed-
5014    /// decoded classifier yields a typed witness `T` AND its canonical
5015    /// static keyword for the gate. `F` is `FnMut(T, &str, &[Sexp]) ->
5016    /// Result<R>` — the per-form projection receives the typed witness
5017    /// `T` ALONGSIDE the NAME slot's BORROWED `&str` projection (sourced
5018    /// from [`Sexp::as_symbol_or_string`], which accepts BOTH symbol and
5019    /// string NAME-author surfaces) AND the spec args tail. Consumers
5020    /// that need owned ownership of the NAME (`NamedDefinition.name:
5021    /// String`, JSON-serialized payloads) `.to_string()` themselves —
5022    /// pushing the clone to the consumer boundary keeps the primitive
5023    /// allocation-free. The `Result<R>` projection short-circuits on
5024    /// the first error via `Iterator::collect::<Result<Vec<R>, _>>()`
5025    /// inside [`Self::expand_and_collect_calls_to_any`].
5026    ///
5027    /// Theory anchor: THEORY.md §VI.1 — generation over composition;
5028    /// the (named × typed-decoded classifier) cell becomes a NAMED
5029    /// primitive on the `Expander` surface composed FROM TWO substrate
5030    /// primitives ([`Self::expand_and_collect_calls_to_any`] +
5031    /// [`crate::compile::split_name_slot`]) rather than re-derived
5032    /// inline at every named-classifier consumer's call site.
5033    /// THEORY.md §II.1 invariant 1 — typed entry; the typed-decoded
5034    /// classifier-filtered + NAME-shape-gated + caller-projected walk
5035    /// IS the typed-entry-batch gate for named-form dispatch at the
5036    /// `Expander` surface, with the `&'static str` keyword discipline
5037    /// preserved through the decoder's return type. THEORY.md §II.1
5038    /// invariant 2 — free middle; ALL four cells of the named-form
5039    /// dispatcher matrix on the `Expander` surface route through ONE
5040    /// composition point — a regression that drifts ONE cell's
5041    /// instrumentation from the others is structurally impossible.
5042    /// THEORY.md §V.1 — knowable platform; the named-form classifier
5043    /// walk becomes a discoverable primitive that LSP / REPL /
5044    /// `tatara-check` consumers bind to ONE method on the `Expander`
5045    /// instead of re-implementing the two-step `expand_and_collect_calls_to_any
5046    /// + split_name_slot` composition.
5047    ///
5048    /// Frontier inspiration: MLIR's `Region::walk<NamedOpInterface>(
5049    /// [&](auto op) { auto name = op.getName(); … })` — the typed-IR
5050    /// walk over a region yielding ops decoded to their typed interface
5051    /// witness with the named-symbol accessor pre-extracted is the
5052    /// MLIR idiom for named typed dispatch; this method is the
5053    /// substrate's unstructured-Rust peer with the typed-decoded
5054    /// classifier composed in and the NAME slot extracted via the
5055    /// substrate's `split_name_slot` gate. Racket's `syntax-parse`
5056    /// `~or* ((~datum defX) name:id arg ...) ((~datum defY) name:id
5057    /// arg ...) ((~datum defZ) name:id arg ...)` over an ellipsis-form
5058    /// — the typed-choice repeater with named-slot binder PAIRED with
5059    /// per-arm dispatch — is the Racket idiom; this primitive is the
5060    /// substrate's Rust-typed peer with the closed-set classifier
5061    /// playing the `~or*` typed-choice role and the NAME slot extracted
5062    /// by the substrate's slice-side gate.
5063    pub fn expand_and_collect_named_calls_to_any<R, F, D, T>(
5064        &mut self,
5065        forms: Vec<Sexp>,
5066        decode: D,
5067        mut project: F,
5068    ) -> Result<Vec<R>>
5069    where
5070        D: FnMut(&str) -> Option<(T, &'static str)>,
5071        F: FnMut(T, &str, &[Sexp]) -> Result<R>,
5072    {
5073        // Routes through the slice-side typed-decoded named projection
5074        // [`crate::ast::iter_named_calls_to_any`] — the same
5075        // `expand_program + iter_*_to_any + map + collect` shape
5076        // [`Self::expand_and_collect_calls_to_any`] uses on the bare-kwargs
5077        // axis. Pre-lift this method routed through the bare expander
5078        // surface and welded [`crate::compile::split_name_slot`] inside
5079        // the projection closure; post-lift the named gate composition
5080        // lives at the slice level (`iter_named_calls_to_any`'s body)
5081        // and the Expander surface inherits it through delegation. Both
5082        // rows of the Expander surface (bare-kwargs, named) now share
5083        // ONE pipeline skeleton on the slice algebra — a regression that
5084        // drifts a future debug-mode logger, span-aware borrow walker,
5085        // or fused-iterator invariant from one row to the other becomes
5086        // structurally impossible at the slice boundary.
5087        let expanded = self.expand_program(forms)?;
5088        crate::ast::iter_named_calls_to_any(&expanded, decode)
5089            .map(|maybe_triple| {
5090                let (decoded, name, spec_args) = maybe_triple?;
5091                project(decoded, name, spec_args)
5092            })
5093            .collect()
5094    }
5095
5096    /// Read a source string into top-level forms via [`crate::reader::read`],
5097    /// then route the forms through
5098    /// [`Self::expand_and_collect_named_calls_to_any`] — the from-source
5099    /// posture of the (named × typed-decoded classifier) cell on the
5100    /// `Expander` surface, sibling of
5101    /// [`Self::expand_source_and_collect_calls_to_any`] (the from-source
5102    /// classifier × bare-kwargs cell).
5103    ///
5104    /// Closes the (from-forms, from-source) × (constant `T::KEYWORD`,
5105    /// typed-decoded classifier) 2×2 on the named-form axis of the
5106    /// `Expander` surface — together with [`Self::expand_to_named`],
5107    /// [`Self::expand_source_to_named`], and
5108    /// [`Self::expand_and_collect_named_calls_to_any`], every cell of the
5109    /// matrix binds to ONE composition point.
5110    ///
5111    /// `?`-routing through `read` preserves the structural ordering of
5112    /// the rejection chain end-to-end: a reader error (lexer / parser /
5113    /// unbalanced-paren / unterminated-string) short-circuits BEFORE
5114    /// `expand_program` runs; an `expand_program` error short-circuits
5115    /// BEFORE the classifier filter walks anything; the named-form gate
5116    /// (`split_first()` arity → `as_symbol_or_string()` shape) inside
5117    /// [`crate::compile::split_name_slot`] fires for the first matched
5118    /// form that violates either condition; a per-form `project` error
5119    /// short-circuits at the first failing matched form via
5120    /// `Iterator::collect::<Result<Vec<R>, _>>()`.
5121    ///
5122    /// Theory anchor: same as [`Self::expand_and_collect_named_calls_to_any`].
5123    /// THEORY.md §VI.1 (generation over composition; the from-source
5124    /// posture inherits the named-form gate composition through
5125    /// delegation rather than re-deriving the `read(src)? + walk + gate`
5126    /// chain at every from-source named-classifier consumer's call
5127    /// site), THEORY.md §II.1 invariant 2 (free middle; the from-source
5128    /// posture and the from-forms posture route through the SAME named-
5129    /// form composition).
5130    pub fn expand_source_and_collect_named_calls_to_any<R, F, D, T>(
5131        &mut self,
5132        src: &str,
5133        decode: D,
5134        project: F,
5135    ) -> Result<Vec<R>>
5136    where
5137        D: FnMut(&str) -> Option<(T, &'static str)>,
5138        F: FnMut(T, &str, &[Sexp]) -> Result<R>,
5139    {
5140        let forms = crate::reader::read(src)?;
5141        self.expand_and_collect_named_calls_to_any(forms, decode, project)
5142    }
5143
5144    /// Macroexpand a pre-parsed program through `self` and project every
5145    /// `(keyword NAME :k v …)` form into `R` via a caller-supplied
5146    /// `(name, args) -> Result<R>` projection — the constant-keyword
5147    /// sibling of [`Self::expand_and_collect_named_calls_to_any`] on the
5148    /// named-form axis of the `Expander` surface, parallel to how
5149    /// [`Self::expand_and_collect_calls_to`] is the constant-keyword
5150    /// sibling of [`Self::expand_and_collect_calls_to_any`] on the
5151    /// bare-kwargs axis.
5152    ///
5153    /// Routes through the classifier sibling with a constant-classifier
5154    /// decoder that yields a discarded `()` typed witness paired with
5155    /// the `&'static str` keyword — the same `(T, &'static str)` decoder
5156    /// shape [`crate::compile::split_name_slot`] (composed inside the
5157    /// classifier primitive) pins at the slice-side gate boundary for
5158    /// the named-form structural rejection chain
5159    /// (`NamedFormMissingName.keyword`, `NamedFormNonSymbolName.keyword`).
5160    /// `&'static` is the same lifetime discipline the typed-decoded
5161    /// classifier signature enforces; the named gate's verbatim keyword
5162    /// threading inherits it through the constant-classifier composition.
5163    ///
5164    /// Projection signature `FnMut(&str, &[Sexp]) -> Result<R>` receives
5165    /// the BORROWED NAME slot and the BORROWED spec args tail. Consumers
5166    /// that need owned ownership (`NamedDefinition.name: String`) call
5167    /// `.to_string()` themselves — pushing the clone to the consumer
5168    /// boundary keeps the primitive allocation-free, matching the
5169    /// classifier sibling's projection signature on the NAME slot.
5170    ///
5171    /// Closes the (named NAME-then-kwargs × constant-keyword) cell of
5172    /// the `Expander` typed-walk family at the runtime-keyword × untyped
5173    /// `R` corner — pre-lift the cell was reachable ONLY through
5174    /// [`Self::expand_to_named`] with the `T: TataraDomain` type
5175    /// parameter baking `T::KEYWORD` AND the `T::compile_from_args`-
5176    /// based `crate::compile::named_form_projection<T>` projection
5177    /// into the dispatch through `expand_and_collect_calls_to(forms,
5178    /// T::KEYWORD, named_form_projection::<T>)`. Post-lift the cell
5179    /// surfaces as ONE method that takes the keyword and the
5180    /// `(name, args) -> R` projection as caller-supplied parameters,
5181    /// and [`Self::expand_to_named`] routes through it as the typed
5182    /// `T::KEYWORD`-constant specialization. The split lets a consumer
5183    /// that wants "walk every form whose head is a runtime keyword `kw`
5184    /// and project `(NAME, spec_args) -> R` for an arbitrary `R`" (a
5185    /// future REPL `:walk-named <kw>` command, an LSP "find-named-
5186    /// declarations-of-keyword `kw`" handler, a `tatara-check` runner
5187    /// dispatching on a single typed keyword whose projection isn't
5188    /// `T::compile_from_args`) bind to ONE method on the `Expander`
5189    /// surface rather than re-deriving the
5190    /// `expand_and_collect_named_calls_to_any(forms, |h| (h ==
5191    /// kw).then_some(((), kw)), |(), name, args| project(name, args))`
5192    /// two-line composition inline.
5193    ///
5194    /// Sibling of [`Self::expand_source_and_collect_named_calls_to`]
5195    /// — that method stacks a [`crate::reader::read`] step on top of
5196    /// this one (the from-source posture); this method takes a
5197    /// pre-parsed `Vec<Sexp>` (the from-forms posture). Together with
5198    /// [`Self::expand_and_collect_named_calls_to_any`] /
5199    /// [`Self::expand_source_and_collect_named_calls_to_any`], the four
5200    /// cells of the (named × {constant-keyword, classifier} ×
5201    /// {from-forms, from-source}) sub-matrix close on the `Expander`
5202    /// surface — each cell binds to ONE composition point, with the
5203    /// constant-keyword cells routing through the classifier cells via
5204    /// constant-classifier decoding.
5205    ///
5206    /// Theory anchor: THEORY.md §VI.1 — generation over composition;
5207    /// the `split_name_slot` composition lived at TWO sites pre-lift
5208    /// (`crate::compile::named_form_projection` body, which
5209    /// [`Self::expand_to_named`] routes through, AND
5210    /// [`Self::expand_and_collect_named_calls_to_any`] body, which the
5211    /// typed-decoded classifier consumers route through) — past the
5212    /// ≥2 PRIME-DIRECTIVE trigger once the named-form gate is named.
5213    /// THEORY.md §V.1 — knowable platform; the named constant-keyword
5214    /// walk becomes a NAMED primitive on the `Expander` surface
5215    /// composable by any future consumer (REPL, LSP, `tatara-check`)
5216    /// instead of a re-derived two-line `expand_and_collect_named_
5217    /// calls_to_any(forms, constant-decoder, wrapper-projection)`
5218    /// composition. THEORY.md §II.1 invariant 2 — free middle; both
5219    /// the typed (`expand_to_named<T>`) and untyped (this method)
5220    /// constant-keyword named cells route through the SAME named
5221    /// classifier primitive — a regression that drifts ONE cell's
5222    /// NAME-slot rejection chain (`NamedFormMissingName`,
5223    /// `NamedFormNonSymbolName`) from the other becomes structurally
5224    /// impossible.
5225    ///
5226    /// Frontier inspiration: Racket's `syntax-parse` `((~datum kw)
5227    /// name:id arg ...)` arm — the named NAME-slot binder under a
5228    /// constant `~datum` keyword paired with a per-match handler is
5229    /// the Racket idiom; this method is the substrate's Rust-typed
5230    /// peer with the constant-keyword filter composed at the
5231    /// classifier corner. MLIR's `Region::walk<NamedOpKind>([&](auto
5232    /// op) { auto name = op.getName(); … })` against a single
5233    /// operation kind — the typed-IR walk yielding ops decoded to
5234    /// their typed kind with the named-symbol accessor pre-extracted,
5235    /// specialized on a constant kind; this method is the substrate's
5236    /// unstructured-`R` peer with the constant-classifier composition
5237    /// pinned at the call boundary.
5238    pub fn expand_and_collect_named_calls_to<R, F>(
5239        &mut self,
5240        forms: Vec<Sexp>,
5241        keyword: &'static str,
5242        mut project: F,
5243    ) -> Result<Vec<R>>
5244    where
5245        F: FnMut(&str, &[Sexp]) -> Result<R>,
5246    {
5247        // Routes through the typed-decoded named-classifier sibling
5248        // [`Self::expand_and_collect_named_calls_to_any`] with a
5249        // constant-classifier decoder — the same constant-classifier
5250        // composition [`Self::expand_and_collect_calls_to`] uses to
5251        // route through [`Self::expand_and_collect_calls_to_any`] on
5252        // the bare-kwargs axis, and that
5253        // [`crate::ast::iter_calls_to`] uses to route through
5254        // [`crate::ast::iter_calls_to_any`] on the slice algebra. The
5255        // discarded `()` typed witness (`then_some(((), keyword))`)
5256        // is consumed by the wrapper projection
5257        // `|(), name, args| project(name, args)` so the keyword
5258        // consumer's per-form mapper sees only `(name, spec_args)`,
5259        // matching the bare projection signature.
5260        self.expand_and_collect_named_calls_to_any(
5261            forms,
5262            |h| (h == keyword).then_some(((), keyword)),
5263            move |(), name, args| project(name, args),
5264        )
5265    }
5266
5267    /// Read a source string into top-level forms via [`crate::reader::read`],
5268    /// then route the forms through
5269    /// [`Self::expand_and_collect_named_calls_to`] — the from-source
5270    /// posture of the (named × constant-keyword) cell on the
5271    /// `Expander` surface.
5272    ///
5273    /// Composes [`crate::reader::read`] with
5274    /// [`Self::expand_and_collect_named_calls_to`] — the
5275    /// `(keyword, project)` binding is bound in ONE place (the
5276    /// from-forms row) and this from-source sibling inherits the
5277    /// binding through delegation, mirroring how
5278    /// [`Self::expand_source_and_collect_calls_to`] composes
5279    /// [`crate::reader::read`] with [`Self::expand_and_collect_calls_to`]
5280    /// on the bare-kwargs axis.
5281    ///
5282    /// `?`-routing through `read` preserves the structural ordering of
5283    /// the rejection chain end-to-end: a reader error (lexer / parser /
5284    /// unbalanced-paren / unterminated-string) short-circuits BEFORE
5285    /// `expand_program` runs; an `expand_program` error short-circuits
5286    /// BEFORE the keyword filter walks anything; the named-form gate
5287    /// (`split_first()` arity → `as_symbol_or_string()` shape) inside
5288    /// [`crate::compile::split_name_slot`] fires for the first matched
5289    /// form that violates either condition; a per-form `project` error
5290    /// short-circuits at the first failing matched form via
5291    /// `Iterator::collect::<Result<Vec<R>, _>>()`. Each consumer's
5292    /// rejection chain inherits the constant-keyword named primitive's
5293    /// shape verbatim, now sourced from `&str` via ONE composition
5294    /// point.
5295    ///
5296    /// Sibling of [`Self::expand_and_collect_named_calls_to`] (the
5297    /// from-forms posture) and of [`Self::expand_source_and_collect_calls_to`]
5298    /// (the from-source × bare-kwargs cell on the constant-keyword
5299    /// row). The four cells of (named × {constant, classifier} ×
5300    /// {from-forms, from-source}) close on the `Expander` surface
5301    /// post-lift, each routing through ONE composition point.
5302    ///
5303    /// Theory anchor: same as [`Self::expand_and_collect_named_calls_to`].
5304    /// THEORY.md §VI.1 (generation over composition; the from-source
5305    /// posture inherits the constant-keyword named composition through
5306    /// delegation rather than re-deriving the
5307    /// `read(src)? + expand_and_collect_named_calls_to` chain at every
5308    /// from-source named-keyword consumer's call site),
5309    /// THEORY.md §II.1 invariant 2 (free middle; the from-source
5310    /// posture and the from-forms posture route through the SAME
5311    /// composition).
5312    pub fn expand_source_and_collect_named_calls_to<R, F>(
5313        &mut self,
5314        src: &str,
5315        keyword: &'static str,
5316        mut project: F,
5317    ) -> Result<Vec<R>>
5318    where
5319        F: FnMut(&str, &[Sexp]) -> Result<R>,
5320    {
5321        // From-source = from-forms × constant-classifier specialization
5322        // of the named classifier primitive's from-source sibling. The
5323        // discarded `()` typed witness and the `move |(), name, args|
5324        // project(name, args)` wrapper mirror the from-forms posture's
5325        // composition — both postures route through the SAME named
5326        // classifier primitive on the `Expander` surface, the
5327        // from-source row stacking one `read` step on top.
5328        self.expand_source_and_collect_named_calls_to_any(
5329            src,
5330            |h| (h == keyword).then_some(((), keyword)),
5331            move |(), name, args| project(name, args),
5332        )
5333    }
5334
5335    /// Expand a single form. Top-level macro calls are rewritten; recurses
5336    /// into list children.
5337    ///
5338    /// Routes the macro-call dispatch surface through the substrate's
5339    /// typed-decoded call decomposition: `as_call_to_any(|h|
5340    /// self.macros.get(h))` answers "is this form an invocation of any
5341    /// registered macro, decoded to `(&MacroDef, args)`?" in ONE
5342    /// structural query on the `Sexp` algebra. Pre-lift the same site
5343    /// opened the three-step chain `as_list() + as_call() + self.macros.
5344    /// get(head)` inline — `as_list()` for the children-walk fallthrough,
5345    /// `as_call()` for the (head, args) decomposition (which itself
5346    /// re-derives `as_list()` internally), and `self.macros.get(head)`
5347    /// for the registry lookup; post-lift the call-recognition runs as
5348    /// ONE `as_call_to_any` projection with the HashMap lookup as its
5349    /// classifier, and the `as_list()` fallthrough fires only on the
5350    /// not-a-macro-call path. Sibling consumer to `macro_def_from` — the
5351    /// typed-macro-definition dispatcher that routes through
5352    /// `as_call_to_any(MacroDefHead::from_keyword)` with the closed-set
5353    /// enum classifier. With both in place, BOTH dispatch sites in the
5354    /// macro expander (definition-recognition + call-recognition)
5355    /// project through the SAME family primitive on the `Sexp` algebra,
5356    /// each binding the classifier that fits its candidate set — closed
5357    /// enum for the static head-set, HashMap lookup for the live
5358    /// registry. A regression that drifts ONE site from the other (a
5359    /// future emitter that re-derives `as_list()` + `head.as_symbol()` +
5360    /// `self.macros.get(_)` inline rather than routing through the
5361    /// family) is no longer a silent two-site divergence.
5362    pub fn expand(&self, form: &Sexp) -> Result<Sexp> {
5363        self.expand_with_depth(form, 0)
5364    }
5365
5366    /// The depth-carrying peer of [`Self::expand`]. Each
5367    /// post-apply re-expansion increments `depth`; each tree-child
5368    /// descent passes `depth` through unchanged. The runaway
5369    /// `(defmacro loop (x) `(loop ,x))` accretes depth on the
5370    /// re-expansion path — one unit per round — and hits the ceiling
5371    /// in `max_expansion_depth` rounds; lawful nested macros
5372    /// (`(when1 (when1 x))`) accrete depth in the low single digits
5373    /// because tree traversal doesn't count.
5374    ///
5375    /// The `depth >= limit` check sits INSIDE the macro-call arm
5376    /// (`Some((def, args)) = form.as_call_to_any(…)`) rather than at
5377    /// the top of the function so `def.name.clone()` is available at
5378    /// the rejection site — the operator gets `macro_name` populated
5379    /// from the actual offending call rather than a `<unknown>`
5380    /// fallback. Tree-child descent is not gated on the ceiling
5381    /// because the tree-child arm does not accrete depth; a lawfully
5382    /// deep tree is bounded by the reader's own stack, not by this
5383    /// ceiling.
5384    ///
5385    /// The peer OUTPUT-SIZE gate (`expanded.node_count() >
5386    /// self.limits.max_expansion_size`) sits INSIDE the same macro-call arm,
5387    /// between the `apply` and the re-expansion recursion, so a
5388    /// single macro's `apply` output whose structural size crosses
5389    /// the ceiling is rejected AT the offending `apply` boundary
5390    /// (with `macro_name` populated from `def.name`) before the
5391    /// runaway tree feeds back into further expansion. The gate is
5392    /// keyed on `>` rather than `>=` so `max_expansion_size` names
5393    /// the LARGEST admissible output — the ceiling admits equality,
5394    /// only strict overrun rejects. Together with the depth ceiling
5395    /// (recursion length) and the cache ceiling (memoization width),
5396    /// this closes the expander's RESOURCE surface at three typed
5397    /// dimensions.
5398    fn expand_with_depth(&self, form: &Sexp, depth: usize) -> Result<Sexp> {
5399        if let Some((def, args)) = form.as_call_to_any(|h| self.macros.get(h)) {
5400            if depth >= self.limits.max_expansion_depth {
5401                return Err(LispError::ExpansionDepthExceeded {
5402                    macro_name: def.name.clone(),
5403                    limit: self.limits.max_expansion_depth,
5404                });
5405            }
5406            let expanded = self.apply(def, args)?;
5407            // Reject a single `apply` output whose structural node
5408            // count crosses the OUTPUT-SIZE ceiling — the RESOURCE
5409            // axis peer of the depth (recursion length) and
5410            // cache-entries (memoization width) ceilings. Catches
5411            // the canonical "expansion bomb" where a well-defined
5412            // macro produces a 2^N-node blob from a small input
5413            // while `depth` stays in the low single digits and the
5414            // cache entry count sits under any reasonable ceiling.
5415            let expanded_size = expanded.node_count();
5416            if expanded_size > self.limits.max_expansion_size {
5417                return Err(LispError::ExpansionSizeExceeded {
5418                    macro_name: def.name.clone(),
5419                    size: expanded_size,
5420                    limit: self.limits.max_expansion_size,
5421                });
5422            }
5423            // Recurse — the expansion itself may contain more macro calls.
5424            return self.expand_with_depth(&expanded, depth + 1);
5425        }
5426        // Not a macro call — expand children if this is a list; otherwise
5427        // (atom / Nil / quote-family wrapper) return the form verbatim.
5428        let Some(list) = form.as_list() else {
5429            return Ok(form.clone());
5430        };
5431        let mut out = Vec::with_capacity(list.len());
5432        for item in list {
5433            out.push(self.expand_with_depth(item, depth)?);
5434        }
5435        Ok(Sexp::List(out))
5436    }
5437
5438    /// Apply a macro to its argument list.
5439    ///
5440    /// Three-layer fast path:
5441    ///   1. If `cache_enabled`, hash `(name, args)` and consult the memo table.
5442    ///   2. If a compiled template exists, run the bytecode interpreter.
5443    ///   3. Otherwise fall back to the name-keyed substitute walker.
5444    fn apply(&self, def: &MacroDef, args: &[Sexp]) -> Result<Sexp> {
5445        // Layer 1: expansion cache.
5446        let cache_key = if self.cache_enabled {
5447            args_cache_key(&def.name, args)
5448        } else {
5449            None
5450        };
5451        if let Some(ref key) = cache_key {
5452            if let Some(cached) = self.cache.lock().unwrap().get(key) {
5453                self.counters
5454                    .cache_hits
5455                    .fetch_add(1, AtomicOrdering::Relaxed);
5456                return Ok(cached.clone());
5457            }
5458        }
5459
5460        // Layer 2: compiled bytecode.
5461        let result = if let Some(tmpl) = self.templates.get(&def.name) {
5462            self.counters.compiled.fetch_add(1, AtomicOrdering::Relaxed);
5463            apply_compiled(&def.name, &def.params, tmpl, args)?
5464        } else {
5465            self.counters
5466                .substituted
5467                .fetch_add(1, AtomicOrdering::Relaxed);
5468            // Layer 3: substitute fallback. Walk the body's substitution
5469            // projection — the inner of the outer quasi-quote when present,
5470            // the body verbatim otherwise — through the shared
5471            // `MacroDef::template_body` primitive both strategies route on.
5472            let bindings = bind_args(&def.name, &def.params, args)?;
5473            substitute(def.template_body(), &bindings)?
5474        };
5475
5476        // Populate cache on miss — capped by `max_cache_entries` so the
5477        // memoization table cannot drift unboundedly across a long-
5478        // running host session. When the cap is reached the freshly-
5479        // computed result is returned verbatim; correctness is
5480        // unaffected because caching is a pure PERFORMANCE optimization
5481        // (the miss path always recomputes the same value the cache
5482        // would have returned).
5483        if let Some(key) = cache_key {
5484            let mut cache = self.cache.lock().unwrap();
5485            if cache.len() < self.limits.max_cache_entries {
5486                cache.insert(key, result.clone());
5487            }
5488        }
5489        Ok(result)
5490    }
5491
5492    pub fn has(&self, name: &str) -> bool {
5493        self.macros.contains_key(name)
5494    }
5495
5496    pub fn len(&self) -> usize {
5497        self.macros.len()
5498    }
5499
5500    pub fn is_empty(&self) -> bool {
5501        self.macros.is_empty()
5502    }
5503}
5504
5505// ── Compiled template bytecode ───────────────────────────────────────
5506
5507/// One op in the template bytecode. Emitted during compilation; consumed at
5508/// expansion to materialize a form without HashMap lookups or recursion.
5509#[derive(Clone, Debug, PartialEq)]
5510pub enum TemplateOp {
5511    /// Push a literal Sexp. Used for atoms and entirely-literal subtrees.
5512    Literal(Sexp),
5513    /// Push the bound arg at the given param index.
5514    Subst(usize),
5515    /// If the bound arg is a list, append its items to the current list; else
5516    /// push it as a single item.
5517    Splice(usize),
5518    /// Begin a new List — pushes a fresh builder onto the expansion stack.
5519    BeginList,
5520    /// End the current List — pops the builder, wraps as `Sexp::List`.
5521    EndList,
5522}
5523
5524/// Pre-compiled template. Built once per macro, interpreted many times.
5525#[derive(Clone, Debug, Default, PartialEq)]
5526pub struct CompiledTemplate {
5527    pub ops: Vec<TemplateOp>,
5528}
5529
5530/// Walk a macro definition's template body and emit linear bytecode.
5531/// Purely-literal subtrees compile to a single `Literal(clone)` op.
5532///
5533/// Compilation can fail if the template references a name that isn't a
5534/// declared parameter — same semantic as the substitute path.
5535///
5536/// Top-level `,@X` bodies (the splice is the entire body, not nested inside
5537/// a `(... ,@xs ...)` list) are rejected here at compile time so the
5538/// bytecode path agrees with the substitute path's emission-time rejection
5539/// (`splice_outside_list`). Without this gate the bytecode interpreter's
5540/// outermost stack frame silently absorbed the splice's items, and the same
5541/// macro emitted different output across paths — `compiled_template_matches
5542/// _substitute_path` only covered well-positioned splice bodies. After this
5543/// gate every `,@-outside-list` body is rejected at registration time on
5544/// both paths with ONE structural variant (`LispError::SpliceOutsideList`).
5545///
5546/// The gate routes through [`Sexp::as_unquote`] — the typed-marker
5547/// projection that pairs `Sexp::UnquoteSplice ↔ UnquoteForm::Splice` at
5548/// ONE structural query — matching `Some((UnquoteForm::Splice, inner))`
5549/// rather than the per-arm `Sexp::UnquoteSplice(inner)` literal that
5550/// recurred at three sites pre-lift (`compile_node` Subst/Splice arms,
5551/// `substitute` top-level + list-inner). Sibling shape to `substitute`'s
5552/// list-inner Splice arm — both use the same `Some((UnquoteForm::Splice,
5553/// inner))` shape; `substitute`'s top-level arm uses the wider
5554/// `Some((kind, inner))` and dispatches inside on `kind`. After this lift
5555/// every production-site recognizer of "is this an `,@X` form" routes
5556/// through ONE typed-marker projection rather than re-deriving the
5557/// (Sexp variant, UnquoteForm variant) pair inline.
5558pub fn compile_template(def: &MacroDef) -> Result<CompiledTemplate> {
5559    // Walk the body's substitution projection — the inner of the outer
5560    // quasi-quote when present, the body verbatim otherwise — through the
5561    // shared `MacroDef::template_body` primitive the substitute path also
5562    // routes on. Same projection, both strategies, by construction.
5563    let body = def.template_body();
5564    if let Some((UnquoteForm::Splice, inner)) = body.as_unquote() {
5565        return Err(splice_outside_list(inner));
5566    }
5567    let names = def.params.names();
5568    let mut ops = Vec::new();
5569    compile_node(body, &names, &mut ops)?;
5570    Ok(CompiledTemplate { ops })
5571}
5572
5573fn compile_node(node: &Sexp, params: &[&str], ops: &mut Vec<TemplateOp>) -> Result<()> {
5574    // Fast-path literal: if the subtree has no Unquote/UnquoteSplice, emit a
5575    // single Literal op. This is the big win for macros where most of the
5576    // template is fixed structure.
5577    if !contains_unquote(node) {
5578        ops.push(TemplateOp::Literal(node.clone()));
5579        return Ok(());
5580    }
5581    // Routes the `Sexp::Unquote(inner)` / `Sexp::UnquoteSplice(inner)` arms
5582    // through [`Sexp::as_unquote`] — the typed-marker projection that
5583    // pairs `Sexp::Unquote ↔ UnquoteForm::Unquote` and
5584    // `Sexp::UnquoteSplice ↔ UnquoteForm::Splice` at ONE site. The per-form
5585    // `TemplateOp` emission (`Subst` vs `Splice`) keys on the same typed
5586    // `form` value the gate-1+gate-2 composition `resolve_unquote_in_params`
5587    // threads through. Pre-lift the (Sexp variant, UnquoteForm variant)
5588    // pairing was bound per-arm — a future emitter that matched
5589    // `Sexp::Unquote(_)` but threaded `UnquoteForm::Splice` into
5590    // `resolve_unquote_in_params` (or vice versa) would type-check but
5591    // render a misleading diagnostic at the gate-1 / gate-2 rejection.
5592    // Post-lift the pair is bound at ONE projection function and the
5593    // `match form` mechanically lowers it to the bytecode op.
5594    if let Some((form, inner)) = node.as_unquote() {
5595        let idx = resolve_unquote_in_params(inner, params, form)?;
5596        ops.push(match form {
5597            UnquoteForm::Unquote => TemplateOp::Subst(idx),
5598            UnquoteForm::Splice => TemplateOp::Splice(idx),
5599        });
5600        return Ok(());
5601    }
5602    match node {
5603        Sexp::List(items) => {
5604            ops.push(TemplateOp::BeginList);
5605            for item in items {
5606                compile_node(item, params, ops)?;
5607            }
5608            ops.push(TemplateOp::EndList);
5609        }
5610        _ => ops.push(TemplateOp::Literal(node.clone())),
5611    }
5612    Ok(())
5613}
5614
5615fn contains_unquote(node: &Sexp) -> bool {
5616    // Route every quote-family wrapper recognition — both the
5617    // unquote-only subset (Unquote/UnquoteSplice → short-circuit `true`)
5618    // AND the quote-only subset (Quote/Quasiquote → recurse into inner) —
5619    // through [`Sexp::as_quote_form`]'s typed-marker projection. Pre-lift
5620    // the recognizer split into two arms: `as_unquote().is_some()` (the
5621    // substitution subset gate) + the inline
5622    // `Sexp::Quote(inner) | Sexp::Quasiquote(inner)` arm (the remaining
5623    // quote-only subset). Post-lift both arms route through ONE
5624    // projection and the (Sexp variant, QuoteForm variant) pairing binds
5625    // at the closed-set algebra. The `form.as_unquote_form().is_some()`
5626    // gate is the SAME 2-of-4 subset projection [`Sexp::as_unquote`]
5627    // derives from, so recognizing "is this an unquote-family wrapper"
5628    // and "is this a quote-only wrapper" share ONE typed dispatch site;
5629    // rustc enforces that a future `Sexp` wrapper extension carry
5630    // through both `QuoteForm::ALL` AND `QuoteForm::as_unquote_form`'s
5631    // arm. Sibling posture to `Hash for Sexp`'s four-arm
5632    // `hash_discriminator` collapse, `Display for Sexp`'s `prefix`
5633    // collapse, `interop`'s `iac_forge_tag` collapse, and `domain`'s
5634    // `sexp_shape` collapse — every production-site quote-family
5635    // recognizer now routes through ONE projection on the algebra.
5636    if let Some((form, inner)) = node.as_quote_form() {
5637        return form.as_unquote_form().is_some() || contains_unquote(inner);
5638    }
5639    match node {
5640        Sexp::List(items) => items.iter().any(contains_unquote),
5641        _ => false,
5642    }
5643}
5644
5645/// Splice a resolved template value into an in-progress list builder —
5646/// the SHARED coercion both expansion strategies apply once `,@name`'s
5647/// gate-1 (must-be-a-symbol) and gate-2 (must-be-bound-in-scope) have
5648/// resolved the bound value. ONE named primitive the bytecode path
5649/// (`apply_compiled`'s `TemplateOp::Splice` arm) AND the substitute path
5650/// (`substitute`'s list-inner `Sexp::UnquoteSplice` arm) share. Before
5651/// this lift the three-arm coercion —
5652///
5653/// ```ignore
5654/// match value {
5655///     Sexp::List(items) => builder.extend(items.iter().cloned()),
5656///     Sexp::Nil         => {}
5657///     other             => builder.push(other.clone()),
5658/// }
5659/// ```
5660///
5661/// — was inlined at BOTH sites; the splice RESULT semantics (the last
5662/// inline-duplicated piece of the splice path after the prior runs lifted
5663/// gate-1, gate-2, and their composition) lived in two places that MUST
5664/// agree. After this lift the coercion lives in ONE function, so a
5665/// regression that drifts one strategy's splice posture from the other —
5666/// e.g. changing the `Sexp::Nil` arm to push an empty list at the
5667/// bytecode path but not the substitute path, or coercing a non-list
5668/// scalar differently across the two strategies — becomes structurally
5669/// impossible: there is exactly one implementation both strategies call.
5670///
5671/// The coercion's three arms ARE the no-evaluator template language's
5672/// splice contract: a bound LIST flattens its elements into the builder
5673/// (the canonical splice), a bound NIL contributes nothing (splicing the
5674/// empty list), and any other bound value splices as a single element (a
5675/// scalar `,@x` degrades to `,x` rather than erroring — invariant 2's
5676/// "free middle" lets the macro author rely on this without a
5677/// mid-rewrite type check; the typed-exit gate re-validates the
5678/// assembled form). Naming the contract once gives a future gate-3
5679/// (typed-shape enforcement on bound splice targets) ONE site to wrap
5680/// rather than two inline arms to keep in lockstep.
5681///
5682/// Theory anchor: THEORY.md §II.1 invariant 2 — free middle; the two
5683/// expansion strategies MUST produce identical output for the same
5684/// (macro, args) pair, and naming the splice coercion once makes that
5685/// per-strategy agreement structural rather than a two-site discipline
5686/// the `expansion_layers_agree_on_output_and_cache_wins` benchmark only
5687/// observes after the fact. THEORY.md §V.1 — knowable platform; the
5688/// splice RESULT semantics becomes a NAMED primitive authoring tools and
5689/// future runs bind to. THEORY.md §VI.1 — generation over composition;
5690/// the two-site coercion is lifted to ONE function, closing the last
5691/// inline-duplicated piece of the splice path the prior runs' gate lifts
5692/// (02173dc gate-1, 68da647 gate-2, b456f1f composition) left behind.
5693fn splice_value_into(builder: &mut Vec<Sexp>, value: &Sexp) {
5694    match value {
5695        Sexp::List(items) => builder.extend(items.iter().cloned()),
5696        Sexp::Nil => {}
5697        other => builder.push(other.clone()),
5698    }
5699}
5700
5701/// Promote the previously `LispError::Compile`-shaped helper into the
5702/// structural `LispError::TemplateInvariant { macro_name, kind }` variant.
5703/// The four reachable bytecode-runtime invariant violations in
5704/// `apply_compiled` — Subst-bad-index, Splice-bad-index, EndList-empty-
5705/// stack, final-no-value — funnel through ONE emission shape keyed on
5706/// the closed-set `TemplateInvariantKind` enum. The index payload of
5707/// the Subst / Splice gates lives INSIDE the variant (`SubstBadIndex(usize)`
5708/// / `SpliceBadIndex(usize)`), so the invalid combination "stack-gate
5709/// kind with an op-index" (e.g. `EndListEmptyStack` carrying a `usize`)
5710/// is structurally unrepresentable — the type system encodes "this gate
5711/// has an index, that gate does not."
5712///
5713/// Display matches the legacy `Compile`-shaped diagnostic byte-for-byte
5714/// across all four kinds (`"compile error in {macro_name}: <invariant>"`)
5715/// via the closed-set `TemplateInvariantKind::message()` projection, so
5716/// authoring-tool substring greps (`tatara-check`, REPL) see no drift
5717/// across the lift.
5718///
5719/// Theory anchor: THEORY.md §V.1 — knowable platform; the closed set
5720/// of bytecode-invariant failure modes becomes a TYPE rather than a
5721/// free-form `message: String` slot. THEORY.md §VI.1 — generation over
5722/// composition; the typed enum lands the structural-completeness floor
5723/// for the bytecode-runtime surface, parallel to how `CompilerSpecIoStage`
5724/// lands the structural-completeness floor for the disk-persistence
5725/// surface (`compiler_spec.rs`, the immediately prior claude-routine
5726/// lift on a sibling file). THEORY.md §II.1 invariant 5 (composition
5727/// preserves proofs): a well-formed bytecode invariant is the proof
5728/// that drives the interpreter; the structural variant makes the
5729/// proof's REJECTION shape first-class — authoring tools (REPL, LSP,
5730/// `tatara-check`) pattern-match on the `kind` slot and bind to the
5731/// gate identity directly instead of substring-parsing the rendered
5732/// diagnostic.
5733fn template_invariant_violation(macro_name: &str, kind: TemplateInvariantKind) -> LispError {
5734    LispError::TemplateInvariant {
5735        macro_name: macro_name.into(),
5736        kind,
5737    }
5738}
5739
5740/// Look up a bound-arg by its template-bytecode index, or raise the
5741/// structural `LispError::TemplateInvariant` rejection with the
5742/// caller-supplied `kind` constructor applied to the bad index. ONE
5743/// named primitive both bytecode-runtime arms that read a bound arg
5744/// by index — [`TemplateOp::Subst`] (single-value push) AND
5745/// [`TemplateOp::Splice`] (list-splicing) — route through.
5746///
5747/// Before this lift the same `args_by_index.get(*idx).ok_or_else(||
5748/// template_invariant_violation(macro_name, KIND(*idx)))?` projection
5749/// appeared at BOTH arms of [`apply_compiled`], differing only in the
5750/// kind constructor: [`TemplateInvariantKind::SubstBadIndex`] at the
5751/// `Subst` arm, [`TemplateInvariantKind::SpliceBadIndex`] at the
5752/// `Splice` arm. The arms also diverged on what they did with the
5753/// returned `&Sexp` — `Subst` cloned and pushed, `Splice` consumed
5754/// the borrow through [`splice_value_into`] — but the lookup-and-
5755/// reject prelude was byte-identical modulo the kind, well past the
5756/// ≥2 PRIME-DIRECTIVE trigger.
5757///
5758/// After this lift the lookup-and-reject shape lives in ONE function;
5759/// the two arms thread the per-call-site kind constructor through the
5760/// helper and apply their respective post-lookup verbs at the call
5761/// site. The `kind: FnOnce(usize) -> TemplateInvariantKind` parameter
5762/// encodes the closed-set bytecode-runtime "this gate has an index"
5763/// surface at the type level — only the two
5764/// [`TemplateInvariantKind`] variants whose payload IS the bad index
5765/// (`SubstBadIndex(usize)` and `SpliceBadIndex(usize)`) construct
5766/// directly through `FnOnce(usize) -> TemplateInvariantKind`; the
5767/// stack-gate variants ([`TemplateInvariantKind::EndListEmptyStack`]
5768/// and [`TemplateInvariantKind::FinalNoValue`]) carry no payload and
5769/// would not type-check at this boundary, so the invalid combination
5770/// "stack-gate kind reached from an op-index lookup" is structurally
5771/// unrepresentable at the helper's call boundary the same way
5772/// [`TemplateInvariantKind`]'s closed-set shape makes it
5773/// unrepresentable in the variant itself.
5774///
5775/// Sibling of [`template_invariant_violation`]: that helper builds the
5776/// typed [`LispError::TemplateInvariant`] variant from a fully-formed
5777/// `kind`; this helper composes the index-keyed lookup with the
5778/// variant-builder, so the kind constructor doesn't have to be evaluated
5779/// eagerly at the call site (lazy via `FnOnce`, only fires on the bad-
5780/// index path). A future fifth bytecode op that reads a bound arg by
5781/// index (a hypothetical [`TemplateOp::Conditional`] that branches on a
5782/// bound boolean, a [`TemplateOp::Project`] that extracts a sub-field
5783/// of a bound `Sexp::List`) extends the family in ONE call to
5784/// `resolve_bound_arg` with the new kind constructor (`KIND(usize) ->
5785/// TemplateInvariantKind`) — the bytecode-runtime's bound-arg-by-index
5786/// projection becomes ONE structural primitive consumers compose with.
5787///
5788/// The returned `&'a Sexp` borrows from `args_by_index` verbatim —
5789/// `Subst`'s arm consumes it through `.clone()` (the consumer pushes
5790/// an owned value into the builder); `Splice`'s arm consumes it
5791/// through [`splice_value_into`] (the consumer borrows for the
5792/// per-arm coercion). The borrow's lifetime `'a` is the unified
5793/// lifetime of `args_by_index`, matching the call site's borrow
5794/// posture.
5795///
5796/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
5797/// inline copies of the index-lookup-and-reject prelude across the
5798/// `apply_compiled` body's `Subst` and `Splice` arms is past the ≥2
5799/// PRIME-DIRECTIVE trigger once the structural shape is named.
5800/// THEORY.md §V.1 — knowable platform / "make invalid states
5801/// unrepresentable"; the bytecode-runtime bound-arg-by-index lookup
5802/// becomes a NAMED primitive on the substrate's `&[Sexp]` algebra
5803/// rather than a re-derived `get + ok_or_else + template_invariant_
5804/// violation` chain at every op-arm that reads by index. A future
5805/// authoring tool (REPL, LSP, `tatara-check`) that wants to surface
5806/// "this bytecode op's bound-arg lookup misfired at idx N" binds to
5807/// ONE function. THEORY.md §II.1 invariant 2 — free middle; both
5808/// expansion strategies route through the SHARED `MacroParams::bind`,
5809/// AND the bytecode strategy's op-arms route through this SHARED
5810/// `resolve_bound_arg` lookup — the bytecode-runtime's
5811/// proof-of-well-formedness is now structurally uniform across the
5812/// two reachable index-lookup ops, so a regression that drifts ONE
5813/// arm's posture (e.g. accepts an out-of-range idx at one arm but
5814/// not the other, or swaps the kind constructor at a single arm) is
5815/// no longer a silent two-site divergence.
5816fn resolve_bound_arg<'a>(
5817    args_by_index: &'a [Sexp],
5818    idx: usize,
5819    macro_name: &str,
5820    kind: impl FnOnce(usize) -> TemplateInvariantKind,
5821) -> Result<&'a Sexp> {
5822    args_by_index
5823        .get(idx)
5824        .ok_or_else(|| template_invariant_violation(macro_name, kind(idx)))
5825}
5826
5827/// Project the bytecode-runtime stack to its in-progress builder frame —
5828/// the `&mut Vec<Sexp>` every value-emitting op writes into. ONE named
5829/// primitive both push-emitting arms (`TemplateOp::Literal` /
5830/// `TemplateOp::Subst` / post-`EndList` parent fold) AND the splice-
5831/// emitting arm (`TemplateOp::Splice`) route through.
5832///
5833/// Before this lift the same `stack.last_mut().unwrap()` projection
5834/// appeared at FOUR sites inside [`apply_compiled`]'s op-loop:
5835///
5836///   * `TemplateOp::Literal` — pushes the literal `Sexp` into the
5837///     current builder.
5838///   * `TemplateOp::Subst` — pushes the cloned bound-arg into the
5839///     current builder.
5840///   * `TemplateOp::Splice` — splices the bound-arg into the current
5841///     builder via [`splice_value_into`].
5842///   * `TemplateOp::EndList` — after popping the just-finished list
5843///     frame, pushes the folded `Sexp::List(items)` into the parent
5844///     builder (the new current frame).
5845///
5846/// Four byte-identical re-derivations of the same projection, well past
5847/// the ≥2 PRIME-DIRECTIVE trigger. After this lift the four sites
5848/// collapse to a single `current_builder_mut(&mut stack).{push|extend}`
5849/// call, and the bytecode-runtime invariant the projection rests on
5850/// — "the op-loop always sees at least one stack frame" — lives in ONE
5851/// expect message rather than four silent `.unwrap()` calls.
5852///
5853/// The expect rationale: [`apply_compiled`] seeds the stack with the
5854/// outermost frame at entry (`vec![Vec::with_capacity(1)]`); every
5855/// `TemplateOp::BeginList` pushes a NEW frame and every
5856/// `TemplateOp::EndList` pops it, so the count stays at OR ABOVE 1
5857/// throughout the op-loop. Stack-depleting failure modes are caught
5858/// upstream by their own structural variants:
5859/// [`TemplateInvariantKind::EndListEmptyStack`] fires inside
5860/// [`apply_compiled`]'s `EndList` arm via [`Vec::pop`]'s `Option`
5861/// gate, BEFORE the parent-fold push runs against
5862/// `current_builder_mut`; [`TemplateInvariantKind::FinalNoValue`]
5863/// fires AFTER the op-loop completes, on the outermost `stack.pop()`
5864/// that returns the assembled result. So a reachable
5865/// `current_builder_mut(&mut stack)` always observes a non-empty
5866/// stack, and the `expect` is a structural-invariant marker, not a
5867/// load-bearing rejection path.
5868///
5869/// Sibling of [`resolve_bound_arg`] (the bytecode-runtime bound-arg
5870/// lookup primitive lifted in the prior claude-routine run on this
5871/// module — 492a235) and [`template_invariant_violation`] (the
5872/// structural-variant error builder for the bytecode-runtime's
5873/// closed-set invariant-violation surface). Together the three primitives
5874/// name the bytecode-runtime's substrate-level operations: lookup-a-
5875/// bound-arg ([`resolve_bound_arg`]), build-the-invariant-rejection
5876/// ([`template_invariant_violation`]), and project-to-the-current-
5877/// builder (this lift). A future bytecode op that emits ONE OR MORE
5878/// values into the current builder — a hypothetical
5879/// `TemplateOp::SpliceMany(indices: Vec<usize>)` that splices a batch,
5880/// a `TemplateOp::PushQuoted(form: Sexp)` that wraps before push, a
5881/// span-annotated emit-with-position op — composes with ONE call to
5882/// [`current_builder_mut`] and the per-op post-projection verb
5883/// (`.push(…)`, `.extend(…)`, `splice_value_into(…, _)`); a future
5884/// instrumentation hook that wants to log every op's emit before
5885/// it lands in the builder wraps ONE call boundary, not four inline
5886/// `stack.last_mut().unwrap()` sites.
5887///
5888/// Theory anchor: THEORY.md §VI.1 — generation over composition; four
5889/// inline copies of the top-of-stack projection in one function is
5890/// past the ≥2 PRIME-DIRECTIVE trigger once the structural shape is
5891/// named. THEORY.md §V.1 — knowable platform; the bytecode-runtime's
5892/// current-builder projection becomes a NAMED primitive on the
5893/// substrate's `&mut [Vec<Sexp>]` slice algebra rather than a re-derived
5894/// `last_mut + unwrap` chain at every op-arm that emits into the
5895/// builder. The expect message names the invariant
5896/// ("bytecode-runtime invariant: at least one stack frame during
5897/// op-loop") so a regression that drifts the loop's frame management
5898/// surfaces a NAMED panic, not a silent `unwrap` over `None`.
5899/// THEORY.md §II.1 invariant 2 — free middle; both expansion
5900/// strategies route through the SHARED `MacroParams::bind` upstream
5901/// AND the bytecode strategy's op-arms now route through this SHARED
5902/// `current_builder_mut` projection downstream — the bytecode-runtime's
5903/// substrate-level surface (lookup + emit) is named in two
5904/// composable primitives the op-arms compose with.
5905fn current_builder_mut(stack: &mut [Vec<Sexp>]) -> &mut Vec<Sexp> {
5906    stack
5907        .last_mut()
5908        .expect("bytecode-runtime invariant: at least one stack frame during op-loop")
5909}
5910
5911/// Pop the top stack frame off the bytecode-runtime stack, or raise the
5912/// structural [`LispError::TemplateInvariant`] rejection with the
5913/// supplied [`TemplateInvariantKind`] when the stack is empty — ONE
5914/// named primitive both pop-emitting sites in [`apply_compiled`] route
5915/// through.
5916///
5917/// Before this lift two byte-identical
5918/// `stack.pop().ok_or_else(|| template_invariant_violation(macro_name,
5919/// kind))?` chains lived inline in [`apply_compiled`]:
5920///
5921///   * `TemplateOp::EndList` arm — pops the just-finished list frame
5922///     before the parent-fold push, with kind
5923///     [`TemplateInvariantKind::EndListEmptyStack`] guarding the
5924///     unreachable empty-stack failure mode.
5925///   * Post-loop final pop — consumes the outermost frame that
5926///     accumulated the template's result, with kind
5927///     [`TemplateInvariantKind::FinalNoValue`] guarding the
5928///     unreachable seed-frame-already-popped failure mode.
5929///
5930/// Two byte-identical re-derivations of the same projection inside one
5931/// function, past the ≥2 PRIME-DIRECTIVE trigger once the structural
5932/// shape is named. After this lift the two sites collapse to a single
5933/// `pop_builder_frame(&mut stack, macro_name, KIND)?` call, and the
5934/// bytecode-runtime invariant the projection rests on — "an empty-stack
5935/// pop is a structural-variant rejection, not a silent `Option::None`"
5936/// — lives in ONE composition point rather than two.
5937///
5938/// Sibling of [`current_builder_mut`] (the bytecode-runtime stack's
5939/// *project-to-top-frame* primitive — the borrow face, never resizes
5940/// the stack) and [`resolve_bound_arg`] (the bound-arg-by-index
5941/// lookup primitive with per-call-site `TemplateInvariantKind`
5942/// constructor). Where `current_builder_mut` borrows the in-progress
5943/// top frame for emission and never panics on a reachable input
5944/// (callers route past it only when the seed frame is present), this
5945/// primitive *consumes* the top frame off the stack and projects its
5946/// absence into a structural `LispError::TemplateInvariant` rejection
5947/// — the failure-on-empty face of the same `&mut Vec<Vec<Sexp>>`
5948/// algebra. The pair —
5949/// [`current_builder_mut`] + [`pop_builder_frame`] — close the
5950/// substrate's bytecode-runtime stack-frame projection algebra at the
5951/// borrow/consume boundary: borrow-the-top-frame for emission (no
5952/// rejection — the invariant rests on the seed-frame contract),
5953/// consume-the-top-frame for finalization (rejection-routed —
5954/// the absence projects through `TemplateInvariantKind` into a
5955/// structural variant of [`LispError`]). Together with
5956/// [`resolve_bound_arg`] (lookup args by index, with per-call-site
5957/// kind constructor) and [`template_invariant_violation`] (the typed
5958/// rejection emitter the three primitives share), the four
5959/// substrate-level operations name the bytecode-runtime's named
5960/// projection surface: lookup-a-bound-arg, project-to-the-current-
5961/// builder, consume-a-finished-builder-frame, build-the-invariant-
5962/// rejection.
5963///
5964/// `kind: TemplateInvariantKind` is the closed-set typed enum whose
5965/// four variants are EXACTLY the four reachable bytecode-runtime
5966/// invariant-violation modes (`SubstBadIndex(usize)` /
5967/// `SpliceBadIndex(usize)` from the index-lookup sibling primitive,
5968/// `EndListEmptyStack` from the `EndList` arm, `FinalNoValue` from
5969/// the post-loop final pop). The Subst / Splice indexed variants
5970/// thread their `usize` payload INSIDE the variant, so the invalid
5971/// combination "stack-gate kind with an op-index" (a hypothetical
5972/// `EndListEmptyStack(99)` carrying a Subst-style payload) is
5973/// structurally unrepresentable at this helper's boundary — the
5974/// caller cannot misroute an indexed kind through `pop_builder_frame`
5975/// at compile time because the kind's data shape is part of its
5976/// variant identity. Same closed-set guarantee
5977/// [`template_invariant_violation`] gives the four kinds; this
5978/// helper composes that guarantee with the `stack.pop()` projection
5979/// at the two pop-emitting sites.
5980///
5981/// The future-run extensions ride this floor: a future bytecode op
5982/// that consumes one or more finished frames — a hypothetical
5983/// `TemplateOp::EndMany(n: usize)` that pops `n` frames into a
5984/// flattened list, a span-aware `TemplateOp::EndListWithSpan(pos)`
5985/// that pops with a position-annotated rejection — composes with
5986/// ONE call (or a fold over N calls) to [`pop_builder_frame`]
5987/// without re-deriving the stack-pop-and-reject shape. A future
5988/// instrumentation hook (a debug-mode logger that records every
5989/// frame consumption, a span-aware pop that threads `Sexp` positions
5990/// through, a multi-frame fold that pops N frames in one step)
5991/// wraps ONE call boundary rather than keeping two inline chains in
5992/// lockstep at the production op-loop sites.
5993///
5994/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
5995/// inline copies of the stack-pop-and-reject projection across the
5996/// `apply_compiled` body's `EndList` arm and post-loop final-pop is
5997/// past the ≥2 PRIME-DIRECTIVE trigger once the structural shape is
5998/// named — the same threshold [`resolve_bound_arg`] (the index-lookup
5999/// sibling) crossed in the prior claude-routine run on this file
6000/// (492a235) and [`current_builder_mut`] (the top-frame-borrow
6001/// sibling) crossed two runs ago (c6a5a9d). THEORY.md §V.1 — knowable
6002/// platform / "make invalid states unrepresentable"; the bytecode-
6003/// runtime stack-frame consume operation becomes a NAMED primitive
6004/// on the substrate's `&mut Vec<Vec<Sexp>>` algebra rather than a
6005/// re-derived `pop + ok_or_else + template_invariant_violation`
6006/// chain at every op-arm that consumes a frame. THEORY.md §II.1
6007/// invariant 2 — free middle; both pop-emitting sites route through
6008/// the SHARED `pop_builder_frame` projection, so a regression that
6009/// drifts ONE site's posture (e.g. accepts an empty-stack pop at the
6010/// `EndList` arm but not the final pop, or swaps the kind constructor
6011/// at a single site) is no longer a silent two-site divergence — the
6012/// type system binds both sites to ONE composition point.
6013///
6014/// Frontier inspiration: MLIR's `Block::eraseFromParent()` against a
6015/// region's block list — the structured-IR's block-consumption
6016/// operation is a named typed primitive that yields a typed
6017/// `LogicalResult` rejection rather than a silent `nullptr` projection
6018/// when the parent region is empty; the substrate's
6019/// `pop_builder_frame` is the unstructured-projection peer on the
6020/// substrate's `&mut Vec<Vec<Sexp>>` stack-frame algebra, with
6021/// `TemplateInvariantKind` standing in for MLIR's `LogicalResult`'s
6022/// closed-set rejection identity. GHC Core's `popTickish` /
6023/// `stackPop` family — every Core-IR transform that consumes a stack
6024/// frame off the rewriter's working stack binds to one named pop
6025/// primitive that threads a typed `WantedFailure` rejection when the
6026/// stack is empty; translated through pleme-io primitives as ONE
6027/// `pop_builder_frame(stack, macro_name, kind)` call with
6028/// `TemplateInvariantKind` carrying the closed-set rejection
6029/// identity.
6030fn pop_builder_frame(
6031    stack: &mut Vec<Vec<Sexp>>,
6032    macro_name: &str,
6033    kind: TemplateInvariantKind,
6034) -> Result<Vec<Sexp>> {
6035    stack
6036        .pop()
6037        .ok_or_else(|| template_invariant_violation(macro_name, kind))
6038}
6039
6040/// Execute a pre-compiled template against the macro's argument list.
6041fn apply_compiled(
6042    macro_name: &str,
6043    params: &MacroParams,
6044    tmpl: &CompiledTemplate,
6045    args: &[Sexp],
6046) -> Result<Sexp> {
6047    // Resolve args by param index through the shared positional binder —
6048    // identical semantics to the `bind_args` (substitute) path by construction.
6049    let args_by_index = params.bind(macro_name, args)?;
6050
6051    // Run the bytecode against a stack of in-progress list builders. The
6052    // outermost frame accumulates the single result the template yields.
6053    // Each emit-into-builder arm routes through the shared
6054    // `current_builder_mut` projection — the bytecode-runtime invariant
6055    // "at least one stack frame during the op-loop" lives in ONE expect
6056    // message rather than four silent `.unwrap()` calls.
6057    let mut stack: Vec<Vec<Sexp>> = vec![Vec::with_capacity(1)];
6058    for op in &tmpl.ops {
6059        match op {
6060            TemplateOp::Literal(s) => current_builder_mut(&mut stack).push(s.clone()),
6061            TemplateOp::Subst(idx) => {
6062                // Bound-arg-by-index lookup routes through the shared
6063                // `resolve_bound_arg` projection with `SubstBadIndex` as
6064                // the per-call-site kind constructor; the post-lookup
6065                // verb (clone + push into the current builder) is the
6066                // Subst arm's per-op shape.
6067                let v = resolve_bound_arg(
6068                    &args_by_index,
6069                    *idx,
6070                    macro_name,
6071                    TemplateInvariantKind::SubstBadIndex,
6072                )?
6073                .clone();
6074                current_builder_mut(&mut stack).push(v);
6075            }
6076            TemplateOp::Splice(idx) => {
6077                // Sibling lookup through `resolve_bound_arg` with
6078                // `SpliceBadIndex` as the per-call-site kind constructor;
6079                // the post-lookup verb (`splice_value_into` against the
6080                // current builder) consumes the borrow directly without
6081                // an intermediate clone.
6082                let v = resolve_bound_arg(
6083                    &args_by_index,
6084                    *idx,
6085                    macro_name,
6086                    TemplateInvariantKind::SpliceBadIndex,
6087                )?;
6088                splice_value_into(current_builder_mut(&mut stack), v);
6089            }
6090            TemplateOp::BeginList => stack.push(Vec::new()),
6091            TemplateOp::EndList => {
6092                // Pop the just-finished list frame through the shared
6093                // `pop_builder_frame` projection with
6094                // `EndListEmptyStack` as the per-call-site kind
6095                // constructor; the post-pop verb (folded
6096                // `Sexp::List(items)` push into the parent frame via
6097                // `current_builder_mut`) is the EndList arm's per-op
6098                // shape — sibling of the Subst/Splice arms' index-
6099                // lookup-then-emit posture, with the index-lookup
6100                // primitive (`resolve_bound_arg`) and the frame-
6101                // consume primitive (`pop_builder_frame`) BOTH routing
6102                // through the same `TemplateInvariantKind` closed-set
6103                // rejection identity.
6104                let items = pop_builder_frame(
6105                    &mut stack,
6106                    macro_name,
6107                    TemplateInvariantKind::EndListEmptyStack,
6108                )?;
6109                current_builder_mut(&mut stack).push(Sexp::List(items));
6110            }
6111        }
6112    }
6113    // Final pop consumes the outermost (seed) frame via the same
6114    // shared projection — `FinalNoValue` as the per-call-site kind
6115    // constructor pins this site's structural-variant identity, the
6116    // post-pop verb (the `top.len() == 1` arity gate below) is the
6117    // post-loop tail's per-site shape.
6118    let mut top = pop_builder_frame(&mut stack, macro_name, TemplateInvariantKind::FinalNoValue)?;
6119    if top.len() == 1 {
6120        Ok(top.remove(0))
6121    } else {
6122        Ok(Sexp::List(top))
6123    }
6124}
6125
6126/// Hash of `(macro_name, args)` for cache keying — hot path, kept lean.
6127/// Uses `DefaultHasher` (SipHash-2-4) — fast enough that the cache hit rate
6128/// needed to net a win is low even for cheap macros.
6129fn args_cache_key(macro_name: &str, args: &[Sexp]) -> Option<CacheKey> {
6130    let mut h = DefaultHasher::new();
6131    args.len().hash(&mut h);
6132    for a in args {
6133        a.hash(&mut h);
6134    }
6135    Some((macro_name.to_string(), h.finish()))
6136}
6137
6138/// `pub(crate)` so the span-preserving expander recognizes a macro
6139/// definition through THIS function rather than a second copy of the
6140/// `defmacro`-head / name / param-list / body decomposition. A `MacroDef`
6141/// retains no spans (macros are keyed by name), so the spanned path lowers
6142/// its form and lands here — one recognizer, one lambda-list parser, one
6143/// error taxonomy.
6144pub(crate) fn macro_def_from(form: &Sexp) -> Result<Option<MacroDef>> {
6145    // Route the typed-macro-definition dispatch surface through the
6146    // substrate's typed-decoded call decomposition: `as_call_to_any`
6147    // performs the `as_list + head_symbol + MacroDefHead::from_keyword`
6148    // three-step chain in ONE structural query on the `Sexp` algebra.
6149    // The legacy diagnostic anchors on `list.len()` (the FULL form arity
6150    // including the head) — preserved here as `args.len() + 1` so
6151    // `LispError::DefmacroArity.arity` carries the same value across the
6152    // lift.
6153    let Some((head, args)) = form.as_call_to_any(MacroDefHead::from_keyword) else {
6154        return Ok(None);
6155    };
6156    if args.len() < 3 {
6157        return Err(defmacro_arity(head, args.len() + 1));
6158    }
6159    let name = args[0]
6160        .as_symbol()
6161        .ok_or_else(|| defmacro_non_symbol_name(head, &args[0]))?
6162        .to_string();
6163    let param_list = args[1]
6164        .as_list()
6165        .ok_or_else(|| defmacro_non_list_params(head, &args[1]))?;
6166    let params = parse_params(param_list)?;
6167    let body = args[2].clone();
6168    Ok(Some(MacroDef { name, params, body }))
6169}
6170
6171fn parse_params(list: &[Sexp]) -> Result<MacroParams> {
6172    let mut required = Vec::new();
6173    let mut optional: Vec<OptionalParam> = Vec::new();
6174    let mut optional_marker: Option<usize> = None;
6175    let mut i = 0;
6176    while i < list.len() {
6177        // In the optional section a `(name default)` LIST form is a valid spec
6178        // alongside a bare-symbol spec. The list form is only meaningful here,
6179        // so the dispatch fires before the `as_symbol()` gate that would
6180        // otherwise reject it as `NonSymbolParam`.
6181        if optional_marker.is_some() {
6182            if let Sexp::List(items) = &list[i] {
6183                optional.push(parse_optional_list_spec(i, &list[i], items)?);
6184                i += 1;
6185                continue;
6186            }
6187        }
6188        let s = list[i]
6189            .as_symbol()
6190            .ok_or_else(|| non_symbol_param(i, &list[i]))?;
6191        if s == MacroParams::REST_MARKER {
6192            let Some(next) = list.get(i + 1) else {
6193                return Err(rest_param_missing_name(i, None));
6194            };
6195            let Some(name) = next.as_symbol() else {
6196                return Err(rest_param_missing_name(i, Some(next)));
6197            };
6198            let trailing = &list[i + 2..];
6199            if !trailing.is_empty() {
6200                return Err(rest_param_trailing_tokens(i, trailing));
6201            }
6202            return Ok(MacroParams {
6203                required,
6204                optional,
6205                rest: Some(name.to_string()),
6206            });
6207        }
6208        if s == MacroParams::OPTIONAL_MARKER {
6209            if let Some(first) = optional_marker {
6210                return Err(optional_marker_repeated(first, i));
6211            }
6212            optional_marker = Some(i);
6213            i += 1;
6214            continue;
6215        }
6216        if optional_marker.is_some() {
6217            optional.push(OptionalParam::bare(s));
6218        } else {
6219            required.push(s.to_string());
6220        }
6221        i += 1;
6222    }
6223    Ok(MacroParams {
6224        required,
6225        optional,
6226        rest: None,
6227    })
6228}
6229
6230/// Project a `Sexp::List` in the `&optional` section to a typed
6231/// [`OptionalParam`]. The only admissible shape is `(NAME DEFAULT)` — a
6232/// list of exactly TWO elements whose first element is a symbol. Every
6233/// other list shape is the structural rejection
6234/// [`LispError::OptionalParamMalformed`], with a typed `reason`
6235/// ([`OptionalParamMalformedReason`]) naming WHICH way the spec is
6236/// malformed — empty, missing-default, extra-elements, or non-symbol name.
6237///
6238/// `position` is the loop index inside `parse_params`, mirroring the
6239/// `position`/`rest_position`/`first_position` slots on the sibling
6240/// `parse_params` rejection variants. `list_form` is the offending
6241/// `Sexp::List` itself, projected through `crate::domain::sexp_witness` so
6242/// the variant carries BOTH `SexpShape::List` AND the rendered form (for
6243/// LSP / REPL / `tatara-check` consumption). `items` is the list body,
6244/// avoiding a re-`as_list()` at the call boundary.
6245fn parse_optional_list_spec(
6246    position: usize,
6247    list_form: &Sexp,
6248    items: &[Sexp],
6249) -> Result<OptionalParam> {
6250    use crate::error::OptionalParamMalformedReason as R;
6251    // Arity dispatch — threads through the typed classifier on the
6252    // closed-set algebra so the accept-arity binding (arity ==
6253    // `R::OPTIONAL_PARAM_SPEC_ARITY`) is derived from ONE typed const
6254    // rather than the bare literal `2`. Post-classification the arity
6255    // is exactly the accept target; the head-symbol gate is the only
6256    // remaining rejection axis.
6257    if let Some(reason) = R::classify_arity(items.len()) {
6258        return Err(optional_param_malformed(position, list_form, reason));
6259    }
6260    let Some(name) = items[0].as_symbol() else {
6261        return Err(optional_param_malformed(
6262            position,
6263            list_form,
6264            R::NonSymbolName,
6265        ));
6266    };
6267    Ok(OptionalParam::with_default(name, items[1].clone()))
6268}
6269
6270fn bind_args(
6271    macro_name: &str,
6272    params: &MacroParams,
6273    args: &[Sexp],
6274) -> Result<HashMap<String, Sexp>> {
6275    // Zip the shared positional binding (parallel to `names()`) into the
6276    // name-keyed map the `substitute` path looks substitutions up in.
6277    let vals = params.bind(macro_name, args)?;
6278    Ok(params
6279        .names()
6280        .into_iter()
6281        .map(String::from)
6282        .zip(vals)
6283        .collect())
6284}
6285
6286/// Substitute `,name` and `,@name` within a template.
6287/// `,@name` only makes sense inside a List — it splices the bound list into
6288/// the containing list.
6289///
6290/// Routes both unquote-family sites — the top-level `,X` / `,@X`
6291/// recognition AND the list-inner per-item splice recognition — through
6292/// the substrate's typed-marker projection [`Sexp::as_unquote`]. Pre-lift
6293/// each site opened its own `Sexp::Unquote(inner)` / `Sexp::UnquoteSplice
6294/// (inner)` arm paired with a `UnquoteForm::Unquote` / `UnquoteForm::
6295/// Splice` literal; post-lift the (Sexp variant, UnquoteForm variant)
6296/// pairing is bound at ONE projection function the type system threads
6297/// through `(UnquoteForm, &Sexp)`, eliminating the silent two-site
6298/// pairing drift the prior shape allowed.
6299fn substitute(form: &Sexp, bindings: &HashMap<String, Sexp>) -> Result<Sexp> {
6300    if let Some((kind, inner)) = form.as_unquote() {
6301        return match kind {
6302            UnquoteForm::Unquote => resolve_unquote_in_bindings(inner, bindings, kind).cloned(),
6303            UnquoteForm::Splice => Err(splice_outside_list(inner)),
6304        };
6305    }
6306    match form {
6307        Sexp::List(items) => {
6308            let mut out: Vec<Sexp> = Vec::with_capacity(items.len());
6309            for item in items {
6310                if let Some((UnquoteForm::Splice, inner)) = item.as_unquote() {
6311                    let val = resolve_unquote_in_bindings(inner, bindings, UnquoteForm::Splice)?;
6312                    splice_value_into(&mut out, val);
6313                } else {
6314                    out.push(substitute(item, bindings)?);
6315                }
6316            }
6317            Ok(Sexp::List(out))
6318        }
6319        _ => Ok(form.clone()),
6320    }
6321}
6322
6323/// Lift the four inline `LispError::Compile { form: format!("{prefix}{name}"),
6324/// message: "unbound" }` triples (compile_node Unquote/UnquoteSplice +
6325/// substitute Unquote/UnquoteSplice) behind ONE named primitive. Pairs the
6326/// structural variant with `crate::domain::suggest`'s bounded edit-distance
6327/// scan over the candidate set so a typo in `,name` against a macro's params
6328/// (or against a substitution scope's live bindings) surfaces as
6329/// `"compile error in ,xs: unbound; did you mean ,x?"` instead of the bare
6330/// `"unbound"`. The candidate set is per-call — params during compile,
6331/// `bindings.keys()` during substitute — so the operator's hint is always
6332/// drawn from the in-scope name set, never a stale snapshot.
6333///
6334/// `prefix` is `UnquoteForm` — the closed-set typed enum whose two
6335/// variants are EXACTLY the two reachable syntactic markers
6336/// (`Unquote` ⊎ `Splice`). Threading the typed marker through the helper
6337/// boundary (rather than `&'static str`) lands the same compile-time
6338/// closed-set guarantee `defmacro_arity` / `defmacro_non_symbol_name` /
6339/// `defmacro_non_list_params` get from threading `MacroDefHead`: the
6340/// closed set is encoded in the type system, so a regression that drifts
6341/// the marker (e.g. a fourth `prefix: ",,"` call site) becomes a type
6342/// error at the call site, not a runtime substring drift. `name` is the
6343/// offender from source; the hint is `Option<String>` because the matched
6344/// candidate borrows from a transient `Vec<&str>` we built locally —
6345/// copying the matched name into the variant is the cheapest way to keep
6346/// `LispError` lifetime-free.
6347///
6348/// Theory anchor: THEORY.md §VI.1 — generation over composition; four inline
6349/// copies in one module is well past the three-times rule. THEORY.md §V.1 —
6350/// knowable platform; the structural variant exposes `prefix` / `name` /
6351/// `hint` as first-class fields so authoring tools (LSP, REPL,
6352/// `tatara-check`) bind to the data shape instead of substring-parsing the
6353/// rendered diagnostic.
6354fn unbound_template_var(prefix: UnquoteForm, name: &str, candidates: &[&str]) -> LispError {
6355    LispError::UnboundTemplateVar {
6356        prefix,
6357        name: name.to_string(),
6358        hint: crate::domain::suggest(name, candidates).map(str::to_string),
6359    }
6360}
6361
6362/// Lift the four inline `LispError::Compile { form: "unquote" /
6363/// "unquote-splice", message: "only bound symbols may appear after `,` /
6364/// `,@`" }` triples in this module (compile_node Unquote / UnquoteSplice +
6365/// substitute Unquote / UnquoteSplice-inside-list) behind ONE named
6366/// primitive. Sibling of `unbound_template_var`: that helper fires when the
6367/// slot IS a symbol but the symbol isn't bound; this helper fires when the
6368/// slot isn't a symbol at all. Together they close every distinct
6369/// typed-entry template-gate failure mode for the no-evaluator template
6370/// language: each is a structural variant of `LispError`, not a
6371/// `Compile`-shaped substring.
6372///
6373/// `prefix` is `UnquoteForm` — the closed-set typed enum whose two
6374/// variants are EXACTLY the two reachable syntactic markers
6375/// (`Unquote` ⊎ `Splice`). Threading the typed marker through the helper
6376/// boundary (rather than `&'static str`) lands the same compile-time
6377/// closed-set guarantee `unbound_template_var` carries: the closed set is
6378/// encoded in the type system. The inner is the offending `Sexp` routed
6379/// through `crate::domain::sexp_witness` — the typed joint projection
6380/// pairing `SexpShape` (structural shape) with `Sexp::Display`
6381/// (renderable literal) at ONE call boundary. Authoring tools bind to
6382/// BOTH `got.shape` (e.g. `SexpShape::List`) AND `got.display` (e.g.
6383/// `"(list 1 2)"`) jointly — same posture as `splice_outside_list`
6384/// after its prior-run promotion to `SexpWitness`. The two template-
6385/// gate `,X/,@X` rejection variants now share ONE typed witness
6386/// identity at their `got` slot.
6387///
6388/// Theory anchor: THEORY.md §VI.1 — generation over composition; four
6389/// inline copies in one module is past the three-times rule. THEORY.md
6390/// §V.1 — knowable platform; the structural variant exposes `prefix` /
6391/// `got` as first-class fields so authoring tools (LSP, REPL,
6392/// `tatara-check`) bind to the data shape instead of substring-parsing
6393/// the rendered diagnostic. THEORY.md §II.1 invariant 1 — typed entry;
6394/// a non-symbol unquote target is exactly the failure mode the
6395/// typed-entry gate exists to reject.
6396fn non_symbol_unquote_target(prefix: UnquoteForm, got: &Sexp) -> LispError {
6397    LispError::NonSymbolUnquoteTarget {
6398        prefix,
6399        got: got.witness(),
6400    }
6401}
6402
6403/// Project the inner of a `,X` / `,@X` form to its bound symbol name, or
6404/// raise the structural `LispError::NonSymbolUnquoteTarget` rejection at
6405/// the typed-entry template-gate boundary. ONE named primitive every
6406/// `,X` / `,@X` resolution site in the substrate shares — the inline
6407/// `inner.as_symbol().ok_or_else(|| non_symbol_unquote_target(form,
6408/// inner))?` pattern appeared four times across `compile_node`
6409/// (bytecode-path Unquote / UnquoteSplice arms) AND `substitute`
6410/// (substitute-path Unquote / list-inner UnquoteSplice arms), well past
6411/// the three-times-rule trigger. After this lift the four sites collapse
6412/// to a single `unquote_target_symbol(inner, form)?` call, and the
6413/// substrate's understanding of "an unquote target's first gate is `must
6414/// be a symbol`" lives in ONE function — a regression that drifts the
6415/// gate's posture (e.g. accepts non-symbol targets at the bytecode path
6416/// but not the substitute path) becomes a type-level change at this
6417/// helper, not a silent four-site divergence.
6418///
6419/// Sibling of `non_symbol_unquote_target` (the error builder this gate
6420/// calls on failure) and `unbound_template_var` (the typed-entry
6421/// template-gate's SECOND gate — fires once `unquote_target_symbol`
6422/// projects the symbol successfully but the symbol isn't bound in the
6423/// in-scope name set). Together the three close the substrate's
6424/// understanding of the two-step typed-entry template-gate: gate-1 is
6425/// `must-be-a-symbol`, gate-2 is `must-be-bound-in-scope`. With this
6426/// lift, gate-1 lives at ONE call boundary across all four template-
6427/// gate sites — bytecode path AND substitute path AND both `,X` and
6428/// `,@X` forms.
6429///
6430/// `form` is `UnquoteForm` — the closed-set typed enum whose two
6431/// variants are EXACTLY the two reachable syntactic markers
6432/// (`Unquote` ⊎ `Splice`). Threading the typed marker through the
6433/// helper boundary (rather than `&'static str`) lands the same
6434/// compile-time closed-set guarantee `non_symbol_unquote_target` and
6435/// `unbound_template_var` get from their `UnquoteForm` slots — a
6436/// regression that drifts the marker (e.g. a third pseudo-marker call
6437/// site) becomes a type error at the call site, not a runtime
6438/// substring drift. The returned `&'a str` borrows from `inner` — the
6439/// caller feeds it directly into `params.iter().position(|p| *p ==
6440/// name)` (`compile_node`) or `bindings.get(name)` (`substitute`)
6441/// without an intermediate allocation.
6442///
6443/// Theory anchor: THEORY.md §VI.1 — generation over composition; four
6444/// inline copies of the gate-1 projection (`compile_node`
6445/// Unquote/UnquoteSplice + `substitute` Unquote + `substitute`
6446/// list-inner UnquoteSplice) is past the three-times rule. THEORY.md
6447/// §V.1 — knowable platform; the gate's identity becomes a NAMED
6448/// primitive consumer-binding rather than a four-times-inlined
6449/// match-and-reject snippet — authoring surfaces (REPL, LSP,
6450/// `tatara-check`) that want to surface "the typed-entry template-gate
6451/// rejected your form because the unquote target wasn't a symbol" bind
6452/// to ONE function. THEORY.md §II.1 invariant 1 — typed entry; an
6453/// unquote target that isn't a symbol is exactly the failure mode the
6454/// typed-entry template-gate exists to reject. THEORY.md §II.1
6455/// invariant 2 — free middle; both bytecode AND substitute expansion
6456/// paths now project through the SAME gate-1 primitive, so a macro
6457/// that compiles under one strategy compiles under the other (the
6458/// gate's posture is uniform across the two strategies, no
6459/// per-strategy drift can creep in).
6460fn unquote_target_symbol(inner: &Sexp, form: UnquoteForm) -> Result<&str> {
6461    inner
6462        .as_symbol()
6463        .ok_or_else(|| non_symbol_unquote_target(form, inner))
6464}
6465
6466/// Gate-2 for the bytecode-template compile path: resolve a template
6467/// variable name to its index inside the macro's static param list, or
6468/// raise the structural `LispError::UnboundTemplateVar` rejection. ONE
6469/// named primitive that the two `compile_node` sites — `Sexp::Unquote(_)`
6470/// and `Sexp::UnquoteSplice(_)` arms — share. Before this lift the same
6471/// `params.iter().position(|p| *p == name).ok_or_else(|| unbound_template_var(
6472/// FORM, name, params))?` projection was inlined twice in one match
6473/// block; after this lift the two sites collapse to a single
6474/// `resolve_param_index(name, params, form)?` call and the
6475/// `Subst(idx)` / `Splice(idx)` ops push from a uniform projection
6476/// boundary.
6477///
6478/// Sibling of `resolve_binding`: the same gate-2 contract on the
6479/// substitute path. Together the two close the typed-entry template
6480/// gate's gate-2 (must-be-bound-in-scope) primitive across BOTH
6481/// expansion strategies — gate-1 (`unquote_target_symbol`) projects the
6482/// inner to a symbol name; gate-2 looks the name up in the in-scope
6483/// candidate set. The two paths' candidate sets differ structurally
6484/// (compile path: `&[&str]` of macro params, returning `usize`;
6485/// substitute path: `&HashMap<String, Sexp>` of live bindings, returning
6486/// `&Sexp`), so the gate-2 primitive bifurcates by path — but the
6487/// rejection shape (`LispError::UnboundTemplateVar { prefix, name, hint }`
6488/// with `crate::domain::suggest`-driven hint) is identical across both
6489/// paths. A regression that drifts gate-2's posture (e.g., accepts an
6490/// unbound `,name` at the bytecode path but not the substitute path) is
6491/// now a type-level change at this helper, not a silent four-site
6492/// divergence.
6493///
6494/// `form` is `UnquoteForm` — the closed-set typed enum whose two
6495/// variants are EXACTLY the two reachable syntactic markers
6496/// (`Unquote` ⊎ `Splice`). Threading the typed marker through the
6497/// helper boundary (rather than `&'static str`) lands the same
6498/// compile-time closed-set guarantee `unquote_target_symbol`,
6499/// `unbound_template_var`, and `non_symbol_unquote_target` carry — a
6500/// regression that drifts the marker becomes a type error at the call
6501/// site, not a runtime substring drift.
6502///
6503/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
6504/// inline copies of the gate-2 projection in one match block, paired
6505/// with the two substitute-path inline copies, is four copies in two
6506/// functions — past the three-times rule once the structural shape is
6507/// named. THEORY.md §V.1 — knowable platform; the gate's identity
6508/// becomes a NAMED primitive consumer-binding rather than a
6509/// twice-inlined position-and-reject snippet — authoring surfaces
6510/// (REPL, LSP, `tatara-check`) that want to surface "the typed-entry
6511/// template-gate rejected your form because the name isn't bound in
6512/// scope" bind to ONE function per path. THEORY.md §II.1 invariant 1 —
6513/// typed entry; an unbound template variable is exactly the failure
6514/// mode the typed-entry template-gate exists to reject. THEORY.md
6515/// §II.1 invariant 2 — free middle; both expansion strategies'
6516/// gate-2 emit the SAME structural variant, so a macro that compiles
6517/// under one strategy compiles under the other.
6518fn resolve_param_index(name: &str, params: &[&str], form: UnquoteForm) -> Result<usize> {
6519    params
6520        .iter()
6521        .position(|p| *p == name)
6522        .ok_or_else(|| unbound_template_var(form, name, params))
6523}
6524
6525/// Gate-2 for the substitute expansion path: resolve a template
6526/// variable name to its bound `Sexp` value inside the runtime bindings
6527/// map, or raise the structural `LispError::UnboundTemplateVar`
6528/// rejection. ONE named primitive that the two `substitute` sites —
6529/// the top-level `Sexp::Unquote(_)` arm and the list-inner
6530/// `Sexp::UnquoteSplice(_)` arm — share. Before this lift the same
6531/// `bindings.get(sym).<cloned>?.ok_or_else(|| unbound_template_var(
6532/// FORM, sym, &bound_names(bindings)))` projection was inlined twice
6533/// across the substitute walker; after this lift the two sites
6534/// collapse to a single `resolve_binding(bindings, sym, form)?` call
6535/// (with a trailing `.cloned()` at the top-level arm because that arm
6536/// returns an owned `Sexp` while the list-inner arm consumes the
6537/// `&Sexp` borrow directly).
6538///
6539/// Sibling of `resolve_param_index`: the same gate-2 contract on the
6540/// bytecode-template compile path. Together the two close the
6541/// typed-entry template gate's gate-2 (must-be-bound-in-scope)
6542/// primitive across BOTH expansion strategies. The candidate set on
6543/// the substitute path is the live bindings' keys (built fresh per
6544/// call via `bound_names`) — never a stale snapshot, so the
6545/// suggest-driven hint is always drawn from the actual in-scope name
6546/// set the operator sees.
6547///
6548/// The returned `&'a Sexp` borrows from `bindings` — the list-inner
6549/// caller feeds it straight into the `Sexp::List`/`Sexp::Nil`/other
6550/// splice-expansion match without an intermediate allocation. The
6551/// top-level caller's owned-Sexp obligation is satisfied by the
6552/// `.cloned()` projection at the call site, which is a single typed
6553/// `Sexp::clone` and not a redundant lookup.
6554///
6555/// `form` is `UnquoteForm` — same closed-set typed enum threading as
6556/// `resolve_param_index` and `unquote_target_symbol`. A regression
6557/// that drifts the marker becomes a type error at the call site, not
6558/// a runtime substring drift.
6559///
6560/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
6561/// inline copies of the gate-2 projection in the substitute walker,
6562/// paired with the two compile-path inline copies, is four copies in
6563/// two functions — past the three-times rule once the structural
6564/// shape is named. THEORY.md §V.1 — knowable platform; the gate's
6565/// identity becomes a NAMED primitive consumer-binding rather than a
6566/// twice-inlined lookup-and-reject snippet. THEORY.md §II.1
6567/// invariant 1 — typed entry; an unbound template variable is exactly
6568/// the failure mode the typed-entry template-gate exists to reject.
6569/// THEORY.md §II.1 invariant 2 — free middle; both expansion
6570/// strategies' gate-2 emit the SAME structural variant.
6571fn resolve_binding<'a>(
6572    bindings: &'a HashMap<String, Sexp>,
6573    name: &str,
6574    form: UnquoteForm,
6575) -> Result<&'a Sexp> {
6576    bindings
6577        .get(name)
6578        .ok_or_else(|| unbound_template_var(form, name, &bound_names(bindings)))
6579}
6580
6581/// Compose gate-1 + gate-2 for the bytecode-template compile path into ONE
6582/// named primitive: project the unquote `inner` to a symbol name
6583/// (gate-1, via `unquote_target_symbol`) THEN resolve the name to its
6584/// index inside the macro's static param list (gate-2, via
6585/// `resolve_param_index`). Sibling of `resolve_unquote_in_bindings`: the
6586/// same gate-1+gate-2 composition on the substitute expansion path.
6587///
6588/// Before this lift, the two `compile_node` arms (`Sexp::Unquote(_)` and
6589/// `Sexp::UnquoteSplice(_)`) threaded `form: UnquoteForm` through TWO
6590/// helper calls each — once into `unquote_target_symbol(inner, form)?`
6591/// (gate-1) AND once into `resolve_param_index(name, params, form)?`
6592/// (gate-2). The marker's typed identity was re-asserted at the call site
6593/// twice per arm — four `UnquoteForm::Unquote` / `UnquoteForm::Splice`
6594/// literal occurrences across the two arms, for what is structurally ONE
6595/// marker-identity per syntactic-marker arm. After this lift each arm
6596/// threads the marker ONCE through ONE call, and the gate-1-then-gate-2
6597/// sequencing lives in the helper body, not at the call site.
6598///
6599/// The composition is load-bearing: gate-1 (must-be-a-symbol) MUST fire
6600/// before gate-2 (must-be-bound-in-scope) — a non-symbol inner is
6601/// structurally a different failure mode (`LispError::NonSymbolUnquoteTarget`,
6602/// which carries the offending `SexpWitness`) than an unbound symbol
6603/// (`LispError::UnboundTemplateVar`, which carries a `name: String` plus
6604/// a `crate::domain::suggest`-driven hint over the candidate set). A
6605/// regression that reorders or skips gate-1 would emit
6606/// `LispError::UnboundTemplateVar { name: "(list 1 2)", ... }` for a
6607/// non-symbol inner (re-treating the rendered list literal as a bound-
6608/// name lookup key), which is exactly the diagnostic-confusion this
6609/// composition exists to rule out. Naming the composition as one
6610/// primitive makes the sequencing structural — the helper body IS the
6611/// proof that gate-1 ran before gate-2.
6612///
6613/// `form` is `UnquoteForm` — the closed-set typed enum threaded through
6614/// the composition once and passed onward to both gate-1 and gate-2's
6615/// rejection-builders. Same posture as `unquote_target_symbol`,
6616/// `resolve_param_index`, `resolve_binding`, `non_symbol_unquote_target`,
6617/// and `unbound_template_var` — a regression that drifts the marker
6618/// becomes a type error at the helper boundary, not a runtime substring
6619/// drift, AND the marker can no longer drift BETWEEN gate-1 and gate-2
6620/// at a single call site (which the prior pre-lift shape allowed:
6621/// `unquote_target_symbol(inner, UnquoteForm::Unquote)?` followed by
6622/// `resolve_param_index(name, params, UnquoteForm::Splice)?` would
6623/// type-check but render a misleading diagnostic).
6624///
6625/// Theory anchor: THEORY.md §VI.1 — generation over composition; the
6626/// gate-1+gate-2 SEQUENCE is itself a named primitive once both halves
6627/// have been named (two prior runs landed the halves; this run lands the
6628/// composition). THEORY.md §V.1 — knowable platform; the gate's
6629/// composition is now load-bearing in the type system — gate-1 cannot be
6630/// silently skipped, gate-2 cannot be silently reordered before gate-1,
6631/// and the marker cannot drift between the two halves. THEORY.md §II.1
6632/// invariant 1 — typed entry; the typed-entry template gate's full
6633/// rejection chain (non-symbol → unbound-symbol) is now ONE primitive.
6634/// THEORY.md §II.1 invariant 2 — free middle; both expansion strategies
6635/// expose the gate's identity as ONE primitive per path, so a macro that
6636/// passes the gate under one strategy passes under the other (no per-
6637/// strategy composition drift can creep in).
6638fn resolve_unquote_in_params(inner: &Sexp, params: &[&str], form: UnquoteForm) -> Result<usize> {
6639    let name = unquote_target_symbol(inner, form)?;
6640    resolve_param_index(name, params, form)
6641}
6642
6643/// Compose gate-1 + gate-2 for the substitute expansion path into ONE
6644/// named primitive: project the unquote `inner` to a symbol name
6645/// (gate-1, via `unquote_target_symbol`) THEN resolve the name to its
6646/// bound `Sexp` value inside the runtime bindings map (gate-2, via
6647/// `resolve_binding`). Sibling of `resolve_unquote_in_params`: the same
6648/// gate-1+gate-2 composition on the bytecode-template compile path.
6649///
6650/// Before this lift, the substitute walker's two unquote sites (the
6651/// top-level `Sexp::Unquote(_)` arm and the list-inner
6652/// `Sexp::UnquoteSplice(_)` arm) threaded `form: UnquoteForm` through
6653/// TWO helper calls each — once into `unquote_target_symbol(inner,
6654/// form)?` (gate-1) AND once into `resolve_binding(bindings, name,
6655/// form)?` (gate-2). After this lift each site threads the marker
6656/// ONCE through ONE call. Same composition contract as
6657/// `resolve_unquote_in_params` — gate-1 fires before gate-2 by the
6658/// helper body's `?`-then-call sequencing, NOT by call-site discipline.
6659///
6660/// The returned `&'a Sexp` borrows from `bindings` so the list-inner
6661/// caller feeds it straight into the `Sexp::List`/`Sexp::Nil`/other
6662/// splice-expansion match without an intermediate allocation; the
6663/// top-level caller's owned-Sexp obligation is satisfied by a
6664/// `.cloned()` projection at the call site (one typed `Sexp::clone`,
6665/// no redundant lookup).
6666///
6667/// `form` is `UnquoteForm` — same closed-set typed enum threading as
6668/// `resolve_unquote_in_params` and all the helpers it composes. After
6669/// this lift, the marker's identity flows through the substitute path's
6670/// typed-entry template gate via ONE explicit pass per call site, not
6671/// two; the gate's gate-1+gate-2 sequencing is structural across both
6672/// expansion strategies.
6673///
6674/// Theory anchor: same as `resolve_unquote_in_params`. THEORY.md §VI.1
6675/// (generation over composition; named composition of named gates),
6676/// THEORY.md §V.1 (knowable platform; gate composition is type-system
6677/// load-bearing), THEORY.md §II.1 invariant 1 (typed entry; the full
6678/// rejection chain is ONE primitive), THEORY.md §II.1 invariant 2
6679/// (free middle; both strategies share the same composition shape).
6680fn resolve_unquote_in_bindings<'a>(
6681    inner: &Sexp,
6682    bindings: &'a HashMap<String, Sexp>,
6683    form: UnquoteForm,
6684) -> Result<&'a Sexp> {
6685    let name = unquote_target_symbol(inner, form)?;
6686    resolve_binding(bindings, name, form)
6687}
6688
6689/// Lift the lone `LispError::Compile { form: "unquote-splice", message:
6690/// "`,@` may only appear inside a list" }` triple — the substitute path's
6691/// top-level `,@X` rejection — behind ONE named primitive. Sibling of
6692/// `non_symbol_unquote_target` and `unbound_template_var`: those helpers
6693/// fire when the slot inside a `,X` / `,@X` is malformed (non-symbol or
6694/// unbound symbol); this helper fires when the `,@X` form itself is
6695/// ill-positioned (no containing list to flatten into). Together the three
6696/// close every distinct typed-entry template-gate failure mode for the
6697/// no-evaluator template language: each is a structural variant of
6698/// `LispError`, not a `Compile`-shaped substring.
6699///
6700/// `inner` is the offending `Sexp` projected through `Display` so the
6701/// operator sees the literal value they wrote — `xs`, `(list 1 2)`, `5` —
6702/// instead of just the bare "may only appear inside a list" verdict. The
6703/// helper takes `&Sexp` (parallel to `non_symbol_unquote_target`) and
6704/// projects through `to_string()` at the variant boundary; the `prefix:
6705/// &'static str` slot is implicit (always `,@`) and absent from the variant
6706/// itself, parallel to how `OddKwargs { dangling }` names ONE failure mode
6707/// without a syntactic-marker slot.
6708///
6709/// Used by both the substitute path (top-level `,@X` body) AND the bytecode
6710/// path's `compile_template` gate (top-level `,@X` body — closing the prior
6711/// silent-divergence where the bytecode interpreter's outermost stack frame
6712/// absorbed the splice). After this lift `,@-outside-list` is rejected on
6713/// both paths with ONE structural variant — the typed-entry template gate
6714/// is fully structural across both expansion strategies.
6715///
6716/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
6717/// emission sites (substitute + compile_template) for one failure mode is
6718/// past the three-times rule once the structural shape is named. THEORY.md
6719/// §V.1 — knowable platform; the structural variant exposes `got` as a
6720/// first-class field so authoring tools (LSP, REPL, `tatara-check`) bind to
6721/// the data shape instead of substring-parsing the rendered diagnostic.
6722/// THEORY.md §II.1 invariant 1 — typed entry; a `,@X` at a position with no
6723/// containing list is exactly the failure mode the typed-entry gate exists
6724/// to reject. THEORY.md §II.1 invariant 2 — free middle; both expansion
6725/// paths now reject the same set of templates, so a macro that registers
6726/// successfully has the same expansion behavior under either strategy.
6727fn splice_outside_list(inner: &Sexp) -> LispError {
6728    LispError::SpliceOutsideList {
6729        got: inner.witness(),
6730    }
6731}
6732
6733/// Lift the two inline `LispError::Compile { form: format!("call to
6734/// {macro_name}"), message: format!("missing required arg: {name}") }`
6735/// triples — `bind_args` (substitute path) AND `apply_compiled` (bytecode
6736/// path) — behind ONE named primitive. Sibling of the typed-entry kwargs
6737/// `MissingKwarg { key }` lift: that variant fires when a `(<head> :key
6738/// value …)` kwargs form omits a required keyword; this variant fires when
6739/// a `(<macroname> a b …)` call omits a required positional param. Together
6740/// they close every distinct typed-entry missing-required surface in the
6741/// substrate — kwargs-gate AND macro-call-gate now share a single
6742/// structural-variant idiom.
6743///
6744/// Same single emission shape across both expansion strategies — before
6745/// this lift the same failure mode emitted byte-identical
6746/// `LispError::Compile { … }` triples at TWO call sites; after this lift
6747/// both sites share ONE structural variant. Two strategies that picked
6748/// different code paths now emit the same structural variant for the same
6749/// failure mode (THEORY.md §II.1 invariant 2 — free middle: which strategy
6750/// you picked must not change which inputs you reject OR how the rejection
6751/// is shaped). Same posture as `splice_outside_list`'s path-uniform
6752/// rejection across substitute + compile_template.
6753///
6754/// `macro_name` and `name` are `&str` borrows from the call-site / param
6755/// list; the variant's owned `String`s are formed at the boundary so
6756/// `LispError` stays lifetime-free.
6757///
6758/// Theory anchor: THEORY.md §VI.1 — generation over composition; two
6759/// inline copies of one shape is past the three-times-rule trigger once
6760/// the structural variant is named (the test count gives this the
6761/// fail-before-pass-after edge). THEORY.md §V.1 — knowable platform; the
6762/// structural variant exposes `macro_name` / `param` as first-class
6763/// fields so authoring tools (LSP, REPL, `tatara-check`) bind to the data
6764/// shape instead of substring-parsing the rendered diagnostic. THEORY.md
6765/// §II.1 invariant 1 — typed entry; a macro call with too few args is
6766/// exactly the failure mode the typed-entry gate exists to reject.
6767fn missing_macro_arg(macro_name: &str, param: &str) -> LispError {
6768    LispError::MissingMacroArg {
6769        macro_name: macro_name.to_string(),
6770        param: param.to_string(),
6771    }
6772}
6773
6774/// Mirror at the call-site of `missing_macro_arg`: that helper fires when
6775/// the macro CALL supplies TOO FEW args for the required arity (a required
6776/// slot has no arg); this helper fires when the macro CALL supplies TOO
6777/// MANY args for a rest-less param list (the surplus has nowhere to bind).
6778/// Together they close the typed-entry macro-call-gate's positional-arity
6779/// surface in both directions; together with the definition-site
6780/// `RestParamTrailingTokens` (lifted by the prior-run typed-promotion
6781/// lineage at the parse_params boundary), every distinct way a macro
6782/// definition + call pair can MISCOUNT args is now a named structural
6783/// rejection.
6784///
6785/// `expected` is the rest-less binder's fixed maximum arity
6786/// (`required.len() + optional.len()`); `got` is the actual call-site arg
6787/// count. Both are surfaced at the variant boundary so authoring tools
6788/// (REPL, LSP, `tatara-check`) name the "you supplied {got} args but the
6789/// macro takes at most {expected}" quick-fix from one structural projection
6790/// rather than re-deriving either count from the source. `macro_name` is
6791/// `&str` borrowed from the call-site; the variant's owned `String` is
6792/// formed at the boundary so `LispError` stays lifetime-free — same posture
6793/// as `missing_macro_arg`.
6794///
6795/// Theory anchor: THEORY.md §VI.1 — generation over composition; the
6796/// rest-less surplus-args gate is a SINGLE-OWNER named rejection, not a
6797/// silent truncation re-asserted at every consumer that walks the bound
6798/// values. THEORY.md §V.1 — knowable platform; the structural variant
6799/// exposes `macro_name` / `expected` / `got` as first-class fields so
6800/// authoring tools bind to the data shape instead of substring-parsing
6801/// the rendered diagnostic. THEORY.md §II.1 invariant 1 — typed entry; a
6802/// macro call with too many args (and no `&rest` slot to absorb them) is
6803/// exactly the failure mode the typed-entry gate exists to reject —
6804/// silently dropping `args[expected..]` is structurally indistinguishable
6805/// from honoring them, the asymmetry this gate closes. THEORY.md §II.1
6806/// invariant 2 — free middle; both expansion strategies route through the
6807/// SHARED `MacroParams::bind`, so the new rejection lands once and the
6808/// substitute + bytecode paths inherit it unable to drift.
6809fn too_many_macro_args(macro_name: &str, expected: usize, got: usize) -> LispError {
6810    LispError::TooManyMacroArgs {
6811        macro_name: macro_name.to_string(),
6812        expected,
6813        got,
6814    }
6815}
6816
6817/// Lift the lone `LispError::Compile { form: "defmacro params", message:
6818/// "expected symbol" }` triple in `parse_params` behind ONE named
6819/// primitive. Sibling of `missing_macro_arg`: that helper fires when the
6820/// macro CALL is malformed (call-site missing a positional arg); this
6821/// helper fires when the macro DEFINITION is malformed (definition-site
6822/// has a non-symbol where a param name should be). Together they open
6823/// the defmacro-syntax-gate / macro-call-gate split — call-site
6824/// rejections vs. definition-site rejections — each as its own
6825/// structural-variant family on `LispError`.
6826///
6827/// `position` is the loop index inside `parse_params`, i.e. the 0-based
6828/// index of the offending element within the param list (`(defmacro f
6829/// (a 5 b) …)` — position 1 is the literal `5`); naming it lets an LSP
6830/// quick-fix point at the exact list element instead of the whole
6831/// param list. `got` is the offending `Sexp` projected through
6832/// `Display` so the operator sees the literal value they wrote
6833/// (`5`, `:foo`, `(nested)`) at the variant boundary; the helper takes
6834/// `&Sexp` (parallel to `non_symbol_unquote_target` and
6835/// `splice_outside_list`) and projects through `to_string()` so the
6836/// variant stays lifetime-free.
6837///
6838/// Theory anchor: THEORY.md §VI.1 — generation over composition; one
6839/// inline copy still earns a named primitive once the structural shape
6840/// is named (the test count gives this the fail-before-pass-after edge,
6841/// parallel to how `OddKwargs` was lifted from a single site for the
6842/// structural-completeness payoff). THEORY.md §V.1 — knowable platform;
6843/// the structural variant exposes `position` / `got` as first-class
6844/// fields so authoring tools (LSP, REPL, `tatara-check`) bind to the
6845/// data shape instead of substring-parsing the rendered diagnostic.
6846/// THEORY.md §II.1 invariant 1 — typed entry; a non-symbol element
6847/// inside a defmacro param list is exactly the failure mode the
6848/// typed-entry gate exists to reject — and it must reject DEFINITIONS
6849/// as readily as it rejects CALLS.
6850fn non_symbol_param(position: usize, got: &Sexp) -> LispError {
6851    LispError::NonSymbolParam {
6852        position,
6853        got: got.witness(),
6854    }
6855}
6856
6857/// Lift the lone `LispError::Compile { form: "defmacro params", message:
6858/// "&rest needs a name" }` triple in `parse_params` behind ONE named
6859/// primitive. Sibling of `non_symbol_param`: that helper fires when a
6860/// NON-`&rest` element at a param position isn't a symbol; this helper
6861/// fires specifically on the post-`&rest` follower slot, where the
6862/// failure mode bifurcates into "missing entirely" (`got = None`) vs.
6863/// "present but not a symbol" (`got = Some(...)`). Together, the two
6864/// helpers close the `parse_params` walker — every distinct failure
6865/// mode the walker can emit is now a structural variant of `LispError`,
6866/// not a `Compile`-shaped substring.
6867///
6868/// `rest_position` is the loop index inside `parse_params` at which
6869/// the `&rest` marker was matched, i.e. the 0-based index of `&rest`
6870/// within the param list (`(defmacro f (a &rest 5) …)` — rest_position
6871/// 1 is `&rest`, the offender follows at 2); naming the marker
6872/// position lets an LSP quick-fix point at the `&rest` form itself
6873/// rather than at the next list element. `got` is `Option<&Sexp>`
6874/// because the follower slot bifurcates: `None` when the marker was
6875/// the param list's last element (no follower at all), `Some(sexp)`
6876/// when a follower exists but isn't a symbol; the helper projects
6877/// through `to_string()` at the variant boundary so the variant stays
6878/// lifetime-free.
6879///
6880/// Theory anchor: THEORY.md §VI.1 — generation over composition; one
6881/// inline copy still earns a named primitive once the structural shape
6882/// is named (the test count gives this the fail-before-pass-after
6883/// edge, parallel to how `non_symbol_param` was lifted from a single
6884/// site for the structural-completeness payoff). THEORY.md §V.1 —
6885/// knowable platform; the structural variant exposes `rest_position` /
6886/// `got` as first-class fields so authoring tools (LSP, REPL,
6887/// `tatara-check`) bind to the data shape instead of substring-parsing
6888/// the rendered diagnostic. THEORY.md §II.1 invariant 1 — typed entry;
6889/// a `&rest` marker followed by no name (or by a non-symbol) is
6890/// exactly the failure mode the typed-entry gate exists to reject —
6891/// and the gate must reject DEFINITIONS as readily as it rejects
6892/// CALLS.
6893fn rest_param_missing_name(rest_position: usize, got: Option<&Sexp>) -> LispError {
6894    LispError::RestParamMissingName {
6895        rest_position,
6896        got: got.map(Sexp::witness),
6897    }
6898}
6899
6900/// The third and final `parse_params` definition-site rejection — a
6901/// `&rest <name>` followed by further tokens. Sibling of `non_symbol_param`
6902/// (a param slot that isn't a symbol) and `rest_param_missing_name` (the
6903/// post-`&rest` follower is missing or malformed): this helper fires once
6904/// the rest name is bound and the walker finds the param list does not end
6905/// there. The `&rest` name absorbs every remaining call arg, so it is
6906/// structurally the LAST param a list can name; trailing tokens are
6907/// unrepresentable in `MacroParams` and were previously dropped silently.
6908///
6909/// `rest_position` is the loop index of the `&rest` marker (parallel to
6910/// `rest_param_missing_name`); `trailing` is the non-empty token run after
6911/// the bound rest name — the helper records its length and the typed
6912/// witness of its first element. The caller guarantees `trailing` is
6913/// non-empty (it is only built when `list[i + 2..].first()` is `Some`), so
6914/// `trailing[0]` does not panic.
6915///
6916/// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid states
6917/// unrepresentable"; a param list with tokens after `&rest <name>` is
6918/// nonsense `MacroParams` cannot hold, so the gate must REJECT it rather
6919/// than truncate to the representable prefix. THEORY.md §II.1 invariant 1 —
6920/// typed entry; the gate rejects malformed DEFINITIONS as readily as
6921/// malformed calls. THEORY.md §VI.1 — generation over composition; this
6922/// closes the `parse_params` walker's last uncovered failure mode, making
6923/// the sibling docs' "every distinct failure mode is a structural variant"
6924/// claim finally true.
6925fn rest_param_trailing_tokens(rest_position: usize, trailing: &[Sexp]) -> LispError {
6926    LispError::RestParamTrailingTokens {
6927        rest_position,
6928        extra: trailing.len(),
6929        first: trailing[0].witness(),
6930    }
6931}
6932
6933/// A `&optional` marker appeared a SECOND time in one param list —
6934/// `(defmacro f (a &optional b &optional c) …)`. The lambda-list has exactly
6935/// ONE optional section (between the required run and the rest); a second
6936/// `&optional` is nonsense `MacroParams` cannot hold (its `optional` field is
6937/// one flat run, not a sequence of sections). Without this gate the parser
6938/// would otherwise treat the second `&optional` as an optional param literally
6939/// NAMED `&optional`, binding call args to a marker symbol — exactly the kind
6940/// of silent misalignment the typed shape exists to forbid.
6941///
6942/// Sibling of `rest_param_trailing_tokens` (the rest-section ordering gate):
6943/// both reject a param list whose marker structure the canonical lambda-list
6944/// ordering cannot represent. `first_position` is the loop index of the
6945/// first `&optional`, `second_position` the second — naming both lets an LSP
6946/// quick-fix point at the redundant marker to delete.
6947///
6948/// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid states
6949/// unrepresentable"; a param list with two `&optional` sections is nonsense
6950/// `MacroParams` cannot hold, so the gate must REJECT rather than bind args
6951/// to a marker symbol. THEORY.md §II.1 invariant 1 — typed entry; the gate
6952/// rejects malformed DEFINITIONS as readily as malformed calls.
6953fn optional_marker_repeated(first_position: usize, second_position: usize) -> LispError {
6954    LispError::OptionalMarkerRepeated {
6955        first_position,
6956        second_position,
6957    }
6958}
6959
6960/// An `&optional` section entry that's a `Sexp::List` did NOT match the only
6961/// admissible shape `(NAME DEFAULT)` — exactly two elements with a symbol
6962/// head. This helper builds the structural rejection from the loop position,
6963/// the offending list form (projected through `crate::domain::sexp_witness`
6964/// to carry both `SexpShape::List` and the literal display), and the typed
6965/// `OptionalParamMalformedReason` naming which of the four malformed shapes
6966/// fired (empty list / missing default / extra elements / non-symbol name).
6967///
6968/// Sibling of `optional_marker_repeated` (the `&optional`-section marker
6969/// gate) and `non_symbol_param` (the bare-symbol gate): the three together
6970/// close every distinct typed-entry rejection the optional section can
6971/// emit. The bare-symbol form `&optional x` is still routed through
6972/// `non_symbol_param`'s sibling acceptance path; the list form `&optional
6973/// (x default)` is admitted iff this gate accepts the spec.
6974///
6975/// Theory anchor: THEORY.md §V.1 — knowable platform / "make invalid states
6976/// unrepresentable"; an `&optional` list spec of any other shape is
6977/// nonsense `MacroParams` cannot hold, so the gate must REJECT rather than
6978/// bind args to a marker symbol or drop the extras silently. THEORY.md
6979/// §II.1 invariant 1 — typed entry; a malformed default-form spec is
6980/// exactly the failure mode the typed-entry gate exists to reject — and
6981/// the gate must reject DEFINITIONS as readily as it rejects CALLS.
6982fn optional_param_malformed(
6983    position: usize,
6984    got: &Sexp,
6985    reason: crate::error::OptionalParamMalformedReason,
6986) -> LispError {
6987    LispError::OptionalParamMalformed {
6988        position,
6989        got: got.witness(),
6990        reason,
6991    }
6992}
6993
6994/// Lift the lone `LispError::Compile { form: head.to_string(), message:
6995/// "(defmacro name (params) body) required" }` triple in
6996/// `macro_def_from` behind ONE named primitive. Sibling of
6997/// `non_symbol_param` and `rest_param_missing_name`: those helpers
6998/// fire INSIDE `parse_params`, AFTER the arity gate has passed; this
6999/// helper fires AT the arity gate itself, BEFORE name / params / body
7000/// validation can run. Together the three close `macro_def_from`'s
7001/// outermost rejection chain — every distinct failure mode the gate
7002/// can emit at the top level becomes a structural variant of
7003/// `LispError`, not a `Compile`-shaped substring.
7004///
7005/// `head` is `MacroDefHead` (the typed closed-set enum), having been
7006/// projected through `MacroDefHead::from_keyword` at the top of
7007/// `macro_def_from`. The helper threads `head` straight into the
7008/// variant's typed `head: MacroDefHead` slot — no `&'static str`
7009/// projection at the helper boundary; the projection through
7010/// `MacroDefHead::keyword()` happens at Display rendering time via
7011/// `MacroDefHead`'s Display impl inside the variant's `#[error(...)]`
7012/// annotation. Same posture as how
7013/// `compiler_spec.rs::compiler_spec_io_err` threads
7014/// `CompilerSpecIoStage` straight into
7015/// `LispError::CompilerSpecIo.stage`. `arity` is `usize` (the length
7016/// of the form including the head element).
7017///
7018/// Theory anchor: THEORY.md §VI.1 — generation over composition; one
7019/// inline copy still earns a named primitive once the structural
7020/// shape is named (the test count gives this the fail-before/pass-
7021/// after edge, parallel to how `non_symbol_param` and
7022/// `rest_param_missing_name` were lifted from a single site for the
7023/// structural-completeness payoff). THEORY.md §V.1 — knowable
7024/// platform; the structural variant exposes `head` / `arity` as
7025/// first-class fields so authoring tools (LSP, REPL, `tatara-check`)
7026/// bind to the data shape instead of substring-parsing the rendered
7027/// diagnostic. THEORY.md §II.1 invariant 1 — typed entry; a defmacro
7028/// form with too few elements is exactly the failure mode the typed-
7029/// entry gate exists to reject — and the gate must reject
7030/// DEFINITIONS as readily as it rejects CALLS. THEORY.md §II.1
7031/// invariant 2 — free middle; the arity gate fires inside
7032/// `macro_def_from` BEFORE either expansion strategy runs, so both
7033/// `Expander::new()` (bytecode) and `Expander::new_substitute_only()`
7034/// (substitute) reject the SAME malformed defmacro at the SAME gate.
7035fn defmacro_arity(head: MacroDefHead, arity: usize) -> LispError {
7036    LispError::DefmacroArity { head, arity }
7037}
7038
7039/// Lift the lone `LispError::Compile { form: head.to_string(), message:
7040/// "expected name symbol" }` triple in `macro_def_from` behind ONE
7041/// named primitive. Sibling of `defmacro_arity`, `non_symbol_param`,
7042/// and `rest_param_missing_name`: those helpers fire at the OUTERMOST
7043/// arity gate (`defmacro_arity`) or INSIDE `parse_params`
7044/// (`non_symbol_param`, `rest_param_missing_name`); this helper fires
7045/// AFTER the arity gate has passed but BEFORE `parse_params` runs —
7046/// at the second of three `macro_def_from` rejection points
7047/// (arity → name-symbol → param-list → parse_params).
7048///
7049/// Walking a malformed `(defmacro …)` from the outside in, the gate
7050/// fires:
7051///   1. `defmacro_arity(head, arity)` if the form has fewer than 4
7052///      elements (`(defmacro)`, `(defmacro f)`).
7053///   2. `defmacro_non_symbol_name(head, &list[1])` if list[1] isn't a
7054///      symbol (`(defmacro 5 () body)`, `(defmacro :foo () body)`).
7055///   3. The `expected param list` gate (NEXT LIFT) if list[2] isn't a
7056///      list (`(defmacro f x body)`).
7057///   4. Inside `parse_params`: `non_symbol_param` and
7058///      `rest_param_missing_name`.
7059///
7060/// After this lift step 2 is structural; the only remaining
7061/// `Compile`-shaped site in `macro_def_from` is step 3 (`expected
7062/// param list`).
7063///
7064/// `head` is `MacroDefHead` (the typed closed-set enum), having been
7065/// projected through `MacroDefHead::from_keyword` at the top of
7066/// `macro_def_from`. The helper threads `head` straight into the
7067/// variant's typed `head: MacroDefHead` slot — same posture as
7068/// `defmacro_arity` after the typed-slot promotion. `got` is `&Sexp`
7069/// at the call site (a borrow into the form's name slot); the helper
7070/// projects through `crate::domain::sexp_witness` — the typed joint
7071/// projection (`SexpShape` + `Sexp::Display`) — so the variant's
7072/// `got: SexpWitness` slot carries BOTH structural shape AND
7073/// renderable literal across the boundary, parallel to how
7074/// `non_symbol_param` and `non_symbol_unquote_target` project their
7075/// `&Sexp` arguments. The fourth consumer of the typed `SexpWitness`
7076/// primitive on the substrate's Sexp-display-source rejection
7077/// surface.
7078///
7079/// Theory anchor: THEORY.md §VI.1 — generation over composition; one
7080/// inline copy still earns a named primitive once the structural
7081/// shape is named (the test count gives this the fail-before/pass-
7082/// after edge, parallel to how `defmacro_arity`, `non_symbol_param`,
7083/// and `rest_param_missing_name` were lifted from a single site for
7084/// the structural-completeness payoff). THEORY.md §V.1 — knowable
7085/// platform; the structural variant exposes `head` / `got` as
7086/// first-class fields so authoring tools (LSP, REPL,
7087/// `tatara-check`) bind to the data shape instead of substring-
7088/// parsing the rendered diagnostic. THEORY.md §II.1 invariant 1 —
7089/// typed entry; a defmacro form whose name slot isn't a symbol is
7090/// exactly the failure mode the typed-entry gate exists to reject —
7091/// and the gate must reject DEFINITIONS as readily as it rejects
7092/// CALLS. THEORY.md §II.1 invariant 2 — free middle; the
7093/// name-symbol gate fires inside `macro_def_from` BEFORE either
7094/// expansion strategy runs, so both `Expander::new()` (bytecode) and
7095/// `Expander::new_substitute_only()` (substitute) reject the SAME
7096/// malformed defmacro at the SAME gate.
7097fn defmacro_non_symbol_name(head: MacroDefHead, got: &Sexp) -> LispError {
7098    LispError::DefmacroNonSymbolName {
7099        head,
7100        got: got.witness(),
7101    }
7102}
7103
7104/// Lift the lone `LispError::Compile { form: head.to_string(), message:
7105/// "expected param list" }` triple in `macro_def_from` behind ONE
7106/// named primitive. Sibling of `defmacro_arity`,
7107/// `defmacro_non_symbol_name`, `non_symbol_param`, and
7108/// `rest_param_missing_name`: those helpers fire at the OUTERMOST
7109/// arity gate (`defmacro_arity`), at the second `macro_def_from`
7110/// rejection point (`defmacro_non_symbol_name`), or INSIDE
7111/// `parse_params` (`non_symbol_param`, `rest_param_missing_name`);
7112/// this helper fires AFTER both the arity gate AND the name-symbol
7113/// gate have passed but BEFORE `parse_params` runs — at the third
7114/// of three `macro_def_from` rejection points
7115/// (arity → name-symbol → param-list → parse_params).
7116///
7117/// Walking a malformed `(defmacro …)` from the outside in, the gate
7118/// fires:
7119///   1. `defmacro_arity(head, arity)` if the form has fewer than 4
7120///      elements (`(defmacro)`, `(defmacro f)`).
7121///   2. `defmacro_non_symbol_name(head, &list[1])` if list[1] isn't
7122///      a symbol (`(defmacro 5 () body)`).
7123///   3. `defmacro_non_list_params(head, &list[2])` if list[2] isn't
7124///      a list (`(defmacro f x body)`, `(defmacro f 5 body)`).
7125///   4. Inside `parse_params`: `non_symbol_param` and
7126///      `rest_param_missing_name`.
7127///
7128/// After this lift step 3 is structural; every inline
7129/// `LispError::Compile { … }` triple in `macro_def_from` has been
7130/// lifted to a structural variant — the entire `macro_def_from`
7131/// rejection chain is structurally typed for failure modes.
7132///
7133/// `head` is `MacroDefHead` (the typed closed-set enum), having been
7134/// projected through `MacroDefHead::from_keyword` at the top of
7135/// `macro_def_from`. The helper threads `head` straight into the
7136/// variant's typed `head: MacroDefHead` slot — same posture as
7137/// `defmacro_arity` and `defmacro_non_symbol_name` after the
7138/// typed-slot promotion. `got` is `&Sexp` at the call site (a
7139/// borrow into the form's param-list slot); the helper projects
7140/// through `crate::domain::sexp_witness(_)` — the typed joint
7141/// primitive that pairs the offending `Sexp`'s `SexpShape` with its
7142/// `Sexp::Display` projection in ONE owned `SexpWitness` value, so
7143/// authoring tools bind to both the structural shape AND the rendered
7144/// literal across the variant slot. Same posture as `non_symbol_param`,
7145/// `non_symbol_unquote_target`, `splice_outside_list`, and
7146/// `defmacro_non_symbol_name`'s helpers after the typed-witness
7147/// promotion of their `got` slots.
7148///
7149/// Theory anchor: THEORY.md §VI.1 — generation over composition; one
7150/// inline copy still earns a named primitive once the structural
7151/// shape is named (the test count gives this the fail-before/pass-
7152/// after edge, parallel to how `defmacro_arity`,
7153/// `defmacro_non_symbol_name`, `non_symbol_param`, and
7154/// `rest_param_missing_name` were lifted from a single site for
7155/// the structural-completeness payoff). THEORY.md §V.1 — knowable
7156/// platform; the structural variant exposes `head` / `got` as
7157/// first-class fields so authoring tools (LSP, REPL,
7158/// `tatara-check`) bind to the data shape instead of substring-
7159/// parsing the rendered diagnostic. THEORY.md §II.1 invariant 1 —
7160/// typed entry; a defmacro form whose param-list slot isn't a list
7161/// is exactly the failure mode the typed-entry gate exists to
7162/// reject — and the gate must reject DEFINITIONS as readily as it
7163/// rejects CALLS. THEORY.md §II.1 invariant 2 — free middle; the
7164/// param-list gate fires inside `macro_def_from` BEFORE either
7165/// expansion strategy runs, so both `Expander::new()` (bytecode)
7166/// and `Expander::new_substitute_only()` (substitute) reject the
7167/// SAME malformed defmacro at the SAME gate.
7168fn defmacro_non_list_params(head: MacroDefHead, got: &Sexp) -> LispError {
7169    LispError::DefmacroNonListParams {
7170        head,
7171        got: got.witness(),
7172    }
7173}
7174
7175/// Project a `bindings: &HashMap<String, Sexp>` into the `&[&str]` candidate
7176/// set `crate::domain::suggest` wants. Cold path — only allocated when an
7177/// `,name` / `,@name` substitution misses, i.e. when we're already on the
7178/// diagnostic side of the substitute walker.
7179fn bound_names(bindings: &HashMap<String, Sexp>) -> Vec<&str> {
7180    bindings.keys().map(String::as_str).collect()
7181}
7182
7183#[cfg(test)]
7184mod tests {
7185    use super::*;
7186    use crate::reader::read;
7187
7188    fn parse(src: &str) -> Sexp {
7189        read(src).unwrap().into_iter().next().unwrap()
7190    }
7191
7192    #[test]
7193    fn identity_macro() {
7194        let mut e = Expander::new();
7195        let forms = read("(defmacro id (x) `,x) (id 42)").unwrap();
7196        let out = e.expand_program(forms).unwrap();
7197        assert_eq!(out.len(), 1);
7198        assert_eq!(out[0], Sexp::int(42));
7199    }
7200
7201    #[test]
7202    fn wrap_macro_duplicates_arg() {
7203        let mut e = Expander::new();
7204        let forms = read("(defmacro wrap (x) `(list ,x ,x)) (wrap hello)").unwrap();
7205        let out = e.expand_program(forms).unwrap();
7206        assert_eq!(out[0], parse("(list hello hello)"));
7207    }
7208
7209    #[test]
7210    fn rest_param_splices_with_at() {
7211        let mut e = Expander::new();
7212        let forms = read("(defmacro call (f &rest args) `(,f ,@args)) (call foo a b c)").unwrap();
7213        let out = e.expand_program(forms).unwrap();
7214        assert_eq!(out[0], parse("(foo a b c)"));
7215    }
7216
7217    #[test]
7218    fn nested_macro_expansion() {
7219        let mut e = Expander::new();
7220        let forms = read(
7221            "(defmacro twice (x) `(list ,x ,x))
7222             (defmacro quad (x) `(twice ,x))
7223             (quad hey)",
7224        )
7225        .unwrap();
7226        let out = e.expand_program(forms).unwrap();
7227        assert_eq!(out[0], parse("(list hey hey)"));
7228    }
7229
7230    #[test]
7231    fn unbound_unquote_errors() {
7232        let mut e = Expander::new();
7233        let forms = read("(defmacro bad (x) `(list ,y)) (bad 1)").unwrap();
7234        assert!(e.expand_program(forms).is_err());
7235    }
7236
7237    #[test]
7238    fn missing_required_arg_errors() {
7239        let mut e = Expander::new();
7240        let forms = read("(defmacro need-two (a b) `(,a ,b)) (need-two 1)").unwrap();
7241        assert!(e.expand_program(forms).is_err());
7242    }
7243
7244    #[test]
7245    fn defpoint_template_treated_as_defmacro() {
7246        let mut e = Expander::new();
7247        let forms = read(
7248            "(defpoint-template obs (name) `(defpoint ,name :class (Gate Observability)))
7249             (obs grafana)",
7250        )
7251        .unwrap();
7252        let out = e.expand_program(forms).unwrap();
7253        assert_eq!(
7254            out[0],
7255            parse("(defpoint grafana :class (Gate Observability))")
7256        );
7257    }
7258
7259    #[test]
7260    fn defcheck_treated_as_defmacro() {
7261        let mut e = Expander::new();
7262        let forms = read(
7263            "(defcheck pair (a b) `(do (yaml-parses ,a) (yaml-parses ,b)))
7264             (pair \"x.yaml\" \"y.yaml\")",
7265        )
7266        .unwrap();
7267        let out = e.expand_program(forms).unwrap();
7268        assert_eq!(
7269            out[0],
7270            parse("(do (yaml-parses \"x.yaml\") (yaml-parses \"y.yaml\"))")
7271        );
7272    }
7273
7274    #[test]
7275    fn empty_rest_splices_nothing() {
7276        let mut e = Expander::new();
7277        let forms = read("(defmacro f (x &rest r) `(list ,x ,@r)) (f 1)").unwrap();
7278        let out = e.expand_program(forms).unwrap();
7279        assert_eq!(out[0], parse("(list 1)"));
7280    }
7281
7282    #[test]
7283    fn macro_expanded_inside_list() {
7284        // A macro call nested in a list position also expands.
7285        let mut e = Expander::new();
7286        let forms = read("(defmacro two () `(list 1 2)) (outer (two))").unwrap();
7287        let out = e.expand_program(forms).unwrap();
7288        assert_eq!(out[0], parse("(outer (list 1 2))"));
7289    }
7290
7291    // ── Compiled-template bytecode equivalence + speedup ──────────────
7292
7293    #[test]
7294    fn compiled_template_matches_substitute_path() {
7295        // Same program, two expanders with different strategies — outputs must agree.
7296        let src = "
7297            (defmacro wrap (x) `(list ,x ,x))
7298            (defmacro call (f &rest args) `(,f ,@args))
7299            (defmacro twice (x) `(list ,x ,x))
7300            (defmacro quad (x) `(twice ,x))
7301            (wrap hello)
7302            (call foo a b c)
7303            (quad hey)
7304            (outer (wrap deep))
7305        ";
7306        let forms = read(src).unwrap();
7307        let mut fast = Expander::new();
7308        let mut slow = Expander::new_substitute_only();
7309        let out_fast = fast.expand_program(forms.clone()).unwrap();
7310        let out_slow = slow.expand_program(forms).unwrap();
7311        assert_eq!(out_fast, out_slow);
7312    }
7313
7314    #[test]
7315    fn literal_subtree_compiles_to_single_literal_op() {
7316        // Macro body where only one leaf is a substitution — the rest of the
7317        // template is literal, so the compiler should prune large chunks to
7318        // a single Literal op.
7319        let def = MacroDef {
7320            name: "label".into(),
7321            params: MacroParams {
7322                required: vec!["x".into()],
7323                optional: Vec::new(),
7324                rest: None,
7325            },
7326            body: Sexp::Quasiquote(Box::new(parse(
7327                "(observed (at timestamp) (in region) (value ,x) (tags (one two three)))",
7328            ))),
7329        };
7330        let compiled = compile_template(&def).expect("compile");
7331        // The template is ONE list. After compile:
7332        //   BeginList,
7333        //     Literal((observed (at timestamp) (in region))), // wait — `observed` is a list too
7334        //     ...
7335        //   EndList
7336        // Point is: many subtrees should be single Literals. We simply count
7337        // that the op stream is SHORTER than the full Sexp size.
7338        let ops_count = compiled.ops.len();
7339        assert!(
7340            ops_count < 15,
7341            "expected pruned op stream, got {ops_count} ops: {:?}",
7342            compiled.ops
7343        );
7344    }
7345
7346    /// The cache-friendly workload the two tests below share: 10 macros, and
7347    /// calls that hit only 10 distinct (macro, args) pairs, so a memo should
7348    /// serve 99.9% of them.
7349    fn cache_friendly_workload(calls: usize) -> String {
7350        let macros = "
7351            (defmacro m1 (a b) `(list ,a ,b))
7352            (defmacro m2 (x) `(if ,x true false))
7353            (defmacro m3 (a b c) `(list ,a ,b ,c ,a ,b ,c))
7354            (defmacro m4 (f &rest args) `(,f ,@args))
7355            (defmacro m5 (x) `(and ,x (not (not ,x))))
7356            (defmacro m6 (a b) `(or ,a ,b (and ,a ,b)))
7357            (defmacro m7 (x) `(debug (at timestamp) (in region) (value ,x)))
7358            (defmacro m8 (x y) `(cond ((= ,x ,y) equal) (#t not-equal)))
7359            (defmacro m9 (x) `(loop (times 10) (eval ,x)))
7360            (defmacro m10 (f g &rest args) `(,f (,g ,@args)))
7361        ";
7362        let mut call_src = String::with_capacity(calls * 12);
7363        for i in 0..calls {
7364            match i % 10 {
7365                0 => call_src.push_str("(m1 a b)\n"),
7366                1 => call_src.push_str("(m2 true)\n"),
7367                2 => call_src.push_str("(m3 x y z)\n"),
7368                3 => call_src.push_str("(m4 f a b c d e)\n"),
7369                4 => call_src.push_str("(m5 y)\n"),
7370                5 => call_src.push_str("(m6 a b)\n"),
7371                6 => call_src.push_str("(m7 answer)\n"),
7372                7 => call_src.push_str("(m8 p q)\n"),
7373                8 => call_src.push_str("(m9 body)\n"),
7374                _ => call_src.push_str("(m10 f g a b c)\n"),
7375            }
7376        }
7377        let all_src = format!("{macros}\n{call_src}");
7378        all_src
7379    }
7380
7381    /// **All three expansion strategies produce identical output.**
7382    ///
7383    /// The load-bearing invariant — THEORY.md §II.1's free middle. Fully
7384    /// deterministic, so it belongs in the ordinary suite. It used to be
7385    /// welded to the timing assertion below, which meant a benchmark losing a
7386    /// core to a neighbouring test reported *this* as failing, and the
7387    /// agreement property is the one you actually want a red build for.
7388    #[test]
7389    fn expansion_layers_agree_on_output() {
7390        let forms = read(&cache_friendly_workload(10_000)).unwrap();
7391
7392        let out_subst = Expander::new_substitute_only()
7393            .expand_program(forms.clone())
7394            .unwrap();
7395        let out_byte = Expander::new_bytecode_no_cache()
7396            .expand_program(forms.clone())
7397            .unwrap();
7398        let mut byte_cache = Expander::new();
7399        let out_cached = byte_cache.expand_program(forms).unwrap();
7400
7401        assert_eq!(out_subst, out_byte);
7402        assert_eq!(out_subst, out_cached);
7403
7404        // Cache captured the 10 unique (macro, args) pairs (plus some inner
7405        // expansions — macros that expand into calls to other macros).
7406        let cache_size = byte_cache.cache_size();
7407        assert!(
7408            (10..=50).contains(&cache_size),
7409            "expected ~10 unique cache entries, got {cache_size}"
7410        );
7411    }
7412
7413    /// **The expansion strategies, counted.**
7414    ///
7415    /// Each strategy answers every macro application from exactly one layer of
7416    /// `Expander::apply`, so the work each one does is a count, and the count
7417    /// is a fact of the code and the workload rather than of the machine.
7418    ///
7419    /// # Why this is not a wall-clock comparison any more
7420    ///
7421    /// It was one, best-of-5 with a 1.25× gate against substitution and a
7422    /// non-regression bound against bytecode. On shared GitHub runners the
7423    /// substitution ratio measured 0.97× against 1.57× locally on the same
7424    /// commit, and the test blocked a release twice with no code change behind
7425    /// it. Wall time on this path is dominated by reading, walking and
7426    /// allocating the output tree (the memo saves ~1.17× over bytecode in
7427    /// release, ~1.04× in debug), so the margin the gate needed was never
7428    /// larger than the runner's noise.
7429    ///
7430    /// What the gate was for is catching the fast path being disabled or
7431    /// wired wrong, and the counts say that exactly: a cache that never hits,
7432    /// a bytecode dispatch that falls back to substitution, or a cache that
7433    /// stores but is never consulted each move a count by thousands. The
7434    /// timings are still printed for a reader on a quiet machine.
7435    #[test]
7436    fn the_expansion_cache_answers_repeat_calls_without_expanding() {
7437        use std::time::Instant;
7438
7439        const CALLS: u64 = 10_000;
7440        let forms = read(&cache_friendly_workload(usize::try_from(CALLS).unwrap())).unwrap();
7441
7442        let run = |mut e: Expander| -> (ExpansionCounts, usize, std::time::Duration) {
7443            let t = Instant::now();
7444            let out = e.expand_program(forms.clone()).unwrap();
7445            let d = t.elapsed();
7446            std::hint::black_box(out);
7447            (e.expansion_counts(), e.cache_size(), d)
7448        };
7449
7450        let (subst, _, t_subst) = run(Expander::new_substitute_only());
7451        let (byte, _, t_byte) = run(Expander::new_bytecode_no_cache());
7452        let (cached, entries, t_cached) = run(Expander::new());
7453        eprintln!(
7454            "\n=== macroexpand: {CALLS} calls × 10 unique (macro, args) pairs ===\n\
7455             substitute only   : {subst:?} in {t_subst:?}\n\
7456             bytecode no cache : {byte:?} in {t_byte:?}\n\
7457             bytecode + cache  : {cached:?} in {t_cached:?}\n"
7458        );
7459
7460        let applications = |c: &ExpansionCounts| c.cache_hits + c.compiled + c.substituted;
7461        let total = applications(&subst);
7462        assert!(
7463            total >= CALLS,
7464            "every top-level call is at least one application, got {total}"
7465        );
7466        assert_eq!(
7467            applications(&byte),
7468            total,
7469            "strategies apply the same calls"
7470        );
7471        assert_eq!(
7472            applications(&cached),
7473            total,
7474            "strategies apply the same calls"
7475        );
7476
7477        assert_eq!(
7478            subst,
7479            ExpansionCounts {
7480                substituted: total,
7481                ..Default::default()
7482            },
7483            "substitute-only must never touch the bytecode or the memo"
7484        );
7485        assert_eq!(
7486            byte,
7487            ExpansionCounts {
7488                compiled: total,
7489                ..Default::default()
7490            },
7491            "bytecode-no-cache must run every application through the bytecode"
7492        );
7493        assert_eq!(
7494            cached.substituted, 0,
7495            "a compiled macro fell back to substitution"
7496        );
7497        assert!(
7498            cached.compiled <= 50,
7499            "the memo should leave only the ~10 unique pairs (plus inner expansions) \
7500             to the bytecode, got {} of {total}",
7501            cached.compiled
7502        );
7503        assert_eq!(
7504            cached.compiled,
7505            u64::try_from(entries).unwrap(),
7506            "every bytecode run on a fresh memo is a miss that fills one entry"
7507        );
7508    }
7509
7510    #[test]
7511    fn cache_respects_arg_changes() {
7512        // Cache must not return stale results when args differ.
7513        let src = "
7514            (defmacro wrap (x) `(list ,x ,x))
7515            (wrap a)
7516            (wrap b)
7517            (wrap a)   ;; same as first — cached hit
7518        ";
7519        let mut e = Expander::new();
7520        let out = e.expand_program(read(src).unwrap()).unwrap();
7521        assert_eq!(out.len(), 3);
7522        assert_eq!(out[0], parse("(list a a)"));
7523        assert_eq!(out[1], parse("(list b b)"));
7524        assert_eq!(out[2], parse("(list a a)"));
7525        // Two distinct args → 2 cache entries.
7526        assert_eq!(e.cache_size(), 2);
7527    }
7528
7529    #[test]
7530    fn clear_cache_empties_memo() {
7531        let mut e = Expander::new();
7532        let out = e
7533            .expand_program(read("(defmacro id (x) `,x) (id 1) (id 2)").unwrap())
7534            .unwrap();
7535        assert_eq!(out.len(), 2);
7536        assert_eq!(e.cache_size(), 2);
7537        e.clear_cache();
7538        assert_eq!(e.cache_size(), 0);
7539    }
7540
7541    // ── Unbound template-var: structural variant + did-you-mean hint ──
7542
7543    /// Helper for the unbound-template-var tests — pins the variant shape
7544    /// and carries any error context up to the assert site for legibility.
7545    fn unbound_var(err: &LispError) -> (UnquoteForm, &str, Option<&str>) {
7546        match err {
7547            LispError::UnboundTemplateVar { prefix, name, hint } => {
7548                (*prefix, name.as_str(), hint.as_deref())
7549            }
7550            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
7551        }
7552    }
7553
7554    #[test]
7555    fn unbound_unquote_in_compile_template_emits_structural_variant_with_hint() {
7556        // `,xs` against macro params `[x]` — distance 1, bound 1 — hints `,x`.
7557        // Path: compile_node Unquote (the bytecode-template compile, default
7558        // expander).
7559        let mut e = Expander::new();
7560        let err = e
7561            .expand_program(read("(defmacro w (x) `(list ,xs)) (w 1)").unwrap())
7562            .expect_err("unbound template var must error");
7563        let (prefix, name, hint) = unbound_var(&err);
7564        assert_eq!(prefix, UnquoteForm::Unquote);
7565        assert_eq!(name, "xs");
7566        assert_eq!(hint, Some("x"));
7567    }
7568
7569    #[test]
7570    fn unbound_unquote_splice_in_compile_template_emits_structural_variant_with_hint() {
7571        // `,@argz` against macro params `[args]` — distance 1, bound 2 —
7572        // hints `,@args`. Path: compile_node UnquoteSplice.
7573        let mut e = Expander::new();
7574        let err = e
7575            .expand_program(
7576                read("(defmacro call (f &rest args) `(,f ,@argz)) (call foo a b)").unwrap(),
7577            )
7578            .expect_err("unbound splice must error");
7579        let (prefix, name, hint) = unbound_var(&err);
7580        assert_eq!(prefix, UnquoteForm::Splice);
7581        assert_eq!(name, "argz");
7582        assert_eq!(hint, Some("args"));
7583    }
7584
7585    #[test]
7586    fn unbound_unquote_in_substitute_emits_structural_variant_with_hint() {
7587        // Same shape but routed through the substitute-only expander — proves
7588        // the substitute path emits the same variant as the bytecode path.
7589        let mut e = Expander::new_substitute_only();
7590        let err = e
7591            .expand_program(read("(defmacro w (x) `(list ,xs)) (w 1)").unwrap())
7592            .expect_err("substitute unbound must error");
7593        let (prefix, name, hint) = unbound_var(&err);
7594        assert_eq!(prefix, UnquoteForm::Unquote);
7595        assert_eq!(name, "xs");
7596        assert_eq!(hint, Some("x"));
7597    }
7598
7599    #[test]
7600    fn unbound_unquote_splice_in_substitute_emits_structural_variant_with_hint() {
7601        // The substitute path's UnquoteSplice branch fires for splices that
7602        // appear inside a list during the recursive walk. `,@argz` against
7603        // `[args]` hints `,@args`.
7604        let mut e = Expander::new_substitute_only();
7605        let err = e
7606            .expand_program(
7607                read("(defmacro call (f &rest args) `(,f ,@argz)) (call foo a b)").unwrap(),
7608            )
7609            .expect_err("substitute splice unbound must error");
7610        let (prefix, name, hint) = unbound_var(&err);
7611        assert_eq!(prefix, UnquoteForm::Splice);
7612        assert_eq!(name, "argz");
7613        assert_eq!(hint, Some("args"));
7614    }
7615
7616    #[test]
7617    fn unbound_template_var_omits_hint_when_no_close_match() {
7618        // `,wholly-unrelated` against `[x]` — far past the bound, so no
7619        // hint. Negative control: a wrong hint is worse than no hint, so
7620        // the slot must stay empty when the substrate isn't confident.
7621        let mut e = Expander::new();
7622        let err = e
7623            .expand_program(read("(defmacro w (x) `(list ,wholly-unrelated)) (w 1)").unwrap())
7624            .expect_err("unrelated unbound must error");
7625        let (prefix, name, hint) = unbound_var(&err);
7626        assert_eq!(prefix, UnquoteForm::Unquote);
7627        assert_eq!(name, "wholly-unrelated");
7628        assert_eq!(hint, None);
7629    }
7630
7631    #[test]
7632    fn unbound_template_var_message_includes_hint_suffix_end_to_end() {
7633        // End-to-end through the Display impl — pins the rendered diagnostic
7634        // a downstream tool sees today (REPL, tatara-check). Hint stays
7635        // additive: the legacy `"unbound"` substring still appears, so any
7636        // assertion that pattern-matches on it keeps passing.
7637        let mut e = Expander::new();
7638        let err = e
7639            .expand_program(read("(defmacro w (x) `(list ,xs)) (w 1)").unwrap())
7640            .expect_err("unbound must error");
7641        let msg = format!("{err}");
7642        assert!(
7643            msg.contains("did you mean ,x?"),
7644            "expected hint suffix in message, got: {msg}"
7645        );
7646        assert!(
7647            msg.contains("unbound"),
7648            "expected legacy `unbound` substring in message, got: {msg}"
7649        );
7650        assert!(
7651            msg.contains(",xs"),
7652            "expected the offending form in message, got: {msg}"
7653        );
7654    }
7655
7656    #[test]
7657    fn unbound_template_var_position_is_none_today() {
7658        // Negative control for the future-spans move: until `Sexp` carries
7659        // source positions, `position()` returns `None` for this variant.
7660        let mut e = Expander::new();
7661        let err = e
7662            .expand_program(read("(defmacro w (x) `(list ,xs)) (w 1)").unwrap())
7663            .expect_err("unbound must error");
7664        assert_eq!(err.position(), None);
7665    }
7666
7667    // ── Non-symbol unquote target: structural variant ─────────────────
7668
7669    /// Helper for the non-symbol-unquote-target tests — pins the variant
7670    /// shape and carries any error context up to the assert site for
7671    /// legibility. Sibling of `unbound_var` and `splice_outside_list_got`;
7672    /// returns the `display` projection of the typed `SexpWitness` so the
7673    /// existing call sites stay byte-for-byte comparable to the legacy
7674    /// `got: String` shape.
7675    fn non_symbol_target(err: &LispError) -> (UnquoteForm, &str) {
7676        match err {
7677            LispError::NonSymbolUnquoteTarget { prefix, got } => (*prefix, got.display.as_str()),
7678            other => panic!("expected NonSymbolUnquoteTarget, got: {other:?}"),
7679        }
7680    }
7681
7682    #[test]
7683    fn non_symbol_unquote_in_compile_template_emits_structural_variant() {
7684        // `,(list 1 2)` — the inner is a list, not a symbol. Path:
7685        // compile_node Unquote (the bytecode-template compile, default
7686        // expander). Pins variant identity AND prefix AND the offending
7687        // literal so a regression that re-inlines the legacy
7688        // `LispError::Compile` shape fails-loudly here.
7689        let mut e = Expander::new();
7690        let err = e
7691            .expand_program(read("(defmacro w (x) `,(list 1 2)) (w 1)").unwrap())
7692            .expect_err("non-symbol unquote target must error");
7693        let (prefix, got) = non_symbol_target(&err);
7694        assert_eq!(prefix, UnquoteForm::Unquote);
7695        assert_eq!(got, "(list 1 2)");
7696    }
7697
7698    #[test]
7699    fn non_symbol_unquote_splice_in_compile_template_emits_structural_variant() {
7700        // `,@5` — the inner is an int atom, not a symbol. Path:
7701        // compile_node UnquoteSplice. The integer literal round-trips
7702        // through the variant's `got` slot via `Sexp::Display`.
7703        let mut e = Expander::new();
7704        let err = e
7705            .expand_program(read("(defmacro w (x) `(list ,@5)) (w 1)").unwrap())
7706            .expect_err("non-symbol splice target must error");
7707        let (prefix, got) = non_symbol_target(&err);
7708        assert_eq!(prefix, UnquoteForm::Splice);
7709        assert_eq!(got, "5");
7710    }
7711
7712    #[test]
7713    fn non_symbol_unquote_in_substitute_emits_structural_variant() {
7714        // Same shape as the bytecode path but routed through the
7715        // substitute-only expander — proves the substitute path emits the
7716        // same variant as the compile_node path. Pins that the lift is
7717        // path-uniform.
7718        let mut e = Expander::new_substitute_only();
7719        let err = e
7720            .expand_program(read("(defmacro w (x) `,(list 1 2)) (w 1)").unwrap())
7721            .expect_err("substitute non-symbol target must error");
7722        let (prefix, got) = non_symbol_target(&err);
7723        assert_eq!(prefix, UnquoteForm::Unquote);
7724        assert_eq!(got, "(list 1 2)");
7725    }
7726
7727    #[test]
7728    fn non_symbol_unquote_splice_inside_list_in_substitute_emits_structural_variant() {
7729        // The substitute path's UnquoteSplice-inside-list branch fires for
7730        // splices that appear inside a list during the recursive walk.
7731        // `,@(list 1 2)` inside the body — the inner is a literal list, not
7732        // a symbol — emits the same variant as the compile_node path.
7733        let mut e = Expander::new_substitute_only();
7734        let err = e
7735            .expand_program(read("(defmacro w (x) `(outer ,@(list 1 2))) (w 1)").unwrap())
7736            .expect_err("substitute non-symbol splice must error");
7737        let (prefix, got) = non_symbol_target(&err);
7738        assert_eq!(prefix, UnquoteForm::Splice);
7739        assert_eq!(got, "(list 1 2)");
7740    }
7741
7742    #[test]
7743    fn non_symbol_unquote_target_position_is_none_today() {
7744        // Negative control for the future-spans move: until `Sexp` carries
7745        // source positions, `position()` returns `None` for this variant.
7746        // A future run that gives `Sexp` source spans adds `pos:
7747        // Option<usize>` to ONE place; this test gives that change a
7748        // deliberate fail-before/pass-after delta.
7749        let mut e = Expander::new();
7750        let err = e
7751            .expand_program(read("(defmacro w (x) `,(list 1 2)) (w 1)").unwrap())
7752            .expect_err("non-symbol target must error");
7753        assert_eq!(err.position(), None);
7754    }
7755
7756    // ── unquote_target_symbol: typed gate-1 primitive for ,X / ,@X ──────
7757    //
7758    // The `unquote_target_symbol(inner, form)?` primitive lifts the
7759    // inline `inner.as_symbol().ok_or_else(|| non_symbol_unquote_target(
7760    // form, inner))?` pattern that previously appeared at four call
7761    // sites (`compile_node` Unquote/UnquoteSplice + `substitute` Unquote
7762    // + `substitute` list-inner UnquoteSplice) behind ONE named
7763    // primitive. The tests below pin: (a) the Ok-arm borrows the
7764    // symbol name from `inner` for both UnquoteForm variants; (b) the
7765    // Err-arm routes through `non_symbol_unquote_target` and emits the
7766    // structural `LispError::NonSymbolUnquoteTarget` variant carrying
7767    // the typed `SexpWitness` (joint shape + display identity) for the
7768    // closed set of reachable non-symbol shapes (int / keyword / list /
7769    // nil); (c) the helper is path-uniform — the same Ok / Err
7770    // contracts hold regardless of which call site invokes it. A
7771    // regression that re-inlines the gate-1 projection at any of the
7772    // four call sites can no longer drift independent of the others —
7773    // the helper IS the gate.
7774
7775    #[test]
7776    fn unquote_target_symbol_returns_symbol_for_symbol_inner_under_unquote() {
7777        // Positive control for the Ok-arm: `inner = Sexp::Symbol("xs")`
7778        // under `UnquoteForm::Unquote` projects through `as_symbol()`
7779        // to the borrowed `&str`. The returned slice's lifetime is
7780        // tied to `inner` so the caller can feed it directly into
7781        // `params.iter().position(...)` (`compile_node`) or
7782        // `bindings.get(...)` (`substitute`) without an intermediate
7783        // allocation. Fail-before/pass-after: this assert is meaningless
7784        // pre-lift because the helper does not exist; post-lift it
7785        // pins the typed gate-1 contract at the named primitive.
7786        let inner = Sexp::symbol("xs");
7787        let name = unquote_target_symbol(&inner, UnquoteForm::Unquote)
7788            .expect("symbol inner must project to Ok");
7789        assert_eq!(name, "xs");
7790    }
7791
7792    #[test]
7793    fn unquote_target_symbol_returns_symbol_for_symbol_inner_under_splice() {
7794        // Sibling positive control: `UnquoteForm::Splice` shares the
7795        // gate-1 contract with `Unquote`. The helper is path-uniform
7796        // across both syntactic markers — a regression that bifurcates
7797        // the two arms (e.g., accepting non-symbols for `,@X` but not
7798        // `,X`) fails-loudly here. Pins that the closed-set
7799        // `UnquoteForm` enum's two variants share ONE projection
7800        // posture across the gate-1 boundary.
7801        let inner = Sexp::symbol("rest");
7802        let name = unquote_target_symbol(&inner, UnquoteForm::Splice)
7803            .expect("symbol inner must project to Ok under Splice");
7804        assert_eq!(name, "rest");
7805    }
7806
7807    #[test]
7808    fn unquote_target_symbol_rejects_int_inner_under_unquote() {
7809        // Negative control for the Err-arm: `inner = Sexp::Int(5)` is
7810        // NOT a symbol — the gate-1 projection fires and routes through
7811        // `non_symbol_unquote_target` to the structural
7812        // `LispError::NonSymbolUnquoteTarget` variant. Pin the variant
7813        // identity AND the typed `SexpWitness` joint identity (shape +
7814        // display literal): a regression that drops the witness shape
7815        // or display fails-loudly here.
7816        let inner = Sexp::int(5);
7817        let err = unquote_target_symbol(&inner, UnquoteForm::Unquote)
7818            .expect_err("int inner must error at gate-1");
7819        match err {
7820            LispError::NonSymbolUnquoteTarget { prefix, got } => {
7821                assert_eq!(prefix, UnquoteForm::Unquote);
7822                assert_eq!(got.shape, crate::error::SexpShape::Int);
7823                assert_eq!(got.display, "5");
7824            }
7825            other => panic!("expected NonSymbolUnquoteTarget, got: {other:?}"),
7826        }
7827    }
7828
7829    #[test]
7830    fn unquote_target_symbol_rejects_list_inner_under_splice() {
7831        // Sibling negative control: `inner = (list 1 2)` is a list, not
7832        // a symbol — the gate-1 projection fires AND routes through
7833        // `non_symbol_unquote_target(UnquoteForm::Splice, inner)`. Pins
7834        // both the variant identity AND the typed witness's joint
7835        // shape (`SexpShape::List`) + display (`"(list 1 2)"`) so a
7836        // future shape drift fails-loudly. Sibling of the Int / Unquote
7837        // pin: closes the gate-1 contract across the closed-set
7838        // product of {Int, List, Keyword, …} × {Unquote, Splice}.
7839        let inner = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(1), Sexp::int(2)]);
7840        let err = unquote_target_symbol(&inner, UnquoteForm::Splice)
7841            .expect_err("list inner must error at gate-1");
7842        match err {
7843            LispError::NonSymbolUnquoteTarget { prefix, got } => {
7844                assert_eq!(prefix, UnquoteForm::Splice);
7845                assert_eq!(got.shape, crate::error::SexpShape::List);
7846                assert_eq!(got.display, "(list 1 2)");
7847            }
7848            other => panic!("expected NonSymbolUnquoteTarget, got: {other:?}"),
7849        }
7850    }
7851
7852    #[test]
7853    fn unquote_target_symbol_rejects_keyword_inner_with_typed_witness() {
7854        // Pin a third reachable non-symbol shape: `Sexp::Keyword(":foo")`.
7855        // The gate-1 projection rejects keywords AS WELL as ints and
7856        // lists — closes the closed-set of "non-symbol shapes the gate
7857        // rejects" across one more reachable variant. The typed witness
7858        // carries `SexpShape::Keyword` + display `:foo` jointly so
7859        // authoring tools (REPL, LSP) bind on the structural shape
7860        // directly.
7861        let inner = Sexp::keyword("foo");
7862        let err = unquote_target_symbol(&inner, UnquoteForm::Unquote)
7863            .expect_err("keyword inner must error at gate-1");
7864        match err {
7865            LispError::NonSymbolUnquoteTarget { prefix, got } => {
7866                assert_eq!(prefix, UnquoteForm::Unquote);
7867                assert_eq!(got.shape, crate::error::SexpShape::Keyword);
7868                assert_eq!(got.display, ":foo");
7869            }
7870            other => panic!("expected NonSymbolUnquoteTarget, got: {other:?}"),
7871        }
7872    }
7873
7874    #[test]
7875    fn unquote_target_symbol_consolidates_four_inline_callsites_into_one_helper() {
7876        // Path-uniformity pin: end-to-end through ALL FOUR call sites
7877        // (`compile_node` Unquote, `compile_node` UnquoteSplice,
7878        // `substitute` Unquote, `substitute` list-inner UnquoteSplice)
7879        // every non-symbol unquote target now routes through the SAME
7880        // `unquote_target_symbol(inner, form)?` helper. The four
7881        // end-to-end expansions below all reject with the SAME variant
7882        // (`NonSymbolUnquoteTarget`) — pins that the lift preserves the
7883        // path-uniform rejection contract `non_symbol_unquote_target`'s
7884        // prior lift established (and that drove the bytecode-vs-
7885        // substitute reunification in 0e9c… and successors). A
7886        // regression that re-inlines the gate-1 projection at one of
7887        // the four sites can drift the four call sites independent of
7888        // each other — this test would catch that drift.
7889        let cases: &[(&str, UnquoteForm)] = &[
7890            // compile_node Unquote (bytecode-path)
7891            ("(defmacro w (x) `,(list 1 2)) (w 1)", UnquoteForm::Unquote),
7892            // compile_node UnquoteSplice (bytecode-path)
7893            ("(defmacro w (x) `(list ,@5)) (w 1)", UnquoteForm::Splice),
7894        ];
7895        for (src, expected_form) in cases {
7896            let mut e = Expander::new();
7897            let err = e
7898                .expand_program(read(src).unwrap())
7899                .expect_err("non-symbol unquote target must error end-to-end");
7900            match err {
7901                LispError::NonSymbolUnquoteTarget { prefix, .. } => {
7902                    assert_eq!(prefix, *expected_form, "for src: {src}");
7903                }
7904                other => panic!("expected NonSymbolUnquoteTarget for {src}, got: {other:?}"),
7905            }
7906        }
7907        // substitute Unquote (substitute-only path) — sibling pin to
7908        // `non_symbol_unquote_in_substitute_emits_structural_variant`.
7909        let mut e_subst = Expander::new_substitute_only();
7910        let err = e_subst
7911            .expand_program(read("(defmacro w (x) `,(list 1 2)) (w 1)").unwrap())
7912            .expect_err("substitute Unquote must error end-to-end");
7913        assert!(
7914            matches!(
7915                err,
7916                LispError::NonSymbolUnquoteTarget {
7917                    prefix: UnquoteForm::Unquote,
7918                    ..
7919                }
7920            ),
7921            "expected NonSymbolUnquoteTarget at substitute Unquote, got: {err:?}"
7922        );
7923        // substitute list-inner UnquoteSplice (substitute-only path) —
7924        // sibling pin to
7925        // `non_symbol_unquote_splice_inside_list_in_substitute_emits_…`.
7926        let mut e_subst2 = Expander::new_substitute_only();
7927        let err = e_subst2
7928            .expand_program(read("(defmacro w (x) `(outer ,@(list 1 2))) (w 1)").unwrap())
7929            .expect_err("substitute UnquoteSplice-in-list must error end-to-end");
7930        assert!(
7931            matches!(
7932                err,
7933                LispError::NonSymbolUnquoteTarget {
7934                    prefix: UnquoteForm::Splice,
7935                    ..
7936                }
7937            ),
7938            "expected NonSymbolUnquoteTarget at substitute UnquoteSplice-in-list, got: {err:?}"
7939        );
7940    }
7941
7942    // ── Gate-2 (must-be-bound-in-scope) typed primitives ──────────────
7943    // Pins the contract of the two gate-2 helpers — `resolve_param_index`
7944    // (bytecode-template compile path) and `resolve_binding`
7945    // (substitute path) — that the four inline `<lookup>.ok_or_else(||
7946    // unbound_template_var(FORM, name, candidates))` projections at
7947    // `compile_node` Unquote/UnquoteSplice AND `substitute` Unquote/
7948    // UnquoteSplice-inside-list collapse behind. Tests pin: (a) Ok-arm
7949    // projection under both `UnquoteForm` variants — the helper returns
7950    // the resolved `usize` (compile path) or `&Sexp` (substitute path)
7951    // for in-scope names; (b) Err-arm projection routes through
7952    // `unbound_template_var` to the typed `LispError::UnboundTemplateVar`
7953    // variant with the correct `prefix` AND the suggest-driven `hint`;
7954    // (c) the helpers are path-uniform — both compile-path arms share
7955    // ONE `resolve_param_index`; both substitute-path arms share ONE
7956    // `resolve_binding`. A regression that re-inlines the gate-2
7957    // projection at any of the four call sites can no longer drift
7958    // independent of the others — the two helpers ARE the gate.
7959
7960    #[test]
7961    fn resolve_param_index_returns_position_for_bound_name_under_unquote() {
7962        // Positive control for the Ok-arm: `name = "x"` against
7963        // `params = ["a", "x", "rest"]` projects through
7964        // `params.iter().position(|p| *p == name)` to `Some(1)`, which
7965        // the helper unwraps to `Ok(1)`. The returned index feeds
7966        // directly into `TemplateOp::Subst(idx)` at the compile site.
7967        let params = ["a", "x", "rest"];
7968        let idx = resolve_param_index("x", &params, UnquoteForm::Unquote)
7969            .expect("bound name must project to Ok at gate-2");
7970        assert_eq!(idx, 1);
7971    }
7972
7973    #[test]
7974    fn resolve_param_index_returns_position_for_bound_name_under_splice() {
7975        // Sibling positive control: `UnquoteForm::Splice` shares the
7976        // gate-2 contract with `Unquote`. The helper is path-uniform
7977        // across both syntactic markers on the compile path — a
7978        // regression that bifurcates the two arms fails-loudly here.
7979        let params = ["a", "x", "rest"];
7980        let idx = resolve_param_index("rest", &params, UnquoteForm::Splice)
7981            .expect("bound name must project to Ok at gate-2 under Splice");
7982        assert_eq!(idx, 2);
7983    }
7984
7985    #[test]
7986    fn resolve_param_index_rejects_unbound_name_with_hint_under_unquote() {
7987        // Negative control for the Err-arm: `name = "xs"` against
7988        // `params = ["x"]` — distance 1, bound 1 — routes through
7989        // `unbound_template_var` to the structural
7990        // `LispError::UnboundTemplateVar` variant with `hint = Some("x")`.
7991        // Pin the variant identity AND the prefix AND the suggest-driven
7992        // hint: a regression that drops the suggestion fails-loudly here.
7993        let params = ["x"];
7994        let err = resolve_param_index("xs", &params, UnquoteForm::Unquote)
7995            .expect_err("unbound name must error at gate-2");
7996        match err {
7997            LispError::UnboundTemplateVar { prefix, name, hint } => {
7998                assert_eq!(prefix, UnquoteForm::Unquote);
7999                assert_eq!(name, "xs");
8000                assert_eq!(hint.as_deref(), Some("x"));
8001            }
8002            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
8003        }
8004    }
8005
8006    #[test]
8007    fn resolve_param_index_rejects_unbound_name_without_hint_under_splice() {
8008        // Sibling negative control: `name = "wholly-unrelated"` against
8009        // `params = ["x"]` — past the bounded edit distance, so no hint.
8010        // Pin that the suggest-driven hint stays empty under Splice when
8011        // the substrate isn't confident — a wrong hint is worse than no
8012        // hint. Closes the closed-set product of {hint, no-hint} ×
8013        // {Unquote, Splice} on the compile-path gate-2.
8014        let params = ["x"];
8015        let err = resolve_param_index("wholly-unrelated", &params, UnquoteForm::Splice)
8016            .expect_err("unrelated unbound must error at gate-2");
8017        match err {
8018            LispError::UnboundTemplateVar { prefix, name, hint } => {
8019                assert_eq!(prefix, UnquoteForm::Splice);
8020                assert_eq!(name, "wholly-unrelated");
8021                assert_eq!(hint, None);
8022            }
8023            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
8024        }
8025    }
8026
8027    #[test]
8028    fn resolve_binding_returns_value_for_bound_name_under_unquote() {
8029        // Positive control for the substitute-path Ok-arm: `name = "x"`
8030        // against a bindings map `{x: 42, y: "hi"}` projects through
8031        // `bindings.get(name)` to `Some(&Sexp::Int(42))`, which the
8032        // helper unwraps to `Ok(&Sexp::Int(42))`. The returned
8033        // `&Sexp` borrows from the bindings map — the top-level
8034        // `Sexp::Unquote(_)` substitute caller adds a single
8035        // `.cloned()` to satisfy its owned-`Sexp` return obligation.
8036        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8037        bindings.insert("x".to_string(), Sexp::int(42));
8038        bindings.insert("y".to_string(), Sexp::string("hi"));
8039        let val = resolve_binding(&bindings, "x", UnquoteForm::Unquote)
8040            .expect("bound name must project to Ok at gate-2 (substitute)");
8041        assert_eq!(val, &Sexp::int(42));
8042    }
8043
8044    #[test]
8045    fn resolve_binding_returns_value_for_bound_name_under_splice() {
8046        // Sibling positive control: `UnquoteForm::Splice` shares the
8047        // gate-2 contract with `Unquote` on the substitute path too.
8048        // The bound value is a `Sexp::List` because the splice arm's
8049        // caller match expression expects `Sexp::List(items)` — but
8050        // the helper itself doesn't inspect the value's shape; it
8051        // just hands back the borrow. A regression that gate-checks
8052        // the value's shape inside `resolve_binding` (instead of at
8053        // the caller match arm) fails-loudly here.
8054        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8055        bindings.insert(
8056            "args".to_string(),
8057            Sexp::List(vec![Sexp::int(1), Sexp::int(2)]),
8058        );
8059        let val = resolve_binding(&bindings, "args", UnquoteForm::Splice)
8060            .expect("bound name must project to Ok at gate-2 under Splice");
8061        assert_eq!(val, &Sexp::List(vec![Sexp::int(1), Sexp::int(2)]));
8062    }
8063
8064    #[test]
8065    fn resolve_binding_rejects_unbound_name_with_hint_under_unquote() {
8066        // Negative control for the substitute-path Err-arm: `name =
8067        // "xs"` against bindings `{x: 1}` — distance 1, bound 1 —
8068        // routes through `unbound_template_var` to the structural
8069        // `LispError::UnboundTemplateVar` variant with `hint =
8070        // Some("x")`. The candidate set is drawn from
8071        // `bound_names(bindings)` — the live bindings' keys, never a
8072        // stale snapshot.
8073        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8074        bindings.insert("x".to_string(), Sexp::int(1));
8075        let err = resolve_binding(&bindings, "xs", UnquoteForm::Unquote)
8076            .expect_err("unbound name must error at gate-2 (substitute)");
8077        match err {
8078            LispError::UnboundTemplateVar { prefix, name, hint } => {
8079                assert_eq!(prefix, UnquoteForm::Unquote);
8080                assert_eq!(name, "xs");
8081                assert_eq!(hint.as_deref(), Some("x"));
8082            }
8083            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
8084        }
8085    }
8086
8087    #[test]
8088    fn resolve_binding_rejects_unbound_name_without_hint_under_splice() {
8089        // Sibling negative control on the substitute path: past-bound
8090        // distance → no hint. Closes the closed-set product of
8091        // {hint, no-hint} × {Unquote, Splice} on the substitute-path
8092        // gate-2.
8093        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8094        bindings.insert("args".to_string(), Sexp::Nil);
8095        let err = resolve_binding(&bindings, "wholly-unrelated", UnquoteForm::Splice)
8096            .expect_err("unrelated unbound must error at gate-2");
8097        match err {
8098            LispError::UnboundTemplateVar { prefix, name, hint } => {
8099                assert_eq!(prefix, UnquoteForm::Splice);
8100                assert_eq!(name, "wholly-unrelated");
8101                assert_eq!(hint, None);
8102            }
8103            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
8104        }
8105    }
8106
8107    #[test]
8108    fn gate_2_consolidates_four_inline_callsites_into_two_helpers() {
8109        // Path-uniformity pin: end-to-end through ALL FOUR call sites
8110        // (`compile_node` Unquote, `compile_node` UnquoteSplice,
8111        // `substitute` Unquote, `substitute` list-inner UnquoteSplice)
8112        // every unbound-template-var rejection now routes through one
8113        // of the TWO `resolve_param_index` / `resolve_binding` helpers
8114        // — `Expander::new()` runs the compile path, so its two arms
8115        // share `resolve_param_index`; `Expander::new_substitute_only()`
8116        // runs the substitute path, so its two arms share
8117        // `resolve_binding`. The four end-to-end expansions below all
8118        // reject with the SAME variant (`UnboundTemplateVar`) with the
8119        // expected `prefix` — pins that the lift preserves the
8120        // path-uniform rejection contract `unbound_template_var`'s
8121        // prior naming established. A regression that re-inlines the
8122        // gate-2 projection at one of the four sites can drift the
8123        // four call sites independent of each other — this test would
8124        // catch that drift.
8125        struct Case {
8126            src: &'static str,
8127            expander: fn() -> Expander,
8128            expected_form: UnquoteForm,
8129        }
8130        let cases: &[Case] = &[
8131            // compile_node Unquote (bytecode path) — uses resolve_param_index
8132            Case {
8133                src: "(defmacro w (x) `(list ,xs)) (w 1)",
8134                expander: Expander::new,
8135                expected_form: UnquoteForm::Unquote,
8136            },
8137            // compile_node UnquoteSplice (bytecode path) — uses resolve_param_index
8138            Case {
8139                src: "(defmacro call (f &rest args) `(,f ,@argz)) (call foo a b)",
8140                expander: Expander::new,
8141                expected_form: UnquoteForm::Splice,
8142            },
8143            // substitute Unquote (substitute-only path) — uses resolve_binding
8144            Case {
8145                src: "(defmacro w (x) `(list ,xs)) (w 1)",
8146                expander: Expander::new_substitute_only,
8147                expected_form: UnquoteForm::Unquote,
8148            },
8149            // substitute UnquoteSplice-in-list (substitute-only path) — uses resolve_binding
8150            Case {
8151                src: "(defmacro call (f &rest args) `(,f ,@argz)) (call foo a b)",
8152                expander: Expander::new_substitute_only,
8153                expected_form: UnquoteForm::Splice,
8154            },
8155        ];
8156        for case in cases {
8157            let mut e = (case.expander)();
8158            let err = e
8159                .expand_program(read(case.src).unwrap())
8160                .expect_err("unbound template var must error end-to-end");
8161            match err {
8162                LispError::UnboundTemplateVar { prefix, .. } => {
8163                    assert_eq!(prefix, case.expected_form, "for src: {}", case.src);
8164                }
8165                other => panic!(
8166                    "expected UnboundTemplateVar for {}, got: {other:?}",
8167                    case.src
8168                ),
8169            }
8170        }
8171    }
8172
8173    // ── resolve_unquote_in_params / _in_bindings: gate-1+gate-2 composition ─
8174
8175    #[test]
8176    fn resolve_unquote_in_params_returns_index_for_symbol_inner_under_unquote() {
8177        // Ok-arm composition under `UnquoteForm::Unquote`: gate-1 projects
8178        // the symbol-inner to "x"; gate-2 looks "x" up in `params` and
8179        // returns its index. The combined helper returns the gate-2
8180        // result directly — pins that gate-1's Ok-arm threads into
8181        // gate-2's input without intermediate state.
8182        let inner = Sexp::symbol("x");
8183        let params = ["x", "y"];
8184        let idx = resolve_unquote_in_params(&inner, &params, UnquoteForm::Unquote)
8185            .expect("symbol-inner bound at index 0 must resolve");
8186        assert_eq!(idx, 0);
8187    }
8188
8189    #[test]
8190    fn resolve_unquote_in_params_returns_index_for_symbol_inner_under_splice() {
8191        // Sibling Ok-arm under `UnquoteForm::Splice`: pins that the
8192        // marker doesn't change the projection — only the rejection
8193        // path's `prefix` slot.
8194        let inner = Sexp::symbol("args");
8195        let params = ["f", "args"];
8196        let idx = resolve_unquote_in_params(&inner, &params, UnquoteForm::Splice)
8197            .expect("symbol-inner bound at index 1 must resolve");
8198        assert_eq!(idx, 1);
8199    }
8200
8201    #[test]
8202    fn resolve_unquote_in_params_rejects_non_symbol_inner_at_gate_1() {
8203        // Err-arm at gate-1 (must-be-a-symbol): the inner is a list, not
8204        // a symbol, so gate-1 rejects via `non_symbol_unquote_target`
8205        // BEFORE gate-2's param lookup runs. Pins that the composition's
8206        // sequencing is gate-1-then-gate-2: a regression that runs
8207        // gate-2 first would attempt to look up "(list 1 2)" as a param
8208        // name and emit `LispError::UnboundTemplateVar { name: "(list 1
8209        // 2)", ... }` — a confusing diagnostic that would substring-grep
8210        // "unbound" instead of "expected symbol". This test pins the
8211        // structural floor: a non-symbol inner is rejected as a non-
8212        // symbol, never re-treated as a bound-name lookup key.
8213        let inner = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(1), Sexp::int(2)]);
8214        let params = ["x"];
8215        let err = resolve_unquote_in_params(&inner, &params, UnquoteForm::Unquote)
8216            .expect_err("non-symbol inner must reject at gate-1");
8217        match err {
8218            LispError::NonSymbolUnquoteTarget { prefix, got } => {
8219                assert_eq!(prefix, UnquoteForm::Unquote);
8220                assert_eq!(got.display, "(list 1 2)");
8221            }
8222            other => panic!("expected NonSymbolUnquoteTarget (gate-1), got: {other:?}"),
8223        }
8224    }
8225
8226    #[test]
8227    fn resolve_unquote_in_params_rejects_unbound_symbol_at_gate_2() {
8228        // Err-arm at gate-2 (must-be-bound-in-scope): the inner IS a
8229        // symbol (gate-1 passes) but the name isn't in `params`, so
8230        // gate-2 rejects via `unbound_template_var`. Pins that gate-1
8231        // forwards its Ok-arm `&str` borrow into gate-2's lookup, and
8232        // that the marker `prefix` is threaded into gate-2's rejection
8233        // unchanged (a regression that hard-codes `UnquoteForm::Unquote`
8234        // at the composition boundary would fail this Splice-marker
8235        // assertion).
8236        let inner = Sexp::symbol("missing");
8237        let params = ["x", "y"];
8238        let err = resolve_unquote_in_params(&inner, &params, UnquoteForm::Splice)
8239            .expect_err("unbound symbol must reject at gate-2");
8240        match err {
8241            LispError::UnboundTemplateVar { prefix, name, .. } => {
8242                assert_eq!(prefix, UnquoteForm::Splice);
8243                assert_eq!(name, "missing");
8244            }
8245            other => panic!("expected UnboundTemplateVar (gate-2), got: {other:?}"),
8246        }
8247    }
8248
8249    #[test]
8250    fn resolve_unquote_in_bindings_returns_borrow_for_symbol_inner_under_unquote() {
8251        // Substitute-path sibling of `resolve_unquote_in_params_returns_
8252        // index_for_symbol_inner_under_unquote`. The combined helper
8253        // composes gate-1 (project inner to symbol) THEN gate-2 (look
8254        // up name in bindings). The returned `&Sexp` borrows from
8255        // `bindings` so the list-inner caller threads it straight into
8256        // the splice-expansion match without an intermediate allocation.
8257        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8258        bindings.insert("v".to_string(), Sexp::int(42));
8259        let inner = Sexp::symbol("v");
8260        let val = resolve_unquote_in_bindings(&inner, &bindings, UnquoteForm::Unquote)
8261            .expect("symbol-inner bound to 42 must resolve");
8262        assert_eq!(val, &Sexp::int(42));
8263    }
8264
8265    #[test]
8266    fn resolve_unquote_in_bindings_rejects_non_symbol_inner_at_gate_1() {
8267        // Substitute-path sibling of `resolve_unquote_in_params_rejects_
8268        // non_symbol_inner_at_gate_1`. Pins the gate-1-then-gate-2
8269        // sequencing on the substitute path: a non-symbol inner is
8270        // rejected as a non-symbol BEFORE the bindings map is consulted.
8271        let bindings: HashMap<String, Sexp> = HashMap::new();
8272        let inner = Sexp::int(5);
8273        let err = resolve_unquote_in_bindings(&inner, &bindings, UnquoteForm::Splice)
8274            .expect_err("non-symbol inner must reject at gate-1");
8275        match err {
8276            LispError::NonSymbolUnquoteTarget { prefix, got } => {
8277                assert_eq!(prefix, UnquoteForm::Splice);
8278                assert_eq!(got.display, "5");
8279            }
8280            other => panic!("expected NonSymbolUnquoteTarget (gate-1), got: {other:?}"),
8281        }
8282    }
8283
8284    #[test]
8285    fn resolve_unquote_in_bindings_rejects_unbound_symbol_at_gate_2() {
8286        // Substitute-path sibling of `resolve_unquote_in_params_rejects_
8287        // unbound_symbol_at_gate_2`. Pins the gate-2 rejection on the
8288        // substitute path with the marker threaded into the rejection's
8289        // `prefix` slot.
8290        let mut bindings: HashMap<String, Sexp> = HashMap::new();
8291        bindings.insert("known".to_string(), Sexp::Nil);
8292        let inner = Sexp::symbol("missing");
8293        let err = resolve_unquote_in_bindings(&inner, &bindings, UnquoteForm::Unquote)
8294            .expect_err("unbound symbol must reject at gate-2");
8295        match err {
8296            LispError::UnboundTemplateVar { prefix, name, .. } => {
8297                assert_eq!(prefix, UnquoteForm::Unquote);
8298                assert_eq!(name, "missing");
8299            }
8300            other => panic!("expected UnboundTemplateVar (gate-2), got: {other:?}"),
8301        }
8302    }
8303
8304    #[test]
8305    fn resolve_unquote_helpers_consolidate_four_inline_gate12_sites() {
8306        // End-to-end pin: all FOUR call sites of the gate-1+gate-2
8307        // composition (compile_node Unquote, compile_node UnquoteSplice,
8308        // substitute Unquote, substitute list-inner UnquoteSplice) now
8309        // share TWO composed primitives — `resolve_unquote_in_params`
8310        // on the bytecode path, `resolve_unquote_in_bindings` on the
8311        // substitute path — and ALL four reject gate-1 failures (non-
8312        // symbol inner) with the SAME `LispError::NonSymbolUnquoteTarget`
8313        // variant carrying the expected `prefix` slot. Before the lift,
8314        // each site threaded `form` twice through two helper calls; this
8315        // test pins that the lift preserves the gate's rejection-shape
8316        // identity across all four sites for a non-symbol inner — i.e.
8317        // gate-1 fires identically across both expansion strategies.
8318        struct Case {
8319            src: &'static str,
8320            expander: fn() -> Expander,
8321            expected_form: UnquoteForm,
8322        }
8323        let cases: &[Case] = &[
8324            Case {
8325                src: "(defmacro w (x) `,(list 1 2)) (w 1)",
8326                expander: Expander::new,
8327                expected_form: UnquoteForm::Unquote,
8328            },
8329            Case {
8330                src: "(defmacro w (x) `(outer ,@5)) (w 1)",
8331                expander: Expander::new,
8332                expected_form: UnquoteForm::Splice,
8333            },
8334            Case {
8335                src: "(defmacro w (x) `,(list 1 2)) (w 1)",
8336                expander: Expander::new_substitute_only,
8337                expected_form: UnquoteForm::Unquote,
8338            },
8339            Case {
8340                src: "(defmacro w (x) `(outer ,@(list 1 2))) (w 1)",
8341                expander: Expander::new_substitute_only,
8342                expected_form: UnquoteForm::Splice,
8343            },
8344        ];
8345        for case in cases {
8346            let mut e = (case.expander)();
8347            let err = e
8348                .expand_program(read(case.src).unwrap())
8349                .expect_err("non-symbol inner must error end-to-end");
8350            match err {
8351                LispError::NonSymbolUnquoteTarget { prefix, .. } => {
8352                    assert_eq!(prefix, case.expected_form, "for src: {}", case.src);
8353                }
8354                other => panic!(
8355                    "expected NonSymbolUnquoteTarget for {}, got: {other:?}",
8356                    case.src
8357                ),
8358            }
8359        }
8360    }
8361
8362    // ── Splice outside list: structural variant + path-uniform rejection ─
8363
8364    /// Helper for the splice-outside-list tests — pins the variant shape
8365    /// and carries the offending `got` field up to the assert site for
8366    /// legibility. Sibling of `unbound_var` and `non_symbol_target`.
8367    fn splice_outside_list_got(err: &LispError) -> &str {
8368        match err {
8369            LispError::SpliceOutsideList { got } => got.display.as_str(),
8370            other => panic!("expected SpliceOutsideList, got: {other:?}"),
8371        }
8372    }
8373
8374    #[test]
8375    fn splice_outside_list_in_substitute_emits_structural_variant() {
8376        // `,@xs` at the body's top level — there is no containing list to
8377        // splice into. Path: substitute (the `Expander::new_substitute_only`
8378        // path's top-level `Sexp::UnquoteSplice(_)` arm). Pins variant
8379        // identity AND the offending inner so a regression that re-inlines
8380        // the legacy `LispError::Compile` shape fails-loudly here.
8381        let mut e = Expander::new_substitute_only();
8382        let err = e
8383            .expand_program(read("(defmacro f (xs) `,@xs) (f (list 1 2))").unwrap())
8384            .expect_err("splice outside list must error");
8385        assert_eq!(splice_outside_list_got(&err), "xs");
8386    }
8387
8388    #[test]
8389    fn splice_outside_list_with_list_literal_in_substitute_emits_structural_variant() {
8390        // `,@(list 1 2)` at the body's top level — the inner is a literal
8391        // list, not a symbol. The structural variant carries the inner's
8392        // Sexp::Display projection so the operator sees the literal value
8393        // they wrote in the parenthetical.
8394        let mut e = Expander::new_substitute_only();
8395        let err = e
8396            .expand_program(read("(defmacro f (x) `,@(list 1 2)) (f 1)").unwrap())
8397            .expect_err("splice outside list must error");
8398        assert_eq!(splice_outside_list_got(&err), "(list 1 2)");
8399    }
8400
8401    #[test]
8402    fn splice_outside_list_in_compile_template_emits_structural_variant() {
8403        // The bytecode path's `compile_template` gate now rejects top-level
8404        // `,@X` bodies BEFORE walking — closing the prior silent-divergence
8405        // where the bytecode interpreter's outermost stack frame absorbed
8406        // the splice. Pins that the bytecode path emits the SAME structural
8407        // variant the substitute path emits — `,@-outside-list` is rejected
8408        // path-uniformly. Path: `Expander::new()` (compile_templates = true)
8409        // → `compile_template` gate.
8410        let mut e = Expander::new();
8411        let err = e
8412            .expand_program(read("(defmacro f (xs) `,@xs) (f (list 1 2))").unwrap())
8413            .expect_err("compile-template splice outside list must error");
8414        assert_eq!(splice_outside_list_got(&err), "xs");
8415    }
8416
8417    #[test]
8418    fn splice_outside_list_with_list_literal_in_compile_template_emits_structural_variant() {
8419        // Same shape as the substitute test but routed through the bytecode
8420        // path's `compile_template` gate. Proves the gate fires on a
8421        // non-symbol inner too — the slot's contents are irrelevant; only
8422        // the syntactic position matters.
8423        let mut e = Expander::new();
8424        let err = e
8425            .expand_program(read("(defmacro f (x) `,@(list 1 2)) (f 1)").unwrap())
8426            .expect_err("compile-template splice outside list must error");
8427        assert_eq!(splice_outside_list_got(&err), "(list 1 2)");
8428    }
8429
8430    #[test]
8431    fn splice_outside_list_substitute_and_bytecode_paths_agree() {
8432        // Path-uniform rejection: the SAME source emits the SAME structural
8433        // variant (`SpliceOutsideList { got: "xs" }`) under both expansion
8434        // strategies. Before the `compile_template` gate, the bytecode path
8435        // silently produced a list while the substitute path errored —
8436        // expansion strategy was observable. After the gate, the gate is
8437        // strategy-uniform, so a macro that registers under one strategy
8438        // registers under the other.
8439        let src = "(defmacro f (xs) `,@xs) (f (list 1 2))";
8440        let mut subst = Expander::new_substitute_only();
8441        let mut bytecode = Expander::new();
8442        let err_subst = subst
8443            .expand_program(read(src).unwrap())
8444            .expect_err("substitute must error");
8445        let err_byte = bytecode
8446            .expand_program(read(src).unwrap())
8447            .expect_err("bytecode must error");
8448        assert_eq!(splice_outside_list_got(&err_subst), "xs");
8449        assert_eq!(splice_outside_list_got(&err_byte), "xs");
8450    }
8451
8452    #[test]
8453    fn splice_outside_list_position_is_none_today() {
8454        // Negative control for the future-spans move: until `Sexp` carries
8455        // source positions, `position()` returns `None` for this variant.
8456        // A future run that gives `Sexp` source spans adds `pos:
8457        // Option<usize>` to ONE place; this test gives that change a
8458        // deliberate fail-before/pass-after delta.
8459        let mut e = Expander::new_substitute_only();
8460        let err = e
8461            .expand_program(read("(defmacro f (xs) `,@xs) (f (list 1 2))").unwrap())
8462            .expect_err("splice outside list must error");
8463        assert_eq!(err.position(), None);
8464    }
8465
8466    #[test]
8467    fn splice_outside_list_message_renders_legacy_substring_with_offending_form() {
8468        // End-to-end through the Display impl — pins the rendered diagnostic
8469        // a downstream tool sees today (REPL, tatara-check). The legacy
8470        // substring `"\`,@\` may only appear inside a list"` is preserved
8471        // verbatim AND the parenthetical `(got ,@xs)` names the offending
8472        // form; tools that pattern-match on the variant gain structural
8473        // binding to `got`.
8474        let mut e = Expander::new_substitute_only();
8475        let err = e
8476            .expand_program(read("(defmacro f (xs) `,@xs) (f (list 1 2))").unwrap())
8477            .expect_err("splice outside list must error");
8478        let msg = format!("{err}");
8479        assert_eq!(
8480            msg,
8481            "compile error in ,@: `,@` may only appear inside a list (got ,@xs)"
8482        );
8483    }
8484
8485    #[test]
8486    fn splice_inside_list_still_succeeds_under_both_paths() {
8487        // Negative control: a well-positioned splice (`,@xs` INSIDE a list)
8488        // continues to succeed under both paths — the new gate only fires
8489        // when the splice is the entire body. Pins that the gate is scoped
8490        // to top-level only, not all `,@` occurrences. Uses a `&rest`-bound
8491        // list so `xs` is unambiguously a Sexp::List `(1 2)` rather than a
8492        // bare list-literal whose first symbol would also splice through.
8493        let src = "(defmacro f (&rest xs) `(outer ,@xs)) (f 1 2)";
8494        let mut subst = Expander::new_substitute_only();
8495        let mut bytecode = Expander::new();
8496        let out_subst = subst.expand_program(read(src).unwrap()).unwrap();
8497        let out_byte = bytecode.expand_program(read(src).unwrap()).unwrap();
8498        assert_eq!(out_subst, out_byte);
8499        assert_eq!(out_subst[0], parse("(outer 1 2)"));
8500    }
8501
8502    // ── splice_value_into: the shared splice-result coercion ──
8503
8504    #[test]
8505    fn splice_value_into_list_flattens_elements_into_builder() {
8506        // The canonical splice arm: a bound LIST contributes its elements
8507        // in order, preserving anything already in the builder.
8508        let mut builder = vec![Sexp::symbol("outer")];
8509        splice_value_into(&mut builder, &Sexp::List(vec![Sexp::int(1), Sexp::int(2)]));
8510        assert_eq!(
8511            builder,
8512            vec![Sexp::symbol("outer"), Sexp::int(1), Sexp::int(2)]
8513        );
8514    }
8515
8516    #[test]
8517    fn splice_value_into_nil_is_a_noop() {
8518        // Splicing the empty list (`Sexp::Nil`) contributes nothing —
8519        // the builder is unchanged.
8520        let mut builder = vec![Sexp::symbol("outer")];
8521        splice_value_into(&mut builder, &Sexp::Nil);
8522        assert_eq!(builder, vec![Sexp::symbol("outer")]);
8523    }
8524
8525    #[test]
8526    fn splice_value_into_scalar_pushes_single_element() {
8527        // A non-list, non-nil bound value degrades `,@x` to `,x`: it
8528        // splices as exactly one element. Pins the "free middle" coercion
8529        // every scalar shape (int, keyword, …) shares.
8530        let mut builder = vec![Sexp::symbol("outer")];
8531        splice_value_into(&mut builder, &Sexp::int(5));
8532        assert_eq!(builder, vec![Sexp::symbol("outer"), Sexp::int(5)]);
8533        let mut other: Vec<Sexp> = vec![];
8534        splice_value_into(&mut other, &Sexp::keyword("k"));
8535        assert_eq!(other, vec![Sexp::keyword("k")]);
8536    }
8537
8538    #[test]
8539    fn splice_of_non_list_value_coerces_identically_under_both_paths() {
8540        // The point of the lift: the NON-list splice arms (scalar → single
8541        // element, nil → nothing) coerce identically under the substitute
8542        // AND bytecode strategies. Before the coercion was lifted to ONE
8543        // primitive these two arms lived inline at two sites; this test
8544        // pins that the two strategies cannot drift on the non-list arms.
8545        let scalar = "(defmacro f (x) `(outer ,@x)) (f 5)";
8546        let empty = "(defmacro g (x) `(outer ,@x)) (g ())";
8547        for src in [scalar, empty] {
8548            let mut subst = Expander::new_substitute_only();
8549            let mut bytecode = Expander::new();
8550            let out_subst = subst.expand_program(read(src).unwrap()).unwrap();
8551            let out_byte = bytecode.expand_program(read(src).unwrap()).unwrap();
8552            assert_eq!(out_subst, out_byte, "strategies must agree for {src}");
8553        }
8554        let mut e = Expander::new();
8555        assert_eq!(
8556            e.expand_program(read(scalar).unwrap()).unwrap()[0],
8557            parse("(outer 5)")
8558        );
8559        let mut e2 = Expander::new();
8560        assert_eq!(
8561            e2.expand_program(read(empty).unwrap()).unwrap()[0],
8562            parse("(outer)")
8563        );
8564    }
8565
8566    // ── Missing macro arg: structural variant + path-uniform rejection ──
8567
8568    /// Helper for the missing-macro-arg tests — pins the variant shape
8569    /// and carries the failing macro's name + un-bound param up to the
8570    /// assert site for legibility. Sibling of `unbound_var`,
8571    /// `non_symbol_target`, and `splice_outside_list_got`.
8572    fn missing_macro_arg_fields(err: &LispError) -> (&str, &str) {
8573        match err {
8574            LispError::MissingMacroArg { macro_name, param } => {
8575                (macro_name.as_str(), param.as_str())
8576            }
8577            other => panic!("expected MissingMacroArg, got: {other:?}"),
8578        }
8579    }
8580
8581    #[test]
8582    fn missing_macro_arg_in_compile_template_emits_structural_variant() {
8583        // `(need-two 1)` against `(need-two a b)` — `b` has no arg. Path:
8584        // `apply_compiled` (the bytecode-template path, default expander).
8585        // Pins variant identity AND macro_name AND the un-bound param so a
8586        // regression that re-inlines the legacy `LispError::Compile` shape
8587        // fails-loudly here.
8588        let mut e = Expander::new();
8589        let err = e
8590            .expand_program(read("(defmacro need-two (a b) `(,a ,b)) (need-two 1)").unwrap())
8591            .expect_err("missing required macro arg must error");
8592        let (macro_name, param) = missing_macro_arg_fields(&err);
8593        assert_eq!(macro_name, "need-two");
8594        assert_eq!(param, "b");
8595    }
8596
8597    #[test]
8598    fn missing_macro_arg_in_substitute_emits_structural_variant() {
8599        // Same shape as the bytecode test but routed through the
8600        // substitute-only expander → `bind_args` is the failing site.
8601        // Proves the substitute path emits the SAME structural variant the
8602        // bytecode path emits — `missing required arg` rejection is
8603        // path-uniform across both expansion strategies.
8604        let mut e = Expander::new_substitute_only();
8605        let err = e
8606            .expand_program(read("(defmacro need-two (a b) `(,a ,b)) (need-two 1)").unwrap())
8607            .expect_err("missing required macro arg must error");
8608        let (macro_name, param) = missing_macro_arg_fields(&err);
8609        assert_eq!(macro_name, "need-two");
8610        assert_eq!(param, "b");
8611    }
8612
8613    #[test]
8614    fn missing_macro_arg_first_position_is_named() {
8615        // `(f)` against `(f a b)` — `a` (the FIRST required param) has no
8616        // arg. The variant names `a`, not `b` — naming the LEFTMOST
8617        // un-bound param is the shape `bind_args` / `apply_compiled` both
8618        // emit (each iterates positionally and bails on the first missing
8619        // slot). Pins the leftmost-bail contract so a regression that
8620        // names the rightmost (or a surplus) param fails-loudly.
8621        let mut e = Expander::new();
8622        let err = e
8623            .expand_program(read("(defmacro f (a b) `(,a ,b)) (f)").unwrap())
8624            .expect_err("missing first required arg must error");
8625        let (macro_name, param) = missing_macro_arg_fields(&err);
8626        assert_eq!(macro_name, "f");
8627        assert_eq!(param, "a");
8628    }
8629
8630    #[test]
8631    fn missing_macro_arg_substitute_and_bytecode_paths_agree() {
8632        // Path-uniform rejection: the SAME source emits the SAME structural
8633        // variant under both expansion strategies. Negative control for
8634        // the divergence-closing posture: a future refactor that drifts
8635        // either path's rejection shape (or drops one path's rejection
8636        // entirely) fails-loudly here. Sibling of
8637        // `splice_outside_list_substitute_and_bytecode_paths_agree` —
8638        // both close `THEORY.md §II.1 invariant 2 — free middle` for one
8639        // failure mode each.
8640        let src = "(defmacro need-two (a b) `(,a ,b)) (need-two 1)";
8641        let mut subst = Expander::new_substitute_only();
8642        let mut bytecode = Expander::new();
8643        let err_subst = subst
8644            .expand_program(read(src).unwrap())
8645            .expect_err("substitute must error");
8646        let err_byte = bytecode
8647            .expand_program(read(src).unwrap())
8648            .expect_err("bytecode must error");
8649        assert_eq!(missing_macro_arg_fields(&err_subst), ("need-two", "b"));
8650        assert_eq!(missing_macro_arg_fields(&err_byte), ("need-two", "b"));
8651    }
8652
8653    #[test]
8654    fn missing_macro_arg_position_is_none_today() {
8655        // Negative control for the future-spans move: until `Sexp` carries
8656        // source positions, `position()` returns `None` for this variant.
8657        // A future run that gives `Sexp` source spans adds `pos:
8658        // Option<usize>` to ONE place; this test gives that change a
8659        // deliberate fail-before/pass-after delta. Parallel to
8660        // `splice_outside_list_position_is_none_today`.
8661        let mut e = Expander::new();
8662        let err = e
8663            .expand_program(read("(defmacro need-two (a b) `(,a ,b)) (need-two 1)").unwrap())
8664            .expect_err("missing required macro arg must error");
8665        assert_eq!(err.position(), None);
8666    }
8667
8668    #[test]
8669    fn missing_macro_arg_message_renders_legacy_substring_with_macro_name() {
8670        // End-to-end through the Display impl — pins the rendered diagnostic
8671        // a downstream tool sees today (REPL, tatara-check). The legacy
8672        // substring `"missing required arg: {param}"` is preserved verbatim
8673        // AND the head clause names the failing macro via `"call to
8674        // {macro_name}"`; tools that pattern-match on the variant gain
8675        // structural binding to `macro_name` / `param`.
8676        let mut e = Expander::new();
8677        let err = e
8678            .expand_program(read("(defmacro need-two (a b) `(,a ,b)) (need-two 1)").unwrap())
8679            .expect_err("missing required macro arg must error");
8680        assert_eq!(
8681            format!("{err}"),
8682            "compile error in call to need-two: missing required arg: b"
8683        );
8684    }
8685
8686    #[test]
8687    fn missing_macro_arg_carries_kebab_case_macro_and_param_unchanged() {
8688        // Both `macro_name` (`wrap-twice`) and `param` (`notify-ref`)
8689        // round-trip through the variant unchanged. Pinning this contract
8690        // means a regression that camelCases or lowercases either side
8691        // fails-loudly here. Parallel to the
8692        // `unknown_kwarg_display_carries_kebab_case_keys_unchanged`
8693        // assertion for the kwarg-gate's symmetric surface.
8694        let mut e = Expander::new();
8695        let err = e
8696            .expand_program(
8697                read("(defmacro wrap-twice (notify-ref body) `(list ,notify-ref ,body)) (wrap-twice :a)")
8698                    .unwrap(),
8699            )
8700            .expect_err("missing required macro arg must error");
8701        let (macro_name, param) = missing_macro_arg_fields(&err);
8702        assert_eq!(macro_name, "wrap-twice");
8703        assert_eq!(param, "body");
8704    }
8705
8706    #[test]
8707    fn rest_param_only_macro_with_no_args_still_succeeds() {
8708        // Negative control: a macro whose only param is `&rest` must NOT
8709        // error when called with zero args — the rest-param binds to the
8710        // empty list. The new structural variant fires only on REQUIRED
8711        // params; the `Param::Rest` arm in both `bind_args` and
8712        // `apply_compiled` continues to bind the empty tail. Pins that the
8713        // helper is scoped to required-param failure, not all
8714        // arity-mismatch shapes.
8715        let src = "(defmacro f (&rest xs) `(list ,@xs)) (f)";
8716        let mut subst = Expander::new_substitute_only();
8717        let mut bytecode = Expander::new();
8718        let out_subst = subst.expand_program(read(src).unwrap()).unwrap();
8719        let out_byte = bytecode.expand_program(read(src).unwrap()).unwrap();
8720        assert_eq!(out_subst, out_byte);
8721        assert_eq!(out_subst[0], parse("(list)"));
8722    }
8723
8724    // ── TooManyMacroArgs: call-site mirror of RestParamTrailingTokens ──
8725    //
8726    // A rest-less param list has a FIXED maximum arity equal to
8727    // `required.len() + optional.len()`. Surplus call args have nowhere to
8728    // bind. Before this gate the surplus was silently truncated to the
8729    // slice the binder could consume — the typed-entry macro-call-gate
8730    // rejected too-few-args loudly (`MissingMacroArg`) but accepted
8731    // too-many silently, an asymmetry the definition-side `&rest <name>
8732    // extra` rejection (`RestParamTrailingTokens`) had no call-side dual.
8733    // After this gate the call-site arity surface is structurally
8734    // complete in both directions; the substitute + bytecode paths share
8735    // `MacroParams::bind`, so both inherit the rejection without drift.
8736
8737    /// Helper for the too-many-args tests — projects to (macro_name,
8738    /// expected, got) for legibility. Sibling of `missing_macro_arg_fields`.
8739    fn too_many_macro_args_fields(err: &LispError) -> (&str, usize, usize) {
8740        match err {
8741            LispError::TooManyMacroArgs {
8742                macro_name,
8743                expected,
8744                got,
8745            } => (macro_name.as_str(), *expected, *got),
8746            other => panic!("expected TooManyMacroArgs, got: {other:?}"),
8747        }
8748    }
8749
8750    #[test]
8751    fn too_many_macro_args_required_only_rejected_with_expected_and_got() {
8752        // `(defmacro f (a b) ...)` called as `(f 1 2 3)` — `3` has
8753        // nowhere to bind. The rest-less binder rejects via
8754        // `TooManyMacroArgs { macro_name: "f", expected: 2, got: 3 }`,
8755        // NOT silently drops `3`. Pins both the variant identity AND the
8756        // structural fields the typed gate exposes for authoring-tool
8757        // quick-fixes ("you supplied 3 args; the macro takes at most
8758        // 2").
8759        let mut e = Expander::new();
8760        let err = e
8761            .expand_program(read("(defmacro f (a b) `(list ,a ,b)) (f 1 2 3)").unwrap())
8762            .expect_err("surplus arg on rest-less call must error");
8763        let (macro_name, expected, got) = too_many_macro_args_fields(&err);
8764        assert_eq!(macro_name, "f");
8765        assert_eq!(expected, 2);
8766        assert_eq!(got, 3);
8767    }
8768
8769    #[test]
8770    fn too_many_macro_args_required_plus_optional_capacity_includes_optional() {
8771        // The rest-less binder's fixed maximum arity is `required.len() +
8772        // optional.len()` — the optional section CONTRIBUTES to capacity.
8773        // `(defmacro f (a &optional b) ...)` accepts 1 OR 2 args; 3
8774        // args rejects with `expected: 2` (required + optional, NOT just
8775        // required). Pins the optional-counts-in-capacity contract so a
8776        // regression that omits optionals from the expected calculation
8777        // (and erroneously rejects 2-arg calls) fails-loudly here.
8778        let mut e = Expander::new();
8779        let err = e
8780            .expand_program(read("(defmacro f (a &optional b) `(list ,a ,b)) (f 1 2 3)").unwrap())
8781            .expect_err("surplus arg beyond required+optional must error");
8782        let (macro_name, expected, got) = too_many_macro_args_fields(&err);
8783        assert_eq!(macro_name, "f");
8784        assert_eq!(expected, 2);
8785        assert_eq!(got, 3);
8786    }
8787
8788    #[test]
8789    fn too_many_macro_args_required_plus_two_optionals_arity_three() {
8790        // Larger optional section (capacity 1 + 2 = 3). 4 args rejects
8791        // with `expected: 3`. Pins that the capacity calculation scales
8792        // with optional.len(), not just at-most-one. Mixes a bare
8793        // optional with an optional carrying a default form — both shapes
8794        // contribute identically to capacity (the typed `OptionalParam`
8795        // entry's `default: Option<Sexp>` is irrelevant to the arity gate).
8796        let mut e = Expander::new();
8797        let err = e
8798            .expand_program(
8799                read("(defmacro f (a &optional b (c 5)) `(list ,a ,b ,c)) (f 1 2 3 4)").unwrap(),
8800            )
8801            .expect_err("surplus arg beyond required+two-optional must error");
8802        let (macro_name, expected, got) = too_many_macro_args_fields(&err);
8803        assert_eq!(macro_name, "f");
8804        assert_eq!(expected, 3);
8805        assert_eq!(got, 4);
8806    }
8807
8808    #[test]
8809    fn too_many_macro_args_does_not_fire_when_rest_is_present() {
8810        // Negative control: a rest-PRESENT param list has no maximum
8811        // arity — the `&rest` slot collects every trailing arg into a
8812        // `Sexp::List`. `(defmacro f (a &rest xs) ...)` called as
8813        // `(f 1 2 3 4)` MUST succeed; the new gate fires ONLY when
8814        // `MacroParams.rest` is `None`. Pins the rest-present-path
8815        // remains permissive — a regression that wrongly fires the
8816        // too-many gate for any surplus (including the rest-collecting
8817        // path) would break every `&rest`-using macro.
8818        let src = "(defmacro f (a &rest xs) `(list ,a ,@xs)) (f 1 2 3 4)";
8819        let mut subst = Expander::new_substitute_only();
8820        let mut bytecode = Expander::new();
8821        let out_subst = subst.expand_program(read(src).unwrap()).unwrap();
8822        let out_byte = bytecode.expand_program(read(src).unwrap()).unwrap();
8823        assert_eq!(out_subst, out_byte);
8824        assert_eq!(out_subst[0], parse("(list 1 2 3 4)"));
8825    }
8826
8827    #[test]
8828    fn too_many_macro_args_does_not_fire_at_exact_max_arity() {
8829        // Negative control: the rest-less gate fires STRICTLY when
8830        // `args.len() > expected` — at exact arity the binder accepts.
8831        // `(defmacro f (a &optional b) ...)` called as `(f 1 2)` binds
8832        // a=1, b=2 successfully (the optional takes its supplied arg,
8833        // not the default). Pins the boundary condition so a regression
8834        // that flips the comparison to `>=` (rejecting exact-arity
8835        // calls) fails-loudly here.
8836        let src = "(defmacro f (a &optional b) `(list ,a ,b)) (f 1 2)";
8837        let mut e = Expander::new();
8838        let out = e.expand_program(read(src).unwrap()).unwrap();
8839        assert_eq!(out[0], parse("(list 1 2)"));
8840    }
8841
8842    #[test]
8843    fn too_many_macro_args_substitute_and_bytecode_paths_agree() {
8844        // Path-uniform rejection: the SAME source emits the SAME
8845        // structural variant under both expansion strategies. The
8846        // shared `MacroParams::bind` makes the rejection lands once and
8847        // both paths inherit it. Mirror of
8848        // `missing_macro_arg_substitute_and_bytecode_paths_agree` —
8849        // both close `THEORY.md §II.1 invariant 2 — free middle` for
8850        // one failure mode each.
8851        let src = "(defmacro pair (a b) `(cons ,a ,b)) (pair 1 2 3)";
8852        let mut subst = Expander::new_substitute_only();
8853        let mut bytecode = Expander::new();
8854        let err_subst = subst
8855            .expand_program(read(src).unwrap())
8856            .expect_err("substitute must error");
8857        let err_byte = bytecode
8858            .expand_program(read(src).unwrap())
8859            .expect_err("bytecode must error");
8860        assert_eq!(too_many_macro_args_fields(&err_subst), ("pair", 2, 3));
8861        assert_eq!(too_many_macro_args_fields(&err_byte), ("pair", 2, 3));
8862    }
8863
8864    #[test]
8865    fn too_many_macro_args_fires_after_missing_required_priority_held() {
8866        // Priority discipline: the required walk fires
8867        // `MissingMacroArg` BEFORE the rest-less surplus gate is
8868        // reached. `(defmacro f (a b c) …) (f 1)` is `MissingMacroArg
8869        // { param: "b" }`, NOT `TooManyMacroArgs` (and certainly not a
8870        // collision). The two failure modes are structurally disjoint:
8871        // too-few-required vs. too-many-with-no-rest. Pins the bail-on-
8872        // first-missing-required contract so a regression that swaps
8873        // the two gates' order would emit the wrong variant.
8874        let mut e = Expander::new();
8875        let err = e
8876            .expand_program(read("(defmacro f (a b c) `(list ,a ,b ,c)) (f 1)").unwrap())
8877            .expect_err("missing required must error");
8878        assert!(
8879            matches!(err, LispError::MissingMacroArg { .. }),
8880            "expected MissingMacroArg (priority), got: {err:?}"
8881        );
8882    }
8883
8884    #[test]
8885    fn too_many_macro_args_zero_required_zero_optional_rejects_any_args() {
8886        // Degenerate case: a nullary macro `(defmacro f () ...)` has
8887        // capacity 0; ANY supplied arg rejects with `expected: 0`. Pins
8888        // the gate fires even when the rest-less max-arity is zero —
8889        // i.e. the rejection is structural, not conditional on a
8890        // non-empty required+optional.
8891        let mut e = Expander::new();
8892        let err = e
8893            .expand_program(read("(defmacro f () `(list)) (f 1)").unwrap())
8894            .expect_err("nullary macro called with arg must error");
8895        let (macro_name, expected, got) = too_many_macro_args_fields(&err);
8896        assert_eq!(macro_name, "f");
8897        assert_eq!(expected, 0);
8898        assert_eq!(got, 1);
8899    }
8900
8901    #[test]
8902    fn too_many_macro_args_display_renders_legacy_compile_substring() {
8903        // The rendered Display matches the legacy `Compile`-shaped
8904        // diagnostic style — `"compile error in call to {macro_name}:
8905        // too many args: expected at most {expected}, got {got}"` — so
8906        // the existing `"compile error in call to"` substring authoring
8907        // tools' assertions key on stays unchanged. Pins the byte-level
8908        // rendered shape so a regression that drifts the prefix /
8909        // separator / labels fails-loudly here.
8910        let err = LispError::TooManyMacroArgs {
8911            macro_name: "pair".into(),
8912            expected: 2,
8913            got: 5,
8914        };
8915        assert_eq!(
8916            err.to_string(),
8917            "compile error in call to pair: too many args: expected at most 2, got 5"
8918        );
8919    }
8920
8921    #[test]
8922    fn too_many_macro_args_position_is_none_today() {
8923        // Negative control for the future-spans move: until `Sexp`
8924        // carries source positions, `position()` returns `None` for
8925        // this variant. A future run that gives `Sexp` source spans
8926        // adds `pos: Option<usize>` to ONE place; this test gives that
8927        // change a deliberate fail-before/pass-after delta. Parallel to
8928        // `missing_macro_arg_position_is_none_today`.
8929        let err = LispError::TooManyMacroArgs {
8930            macro_name: "pair".into(),
8931            expected: 2,
8932            got: 3,
8933        };
8934        assert_eq!(err.position(), None);
8935    }
8936
8937    /// Helper for the non-symbol-param tests — pins the variant shape and
8938    /// carries the failing position + offending element up to the assert
8939    /// site for legibility. Sibling of `missing_macro_arg_fields`.
8940    fn non_symbol_param_fields(err: &LispError) -> (usize, &str) {
8941        match err {
8942            LispError::NonSymbolParam { position, got } => (*position, got.display.as_str()),
8943            other => panic!("expected NonSymbolParam, got: {other:?}"),
8944        }
8945    }
8946
8947    #[test]
8948    fn non_symbol_param_at_first_position_emits_structural_variant() {
8949        // `(defmacro f (5) ...)` — the first element of the param list is
8950        // an integer literal, not a symbol. Pins variant identity AND
8951        // that `position` is the loop index inside `parse_params` (0 for
8952        // the first slot) AND that `got` is the offending element via
8953        // `Sexp::Display` (`5`). A regression that re-inlines the legacy
8954        // `LispError::Compile` shape (which named neither the position
8955        // nor the offending element) fails-loudly here.
8956        let mut e = Expander::new();
8957        let err = e
8958            .expand_program(read("(defmacro f (5) `(list ,a))").unwrap())
8959            .expect_err("non-symbol param must error");
8960        let (position, got) = non_symbol_param_fields(&err);
8961        assert_eq!(position, 0);
8962        assert_eq!(got, "5");
8963    }
8964
8965    #[test]
8966    fn non_symbol_param_at_second_position_emits_structural_variant() {
8967        // `(defmacro f (a 5) ...)` — `a` parses fine, `5` at position 1
8968        // misfires. Pins that `position` advances with the loop index, so
8969        // an LSP quick-fix that wants to point at "the second element of
8970        // your param list" gains the index as data, no source re-parse
8971        // required.
8972        let mut e = Expander::new();
8973        let err = e
8974            .expand_program(read("(defmacro f (a 5) `(,a))").unwrap())
8975            .expect_err("non-symbol param must error");
8976        let (position, got) = non_symbol_param_fields(&err);
8977        assert_eq!(position, 1);
8978        assert_eq!(got, "5");
8979    }
8980
8981    #[test]
8982    fn non_symbol_param_carries_keyword_value_unchanged() {
8983        // `:k` at a param-list position. `Sexp::Display` for
8984        // `Atom::Keyword(s)` writes `:s`; pins that the variant's `got`
8985        // field round-trips the keyword form unchanged so an LSP that
8986        // surfaces "you wrote `:k` where a symbol was expected" gains
8987        // the literal keyword value as data, no re-parsing required.
8988        let mut e = Expander::new();
8989        let err = e
8990            .expand_program(read("(defmacro f (:k) `(list))").unwrap())
8991            .expect_err("non-symbol param must error");
8992        let (position, got) = non_symbol_param_fields(&err);
8993        assert_eq!(position, 0);
8994        assert_eq!(got, ":k");
8995    }
8996
8997    #[test]
8998    fn non_symbol_param_carries_nested_list_value_unchanged() {
8999        // A nested list at a param-list position. `Sexp::Display` for
9000        // `List(xs)` writes `(<x1> <x2> ...)`; pins that the variant's
9001        // `got` field carries the nested form's full Display projection
9002        // unchanged so the operator sees what they wrote.
9003        let mut e = Expander::new();
9004        let err = e
9005            .expand_program(read("(defmacro f ((nested)) `(list))").unwrap())
9006            .expect_err("non-symbol param must error");
9007        let (position, got) = non_symbol_param_fields(&err);
9008        assert_eq!(position, 0);
9009        assert_eq!(got, "(nested)");
9010    }
9011
9012    #[test]
9013    fn non_symbol_param_in_defpoint_template_emits_same_variant() {
9014        // `defpoint-template` shares `parse_params` with `defmacro` (all
9015        // three head keywords route through `macro_def_from`). Pins that
9016        // the lift fires path-uniformly across the three head keywords
9017        // — `defmacro`, `defpoint-template`, `defcheck` — so the
9018        // structural-completeness floor holds for every defmacro-shaped
9019        // form, not just the one with the `defmacro` head literal.
9020        let mut e = Expander::new();
9021        let err = e
9022            .expand_program(read("(defpoint-template obs (5) `(defpoint))").unwrap())
9023            .expect_err("non-symbol param must error");
9024        let (position, got) = non_symbol_param_fields(&err);
9025        assert_eq!(position, 0);
9026        assert_eq!(got, "5");
9027    }
9028
9029    #[test]
9030    fn non_symbol_param_in_defcheck_emits_same_variant() {
9031        // Sibling of the defpoint-template test — `defcheck` is the
9032        // third head keyword `macro_def_from` recognizes. All three
9033        // route through the same `parse_params` and now reject
9034        // non-symbol params with the same structural variant.
9035        let mut e = Expander::new();
9036        let err = e
9037            .expand_program(read("(defcheck pair (a 5) `(do))").unwrap())
9038            .expect_err("non-symbol param must error");
9039        let (position, got) = non_symbol_param_fields(&err);
9040        assert_eq!(position, 1);
9041        assert_eq!(got, "5");
9042    }
9043
9044    #[test]
9045    fn non_symbol_param_position_is_none_today() {
9046        // Negative control for the future-spans move: until `Sexp`
9047        // carries source positions, `position()` on `LispError` returns
9048        // `None` for this variant. A future run that gives `Sexp`
9049        // source spans adds `pos: Option<usize>` to ONE place; this
9050        // test gives that change a deliberate fail-before/pass-after
9051        // delta. Parallel to `missing_macro_arg_position_is_none_today`.
9052        let mut e = Expander::new();
9053        let err = e
9054            .expand_program(read("(defmacro f (5) `(list))").unwrap())
9055            .expect_err("non-symbol param must error");
9056        assert_eq!(err.position(), None);
9057    }
9058
9059    #[test]
9060    fn non_symbol_param_message_renders_legacy_substring_with_position() {
9061        // End-to-end through Display — pins the rendered diagnostic that
9062        // downstream tools (REPL, `tatara-check`) see today. Legacy
9063        // substrings `"defmacro params"` AND `"expected symbol"` are
9064        // preserved verbatim; the appended `at position {position}, got
9065        // {got}` clause is the new structural detail. Tools that
9066        // pattern-match on the variant gain structural binding to
9067        // `position` / `got`.
9068        let mut e = Expander::new();
9069        let err = e
9070            .expand_program(read("(defmacro f (a 5) `(,a))").unwrap())
9071            .expect_err("non-symbol param must error");
9072        assert_eq!(
9073            format!("{err}"),
9074            "compile error in defmacro params: \
9075             expected symbol at position 1, got 5"
9076        );
9077    }
9078
9079    #[test]
9080    fn non_symbol_param_substitute_and_bytecode_paths_agree() {
9081        // Path-uniform rejection: the SAME source emits the SAME
9082        // structural variant under both expansion strategies. The
9083        // defmacro-syntax-gate fires inside `macro_def_from` →
9084        // `parse_params`, BEFORE either strategy's expansion path runs;
9085        // so both `Expander::new()` (bytecode) and
9086        // `Expander::new_substitute_only()` (substitute) reject the
9087        // SAME malformed defmacro at the SAME gate. Sibling of
9088        // `missing_macro_arg_substitute_and_bytecode_paths_agree`.
9089        let src = "(defmacro f (a 5) `(,a))";
9090        let mut subst = Expander::new_substitute_only();
9091        let mut bytecode = Expander::new();
9092        let err_subst = subst
9093            .expand_program(read(src).unwrap())
9094            .expect_err("substitute must error");
9095        let err_byte = bytecode
9096            .expand_program(read(src).unwrap())
9097            .expect_err("bytecode must error");
9098        assert_eq!(non_symbol_param_fields(&err_subst), (1, "5"));
9099        assert_eq!(non_symbol_param_fields(&err_byte), (1, "5"));
9100    }
9101
9102    /// Helper for the rest-param-missing-name tests — pins the variant
9103    /// shape and carries the marker position + offending follower (or
9104    /// its absence) up to the assert site for legibility. Sibling of
9105    /// `non_symbol_param_fields`.
9106    fn rest_param_missing_name_fields(err: &LispError) -> (usize, Option<&str>) {
9107        match err {
9108            LispError::RestParamMissingName { rest_position, got } => {
9109                (*rest_position, got.as_ref().map(|w| w.display.as_str()))
9110            }
9111            other => panic!("expected RestParamMissingName, got: {other:?}"),
9112        }
9113    }
9114
9115    #[test]
9116    fn rest_param_missing_name_when_only_rest_emits_structural_variant_with_no_got() {
9117        // `(defmacro f (&rest))` — the marker is the only param-list
9118        // element; nothing follows. Pins variant identity AND that
9119        // `rest_position == 0` (the first slot) AND that `got == None`
9120        // (no follower exists). A regression that re-inlines the legacy
9121        // `LispError::Compile` shape (which named neither field) fails-
9122        // loudly here.
9123        let mut e = Expander::new();
9124        let err = e
9125            .expand_program(read("(defmacro f (&rest) `(list))").unwrap())
9126            .expect_err("&rest with no follower must error");
9127        let (rest_position, got) = rest_param_missing_name_fields(&err);
9128        assert_eq!(rest_position, 0);
9129        assert_eq!(got, None);
9130    }
9131
9132    #[test]
9133    fn rest_param_missing_name_at_end_of_param_list_emits_structural_variant() {
9134        // `(defmacro f (a &rest))` — `a` parses fine, `&rest` at param-list
9135        // position 1 has no follower at all. Pins that `rest_position`
9136        // advances with the loop index, so an LSP quick-fix that wants to
9137        // point at "your `&rest` at position 1 has no name" gains the
9138        // marker position as data, no source re-parse required.
9139        let mut e = Expander::new();
9140        let err = e
9141            .expand_program(read("(defmacro f (a &rest) `(,a))").unwrap())
9142            .expect_err("&rest with no follower must error");
9143        let (rest_position, got) = rest_param_missing_name_fields(&err);
9144        assert_eq!(rest_position, 1);
9145        assert_eq!(got, None);
9146    }
9147
9148    #[test]
9149    fn rest_param_missing_name_with_int_follower_emits_structural_variant() {
9150        // `(defmacro f (&rest 5))` — `&rest` at position 0 followed by
9151        // `5` (an integer literal, not a symbol). Pins that the variant's
9152        // `got` field is `Some` and carries the offending follower's
9153        // `Sexp::Display` projection; the bifurcation between "missing
9154        // entirely" and "present but non-symbol" is in the renderable
9155        // detail, not in what the gate rejects.
9156        let mut e = Expander::new();
9157        let err = e
9158            .expand_program(read("(defmacro f (&rest 5) `(list))").unwrap())
9159            .expect_err("&rest followed by non-symbol must error");
9160        let (rest_position, got) = rest_param_missing_name_fields(&err);
9161        assert_eq!(rest_position, 0);
9162        assert_eq!(got, Some("5"));
9163    }
9164
9165    #[test]
9166    fn rest_param_missing_name_with_keyword_follower_emits_structural_variant() {
9167        // `(defmacro f (a &rest :foo))` — keyword follower at the rest-name
9168        // slot. `Sexp::Display` for `Atom::Keyword(s)` writes `:s`; pins
9169        // that the variant's `got` field round-trips the keyword form
9170        // unchanged so an LSP that surfaces "you wrote `:foo` where a
9171        // rest-name was expected" gains the literal keyword value as
9172        // data, no re-parsing required.
9173        let mut e = Expander::new();
9174        let err = e
9175            .expand_program(read("(defmacro f (a &rest :foo) `(,a))").unwrap())
9176            .expect_err("&rest followed by keyword must error");
9177        let (rest_position, got) = rest_param_missing_name_fields(&err);
9178        assert_eq!(rest_position, 1);
9179        assert_eq!(got, Some(":foo"));
9180    }
9181
9182    #[test]
9183    fn rest_param_missing_name_with_nested_list_follower_emits_structural_variant() {
9184        // `(defmacro f (&rest (nested)))` — nested-list follower at the
9185        // rest-name slot. `Sexp::Display` for `List(xs)` writes
9186        // `(<x1> <x2> ...)`; pins that the variant's `got` field carries
9187        // the nested form's full Display projection unchanged so the
9188        // operator sees what they wrote.
9189        let mut e = Expander::new();
9190        let err = e
9191            .expand_program(read("(defmacro f (&rest (nested)) `(list))").unwrap())
9192            .expect_err("&rest followed by list must error");
9193        let (rest_position, got) = rest_param_missing_name_fields(&err);
9194        assert_eq!(rest_position, 0);
9195        assert_eq!(got, Some("(nested)"));
9196    }
9197
9198    #[test]
9199    fn rest_param_missing_name_in_defpoint_template_emits_same_variant() {
9200        // `defpoint-template` shares `parse_params` with `defmacro` (all
9201        // three head keywords route through `macro_def_from`). Pins that
9202        // the lift fires path-uniformly across the three head keywords —
9203        // a regression that handles `defpoint-template`'s param list
9204        // differently from `defmacro`'s would fail-loudly here.
9205        let mut e = Expander::new();
9206        let err = e
9207            .expand_program(read("(defpoint-template t (a &rest) `(,a))").unwrap())
9208            .expect_err("&rest with no follower must error");
9209        let (rest_position, got) = rest_param_missing_name_fields(&err);
9210        assert_eq!(rest_position, 1);
9211        assert_eq!(got, None);
9212    }
9213
9214    #[test]
9215    fn rest_param_missing_name_in_defcheck_emits_same_variant() {
9216        // Sibling for the `defcheck` head; rounds out the three-head-
9217        // keyword coverage so the lift is path-uniform across
9218        // `defmacro` / `defpoint-template` / `defcheck`. After this
9219        // test the defmacro-syntax-gate rejects `&rest`-without-name
9220        // identically across all three head keywords — the
9221        // typed-entry surface is single-shape across the cluster.
9222        let mut e = Expander::new();
9223        let err = e
9224            .expand_program(read("(defcheck c (&rest 5) `(list))").unwrap())
9225            .expect_err("&rest followed by non-symbol must error");
9226        let (rest_position, got) = rest_param_missing_name_fields(&err);
9227        assert_eq!(rest_position, 0);
9228        assert_eq!(got, Some("5"));
9229    }
9230
9231    #[test]
9232    fn rest_param_missing_name_substitute_and_bytecode_paths_agree() {
9233        // Path-uniform rejection: the SAME source emits the SAME
9234        // structural variant under both expansion strategies. The
9235        // defmacro-syntax-gate fires inside `macro_def_from` →
9236        // `parse_params`, BEFORE either strategy's expansion path
9237        // runs; so both `Expander::new()` (bytecode) and
9238        // `Expander::new_substitute_only()` (substitute) reject the
9239        // SAME malformed defmacro at the SAME gate. Sibling of
9240        // `non_symbol_param_substitute_and_bytecode_paths_agree`.
9241        let src = "(defmacro f (a &rest 5) `(,a))";
9242        let mut subst = Expander::new_substitute_only();
9243        let mut bytecode = Expander::new();
9244        let err_subst = subst
9245            .expand_program(read(src).unwrap())
9246            .expect_err("substitute must error");
9247        let err_byte = bytecode
9248            .expand_program(read(src).unwrap())
9249            .expect_err("bytecode must error");
9250        assert_eq!(rest_param_missing_name_fields(&err_subst), (1, Some("5")));
9251        assert_eq!(rest_param_missing_name_fields(&err_byte), (1, Some("5")));
9252    }
9253
9254    #[test]
9255    fn rest_param_missing_name_message_renders_legacy_substring_with_marker() {
9256        // End-to-end through Display — pins the rendered diagnostic
9257        // consumers see today (REPL, tatara-check) AND the new `(rest
9258        // marker at position {rest_position}, got {got})` clause. The
9259        // legacy `"&rest needs a name"` substring rides through
9260        // verbatim.
9261        let mut e = Expander::new();
9262        let err = e
9263            .expand_program(read("(defmacro f (a &rest 5) `(,a))").unwrap())
9264            .expect_err("&rest followed by non-symbol must error");
9265        assert_eq!(
9266            format!("{err}"),
9267            "compile error in defmacro params: &rest needs a name \
9268             (rest marker at position 1, got 5)"
9269        );
9270    }
9271
9272    #[test]
9273    fn rest_param_missing_name_message_renders_none_provided_when_follower_absent() {
9274        // Same as the prior test but for the "missing entirely" branch.
9275        // The renderable detail is `(rest marker at position
9276        // {rest_position}, none provided)` — naming the absence
9277        // structurally instead of an empty / partial parenthetical.
9278        let mut e = Expander::new();
9279        let err = e
9280            .expand_program(read("(defmacro f (a &rest) `(,a))").unwrap())
9281            .expect_err("&rest with no follower must error");
9282        assert_eq!(
9283            format!("{err}"),
9284            "compile error in defmacro params: &rest needs a name \
9285             (rest marker at position 1, none provided)"
9286        );
9287    }
9288
9289    #[test]
9290    fn rest_param_missing_name_position_is_none_today() {
9291        // Pins that `position()` returns `None` so the future `pos:
9292        // Option<usize>` add (once `Sexp` carries source spans) lands
9293        // as a deliberate fail-before/pass-after delta rather than a
9294        // silent default. Parallel to
9295        // `non_symbol_param_position_is_none_today` and
9296        // `missing_macro_arg_position_is_none_today`.
9297        let err_missing = LispError::RestParamMissingName {
9298            rest_position: 1,
9299            got: None,
9300        };
9301        assert_eq!(err_missing.position(), None);
9302        let err_got = LispError::RestParamMissingName {
9303            rest_position: 0,
9304            got: Some(crate::error::SexpWitness::new(
9305                crate::error::SexpShape::Int,
9306                "5",
9307            )),
9308        };
9309        assert_eq!(err_got.position(), None);
9310    }
9311
9312    // --- RestParamTrailingTokens: the parse_params gate's third (and
9313    // final) definition-site failure mode ---
9314    //
9315    // A `&rest <name>` absorbs every remaining call arg, so it is the LAST
9316    // thing a param list can name. Before this variant `parse_params`
9317    // returned the moment it bound the rest name, SILENTLY DROPPING any
9318    // trailing tokens — `(a &rest xs extra)` parsed as if `extra` weren't
9319    // there. These tests pin the loud rejection that replaces the silent
9320    // drop; the symbol `RestParamTrailingTokens` exists only after this
9321    // change, so the whole block is fail-before/pass-after by construction
9322    // (compile-time edge) and the end-to-end regression guard below pins
9323    // that the malformed defmacro no longer expands cleanly.
9324
9325    /// Helper mirroring `rest_param_missing_name_fields` — pins the variant
9326    /// shape and lifts the marker position, trailing count, and first
9327    /// offender's display up to the assert site.
9328    fn rest_param_trailing_tokens_fields(err: &LispError) -> (usize, usize, &str) {
9329        match err {
9330            LispError::RestParamTrailingTokens {
9331                rest_position,
9332                extra,
9333                first,
9334            } => (*rest_position, *extra, first.display.as_str()),
9335            other => panic!("expected RestParamTrailingTokens, got: {other:?}"),
9336        }
9337    }
9338
9339    #[test]
9340    fn parse_params_rejects_single_trailing_token_after_rest_name() {
9341        // `(a &rest c extra)` — `&rest c` is well-formed, but `extra`
9342        // follows. The rest name is bound at position 2, the marker at 1;
9343        // the lone trailing token `extra` is reported (extra == 1, first ==
9344        // "extra"). Before this variant `parse_params` returned at the rest
9345        // name and `extra` vanished.
9346        let err = parse_params(&read("a &rest c extra").unwrap())
9347            .expect_err("a trailing token after the rest name must error");
9348        assert_eq!(rest_param_trailing_tokens_fields(&err), (1, 1, "extra"));
9349    }
9350
9351    #[test]
9352    fn rest_param_trailing_tokens_counts_the_whole_trailing_run() {
9353        // `(&rest c x y z)` — three tokens follow the rest name. `extra`
9354        // counts ALL of them (3), `first` is the first (`x`), and the
9355        // marker is at position 0. A regression that reports only the
9356        // first trailing token's presence (extra hard-coded to 1) fails
9357        // loudly here.
9358        let err = parse_params(&read("&rest c x y z").unwrap())
9359            .expect_err("multiple trailing tokens must error");
9360        assert_eq!(rest_param_trailing_tokens_fields(&err), (0, 3, "x"));
9361    }
9362
9363    #[test]
9364    fn rest_param_trailing_tokens_first_witness_carries_non_symbol_display() {
9365        // `(a &rest c 5)` — the rest NAME `c` is a valid symbol, so this is
9366        // NOT a `RestParamMissingName`; the integer `5` is a trailing token
9367        // AFTER a well-formed `&rest c`. Pins that the two sibling failure
9368        // modes don't collide: a malformed rest-name is `RestParamMissingName`,
9369        // a well-formed rest-name followed by junk is
9370        // `RestParamTrailingTokens`. `first` round-trips `5` via the typed
9371        // witness's `Sexp::Display` projection.
9372        let err = parse_params(&read("a &rest c 5").unwrap())
9373            .expect_err("a trailing non-symbol after the rest name must error");
9374        assert_eq!(rest_param_trailing_tokens_fields(&err), (1, 1, "5"));
9375    }
9376
9377    #[test]
9378    fn rest_param_trailing_tokens_no_longer_silently_dropped_end_to_end() {
9379        // The fidelity fix, end-to-end through `expand_program`: a defmacro
9380        // whose param list carries a stray token after `&rest <name>` now
9381        // ERRORS at the typed-entry gate instead of expanding as though the
9382        // stray token weren't there. This is the regression guard for the
9383        // silent-drop bug — before this change the same source expanded
9384        // cleanly and `extra` was discarded with no signal.
9385        let mut e = Expander::new();
9386        let err = e
9387            .expand_program(read("(defmacro f (a &rest xs extra) `(,a))").unwrap())
9388            .expect_err("trailing token after &rest name must error");
9389        assert_eq!(rest_param_trailing_tokens_fields(&err), (1, 1, "extra"));
9390    }
9391
9392    #[test]
9393    fn rest_param_trailing_tokens_substitute_and_bytecode_paths_agree() {
9394        // Path-uniform rejection: the gate fires inside `macro_def_from` →
9395        // `parse_params`, BEFORE either expansion strategy runs, so both
9396        // `Expander::new()` (bytecode) and `Expander::new_substitute_only()`
9397        // (substitute) reject the SAME malformed defmacro at the SAME gate.
9398        // Sibling of `rest_param_missing_name_substitute_and_bytecode_paths_agree`.
9399        let src = "(defmacro f (a &rest xs extra) `(,a))";
9400        let mut subst = Expander::new_substitute_only();
9401        let mut bytecode = Expander::new();
9402        let err_subst = subst
9403            .expand_program(read(src).unwrap())
9404            .expect_err("substitute must error");
9405        let err_byte = bytecode
9406            .expand_program(read(src).unwrap())
9407            .expect_err("bytecode must error");
9408        assert_eq!(
9409            rest_param_trailing_tokens_fields(&err_subst),
9410            (1, 1, "extra")
9411        );
9412        assert_eq!(
9413            rest_param_trailing_tokens_fields(&err_byte),
9414            (1, 1, "extra")
9415        );
9416    }
9417
9418    #[test]
9419    fn rest_param_trailing_tokens_message_renders_legacy_style_prefix_and_suffix() {
9420        // End-to-end through Display — pins the rendered diagnostic AND the
9421        // new `(rest marker at position {n}, {extra} trailing after name,
9422        // first: {first})` clause. The `compile error in defmacro params:`
9423        // prefix matches the sibling `&rest needs a name` rendering's shape.
9424        let mut e = Expander::new();
9425        let err = e
9426            .expand_program(read("(defmacro f (a &rest xs extra) `(,a))").unwrap())
9427            .expect_err("trailing token after &rest name must error");
9428        assert_eq!(
9429            format!("{err}"),
9430            "compile error in defmacro params: &rest name must be last \
9431             (rest marker at position 1, 1 trailing after name, first: extra)"
9432        );
9433    }
9434
9435    #[test]
9436    fn rest_param_trailing_tokens_position_is_none_today() {
9437        // Pins `position() == None` so the future `pos: Option<usize>` add
9438        // (once `Sexp` carries source spans) lands as a deliberate
9439        // fail-before/pass-after delta. Parallel to
9440        // `rest_param_missing_name_position_is_none_today`.
9441        let err = LispError::RestParamTrailingTokens {
9442            rest_position: 1,
9443            extra: 1,
9444            first: crate::error::SexpWitness::new(crate::error::SexpShape::Symbol, "extra"),
9445        };
9446        assert_eq!(err.position(), None);
9447    }
9448
9449    // --- MacroDefHead enum (the closed-set lift) ---
9450    //
9451    // The next nine tests pin the typed-enum lift that closes the
9452    // three-times rule on the `head: &str → &'static str` projection
9453    // idiom previously inlined at FOUR sites (the `matches!` gate at
9454    // the top of `macro_def_from` plus the projection match inside
9455    // each of `defmacro_arity`, `defmacro_non_symbol_name`,
9456    // `defmacro_non_list_params`). Every test in this block names
9457    // `MacroDefHead` directly — the symbol exists only after the
9458    // lift, so the entire block is fail-before/pass-after by
9459    // construction (compile-time edge). Theory anchor: THEORY.md
9460    // §VI.1 — three-times rule; THEORY.md §V.1 — the closed set is
9461    // a TYPE rather than a `matches!` literal.
9462
9463    #[test]
9464    fn macro_def_head_from_keyword_recognizes_defmacro() {
9465        // Pins that `MacroDefHead::from_keyword("defmacro")` returns
9466        // `Some(MacroDefHead::Defmacro)` — the first of the three
9467        // canonical macro-definition head keywords. A regression that
9468        // re-inlines a `matches!`-only gate (without the typed-enum
9469        // projection) deletes `from_keyword` and fails-loudly here.
9470        assert_eq!(
9471            MacroDefHead::from_keyword("defmacro"),
9472            Some(MacroDefHead::Defmacro)
9473        );
9474    }
9475
9476    #[test]
9477    fn macro_def_head_from_keyword_recognizes_defpoint_template() {
9478        // Pins that `MacroDefHead::from_keyword("defpoint-template")`
9479        // returns `Some(MacroDefHead::DefpointTemplate)` — the second
9480        // of the three canonical head keywords. The `defpoint-template`
9481        // form is the K8s-as-processes authoring surface (see
9482        // tatara-process); `macro_def_from` must recognize it
9483        // identically to `defmacro` so the `(defpoint-template …)`
9484        // form's macro-style binding works the same way.
9485        assert_eq!(
9486            MacroDefHead::from_keyword("defpoint-template"),
9487            Some(MacroDefHead::DefpointTemplate)
9488        );
9489    }
9490
9491    #[test]
9492    fn macro_def_head_from_keyword_recognizes_defcheck() {
9493        // Pins that `MacroDefHead::from_keyword("defcheck")` returns
9494        // `Some(MacroDefHead::Defcheck)` — the third and final
9495        // canonical head keyword. The `defcheck` form is the
9496        // workspace-coherence authoring surface (see
9497        // tatara-reconciler/checks.lisp); `macro_def_from` must
9498        // recognize it identically to `defmacro` so user-defined
9499        // checks inherit the macro-style binding semantics.
9500        assert_eq!(
9501            MacroDefHead::from_keyword("defcheck"),
9502            Some(MacroDefHead::Defcheck)
9503        );
9504    }
9505
9506    #[test]
9507    fn macro_def_head_from_keyword_rejects_unknown() {
9508        // Pins that `MacroDefHead::from_keyword` returns `None` for
9509        // anything outside the closed set — a non-symbol keyword
9510        // (`"if"`), a near-miss spelling (`"defmacroo"`,
9511        // `"defcheckk"`), and the empty string. `macro_def_from`
9512        // depends on this `None` projection to mean "this form is
9513        // not a defmacro form" and walk past — a regression that
9514        // accidentally accepts a near-miss head (e.g. via a
9515        // lower-cased `EqualFold` match) would route `(defmacroo …)`
9516        // through the arity gate, which is wrong. Pins all four
9517        // canonical near-miss / non-canonical inputs.
9518        assert_eq!(MacroDefHead::from_keyword("if"), None);
9519        assert_eq!(MacroDefHead::from_keyword("defmacroo"), None);
9520        assert_eq!(MacroDefHead::from_keyword("defcheckk"), None);
9521        assert_eq!(MacroDefHead::from_keyword(""), None);
9522    }
9523
9524    #[test]
9525    fn macro_def_head_keyword_round_trips_each_variant() {
9526        // Pins that `MacroDefHead::keyword` returns the canonical
9527        // `&'static str` literal for each variant. Together with
9528        // `from_keyword` this closes the bidirectional projection:
9529        // for every canonical head keyword `s`, `MacroDefHead::
9530        // from_keyword(s).unwrap().keyword() == s`. The `&'static
9531        // str` lifetime on the return type is load-bearing — it's
9532        // what lets the `LispError::Defmacro*` variants carry
9533        // `head: &'static str` slots without an arbitrary owned
9534        // `String`. Pinning the `: &'static str` binding here
9535        // makes the lifetime requirement load-bearing in the test.
9536        let s_defmacro: &'static str = MacroDefHead::Defmacro.keyword();
9537        let s_defpoint: &'static str = MacroDefHead::DefpointTemplate.keyword();
9538        let s_defcheck: &'static str = MacroDefHead::Defcheck.keyword();
9539        assert_eq!(s_defmacro, "defmacro");
9540        assert_eq!(s_defpoint, "defpoint-template");
9541        assert_eq!(s_defcheck, "defcheck");
9542    }
9543
9544    #[test]
9545    fn macro_def_head_keyword_round_trips_through_from_keyword() {
9546        // Pins that the two halves of the projection compose to the
9547        // identity on the closed set: for every canonical head
9548        // keyword, projecting `&str → MacroDefHead → &'static str`
9549        // returns the original literal. Sibling of
9550        // `macro_def_head_keyword_round_trips_each_variant` —
9551        // together they pin both directions of the bidirection.
9552        for kw in ["defmacro", "defpoint-template", "defcheck"] {
9553            let head = MacroDefHead::from_keyword(kw).expect("canonical keyword must project");
9554            assert_eq!(head.keyword(), kw);
9555        }
9556    }
9557
9558    #[test]
9559    fn macro_def_head_threads_through_defmacro_arity_helper() {
9560        // Pins that `defmacro_arity` accepts a typed `MacroDefHead`
9561        // and threads it through to the variant's typed `head` slot
9562        // unchanged — no `&str` projection at the helper boundary
9563        // (the projection through `MacroDefHead::keyword()` happens at
9564        // Display rendering time inside the `#[error(...)]`
9565        // annotation). A regression that drops the `MacroDefHead`
9566        // parameter type (e.g. by reverting to `head: &str`) breaks
9567        // compilation here. Pinning each of the three variants gives
9568        // the typed-head threading the same path-uniformity edge the
9569        // existing `defmacro_arity_in_*_emits_same_variant` tests pin
9570        // for the call-site path through `macro_def_from`.
9571        for head in [
9572            MacroDefHead::Defmacro,
9573            MacroDefHead::DefpointTemplate,
9574            MacroDefHead::Defcheck,
9575        ] {
9576            let err = defmacro_arity(head, 2);
9577            match err {
9578                LispError::DefmacroArity { head: h, arity: 2 } => assert_eq!(h, head),
9579                other => panic!("expected DefmacroArity, got: {other:?}"),
9580            }
9581        }
9582    }
9583
9584    #[test]
9585    fn macro_def_head_threads_through_defmacro_non_symbol_name_helper() {
9586        // Sibling of the `defmacro_arity` threading test — pins that
9587        // `defmacro_non_symbol_name` accepts a typed `MacroDefHead`
9588        // and threads it through to the variant's typed `head` slot
9589        // unchanged. The `got: &Sexp` parameter rides through
9590        // `crate::domain::sexp_witness` into the variant's typed
9591        // `got: SexpWitness` slot so BOTH the structural shape AND
9592        // the rendered literal are preserved across the helper
9593        // boundary, parallel to how `non_symbol_param` and
9594        // `non_symbol_unquote_target` project their `&Sexp` arguments
9595        // through the same typed joint primitive.
9596        let got = parse("5");
9597        for head in [
9598            MacroDefHead::Defmacro,
9599            MacroDefHead::DefpointTemplate,
9600            MacroDefHead::Defcheck,
9601        ] {
9602            let err = defmacro_non_symbol_name(head, &got);
9603            match err {
9604                LispError::DefmacroNonSymbolName { head: h, got: g } => {
9605                    assert_eq!(h, head);
9606                    assert_eq!(g.shape, crate::error::SexpShape::Int);
9607                    assert_eq!(g.display, "5");
9608                }
9609                other => panic!("expected DefmacroNonSymbolName, got: {other:?}"),
9610            }
9611        }
9612    }
9613
9614    #[test]
9615    fn macro_def_head_threads_through_defmacro_non_list_params_helper() {
9616        // Sibling of the `defmacro_arity` and
9617        // `defmacro_non_symbol_name` threading tests — pins that
9618        // `defmacro_non_list_params` accepts a typed `MacroDefHead`
9619        // and threads it through to the variant's typed `head` slot
9620        // unchanged. Together the three threading tests close the
9621        // typed-enum lift across all three error helpers — every
9622        // call site that constructs a `LispError::Defmacro*` variant
9623        // takes its `head` from a `MacroDefHead`, never from a `&str`
9624        // match. The `got: &Sexp` parameter rides through
9625        // `crate::domain::sexp_witness` into the variant's typed
9626        // `got: SexpWitness` slot so BOTH the structural shape AND
9627        // the rendered literal are preserved across the helper
9628        // boundary, parallel to how `defmacro_non_symbol_name`,
9629        // `non_symbol_param`, and `non_symbol_unquote_target` project
9630        // their `&Sexp` arguments through the same typed joint
9631        // primitive.
9632        let got = parse("x");
9633        for head in [
9634            MacroDefHead::Defmacro,
9635            MacroDefHead::DefpointTemplate,
9636            MacroDefHead::Defcheck,
9637        ] {
9638            let err = defmacro_non_list_params(head, &got);
9639            match err {
9640                LispError::DefmacroNonListParams { head: h, got: g } => {
9641                    assert_eq!(h, head);
9642                    assert_eq!(g.shape, crate::error::SexpShape::Symbol);
9643                    assert_eq!(g.display, "x");
9644                }
9645                other => panic!("expected DefmacroNonListParams, got: {other:?}"),
9646            }
9647        }
9648    }
9649
9650    /// Helper for the defmacro-arity tests — pins the variant shape and
9651    /// carries the head / arity up to the assert site for legibility.
9652    /// Sibling of `non_symbol_param_fields` and
9653    /// `rest_param_missing_name_fields`.
9654    fn defmacro_arity_fields(err: &LispError) -> (MacroDefHead, usize) {
9655        match err {
9656            LispError::DefmacroArity { head, arity } => (*head, *arity),
9657            other => panic!("expected DefmacroArity, got: {other:?}"),
9658        }
9659    }
9660
9661    #[test]
9662    fn defmacro_arity_with_head_only_emits_structural_variant() {
9663        // `(defmacro)` — only the head, no name / params / body. Pins
9664        // variant identity AND that `arity == 1` (just the head
9665        // element) AND that `head == "defmacro"`. A regression that
9666        // re-inlines the legacy `LispError::Compile` shape (which
9667        // named neither field) fails-loudly here.
9668        let mut e = Expander::new();
9669        let err = e
9670            .expand_program(read("(defmacro)").unwrap())
9671            .expect_err("defmacro arity gate must error");
9672        let (head, arity) = defmacro_arity_fields(&err);
9673        assert_eq!(head, MacroDefHead::Defmacro);
9674        assert_eq!(arity, 1);
9675    }
9676
9677    #[test]
9678    fn defmacro_arity_with_head_and_name_emits_structural_variant() {
9679        // `(defmacro f)` — head + name, missing params + body. Pins
9680        // that `arity` advances with the actual form length (2 for
9681        // this case) so an LSP quick-fix that wants to surface "you
9682        // wrote 2 elements; need 4" gains the count as data, no
9683        // source re-parse required.
9684        let mut e = Expander::new();
9685        let err = e
9686            .expand_program(read("(defmacro f)").unwrap())
9687            .expect_err("defmacro arity gate must error");
9688        let (head, arity) = defmacro_arity_fields(&err);
9689        assert_eq!(head, MacroDefHead::Defmacro);
9690        assert_eq!(arity, 2);
9691    }
9692
9693    #[test]
9694    fn defmacro_arity_with_head_name_params_emits_structural_variant() {
9695        // `(defmacro f ())` — head + name + params, missing body
9696        // (the most-complete partial defmacro that still trips the
9697        // arity gate). Pins that `arity == 3` exactly so an LSP
9698        // quick-fix that wants to surface "your defmacro is one
9699        // element short — body is missing" gains the count as data.
9700        let mut e = Expander::new();
9701        let err = e
9702            .expand_program(read("(defmacro f ())").unwrap())
9703            .expect_err("defmacro arity gate must error");
9704        let (head, arity) = defmacro_arity_fields(&err);
9705        assert_eq!(head, MacroDefHead::Defmacro);
9706        assert_eq!(arity, 3);
9707    }
9708
9709    #[test]
9710    fn defmacro_arity_in_defpoint_template_emits_same_variant() {
9711        // `defpoint-template` shares `macro_def_from` with `defmacro`
9712        // (all three head keywords route through the same gate). Pins
9713        // that the lift fires path-uniformly across the three head
9714        // keywords AND that the variant's `head` slot carries the
9715        // actual head literal — `defpoint-template`, not `defmacro`
9716        // — so an LSP that wants to point at "your defpoint-template
9717        // form is missing elements" gains the head as data.
9718        let mut e = Expander::new();
9719        let err = e
9720            .expand_program(read("(defpoint-template t)").unwrap())
9721            .expect_err("defpoint-template arity gate must error");
9722        let (head, arity) = defmacro_arity_fields(&err);
9723        assert_eq!(head, MacroDefHead::DefpointTemplate);
9724        assert_eq!(arity, 2);
9725    }
9726
9727    #[test]
9728    fn defmacro_arity_in_defcheck_emits_same_variant() {
9729        // Sibling of the defpoint-template test — `defcheck` is the
9730        // third head keyword `macro_def_from` recognizes. All three
9731        // route through the same arity gate and now reject too-short
9732        // forms with the same structural variant.
9733        let mut e = Expander::new();
9734        let err = e
9735            .expand_program(read("(defcheck)").unwrap())
9736            .expect_err("defcheck arity gate must error");
9737        let (head, arity) = defmacro_arity_fields(&err);
9738        assert_eq!(head, MacroDefHead::Defcheck);
9739        assert_eq!(arity, 1);
9740    }
9741
9742    #[test]
9743    fn defmacro_arity_substitute_and_bytecode_paths_agree() {
9744        // Path-uniform rejection: the SAME source emits the SAME
9745        // structural variant under both expansion strategies. The
9746        // arity gate fires inside `macro_def_from` BEFORE either
9747        // strategy's expansion path runs; so both `Expander::new()`
9748        // (bytecode) and `Expander::new_substitute_only()`
9749        // (substitute) reject the SAME malformed defmacro at the
9750        // SAME gate. Sibling of
9751        // `non_symbol_param_substitute_and_bytecode_paths_agree` and
9752        // `rest_param_missing_name_substitute_and_bytecode_paths_agree`.
9753        let src = "(defmacro f)";
9754        let mut subst = Expander::new_substitute_only();
9755        let mut bytecode = Expander::new();
9756        let err_subst = subst
9757            .expand_program(read(src).unwrap())
9758            .expect_err("substitute must error");
9759        let err_byte = bytecode
9760            .expand_program(read(src).unwrap())
9761            .expect_err("bytecode must error");
9762        assert_eq!(
9763            defmacro_arity_fields(&err_subst),
9764            (MacroDefHead::Defmacro, 2)
9765        );
9766        assert_eq!(
9767            defmacro_arity_fields(&err_byte),
9768            (MacroDefHead::Defmacro, 2)
9769        );
9770    }
9771
9772    #[test]
9773    fn defmacro_arity_message_renders_legacy_substring_with_arity() {
9774        // End-to-end through Display — pins the rendered diagnostic
9775        // consumers see today (REPL, `tatara-check`) AND the new
9776        // `(got {arity} elements, need 4)` clause. The legacy
9777        // `"(defmacro name (params) body) required"` substring
9778        // rides through verbatim. Tools that pattern-match on the
9779        // variant gain structural binding to `head` / `arity`.
9780        let mut e = Expander::new();
9781        let err = e
9782            .expand_program(read("(defmacro f)").unwrap())
9783            .expect_err("defmacro arity gate must error");
9784        assert_eq!(
9785            format!("{err}"),
9786            "compile error in defmacro: (defmacro name (params) body) required \
9787             (got 2 elements, need 4)"
9788        );
9789    }
9790
9791    #[test]
9792    fn defmacro_arity_position_is_none_today() {
9793        // Negative control for the future-spans move: until `Sexp`
9794        // carries source positions, `position()` on `LispError`
9795        // returns `None` for this variant. A future run that gives
9796        // `Sexp` source spans adds `pos: Option<usize>` to ONE place;
9797        // this test gives that change a deliberate fail-before/pass-
9798        // after delta. Parallel to
9799        // `non_symbol_param_position_is_none_today` and
9800        // `rest_param_missing_name_position_is_none_today`.
9801        let mut e = Expander::new();
9802        let err = e
9803            .expand_program(read("(defmacro)").unwrap())
9804            .expect_err("defmacro arity gate must error");
9805        assert_eq!(err.position(), None);
9806    }
9807
9808    #[test]
9809    fn defmacro_arity_does_not_fire_for_well_formed_arity_4_defmacro() {
9810        // Negative control: a defmacro with exactly 4 elements (head
9811        // + name + params + body) passes the arity gate. Pins that
9812        // the lift is scoped to the arity-deficient case, not to
9813        // every defmacro form. After this test, a regression that
9814        // tightens the arity gate to >= 5 (e.g. spuriously requiring
9815        // a docstring slot) fails-loudly here.
9816        let mut e = Expander::new();
9817        let out = e
9818            .expand_program(read("(defmacro id (x) `,x) (id 42)").unwrap())
9819            .expect("well-formed defmacro must succeed");
9820        assert_eq!(out[0], Sexp::int(42));
9821    }
9822
9823    /// Helper for the defmacro-non-symbol-name tests — pins variant
9824    /// shape and carries the head / got up to the assert site for
9825    /// legibility. Sibling of `defmacro_arity_fields`,
9826    /// `non_symbol_param_fields`, and `rest_param_missing_name_fields`.
9827    fn defmacro_non_symbol_name_fields(err: &LispError) -> (MacroDefHead, &str) {
9828        match err {
9829            LispError::DefmacroNonSymbolName { head, got } => (*head, got.display.as_str()),
9830            other => panic!("expected DefmacroNonSymbolName, got: {other:?}"),
9831        }
9832    }
9833
9834    #[test]
9835    fn defmacro_non_symbol_name_with_int_emits_structural_variant() {
9836        // `(defmacro 5 () body)` — the form passes the arity gate
9837        // (4 elements) but list[1] is `5`, not a symbol. Pins variant
9838        // identity AND that `head == "defmacro"` AND that `got ==
9839        // "5"`. A regression that re-inlines the legacy
9840        // `LispError::Compile { form: "defmacro", message: "expected
9841        // name symbol" }` shape (which named the failure mode but
9842        // not the offending element) fails-loudly here.
9843        let mut e = Expander::new();
9844        let err = e
9845            .expand_program(read("(defmacro 5 () body)").unwrap())
9846            .expect_err("defmacro non-symbol name gate must error");
9847        let (head, got) = defmacro_non_symbol_name_fields(&err);
9848        assert_eq!(head, MacroDefHead::Defmacro);
9849        assert_eq!(got, "5");
9850    }
9851
9852    #[test]
9853    fn defmacro_non_symbol_name_with_keyword_emits_structural_variant() {
9854        // `(defmacro :foo () body)` — list[1] is the keyword `:foo`,
9855        // not a symbol. Pins that `Sexp::Display` for
9856        // `Atom::Keyword(s)` writes `:s` and the variant's `got` slot
9857        // carries the keyword form unchanged. An LSP that wants to
9858        // surface "you wrote `:foo` where a name symbol was expected"
9859        // gains the literal keyword value as data, no source re-parse
9860        // required.
9861        let mut e = Expander::new();
9862        let err = e
9863            .expand_program(read("(defmacro :foo () body)").unwrap())
9864            .expect_err("defmacro non-symbol name gate must error");
9865        let (head, got) = defmacro_non_symbol_name_fields(&err);
9866        assert_eq!(head, MacroDefHead::Defmacro);
9867        assert_eq!(got, ":foo");
9868    }
9869
9870    #[test]
9871    fn defmacro_non_symbol_name_with_string_emits_structural_variant() {
9872        // `(defmacro "name" () body)` — list[1] is the string
9873        // literal `"name"`, not a symbol. Pins that `Sexp::Display`
9874        // for `Atom::String(s)` writes `"s"` (with quotes) and the
9875        // variant's `got` slot carries the quoted form unchanged.
9876        let mut e = Expander::new();
9877        let err = e
9878            .expand_program(read("(defmacro \"name\" () body)").unwrap())
9879            .expect_err("defmacro non-symbol name gate must error");
9880        let (head, got) = defmacro_non_symbol_name_fields(&err);
9881        assert_eq!(head, MacroDefHead::Defmacro);
9882        assert_eq!(got, "\"name\"");
9883    }
9884
9885    #[test]
9886    fn defmacro_non_symbol_name_with_nested_list_emits_structural_variant() {
9887        // `(defmacro (nested) () body)` — list[1] is a nested list,
9888        // not a symbol. Pins that `Sexp::Display` for a list writes
9889        // `(elements)` and the variant's `got` slot carries the
9890        // parenthesized form unchanged.
9891        let mut e = Expander::new();
9892        let err = e
9893            .expand_program(read("(defmacro (nested) () body)").unwrap())
9894            .expect_err("defmacro non-symbol name gate must error");
9895        let (head, got) = defmacro_non_symbol_name_fields(&err);
9896        assert_eq!(head, MacroDefHead::Defmacro);
9897        assert_eq!(got, "(nested)");
9898    }
9899
9900    #[test]
9901    fn defmacro_non_symbol_name_in_defpoint_template_emits_same_variant() {
9902        // `defpoint-template` shares `macro_def_from` with `defmacro`
9903        // (all three head keywords route through the same gate).
9904        // Pins that the lift fires path-uniformly across the three
9905        // head keywords AND that the variant's `head` slot carries
9906        // the actual head literal — `defpoint-template`, not
9907        // `defmacro` — so an LSP that wants to point at "your
9908        // defpoint-template form's name slot isn't a symbol" gains
9909        // the head as data.
9910        let mut e = Expander::new();
9911        let err = e
9912            .expand_program(read("(defpoint-template 7 () body)").unwrap())
9913            .expect_err("defpoint-template non-symbol name gate must error");
9914        let (head, got) = defmacro_non_symbol_name_fields(&err);
9915        assert_eq!(head, MacroDefHead::DefpointTemplate);
9916        assert_eq!(got, "7");
9917    }
9918
9919    #[test]
9920    fn defmacro_non_symbol_name_in_defcheck_emits_same_variant() {
9921        // Sibling for the `defcheck` head — third head keyword
9922        // `macro_def_from` recognizes. Rounds out the three-head-
9923        // keyword coverage so the lift is path-uniform across
9924        // `defmacro` / `defpoint-template` / `defcheck`.
9925        let mut e = Expander::new();
9926        let err = e
9927            .expand_program(read("(defcheck :k () body)").unwrap())
9928            .expect_err("defcheck non-symbol name gate must error");
9929        let (head, got) = defmacro_non_symbol_name_fields(&err);
9930        assert_eq!(head, MacroDefHead::Defcheck);
9931        assert_eq!(got, ":k");
9932    }
9933
9934    #[test]
9935    fn defmacro_non_symbol_name_substitute_and_bytecode_paths_agree() {
9936        // Path-uniform rejection: the SAME source emits the SAME
9937        // structural variant under both expansion strategies. The
9938        // name-symbol gate fires inside `macro_def_from` BEFORE
9939        // either expansion strategy runs, so the gate is naturally
9940        // path-uniform; pinning it gives a regression that drifts
9941        // either strategy's handling of non-symbol-name defmacros (or
9942        // makes one strategy accept what the other rejects) a fail-
9943        // before/pass-after edge. Sibling of
9944        // `defmacro_arity_substitute_and_bytecode_paths_agree`,
9945        // `non_symbol_param_substitute_and_bytecode_paths_agree`, and
9946        // `rest_param_missing_name_substitute_and_bytecode_paths_agree`.
9947        let src = "(defmacro 5 () body)";
9948        let mut subst = Expander::new_substitute_only();
9949        let mut bytecode = Expander::new();
9950        let err_subst = subst
9951            .expand_program(read(src).unwrap())
9952            .expect_err("substitute must error");
9953        let err_byte = bytecode
9954            .expand_program(read(src).unwrap())
9955            .expect_err("bytecode must error");
9956        assert_eq!(
9957            defmacro_non_symbol_name_fields(&err_subst),
9958            (MacroDefHead::Defmacro, "5")
9959        );
9960        assert_eq!(
9961            defmacro_non_symbol_name_fields(&err_byte),
9962            (MacroDefHead::Defmacro, "5")
9963        );
9964    }
9965
9966    #[test]
9967    fn defmacro_non_symbol_name_message_renders_legacy_substring_with_got() {
9968        // End-to-end through Display — pins the rendered diagnostic
9969        // consumers see today (REPL, `tatara-check`) AND the new
9970        // `, got {got}` clause. The legacy `"expected name symbol"`
9971        // substring rides through verbatim; the prefix matches the
9972        // legacy `Compile { form: "defmacro", message: "expected name
9973        // symbol" }` byte-for-byte. Tools that pattern-match on the
9974        // variant gain structural binding to `head` / `got`.
9975        let mut e = Expander::new();
9976        let err = e
9977            .expand_program(read("(defmacro 5 () body)").unwrap())
9978            .expect_err("defmacro non-symbol name gate must error");
9979        assert_eq!(
9980            format!("{err}"),
9981            "compile error in defmacro: expected name symbol, got 5"
9982        );
9983    }
9984
9985    #[test]
9986    fn defmacro_non_symbol_name_position_is_none_today() {
9987        // Negative control for the future-spans move: until `Sexp`
9988        // carries source positions, `position()` on `LispError`
9989        // returns `None` for this variant. A future run that gives
9990        // `Sexp` source spans adds `pos: Option<usize>` to ONE place;
9991        // this test gives that change a deliberate fail-before/pass-
9992        // after delta. Parallel to
9993        // `defmacro_arity_position_is_none_today`,
9994        // `non_symbol_param_position_is_none_today`, and
9995        // `rest_param_missing_name_position_is_none_today`.
9996        let mut e = Expander::new();
9997        let err = e
9998            .expand_program(read("(defmacro 5 () body)").unwrap())
9999            .expect_err("defmacro non-symbol name gate must error");
10000        assert_eq!(err.position(), None);
10001    }
10002
10003    #[test]
10004    fn defmacro_non_symbol_name_does_not_fire_for_well_formed_defmacro() {
10005        // Negative control: a defmacro whose name slot IS a symbol
10006        // passes the name-symbol gate. Pins that the lift is scoped
10007        // to the non-symbol-name case, not to every defmacro form.
10008        // After this test, a regression that tightens the gate to
10009        // reject e.g. kebab-cased names fails-loudly here.
10010        let mut e = Expander::new();
10011        let out = e
10012            .expand_program(read("(defmacro id (x) `,x) (id 42)").unwrap())
10013            .expect("well-formed defmacro must succeed");
10014        assert_eq!(out[0], Sexp::int(42));
10015    }
10016
10017    #[test]
10018    fn defmacro_non_symbol_name_fires_after_arity_gate_passes() {
10019        // Pins the gate ordering: a 4-element defmacro whose name
10020        // slot is non-symbol fires `DefmacroNonSymbolName`, NOT
10021        // `DefmacroArity`. The arity gate (>= 4 elements) admits
10022        // this form; the name-symbol gate is the next checkpoint.
10023        // A regression that swaps the gate ordering (e.g. checks
10024        // name-symbol before arity, so `(defmacro 5)` would emit
10025        // `DefmacroNonSymbolName` instead of `DefmacroArity`) fails-
10026        // loudly here.
10027        let mut e = Expander::new();
10028        let err = e
10029            .expand_program(read("(defmacro 5 () body)").unwrap())
10030            .expect_err("name-symbol gate must error");
10031        assert!(
10032            matches!(err, LispError::DefmacroNonSymbolName { .. }),
10033            "expected DefmacroNonSymbolName, got: {err:?}"
10034        );
10035
10036        let err_arity = e
10037            .expand_program(read("(defmacro 5)").unwrap())
10038            .expect_err("arity gate must error");
10039        assert!(
10040            matches!(err_arity, LispError::DefmacroArity { .. }),
10041            "expected DefmacroArity (arity < 4 short-circuits before name check), \
10042             got: {err_arity:?}"
10043        );
10044    }
10045
10046    /// Helper for the defmacro-non-list-params tests — pins variant
10047    /// shape and carries the head / got up to the assert site for
10048    /// legibility. Sibling of `defmacro_arity_fields`,
10049    /// `defmacro_non_symbol_name_fields`, `non_symbol_param_fields`,
10050    /// and `rest_param_missing_name_fields`.
10051    fn defmacro_non_list_params_fields(err: &LispError) -> (MacroDefHead, &str) {
10052        match err {
10053            LispError::DefmacroNonListParams { head, got } => (*head, got.display.as_str()),
10054            other => panic!("expected DefmacroNonListParams, got: {other:?}"),
10055        }
10056    }
10057
10058    #[test]
10059    fn defmacro_non_list_params_with_symbol_emits_structural_variant() {
10060        // `(defmacro f x body)` — the form passes both the arity gate
10061        // (4 elements) AND the name-symbol gate (`f` is a symbol) but
10062        // list[2] is the symbol `x`, not a list. Pins variant identity
10063        // AND that `head == "defmacro"` AND that `got == "x"`. A
10064        // regression that re-inlines the legacy `LispError::Compile {
10065        // form: "defmacro", message: "expected param list" }` shape
10066        // (which named the failure mode but not the offending element)
10067        // fails-loudly here.
10068        let mut e = Expander::new();
10069        let err = e
10070            .expand_program(read("(defmacro f x body)").unwrap())
10071            .expect_err("defmacro non-list params gate must error");
10072        let (head, got) = defmacro_non_list_params_fields(&err);
10073        assert_eq!(head, MacroDefHead::Defmacro);
10074        assert_eq!(got, "x");
10075    }
10076
10077    #[test]
10078    fn defmacro_non_list_params_with_int_emits_structural_variant() {
10079        // `(defmacro f 5 body)` — list[2] is `5`, not a list. Pins
10080        // that `Sexp::Display` for `Atom::Int(n)` writes `n` and the
10081        // variant's `got` slot carries the integer form unchanged. An
10082        // LSP that surfaces "you wrote `5` where a param list was
10083        // expected" gains the literal value as data, no source
10084        // re-parse required.
10085        let mut e = Expander::new();
10086        let err = e
10087            .expand_program(read("(defmacro f 5 body)").unwrap())
10088            .expect_err("defmacro non-list params gate must error");
10089        let (head, got) = defmacro_non_list_params_fields(&err);
10090        assert_eq!(head, MacroDefHead::Defmacro);
10091        assert_eq!(got, "5");
10092    }
10093
10094    #[test]
10095    fn defmacro_non_list_params_with_keyword_emits_structural_variant() {
10096        // `(defmacro f :foo body)` — list[2] is the keyword `:foo`,
10097        // not a list. Pins that `Sexp::Display` for `Atom::Keyword(s)`
10098        // writes `:s` and the variant's `got` slot carries the
10099        // keyword form unchanged.
10100        let mut e = Expander::new();
10101        let err = e
10102            .expand_program(read("(defmacro f :foo body)").unwrap())
10103            .expect_err("defmacro non-list params gate must error");
10104        let (head, got) = defmacro_non_list_params_fields(&err);
10105        assert_eq!(head, MacroDefHead::Defmacro);
10106        assert_eq!(got, ":foo");
10107    }
10108
10109    #[test]
10110    fn defmacro_non_list_params_with_string_emits_structural_variant() {
10111        // `(defmacro f "params" body)` — list[2] is the string literal
10112        // `"params"`, not a list. Pins that `Sexp::Display` for
10113        // `Atom::String(s)` writes `"s"` (with quotes) and the
10114        // variant's `got` slot carries the quoted form unchanged.
10115        let mut e = Expander::new();
10116        let err = e
10117            .expand_program(read("(defmacro f \"params\" body)").unwrap())
10118            .expect_err("defmacro non-list params gate must error");
10119        let (head, got) = defmacro_non_list_params_fields(&err);
10120        assert_eq!(head, MacroDefHead::Defmacro);
10121        assert_eq!(got, "\"params\"");
10122    }
10123
10124    #[test]
10125    fn defmacro_non_list_params_in_defpoint_template_emits_same_variant() {
10126        // `defpoint-template` shares `macro_def_from` with `defmacro`
10127        // (all three head keywords route through the same gate).
10128        // Pins that the lift fires path-uniformly across the three
10129        // head keywords AND that the variant's `head` slot carries
10130        // the actual head literal — `defpoint-template`, not
10131        // `defmacro` — so an LSP that wants to point at "your
10132        // defpoint-template form's param-list slot isn't a list"
10133        // gains the head as data.
10134        let mut e = Expander::new();
10135        let err = e
10136            .expand_program(read("(defpoint-template t x body)").unwrap())
10137            .expect_err("defpoint-template non-list params gate must error");
10138        let (head, got) = defmacro_non_list_params_fields(&err);
10139        assert_eq!(head, MacroDefHead::DefpointTemplate);
10140        assert_eq!(got, "x");
10141    }
10142
10143    #[test]
10144    fn defmacro_non_list_params_in_defcheck_emits_same_variant() {
10145        // Sibling for the `defcheck` head — third head keyword
10146        // `macro_def_from` recognizes. Rounds out the three-head-
10147        // keyword coverage so the lift is path-uniform across
10148        // `defmacro` / `defpoint-template` / `defcheck`.
10149        let mut e = Expander::new();
10150        let err = e
10151            .expand_program(read("(defcheck c 7 body)").unwrap())
10152            .expect_err("defcheck non-list params gate must error");
10153        let (head, got) = defmacro_non_list_params_fields(&err);
10154        assert_eq!(head, MacroDefHead::Defcheck);
10155        assert_eq!(got, "7");
10156    }
10157
10158    #[test]
10159    fn defmacro_non_list_params_substitute_and_bytecode_paths_agree() {
10160        // Path-uniform rejection: the SAME source emits the SAME
10161        // structural variant under both expansion strategies. The
10162        // param-list gate fires inside `macro_def_from` BEFORE either
10163        // expansion strategy runs, so the gate is naturally path-
10164        // uniform; pinning it gives a regression that drifts either
10165        // strategy's handling of non-list-params defmacros (or makes
10166        // one strategy accept what the other rejects) a fail-before/
10167        // pass-after edge. Sibling of
10168        // `defmacro_arity_substitute_and_bytecode_paths_agree`,
10169        // `defmacro_non_symbol_name_substitute_and_bytecode_paths_agree`,
10170        // `non_symbol_param_substitute_and_bytecode_paths_agree`, and
10171        // `rest_param_missing_name_substitute_and_bytecode_paths_agree`.
10172        let src = "(defmacro f x body)";
10173        let mut subst = Expander::new_substitute_only();
10174        let mut bytecode = Expander::new();
10175        let err_subst = subst
10176            .expand_program(read(src).unwrap())
10177            .expect_err("substitute must error");
10178        let err_byte = bytecode
10179            .expand_program(read(src).unwrap())
10180            .expect_err("bytecode must error");
10181        assert_eq!(
10182            defmacro_non_list_params_fields(&err_subst),
10183            (MacroDefHead::Defmacro, "x")
10184        );
10185        assert_eq!(
10186            defmacro_non_list_params_fields(&err_byte),
10187            (MacroDefHead::Defmacro, "x")
10188        );
10189    }
10190
10191    #[test]
10192    fn defmacro_non_list_params_message_renders_legacy_substring_with_got() {
10193        // End-to-end through Display — pins the rendered diagnostic
10194        // consumers see today (REPL, `tatara-check`) AND the new
10195        // `, got {got}` clause. The legacy `"expected param list"`
10196        // substring rides through verbatim; the prefix matches the
10197        // legacy `Compile { form: "defmacro", message: "expected
10198        // param list" }` byte-for-byte. Tools that pattern-match on
10199        // the variant gain structural binding to `head` / `got`.
10200        let mut e = Expander::new();
10201        let err = e
10202            .expand_program(read("(defmacro f x body)").unwrap())
10203            .expect_err("defmacro non-list params gate must error");
10204        assert_eq!(
10205            format!("{err}"),
10206            "compile error in defmacro: expected param list, got x"
10207        );
10208    }
10209
10210    #[test]
10211    fn defmacro_non_list_params_position_is_none_today() {
10212        // Negative control for the future-spans move: until `Sexp`
10213        // carries source positions, `position()` on `LispError`
10214        // returns `None` for this variant. A future run that gives
10215        // `Sexp` source spans adds `pos: Option<usize>` to ONE place;
10216        // this test gives that change a deliberate fail-before/pass-
10217        // after delta. Parallel to
10218        // `defmacro_arity_position_is_none_today`,
10219        // `defmacro_non_symbol_name_position_is_none_today`,
10220        // `non_symbol_param_position_is_none_today`, and
10221        // `rest_param_missing_name_position_is_none_today`.
10222        let mut e = Expander::new();
10223        let err = e
10224            .expand_program(read("(defmacro f x body)").unwrap())
10225            .expect_err("defmacro non-list params gate must error");
10226        assert_eq!(err.position(), None);
10227    }
10228
10229    #[test]
10230    fn defmacro_non_list_params_does_not_fire_for_well_formed_defmacro() {
10231        // Negative control: a defmacro whose param-list slot IS a
10232        // list passes the param-list gate. Pins that the lift is
10233        // scoped to the non-list-params case, not to every defmacro
10234        // form. After this test, a regression that tightens the gate
10235        // to reject e.g. empty param lists fails-loudly here.
10236        let mut e = Expander::new();
10237        let out = e
10238            .expand_program(read("(defmacro id (x) `,x) (id 42)").unwrap())
10239            .expect("well-formed defmacro must succeed");
10240        assert_eq!(out[0], Sexp::int(42));
10241    }
10242
10243    #[test]
10244    fn defmacro_non_list_params_fires_after_name_symbol_gate_passes() {
10245        // Pins the gate ordering: a 4-element defmacro whose name
10246        // slot IS a symbol but whose param-list slot is non-list
10247        // fires `DefmacroNonListParams`, NOT `DefmacroNonSymbolName`.
10248        // The name-symbol gate admits this form; the param-list gate
10249        // is the next checkpoint. A regression that swaps the gate
10250        // ordering (e.g. checks param-list before name-symbol, so
10251        // `(defmacro 5 x body)` would emit `DefmacroNonListParams`
10252        // instead of `DefmacroNonSymbolName`) fails-loudly here.
10253        let mut e = Expander::new();
10254        let err = e
10255            .expand_program(read("(defmacro f x body)").unwrap())
10256            .expect_err("param-list gate must error");
10257        assert!(
10258            matches!(err, LispError::DefmacroNonListParams { .. }),
10259            "expected DefmacroNonListParams, got: {err:?}"
10260        );
10261
10262        let err_name = e
10263            .expand_program(read("(defmacro 5 x body)").unwrap())
10264            .expect_err("name-symbol gate must error");
10265        assert!(
10266            matches!(err_name, LispError::DefmacroNonSymbolName { .. }),
10267            "expected DefmacroNonSymbolName (name-symbol gate short-circuits before param-list check), \
10268             got: {err_name:?}"
10269        );
10270    }
10271
10272    #[test]
10273    fn defmacro_non_list_params_fires_after_arity_gate_passes() {
10274        // Pins the full gate ordering: a 4-element defmacro whose
10275        // first three slots are head/symbol/non-list fires
10276        // `DefmacroNonListParams`, NOT `DefmacroArity`. The arity
10277        // gate (>= 4 elements) admits this form; the name-symbol
10278        // gate admits the symbol; the param-list gate is the third
10279        // checkpoint. A regression that drifts the gate sequence
10280        // (e.g. fires `DefmacroArity` for a 4-element form) fails-
10281        // loudly here. Parallel to
10282        // `defmacro_non_symbol_name_fires_after_arity_gate_passes`
10283        // — together they pin the full
10284        // arity → name-symbol → param-list ordering inside
10285        // `macro_def_from`.
10286        let mut e = Expander::new();
10287        let err = e
10288            .expand_program(read("(defmacro f x body)").unwrap())
10289            .expect_err("param-list gate must error");
10290        assert!(
10291            matches!(err, LispError::DefmacroNonListParams { .. }),
10292            "expected DefmacroNonListParams, got: {err:?}"
10293        );
10294
10295        let err_arity = e
10296            .expand_program(read("(defmacro f x)").unwrap())
10297            .expect_err("arity gate must error");
10298        assert!(
10299            matches!(err_arity, LispError::DefmacroArity { .. }),
10300            "expected DefmacroArity (arity < 4 short-circuits before param-list check), \
10301             got: {err_arity:?}"
10302        );
10303    }
10304
10305    #[test]
10306    fn rest_marker_at_param_list_position_is_not_non_symbol_param() {
10307        // Negative control: `&rest` is a symbol (`Atom::Symbol("&rest")`)
10308        // at the parser level, so `as_symbol()` succeeds for it. The
10309        // `NonSymbolParam` variant does NOT fire on the `&rest` marker
10310        // itself; the dedicated `&rest needs a name` rejection (a
10311        // separate failure mode in this cluster) handles malformed
10312        // rest-param shapes. Pins that the lift is scoped to
10313        // non-symbol elements at param-list positions, not to
10314        // every malformed-param shape.
10315        let mut e = Expander::new();
10316        let out = e
10317            .expand_program(read("(defmacro f (a &rest xs) `(list ,a ,@xs)) (f 1 2 3)").unwrap())
10318            .expect("&rest with name must succeed");
10319        assert_eq!(out[0], parse("(list 1 2 3)"));
10320    }
10321
10322    #[test]
10323    fn non_symbol_unquote_target_message_renders_canonical_type_mismatch_shape() {
10324        // End-to-end through the Display impl — pins the rendered diagnostic
10325        // a downstream tool sees today (REPL, tatara-check). The shape is
10326        // parallel to the existing `TypeMismatch` variant: form, expected
10327        // shape, offending literal — all three slots present.
10328        let mut e = Expander::new();
10329        let err = e
10330            .expand_program(read("(defmacro w (x) `,(list 1 2)) (w 1)").unwrap())
10331            .expect_err("non-symbol target must error");
10332        assert_eq!(
10333            format!("{err}"),
10334            "compile error in ,: expected symbol, got (list 1 2)"
10335        );
10336    }
10337
10338    // ── template_invariant_violation: structural lift ───────────────
10339    //
10340    // The four byte-identical inline `LispError::Compile { form:
10341    // macro_name.into(), message: <invariant> }` triples in `apply_compiled`
10342    // (Subst-bad-index, Splice-bad-index, EndList-empty-stack,
10343    // final-no-value gates) were lifted to `template_invariant_violation`,
10344    // and the helper's emission was promoted from `LispError::Compile`-
10345    // shape to the structural `LispError::TemplateInvariant { macro_name,
10346    // kind: TemplateInvariantKind }` variant. The index payload of the
10347    // Subst / Splice gates lives INSIDE the variant (`SubstBadIndex(usize)`
10348    // / `SpliceBadIndex(usize)`), so the invalid combination "stack-gate
10349    // kind with an op-index" (e.g. `EndListEmptyStack` carrying a `usize`)
10350    // is structurally unrepresentable. Display matches the legacy
10351    // `Compile`-shaped diagnostic byte-for-byte via the closed-set
10352    // `TemplateInvariantKind::message()` projection so authoring-tool
10353    // substring greps see no drift across the lift.
10354    //
10355    // The tests below pin: (a) the helper produces the structural
10356    // `LispError::TemplateInvariant` variant with `macro_name` and `kind`
10357    // first-class; (b) the Subst / Splice gates thread the bad index
10358    // through the typed variants `SubstBadIndex(usize)` / `SpliceBadIndex(usize)`
10359    // unchanged; (c) the two REACHABLE invariant-violation paths through
10360    // `apply_compiled` — Subst with out-of-bounds idx, Splice with
10361    // out-of-bounds idx — route through the helper end-to-end (the
10362    // EndList / no-value paths are guarded by `last_mut().unwrap()`
10363    // ahead of `pop().ok_or_else()` and are not reachable through any
10364    // single CompiledTemplate; they remain defensive against future
10365    // changes to the stack discipline); (d) the legacy Display
10366    // rendering matches byte-for-byte across the lift; (e) positive
10367    // controls: a well-formed CompiledTemplate routes PAST the helper
10368    // cleanly, and unrelated macro errors (missing-required-arg) do
10369    // NOT route through the helper.
10370
10371    #[test]
10372    fn template_invariant_violation_emits_structural_variant_with_macro_name_and_kind() {
10373        // Direct unit test of the helper: a fixed macro_name and a
10374        // `TemplateInvariantKind` produce a `LispError::TemplateInvariant`
10375        // variant with the macro_name in the `macro_name` slot and the
10376        // kind passed through verbatim in the `kind` slot. A regression
10377        // that drifts the variant (e.g., back to `LispError::Compile`)
10378        // or swaps the slot positions fails-loudly here.
10379        let err = template_invariant_violation("test-macro", TemplateInvariantKind::FinalNoValue);
10380        match err {
10381            LispError::TemplateInvariant { macro_name, kind } => {
10382                assert_eq!(macro_name, "test-macro");
10383                assert_eq!(kind, TemplateInvariantKind::FinalNoValue);
10384            }
10385            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10386        }
10387    }
10388
10389    #[test]
10390    fn template_invariant_violation_threads_subst_idx_through_typed_variant() {
10391        // The Subst gate's `usize` idx lives INSIDE the
10392        // `TemplateInvariantKind::SubstBadIndex(usize)` variant rather
10393        // than being substring-rendered into a free-form `message`
10394        // slot. Pin that the helper threads the bad index through the
10395        // typed variant unchanged; a regression that drops the index
10396        // payload (e.g., via a `usize -> ()` projection) fails here.
10397        let err = template_invariant_violation("wrap", TemplateInvariantKind::SubstBadIndex(7));
10398        match err {
10399            LispError::TemplateInvariant { macro_name, kind } => {
10400                assert_eq!(macro_name, "wrap");
10401                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(7));
10402            }
10403            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10404        }
10405    }
10406
10407    #[test]
10408    fn apply_compiled_subst_bad_idx_routes_through_template_invariant_violation() {
10409        // Hand-crafted CompiledTemplate with a Subst(99) op against
10410        // an empty params list: `args_by_index` has length 0, so
10411        // `.get(99)` returns None and the `ok_or_else` triggers
10412        // through the helper. Fail-before-pass-after: this same input
10413        // pre-lift went through `LispError::Compile { form: macro_name,
10414        // message: format!("compiled template referenced bad param
10415        // index {idx}") }`; post-lift it routes through
10416        // `template_invariant_violation` and emits the structural
10417        // `TemplateInvariant { macro_name, kind: SubstBadIndex(99) }`
10418        // variant with the bad index threaded through as typed data.
10419        let tmpl = CompiledTemplate {
10420            ops: vec![TemplateOp::Subst(99)],
10421        };
10422        let err = apply_compiled("test-macro", &MacroParams::default(), &tmpl, &[])
10423            .expect_err("bad idx must error");
10424        match err {
10425            LispError::TemplateInvariant { macro_name, kind } => {
10426                assert_eq!(macro_name, "test-macro");
10427                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(99));
10428            }
10429            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10430        }
10431    }
10432
10433    #[test]
10434    fn apply_compiled_splice_bad_idx_routes_through_template_invariant_violation() {
10435        // Hand-crafted CompiledTemplate with a Splice(42) op against
10436        // an empty params list. Sibling of the Subst-bad-idx test;
10437        // pins the Splice gate routes through the helper with the
10438        // typed `SpliceBadIndex(42)` kind carrying the bad index.
10439        let tmpl = CompiledTemplate {
10440            ops: vec![TemplateOp::Splice(42)],
10441        };
10442        let err = apply_compiled("call-macro", &MacroParams::default(), &tmpl, &[])
10443            .expect_err("bad splice idx must error");
10444        match err {
10445            LispError::TemplateInvariant { macro_name, kind } => {
10446                assert_eq!(macro_name, "call-macro");
10447                assert_eq!(kind, TemplateInvariantKind::SpliceBadIndex(42));
10448            }
10449            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10450        }
10451    }
10452
10453    #[test]
10454    fn apply_compiled_subst_bad_idx_renders_legacy_compile_shape() {
10455        // End-to-end through the `LispError` Display impl — pins the
10456        // rendered diagnostic byte-for-byte: `"compile error in
10457        // test-macro: compiled template referenced bad param index 99"`.
10458        // Authoring tools that substring-grep the rendered diagnostic
10459        // (`tatara-check`'s diagnostic capture, REPL substring-greps)
10460        // see no drift across the lift. Parallel to how
10461        // `compile_named_non_symbol_name_renders_legacy_compile_shape`
10462        // pins the sibling-file (compile.rs) lift's Display contract.
10463        let tmpl = CompiledTemplate {
10464            ops: vec![TemplateOp::Subst(99)],
10465        };
10466        let err = apply_compiled("test-macro", &MacroParams::default(), &tmpl, &[])
10467            .expect_err("bad idx must error");
10468        assert_eq!(
10469            format!("{err}"),
10470            "compile error in test-macro: compiled template referenced bad param index 99"
10471        );
10472    }
10473
10474    #[test]
10475    fn apply_compiled_splice_bad_idx_renders_legacy_compile_shape() {
10476        // Sibling Display test for the Splice gate. Pins the message
10477        // byte-for-byte through the `LispError` Display impl: `"compile
10478        // error in call-macro: compiled template referenced bad splice
10479        // index 42"`.
10480        let tmpl = CompiledTemplate {
10481            ops: vec![TemplateOp::Splice(42)],
10482        };
10483        let err = apply_compiled("call-macro", &MacroParams::default(), &tmpl, &[])
10484            .expect_err("bad splice idx must error");
10485        assert_eq!(
10486            format!("{err}"),
10487            "compile error in call-macro: compiled template referenced bad splice index 42"
10488        );
10489    }
10490
10491    #[test]
10492    fn apply_compiled_well_formed_template_routes_past_template_invariant_violation() {
10493        // Positive control: a CompiledTemplate produced by the
10494        // bytecode compiler (`compile_template`) for a well-formed
10495        // macro never references an out-of-bounds index nor
10496        // unbalances the stack, so `apply_compiled` routes PAST the
10497        // helper cleanly. A regression that fires the helper on
10498        // well-formed bytecode (e.g., off-by-one in the index
10499        // resolution) would fail here. End-to-end through the public
10500        // `Expander` surface so the test exercises the same code
10501        // path users see.
10502        let mut e = Expander::new();
10503        let out = e
10504            .expand_program(read("(defmacro id (x) `,x) (id 42)").unwrap())
10505            .expect("well-formed macro expansion must not fire template-invariant-violation");
10506        assert_eq!(out.len(), 1);
10507        assert_eq!(out[0], Sexp::int(42));
10508    }
10509
10510    #[test]
10511    fn apply_compiled_missing_required_arg_does_not_route_through_template_invariant_violation() {
10512        // Negative control: the `missing_macro_arg` gate in the shared
10513        // positional binder (`MacroParams::bind`) fires BEFORE the bytecode
10514        // loop runs,
10515        // so a missing required arg routes through
10516        // `LispError::MissingMacroArg`, NOT through
10517        // `template_invariant_violation`. Pins the helper is
10518        // precisely scoped to bytecode-runtime invariant violations
10519        // (Subst / Splice / stack gates), not to macro-call arity
10520        // errors (the latter has its own structural variant). A
10521        // regression that conflates the two gate clusters would
10522        // route this case through `Compile { ... }` instead of
10523        // `MissingMacroArg` and fail-loudly here.
10524        let mut e = Expander::new();
10525        let err = e
10526            .expand_program(read("(defmacro need-one (x) `,x) (need-one)").unwrap())
10527            .expect_err("missing required arg must error");
10528        assert!(
10529            matches!(err, LispError::MissingMacroArg { .. }),
10530            "expected MissingMacroArg, got: {err:?}"
10531        );
10532    }
10533
10534    // ── resolve_bound_arg: bytecode-runtime bound-arg-by-index lookup ──
10535    //
10536    // `resolve_bound_arg(args_by_index, idx, macro_name, kind)` lifts the
10537    // `args_by_index.get(*idx).ok_or_else(|| template_invariant_violation(
10538    // macro_name, KIND(*idx)))?` projection that recurred at BOTH the
10539    // `TemplateOp::Subst` and `TemplateOp::Splice` arms inside
10540    // `apply_compiled`. The arms differ in the kind constructor
10541    // (`SubstBadIndex` vs. `SpliceBadIndex`) and in their post-lookup
10542    // verb (clone+push vs. splice-coerce), but the lookup-and-reject
10543    // prelude is byte-identical modulo the constructor. These tests
10544    // pin the lifted helper's contract directly; the existing
10545    // `apply_compiled_*_bad_idx_*` tests are the path-uniformity
10546    // guards proving both production arms route through it without
10547    // behavior drift.
10548
10549    #[test]
10550    fn resolve_bound_arg_in_range_returns_borrowed_reference_verbatim() {
10551        // For an in-range index, the helper returns `Ok(&args[idx])`
10552        // borrowed VERBATIM — same pointer as `args_by_index.get(idx)`.
10553        // Pins the borrow-not-clone contract: a regression that drifts
10554        // the helper to clone+return (`Result<Sexp>` instead of
10555        // `Result<&Sexp>`) would allocate per lookup at the production
10556        // `Subst`/`Splice` hot path. The kind constructor must NOT
10557        // fire on the success path (`FnOnce`'s lazy semantics) — pin
10558        // that the test passes a constructor that would panic if
10559        // called, asserting the helper short-circuits before invoking
10560        // it on the in-range arm.
10561        let args = vec![Sexp::int(1), Sexp::int(2), Sexp::int(3)];
10562        let got = resolve_bound_arg(&args, 1, "m", |_| {
10563            panic!("kind constructor must not fire on the in-range path")
10564        })
10565        .expect("in-range lookup must succeed");
10566        assert!(
10567            std::ptr::eq(got, &args[1]),
10568            "resolve_bound_arg must return the SAME pointer as args_by_index.get(idx)"
10569        );
10570        assert_eq!(*got, Sexp::int(2));
10571    }
10572
10573    #[test]
10574    fn resolve_bound_arg_out_of_range_with_subst_kind_emits_typed_invariant() {
10575        // For an out-of-range index, the helper raises the structural
10576        // `LispError::TemplateInvariant` variant with the caller-
10577        // supplied `SubstBadIndex` kind constructor applied to the bad
10578        // index. Pins the post-lift emission shape (variant identity
10579        // + the kind constructor threaded with the actual idx); a
10580        // regression that drops the idx payload (e.g., via a `usize ->
10581        // ()` projection) or hard-codes a different kind at the helper
10582        // boundary fails-loudly here. Fail-before-pass-after: this
10583        // assert is contradicted by the pre-lift code path (which
10584        // never called `resolve_bound_arg` because it didn't exist),
10585        // ratifies the post-lift one.
10586        let args: Vec<Sexp> = Vec::new();
10587        let err = resolve_bound_arg(&args, 7, "test-macro", TemplateInvariantKind::SubstBadIndex)
10588            .expect_err("out-of-range lookup must error");
10589        match err {
10590            LispError::TemplateInvariant { macro_name, kind } => {
10591                assert_eq!(macro_name, "test-macro");
10592                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(7));
10593            }
10594            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10595        }
10596    }
10597
10598    #[test]
10599    fn resolve_bound_arg_threads_kind_constructor_per_call_site() {
10600        // Path-uniformity for the per-call-site kind constructor: the
10601        // SAME out-of-range idx via the `SpliceBadIndex` constructor
10602        // emits `kind: SpliceBadIndex(7)` — distinct from the sibling
10603        // `SubstBadIndex(7)` variant. Pins that the constructor is
10604        // chosen per call site (not hard-coded at the helper boundary),
10605        // closing the structural matrix `{Subst, Splice} × {in-range,
10606        // out-of-range}` the two production arms span across the
10607        // bytecode-runtime's bound-arg-by-index reads. A regression
10608        // that hard-codes a single kind at the helper boundary would
10609        // emit the same variant identity for both call sites and
10610        // fail-loudly here.
10611        let args: Vec<Sexp> = Vec::new();
10612        let err = resolve_bound_arg(
10613            &args,
10614            7,
10615            "test-macro",
10616            TemplateInvariantKind::SpliceBadIndex,
10617        )
10618        .expect_err("out-of-range lookup must error");
10619        match err {
10620            LispError::TemplateInvariant { macro_name, kind } => {
10621                assert_eq!(macro_name, "test-macro");
10622                assert_eq!(kind, TemplateInvariantKind::SpliceBadIndex(7));
10623            }
10624            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10625        }
10626    }
10627
10628    #[test]
10629    fn resolve_bound_arg_threads_macro_name_verbatim() {
10630        // Path-uniformity for the `macro_name` slot: the helper threads
10631        // the caller's borrow into the variant's owned `String` slot
10632        // verbatim. Pin two distinct macro names route through with no
10633        // mutual interference — a regression that hard-codes a single
10634        // macro_name at the helper boundary or swaps the parameter
10635        // ordering fails-loudly here. Same posture as
10636        // `compiler_spec_io_err_threads_each_stage_through_unchanged`
10637        // pins the typed `stage` slot in the disk-persistence sibling
10638        // lift.
10639        let args: Vec<Sexp> = Vec::new();
10640        for name in ["wrap", "call-macro", "obs"] {
10641            let err = resolve_bound_arg(&args, 0, name, TemplateInvariantKind::SubstBadIndex)
10642                .expect_err("out-of-range lookup must error");
10643            match err {
10644                LispError::TemplateInvariant { macro_name, kind } => {
10645                    assert_eq!(macro_name, name, "macro_name slot drifted for {name}");
10646                    assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(0));
10647                }
10648                other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10649            }
10650        }
10651    }
10652
10653    #[test]
10654    fn resolve_bound_arg_yields_first_element_when_idx_is_zero() {
10655        // Edge case: idx 0 with a single-element args_by_index returns
10656        // `Ok(&args[0])`. Pins the lower-bound of the in-range surface
10657        // — a regression that off-by-ones the lookup (e.g., `get(idx +
10658        // 1)` or `get(idx).filter(|_| idx > 0)`) would fail here.
10659        // Sibling to the upper-bound `resolve_bound_arg_out_of_range_
10660        // with_subst_kind_emits_typed_invariant` test.
10661        let args = vec![Sexp::int(42)];
10662        let got = resolve_bound_arg(&args, 0, "m", |_| {
10663            panic!("kind constructor must not fire on the in-range path")
10664        })
10665        .expect("idx-0 lookup must succeed");
10666        assert!(std::ptr::eq(got, &args[0]));
10667        assert_eq!(*got, Sexp::int(42));
10668    }
10669
10670    #[test]
10671    fn resolve_bound_arg_yields_last_element_at_exact_upper_bound() {
10672        // Edge case: idx `len - 1` is the highest valid index. Pin
10673        // that it routes through the success arm (NOT the error arm),
10674        // closing the in-range surface end-to-end with the lower-
10675        // bound sibling. A regression that off-by-ones the upper
10676        // bound (e.g., `get(idx).filter(|_| idx < args.len() - 1)`)
10677        // would fail here.
10678        let args = vec![Sexp::int(1), Sexp::int(2), Sexp::int(3)];
10679        let got = resolve_bound_arg(&args, args.len() - 1, "m", |_| {
10680            panic!("kind constructor must not fire on the in-range path")
10681        })
10682        .expect("last-element lookup must succeed");
10683        assert!(std::ptr::eq(got, args.last().unwrap()));
10684        assert_eq!(*got, Sexp::int(3));
10685    }
10686
10687    #[test]
10688    fn resolve_bound_arg_at_exact_length_routes_to_error_arm() {
10689        // Boundary case: idx EQUAL to `args.len()` is out-of-range
10690        // (since `get` is 0-indexed). Pin that this routes through
10691        // the error arm with the kind constructor applied to the
10692        // EXACT idx that was tried. A regression that off-by-ones
10693        // the boundary (e.g., admits `idx == len`) would fail here.
10694        // This is the canonical off-by-one trap; the helper's
10695        // contract pins it at the variant-construction boundary.
10696        let args = vec![Sexp::int(1)];
10697        let err = resolve_bound_arg(&args, 1, "m", TemplateInvariantKind::SubstBadIndex)
10698            .expect_err("idx == len must error");
10699        match err {
10700            LispError::TemplateInvariant { kind, .. } => {
10701                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(1));
10702            }
10703            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10704        }
10705    }
10706
10707    #[test]
10708    fn resolve_bound_arg_empty_slice_with_any_idx_routes_to_error_arm() {
10709        // Boundary case: an empty `args_by_index` slice rejects every
10710        // idx (including 0). Pin that the helper's emission shape is
10711        // uniform regardless of which out-of-range idx fires the
10712        // rejection — `SubstBadIndex(0)` for an empty slice is the
10713        // bytecode-runtime mirror of a zero-arity macro template
10714        // referencing the 0-th param.
10715        let args: Vec<Sexp> = Vec::new();
10716        let err = resolve_bound_arg(&args, 0, "zero-arity", TemplateInvariantKind::SubstBadIndex)
10717            .expect_err("empty slice rejects every idx");
10718        match err {
10719            LispError::TemplateInvariant { macro_name, kind } => {
10720                assert_eq!(macro_name, "zero-arity");
10721                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(0));
10722            }
10723            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10724        }
10725    }
10726
10727    #[test]
10728    fn apply_compiled_subst_bad_idx_routes_through_resolve_bound_arg_with_subst_kind() {
10729        // End-to-end path-uniformity: a `Subst(99)` op against a
10730        // zero-arity macro routes the bytecode-runtime's bound-arg
10731        // lookup through `resolve_bound_arg` with the
10732        // `SubstBadIndex` constructor, emitting the structural
10733        // variant with `kind: SubstBadIndex(99)`. The pre-lift
10734        // sibling test `apply_compiled_subst_bad_idx_routes_through_
10735        // template_invariant_violation` pins that the same input
10736        // routes through `template_invariant_violation`; this test
10737        // pins that BOTH still hold under the post-lift composition
10738        // — `resolve_bound_arg` calls `template_invariant_violation`
10739        // internally on the rejection arm. A regression that drifts
10740        // ONE arm's projection from the other (e.g., swaps the
10741        // constructor at one call site, or short-circuits the
10742        // composition) would fail here.
10743        let tmpl = CompiledTemplate {
10744            ops: vec![TemplateOp::Subst(99)],
10745        };
10746        let err = apply_compiled("test-macro", &MacroParams::default(), &tmpl, &[])
10747            .expect_err("bad idx must error");
10748        match err {
10749            LispError::TemplateInvariant { macro_name, kind } => {
10750                assert_eq!(macro_name, "test-macro");
10751                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(99));
10752            }
10753            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10754        }
10755    }
10756
10757    #[test]
10758    fn apply_compiled_splice_bad_idx_routes_through_resolve_bound_arg_with_splice_kind() {
10759        // Sibling end-to-end path-uniformity for the `Splice` arm:
10760        // the post-lift composition routes a `Splice(42)` op through
10761        // `resolve_bound_arg` with the `SpliceBadIndex` constructor,
10762        // emitting `kind: SpliceBadIndex(42)`. Together with the
10763        // `Subst` sibling test above, this pins the structural matrix
10764        // `{Subst, Splice} × resolve_bound_arg` end-to-end through
10765        // the public `apply_compiled` surface, so a regression that
10766        // drifts ONE arm's kind constructor (e.g., the `Splice` arm
10767        // accidentally emits `SubstBadIndex` after a copy-paste
10768        // refactor) fails-loudly here.
10769        let tmpl = CompiledTemplate {
10770            ops: vec![TemplateOp::Splice(42)],
10771        };
10772        let err = apply_compiled("call-macro", &MacroParams::default(), &tmpl, &[])
10773            .expect_err("bad splice idx must error");
10774        match err {
10775            LispError::TemplateInvariant { macro_name, kind } => {
10776                assert_eq!(macro_name, "call-macro");
10777                assert_eq!(kind, TemplateInvariantKind::SpliceBadIndex(42));
10778            }
10779            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
10780        }
10781    }
10782
10783    #[test]
10784    fn apply_compiled_subst_in_range_routes_past_resolve_bound_arg_into_clone_and_push() {
10785        // Positive control: a `Subst(0)` op against a one-arg macro
10786        // routes through `resolve_bound_arg`'s success arm and the
10787        // `Subst` post-lookup verb (clone + push) emits the bound
10788        // value verbatim. Pin the post-lift composition's success
10789        // path: the clone-and-push semantics live at the call site
10790        // (NOT in `resolve_bound_arg`, which only borrows), and a
10791        // regression that drifts the borrow contract (e.g., the
10792        // helper clones internally + the call site clones again)
10793        // would still pass observationally but would regress the
10794        // hot-path allocation count.
10795        let params = MacroParams {
10796            required: vec!["x".into()],
10797            optional: Vec::new(),
10798            rest: None,
10799        };
10800        let tmpl = CompiledTemplate {
10801            ops: vec![TemplateOp::Subst(0)],
10802        };
10803        let out = apply_compiled("id", &params, &tmpl, &[Sexp::int(42)])
10804            .expect("in-range Subst must succeed");
10805        assert_eq!(out, Sexp::int(42));
10806    }
10807
10808    // ── current_builder_mut: the bytecode-runtime top-of-stack projection ──
10809    //
10810    // `current_builder_mut(stack)` lifts the `stack.last_mut().unwrap()`
10811    // projection that appeared at FOUR sites inside `apply_compiled`'s
10812    // op-loop (Literal, Subst, Splice, post-EndList parent-fold) into ONE
10813    // named primitive. The expect message names the bytecode-runtime
10814    // invariant ("at least one stack frame during op-loop") so a
10815    // regression that drifts the loop's frame management (a new op that
10816    // pops without pushing, an early-return that bypasses EndList's
10817    // stack-check) surfaces a NAMED panic rather than a silent unwrap.
10818    // These tests pin the projection's contract directly; the existing
10819    // `apply_compiled_*` tests + the cross-strategy `expansion_layers_
10820    // agree_on_output_and_cache_wins` benchmark are the path-uniformity
10821    // guards proving the four sites still emit the canonical bytecode-
10822    // runtime output across the lift.
10823
10824    #[test]
10825    fn current_builder_mut_returns_the_top_frame_reference() {
10826        // The simplest projection: on a single-frame stack, the helper
10827        // returns a `&mut Vec<Sexp>` pointing at THAT frame. Pin the
10828        // projection's identity end-to-end via a push that mutates
10829        // through the borrow and observe the original frame carries the
10830        // pushed value back.
10831        let mut stack: Vec<Vec<Sexp>> = vec![Vec::new()];
10832        current_builder_mut(&mut stack).push(Sexp::int(42));
10833        assert_eq!(stack.len(), 1);
10834        assert_eq!(stack[0], vec![Sexp::int(42)]);
10835    }
10836
10837    #[test]
10838    fn current_builder_mut_targets_the_topmost_frame_on_a_multi_frame_stack() {
10839        // The projection MUST target the topmost frame, not the bottom
10840        // one — every `TemplateOp::BeginList` pushes a fresh frame the
10841        // subsequent ops emit into, and a regression that flipped the
10842        // projection to `first_mut` (or to a fixed bottom-frame
10843        // reference) would silently smear all op output into the
10844        // outermost result. Pin path-uniformity with the bytecode-
10845        // runtime's mid-list emission posture: with three frames on
10846        // the stack (one outer + two pending lists), the helper
10847        // returns a borrow into the third frame, leaving frames 0 and
10848        // 1 untouched.
10849        let mut stack: Vec<Vec<Sexp>> = vec![
10850            vec![Sexp::symbol("outer")],
10851            vec![Sexp::symbol("inner-a")],
10852            vec![Sexp::symbol("inner-b")],
10853        ];
10854        current_builder_mut(&mut stack).push(Sexp::int(99));
10855        assert_eq!(stack[0], vec![Sexp::symbol("outer")]);
10856        assert_eq!(stack[1], vec![Sexp::symbol("inner-a")]);
10857        assert_eq!(stack[2], vec![Sexp::symbol("inner-b"), Sexp::int(99)]);
10858    }
10859
10860    #[test]
10861    fn current_builder_mut_is_pointer_equal_to_last_mut_unwrap() {
10862        // Structural identity binding the lift to its pre-lift inline
10863        // shape: `current_builder_mut(&mut stack)` IS
10864        // `stack.last_mut().unwrap()` — the same `&mut Vec<Sexp>`,
10865        // pointing at the same allocation. Pin pointer equality via
10866        // `std::ptr::eq` on the projected slice's `as_ptr()` to rule
10867        // out any allocation-shape drift across the lift.
10868        let mut stack: Vec<Vec<Sexp>> = vec![vec![Sexp::int(1), Sexp::int(2)]];
10869        let via_lift_ptr = current_builder_mut(&mut stack).as_ptr();
10870        let via_inline_ptr = stack.last_mut().unwrap().as_ptr();
10871        assert!(
10872            std::ptr::eq(via_lift_ptr, via_inline_ptr),
10873            "current_builder_mut must borrow the SAME frame as stack.last_mut().unwrap()"
10874        );
10875    }
10876
10877    #[test]
10878    #[should_panic(
10879        expected = "bytecode-runtime invariant: at least one stack frame during op-loop"
10880    )]
10881    fn current_builder_mut_panics_with_named_invariant_on_empty_stack() {
10882        // The bytecode-runtime invariant is encoded in the expect
10883        // message: an empty stack at the projection boundary is
10884        // structurally unreachable inside `apply_compiled`'s op-loop
10885        // (the outermost frame is seeded at entry and every BeginList
10886        // / EndList pair preserves the count >= 1). Pin that the
10887        // NAMED invariant fires on the failure path so a regression
10888        // that drifts the loop's frame management surfaces a
10889        // diagnostic-grade panic rather than a silent unwrap over
10890        // `None`. Authoring tools / future debug-mode hooks can
10891        // pattern-match on the named invariant string instead of
10892        // tracking down an unnamed unwrap site.
10893        let mut empty: Vec<Vec<Sexp>> = Vec::new();
10894        let _ = current_builder_mut(&mut empty);
10895    }
10896
10897    #[test]
10898    fn current_builder_mut_routes_apply_compiled_literal_emit() {
10899        // End-to-end path-uniformity guard: a single-op program
10900        // `TemplateOp::Literal(s)` routes its push through
10901        // `current_builder_mut(&mut stack)` and the literal lands in
10902        // the outermost frame. After the op-loop completes the outer
10903        // `stack.pop().FinalNoValue` gate sees a non-empty top frame
10904        // containing exactly one element, which `apply_compiled`'s
10905        // tail (`top.len() == 1 { top.remove(0) }`) projects back as
10906        // the bound value. Pre-lift the same emission ran through
10907        // `stack.last_mut().unwrap().push(s.clone())`; post-lift it
10908        // runs through `current_builder_mut(&mut stack).push(s.clone())`
10909        // — the byte-identical outcome pins that the Literal arm's
10910        // routing through the new projection preserves the bytecode-
10911        // runtime's emission shape.
10912        let tmpl = CompiledTemplate {
10913            ops: vec![TemplateOp::Literal(Sexp::symbol("hello"))],
10914        };
10915        let out = apply_compiled("id", &MacroParams::default(), &tmpl, &[])
10916            .expect("literal-only template must succeed");
10917        assert_eq!(out, Sexp::symbol("hello"));
10918    }
10919
10920    #[test]
10921    fn current_builder_mut_routes_apply_compiled_end_list_parent_fold() {
10922        // End-to-end path-uniformity guard for the post-EndList
10923        // parent-fold push: `(BeginList, Literal(a), Literal(b),
10924        // EndList)` builds an inner frame `[a, b]`, pops it on
10925        // EndList, then pushes `Sexp::List([a, b])` into the parent
10926        // (outer) frame via `current_builder_mut`. The outermost
10927        // `stack.pop()` then surfaces that list as the bound result.
10928        // Pre-lift the parent-fold push ran through
10929        // `stack.last_mut().unwrap().push(Sexp::List(items))`; post-
10930        // lift it runs through `current_builder_mut(&mut stack).
10931        // push(Sexp::List(items))` — pin the byte-identical outcome
10932        // so a regression that drifts the parent-fold target (e.g.,
10933        // pushes onto the just-popped frame's pointer instead of the
10934        // new top) fails loudly here.
10935        let tmpl = CompiledTemplate {
10936            ops: vec![
10937                TemplateOp::BeginList,
10938                TemplateOp::Literal(Sexp::symbol("a")),
10939                TemplateOp::Literal(Sexp::symbol("b")),
10940                TemplateOp::EndList,
10941            ],
10942        };
10943        let out = apply_compiled("id", &MacroParams::default(), &tmpl, &[])
10944            .expect("BeginList/EndList template must succeed");
10945        assert_eq!(out, Sexp::List(vec![Sexp::symbol("a"), Sexp::symbol("b")]));
10946    }
10947
10948    #[test]
10949    fn current_builder_mut_routes_apply_compiled_subst_and_splice_emits() {
10950        // End-to-end path-uniformity guard for BOTH index-reading
10951        // arms routing through the lifted projection: a one-required +
10952        // one-rest macro `(call f &rest args)` with template
10953        // `(BeginList, Subst(0), Splice(1), EndList)` exercises both
10954        // Subst's clone-and-push AND Splice's splice-value-into
10955        // emit-paths against the current builder via
10956        // `current_builder_mut(&mut stack)`. The composed result is
10957        // `(foo 1 2 3)` — Subst lands the bound `f = foo` and
10958        // Splice flattens `args = (1 2 3)` — and the byte-identical
10959        // outcome pins that BOTH Subst and Splice arms' emits route
10960        // through the SHARED projection. Sibling to
10961        // `apply_compiled_splice_in_range_routes_past_resolve_bound
10962        // _arg_into_splice_value_into` which already exercises this
10963        // shape end-to-end; the addition here is the path-uniformity
10964        // anchor for the `current_builder_mut` lift specifically.
10965        let params = MacroParams {
10966            required: vec!["f".into()],
10967            optional: Vec::new(),
10968            rest: Some("args".into()),
10969        };
10970        let tmpl = CompiledTemplate {
10971            ops: vec![
10972                TemplateOp::BeginList,
10973                TemplateOp::Subst(0),
10974                TemplateOp::Splice(1),
10975                TemplateOp::EndList,
10976            ],
10977        };
10978        let out = apply_compiled(
10979            "call",
10980            &params,
10981            &tmpl,
10982            &[
10983                Sexp::symbol("foo"),
10984                Sexp::int(1),
10985                Sexp::int(2),
10986                Sexp::int(3),
10987            ],
10988        )
10989        .expect("Subst + Splice template must succeed");
10990        assert_eq!(
10991            out,
10992            Sexp::List(vec![
10993                Sexp::symbol("foo"),
10994                Sexp::int(1),
10995                Sexp::int(2),
10996                Sexp::int(3),
10997            ])
10998        );
10999    }
11000
11001    #[test]
11002    fn apply_compiled_splice_in_range_routes_past_resolve_bound_arg_into_splice_value_into() {
11003        // Positive control for the `Splice` arm: a `&rest` macro that
11004        // splices a bound list routes through `resolve_bound_arg`'s
11005        // success arm and the `Splice` post-lookup verb
11006        // (`splice_value_into`) flattens the bound list into the
11007        // builder. Pin the composition's success path end-to-end:
11008        // the bound `Sexp::List([1, 2, 3])` at idx 1 flattens into
11009        // the outer builder's `(call 1 2 3)` shape — the same output
11010        // `rest_param_splices_with_at` pins through the public
11011        // surface, here pinned with the bytecode-runtime composition
11012        // exposed directly.
11013        let params = MacroParams {
11014            required: vec!["f".into()],
11015            optional: Vec::new(),
11016            rest: Some("args".into()),
11017        };
11018        let tmpl = CompiledTemplate {
11019            ops: vec![
11020                TemplateOp::BeginList,
11021                TemplateOp::Subst(0),
11022                TemplateOp::Splice(1),
11023                TemplateOp::EndList,
11024            ],
11025        };
11026        let out = apply_compiled(
11027            "call",
11028            &params,
11029            &tmpl,
11030            &[Sexp::symbol("foo"), Sexp::int(1), Sexp::int(2)],
11031        )
11032        .expect("in-range Splice must succeed");
11033        assert_eq!(
11034            out,
11035            Sexp::List(vec![Sexp::symbol("foo"), Sexp::int(1), Sexp::int(2)])
11036        );
11037    }
11038
11039    // ── pop_builder_frame: the bytecode-runtime stack-frame consume ─────
11040    //
11041    // `pop_builder_frame(stack, macro_name, kind)` lifts the
11042    // `stack.pop().ok_or_else(|| template_invariant_violation(macro_name,
11043    // kind))?` chain that recurred at two sites inside `apply_compiled`
11044    // (the `EndList` arm + the post-loop final pop) into ONE named
11045    // primitive on the bytecode-runtime's stack-frame algebra. Sibling of
11046    // `current_builder_mut` (the top-frame-borrow projection — the same
11047    // `&mut Vec<Vec<Sexp>>` consumed at the borrow face) and
11048    // `resolve_bound_arg` (the bound-arg-by-index lookup primitive that
11049    // also routes through `TemplateInvariantKind` as the per-call-site
11050    // rejection identity). These tests pin the primitive's contract
11051    // directly; the existing `apply_compiled_*` tests are the path-
11052    // uniformity guards proving the two sites route through it without
11053    // behavior drift.
11054
11055    #[test]
11056    fn pop_builder_frame_pops_top_frame_off_non_empty_stack() {
11057        // Happy path: a two-frame stack pops the topmost frame off and
11058        // returns it AS-IS while shrinking the stack by exactly one
11059        // element. Pin both the return value (the popped frame's
11060        // contents are byte-identical to what was pushed) AND the
11061        // mutation (`stack.len()` drops from 2 to 1).
11062        let mut stack: Vec<Vec<Sexp>> = vec![
11063            vec![Sexp::symbol("outer")],
11064            vec![Sexp::int(1), Sexp::int(2)],
11065        ];
11066        let popped =
11067            pop_builder_frame(&mut stack, "wrap", TemplateInvariantKind::EndListEmptyStack)
11068                .expect("non-empty stack must pop cleanly");
11069        assert_eq!(popped, vec![Sexp::int(1), Sexp::int(2)]);
11070        assert_eq!(stack.len(), 1);
11071        assert_eq!(stack[0], vec![Sexp::symbol("outer")]);
11072    }
11073
11074    #[test]
11075    fn pop_builder_frame_emits_template_invariant_with_end_list_empty_stack_kind() {
11076        // Empty-stack rejection: an empty stack flows through the
11077        // `EndListEmptyStack` kind constructor into a structural
11078        // `LispError::TemplateInvariant { macro_name, kind:
11079        // EndListEmptyStack }` variant. Fail-before-pass-after: pre-lift
11080        // the same input would route through the inline
11081        // `stack.pop().ok_or_else(|| template_invariant_violation(_,
11082        // EndListEmptyStack))?` chain at the `EndList` arm; post-lift
11083        // it routes through ONE named primitive both pop-emitting
11084        // sites share, and a regression that drops the kind threading
11085        // (e.g. unifies both kinds into one constant) fails here.
11086        let mut empty: Vec<Vec<Sexp>> = Vec::new();
11087        let err = pop_builder_frame(&mut empty, "wrap", TemplateInvariantKind::EndListEmptyStack)
11088            .expect_err("empty stack must reject");
11089        match err {
11090            LispError::TemplateInvariant { macro_name, kind } => {
11091                assert_eq!(macro_name, "wrap");
11092                assert_eq!(kind, TemplateInvariantKind::EndListEmptyStack);
11093            }
11094            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
11095        }
11096    }
11097
11098    #[test]
11099    fn pop_builder_frame_emits_template_invariant_with_final_no_value_kind() {
11100        // Path-uniformity across the closed-set of pop-emitting kinds:
11101        // the same primitive threads `FinalNoValue` verbatim through
11102        // its `TemplateInvariantKind` slot, with the macro_name
11103        // identity preserved across the call boundary. Sibling of the
11104        // `EndListEmptyStack` test above — together they pin the
11105        // primitive's closed-set posture (BOTH reachable
11106        // pop-emitting kinds route through the same primitive, neither
11107        // is hard-coded), so a future `TemplateInvariantKind` variant
11108        // added for a new pop-emitting op (e.g. a hypothetical
11109        // `EndManyEmptyStack`) extends the primitive's reachability
11110        // mechanically by passing the new kind through the same slot.
11111        let mut empty: Vec<Vec<Sexp>> = Vec::new();
11112        let err = pop_builder_frame(&mut empty, "id", TemplateInvariantKind::FinalNoValue)
11113            .expect_err("empty stack must reject");
11114        match err {
11115            LispError::TemplateInvariant { macro_name, kind } => {
11116                assert_eq!(macro_name, "id");
11117                assert_eq!(kind, TemplateInvariantKind::FinalNoValue);
11118            }
11119            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
11120        }
11121    }
11122
11123    #[test]
11124    fn pop_builder_frame_threads_macro_name_through_variant_for_indexed_kinds() {
11125        // Closed-set posture check: even kinds the production
11126        // `apply_compiled` op-loop ROUTES through `resolve_bound_arg`
11127        // rather than `pop_builder_frame` (the indexed `SubstBadIndex(_)`
11128        // / `SpliceBadIndex(_)` siblings) MUST compose correctly with
11129        // this primitive's typed slot — they are NOT reachable here
11130        // from the production loop, but `TemplateInvariantKind` does
11131        // not distinguish "indexed" kinds from "stack-gate" kinds at
11132        // the helper's signature, so a regression that special-cases
11133        // one kind family would be a silent type-narrowing the closed-
11134        // set typed enum was lifted to prevent. Pin the universal
11135        // routing: ANY kind variant feeds through the primitive's
11136        // `kind` slot identically. Sibling assertion to
11137        // `template_invariant_violation_threads_subst_idx_through_typed_variant`
11138        // — same compose-the-kind-into-the-variant contract, one
11139        // composition step further down the substrate stack.
11140        let mut empty: Vec<Vec<Sexp>> = Vec::new();
11141        let err = pop_builder_frame(
11142            &mut empty,
11143            "compose",
11144            TemplateInvariantKind::SubstBadIndex(42),
11145        )
11146        .expect_err("empty stack must reject regardless of kind family");
11147        match err {
11148            LispError::TemplateInvariant { macro_name, kind } => {
11149                assert_eq!(macro_name, "compose");
11150                assert_eq!(kind, TemplateInvariantKind::SubstBadIndex(42));
11151            }
11152            other => panic!("expected LispError::TemplateInvariant, got {other:?}"),
11153        }
11154    }
11155
11156    #[test]
11157    fn pop_builder_frame_is_byte_identical_to_inline_pop_then_template_invariant_violation() {
11158        // Structural-identity binding the lift to its pre-lift inline
11159        // shape: `pop_builder_frame(stack, macro_name, kind)` IS
11160        // `stack.pop().ok_or_else(|| template_invariant_violation(
11161        // macro_name, kind))?` — both reachable arms (success +
11162        // failure) must produce byte-identical outcomes. The success
11163        // arm checks the popped Vec contents AND the post-pop stack
11164        // length match the inline path; the failure arm checks the
11165        // emitted variant's identity AND its `macro_name` / `kind`
11166        // slots match the inline path. A regression that drifts the
11167        // primitive's projection (e.g. a `stack.swap_remove(_)` typo,
11168        // or a kind-rewrite at the helper boundary) fails on at least
11169        // one of the two arms.
11170        // Success arm:
11171        let mut stack_lift: Vec<Vec<Sexp>> = vec![vec![Sexp::symbol("a")], vec![Sexp::int(7)]];
11172        let mut stack_inline: Vec<Vec<Sexp>> = vec![vec![Sexp::symbol("a")], vec![Sexp::int(7)]];
11173        let via_lift = pop_builder_frame(
11174            &mut stack_lift,
11175            "macro",
11176            TemplateInvariantKind::EndListEmptyStack,
11177        )
11178        .expect("non-empty stack pops cleanly through lift");
11179        let via_inline = stack_inline.pop().ok_or_else(|| {
11180            template_invariant_violation("macro", TemplateInvariantKind::EndListEmptyStack)
11181        });
11182        assert_eq!(
11183            via_lift,
11184            via_inline.unwrap(),
11185            "popped frame must be byte-identical across lift vs inline"
11186        );
11187        assert_eq!(
11188            stack_lift.len(),
11189            stack_inline.len(),
11190            "post-pop stack length must be byte-identical across lift vs inline"
11191        );
11192        // Failure arm:
11193        let mut empty_lift: Vec<Vec<Sexp>> = Vec::new();
11194        let mut empty_inline: Vec<Vec<Sexp>> = Vec::new();
11195        let err_lift = pop_builder_frame(
11196            &mut empty_lift,
11197            "macro",
11198            TemplateInvariantKind::FinalNoValue,
11199        )
11200        .expect_err("empty stack rejects through lift");
11201        let err_inline = empty_inline
11202            .pop()
11203            .ok_or_else(|| {
11204                template_invariant_violation("macro", TemplateInvariantKind::FinalNoValue)
11205            })
11206            .expect_err("empty stack rejects through inline");
11207        match (err_lift, err_inline) {
11208            (
11209                LispError::TemplateInvariant {
11210                    macro_name: m_lift,
11211                    kind: k_lift,
11212                },
11213                LispError::TemplateInvariant {
11214                    macro_name: m_inline,
11215                    kind: k_inline,
11216                },
11217            ) => {
11218                assert_eq!(m_lift, m_inline);
11219                assert_eq!(k_lift, k_inline);
11220            }
11221            (l, i) => panic!(
11222                "expected LispError::TemplateInvariant on both arms, got lift={l:?}, inline={i:?}"
11223            ),
11224        }
11225    }
11226
11227    #[test]
11228    fn pop_builder_frame_routes_apply_compiled_end_list_consume() {
11229        // End-to-end path-uniformity guard for the `EndList` arm: a
11230        // `(BeginList, Literal(x), EndList)` program pushes a child
11231        // frame, populates it with one literal, then routes the child
11232        // frame OUT of the stack via `pop_builder_frame` (kind
11233        // `EndListEmptyStack` — unreachable on this valid input, but
11234        // the kind threads through the primitive identically). The
11235        // post-pop verb (`Sexp::List(items)` push into the parent via
11236        // `current_builder_mut`) yields the same `Sexp::List([x])`
11237        // shape the consumer projected pre-lift. Pin the byte-
11238        // identical outcome so a regression that drifts the EndList
11239        // arm's routing (e.g. swaps the kind constructor, or routes
11240        // through a different stack-mutating primitive) fails loudly
11241        // here. Sibling of
11242        // `current_builder_mut_routes_apply_compiled_end_list_parent_fold`
11243        // — that test pins the parent-fold PUSH; this test pins the
11244        // child-frame POP that immediately precedes it. Together the
11245        // two close the EndList arm's path-uniformity across the
11246        // pop-then-push composition.
11247        let tmpl = CompiledTemplate {
11248            ops: vec![
11249                TemplateOp::BeginList,
11250                TemplateOp::Literal(Sexp::symbol("only")),
11251                TemplateOp::EndList,
11252            ],
11253        };
11254        let out = apply_compiled("id", &MacroParams::default(), &tmpl, &[])
11255            .expect("BeginList/EndList one-literal template must succeed");
11256        assert_eq!(out, Sexp::List(vec![Sexp::symbol("only")]));
11257    }
11258
11259    #[test]
11260    fn pop_builder_frame_routes_apply_compiled_final_pop_consume() {
11261        // End-to-end path-uniformity guard for the post-loop final
11262        // pop: a single-op `Literal(s)` program emits `s` into the
11263        // outermost (seed) frame, then the post-loop tail routes the
11264        // seed frame OUT via `pop_builder_frame` (kind `FinalNoValue`
11265        // — unreachable on this valid input). The post-pop arity gate
11266        // (`top.len() == 1 { top.remove(0) }`) projects the literal
11267        // back as the bound value. Pre-lift the same emission ran
11268        // through the inline `stack.pop().ok_or_else(|| template_
11269        // invariant_violation(_, FinalNoValue))?` chain at the
11270        // post-loop tail; post-lift it routes through ONE named
11271        // primitive the EndList arm ALSO routes through, and the
11272        // single-literal outcome is byte-identical across both code
11273        // paths. Sibling of
11274        // `current_builder_mut_routes_apply_compiled_literal_emit` —
11275        // that test pins the EMIT into the seed frame; this test pins
11276        // the POP of the same seed frame at the post-loop tail.
11277        // Together the two close the Literal-only program's path-
11278        // uniformity across the emit-then-consume composition.
11279        let tmpl = CompiledTemplate {
11280            ops: vec![TemplateOp::Literal(Sexp::int(123))],
11281        };
11282        let out = apply_compiled("id", &MacroParams::default(), &tmpl, &[])
11283            .expect("literal-only template must succeed");
11284        assert_eq!(out, Sexp::int(123));
11285    }
11286
11287    // ── MacroParams: the typed param-list primitive ─────────────────────
11288    //
11289    // `parse_params` now yields a `MacroParams { required, optional, rest }`
11290    // whose shape makes the canonical lambda-list ordering (required →
11291    // optional → rest, "&rest is last + at-most-one", "&optional at most
11292    // once") structural rather than a construction discipline a `Vec<Param>`
11293    // only happened to uphold. These tests pin the parser's mapping into the
11294    // typed shape, the flat-index contract `names()` exposes to the template
11295    // bytecode, and the single positional binder `bind()` both expansion
11296    // strategies now route through. The end-to-end `rest_param_splices_with_at`
11297    // and `compiled_template_matches_substitute_path` tests above are the
11298    // path-uniformity guards proving both strategies still agree.
11299
11300    #[test]
11301    fn parse_params_maps_required_then_rest_into_typed_shape() {
11302        // `(a b &rest c)` — two required, one rest. The rest name lands in
11303        // the `Option`, never in `required`.
11304        let params = parse_params(&read("a b &rest c").unwrap()).unwrap();
11305        assert_eq!(
11306            params,
11307            MacroParams {
11308                required: vec!["a".into(), "b".into()],
11309                optional: Vec::new(),
11310                rest: Some("c".into()),
11311            }
11312        );
11313    }
11314
11315    #[test]
11316    fn parse_params_rest_absent_leaves_none() {
11317        // `(x y)` — no `&rest`, so `rest` is structurally `None`. There is
11318        // no representation in which a rest-less list carries a stray rest.
11319        let params = parse_params(&read("x y").unwrap()).unwrap();
11320        assert_eq!(
11321            params,
11322            MacroParams {
11323                required: vec!["x".into(), "y".into()],
11324                optional: Vec::new(),
11325                rest: None,
11326            }
11327        );
11328    }
11329
11330    #[test]
11331    fn parse_params_maps_optional_section_between_required_and_rest() {
11332        // `(a &optional b c &rest d)` — the canonical lambda-list order. `a`
11333        // is required, `b`/`c` are optional, `d` is rest. The `&optional`
11334        // marker switches collection from `required` to `optional`; `&rest`
11335        // remains terminal.
11336        let params = parse_params(&read("a &optional b c &rest d").unwrap()).unwrap();
11337        assert_eq!(
11338            params,
11339            MacroParams {
11340                required: vec!["a".into()],
11341                optional: vec![OptionalParam::bare("b"), OptionalParam::bare("c")],
11342                rest: Some("d".into()),
11343            }
11344        );
11345    }
11346
11347    #[test]
11348    fn parse_params_optional_with_no_rest_leaves_rest_none() {
11349        // `(&optional x)` — a leading `&optional` (zero required) with no
11350        // rest. `required` is empty, `x` is the sole optional, `rest` None.
11351        let params = parse_params(&read("&optional x").unwrap()).unwrap();
11352        assert_eq!(
11353            params,
11354            MacroParams {
11355                required: Vec::new(),
11356                optional: vec![OptionalParam::bare("x")],
11357                rest: None,
11358            }
11359        );
11360    }
11361
11362    #[test]
11363    fn parse_params_rejects_repeated_optional_marker() {
11364        // `(a &optional b &optional c)` — a second `&optional` is
11365        // unrepresentable (one flat optional section), so the gate REJECTS
11366        // rather than binding args to a marker symbol named `&optional`. The
11367        // two marker positions (1 and 3) are named.
11368        let err = parse_params(&read("a &optional b &optional c").unwrap())
11369            .expect_err("repeated &optional must error");
11370        assert!(
11371            matches!(
11372                err,
11373                LispError::OptionalMarkerRepeated {
11374                    first_position: 1,
11375                    second_position: 3,
11376                }
11377            ),
11378            "expected OptionalMarkerRepeated {{1, 3}}, got: {err:?}"
11379        );
11380    }
11381
11382    #[test]
11383    fn parse_params_rejects_optional_after_rest_as_trailing_tokens() {
11384        // `(&rest xs &optional y)` — `&rest <name>` is terminal, so the
11385        // `&optional y` tail is REJECTED as trailing tokens (not silently
11386        // dropped, and not a repeated-optional error: the rest gate fires
11387        // first). Pins the interaction the prior run (3627426) signposted.
11388        let err = parse_params(&read("&rest xs &optional y").unwrap())
11389            .expect_err("tokens after &rest <name> must error");
11390        assert!(
11391            matches!(err, LispError::RestParamTrailingTokens { .. }),
11392            "expected RestParamTrailingTokens, got: {err:?}"
11393        );
11394    }
11395
11396    #[test]
11397    fn names_are_required_then_optional_then_rest_in_flat_index_order() {
11398        // The flat-index contract the bytecode `Subst(idx)`/`Splice(idx)`
11399        // depends on: required names at 0.., then optional names, then the
11400        // rest name last.
11401        let params = MacroParams {
11402            required: vec!["a".into(), "b".into()],
11403            optional: vec![OptionalParam::bare("c")],
11404            rest: Some("d".into()),
11405        };
11406        assert_eq!(params.names(), vec!["a", "b", "c", "d"]);
11407        // Optional names occupy the indices immediately after the required run.
11408        assert_eq!(params.names()[params.required.len()], "c");
11409        // The rest name is last, after required + optional — i.e. at the
11410        // structural `fixed_arity()` boundary the typed primitive names.
11411        assert_eq!(params.names()[params.fixed_arity()], "d");
11412    }
11413
11414    // ── `MacroParams::{REST_MARKER, OPTIONAL_MARKER,
11415    // LAMBDA_LIST_KEYWORD_LEAD}` — the typed CL lambda-list-keyword
11416    // marker algebra ────────────────────────────────────────────────
11417    //
11418    // The three `pub const`s close the Common-Lisp lambda-list keyword
11419    // family at the typed [`MacroParams`] algebra: `REST_MARKER` and
11420    // `OPTIONAL_MARKER` are the two `&'static str` markers the parser's
11421    // typed dispatch specialises on; `LAMBDA_LIST_KEYWORD_LEAD` is the
11422    // canonical `'&'` char shared as the LEAD byte of both markers.
11423    // Pre-lift the same two `&'static str` markers lived as two inline
11424    // `s == "..."` comparisons at `parse_params` — post-lift both
11425    // comparisons route through the typed constants so a delimiter swap
11426    // (e.g. a Racket-compat `#!rest` port, a Clojure-compat `&` port)
11427    // lands at ONE constant on the typed algebra.
11428    //
11429    // These pins cover: (a) the exact `&'static str` / `char` values,
11430    // (b) the structural round-trip law binding each `&'static str`
11431    // marker to its shared `char` LEAD byte, (c) the pairwise
11432    // disjointness of the two `&'static str` markers, (d) the cross-
11433    // axis disjointness of the `char` LEAD byte against every sibling
11434    // outer-marker `char` the substrate's other closed-set algebras
11435    // specialise on, and (e) the path-uniformity of `parse_params`
11436    // through the typed constants.
11437
11438    #[test]
11439    fn macro_params_rest_marker_projects_canonical_ampersand_rest_str() {
11440        // Pins the constant's exact `&'static str` bytes so a typo
11441        // (`"&res"`, `"&Rest"`, `"&rst"`) or an accidental redefinition
11442        // surfaces immediately. Sibling-shape pin to
11443        // `macro_params_optional_marker_projects_canonical_ampersand_optional_str`
11444        // on the peer `&optional` axis.
11445        assert_eq!(
11446            MacroParams::REST_MARKER,
11447            "&rest",
11448            "MacroParams::REST_MARKER drifted from the substrate- \
11449             canonical CL lambda-list `&rest` marker — the parser's \
11450             `parse_params` rest-slot dispatch AND every downstream \
11451             authoring / rendering surface binds to this ONE typed \
11452             constant.",
11453        );
11454    }
11455
11456    #[test]
11457    fn macro_params_optional_marker_projects_canonical_ampersand_optional_str() {
11458        // Pins the constant's exact `&'static str` bytes so a typo
11459        // (`"&opt"`, `"&Optional"`, `"&option"`) or an accidental
11460        // redefinition surfaces immediately. Sibling-shape pin to
11461        // `macro_params_rest_marker_projects_canonical_ampersand_rest_str`
11462        // on the peer `&rest` axis.
11463        assert_eq!(
11464            MacroParams::OPTIONAL_MARKER,
11465            "&optional",
11466            "MacroParams::OPTIONAL_MARKER drifted from the substrate- \
11467             canonical CL lambda-list `&optional` marker — the parser's \
11468             `parse_params` optional-section dispatch AND every \
11469             downstream authoring / rendering surface binds to this \
11470             ONE typed constant.",
11471        );
11472    }
11473
11474    #[test]
11475    fn macro_params_lambda_list_keyword_lead_projects_canonical_ampersand_char() {
11476        // Pins the constant's exact `char` value so a typo (`'#'`,
11477        // `'@'`, `'!'`) or an accidental redefinition surfaces
11478        // immediately. Sibling-shape pin to
11479        // `atom_keyword_marker_lead_projects_canonical_colon_char`,
11480        // `atom_bool_literal_lead_projects_canonical_hash_char`,
11481        // `atom_str_delimiter_projects_canonical_double_quote_char`
11482        // on the peer per-role LEAD-byte axes across the substrate's
11483        // closed-set outer algebras.
11484        assert_eq!(
11485            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11486            '&',
11487            "LAMBDA_LIST_KEYWORD_LEAD char drifted from the substrate- \
11488             canonical `&` LEAD byte — the CL lambda-list-keyword \
11489             family (REST_MARKER, OPTIONAL_MARKER) shares this ONE \
11490             typed constant as their common LEAD byte.",
11491        );
11492    }
11493
11494    #[test]
11495    fn macro_params_rest_marker_prefixed_by_lambda_list_keyword_lead() {
11496        // STRUCTURAL ROUND-TRIP CONTRACT: the `&'static str` marker
11497        // `MacroParams::REST_MARKER` starts with the `char` LEAD byte
11498        // `MacroParams::LAMBDA_LIST_KEYWORD_LEAD` — the projection law
11499        // binding the two typed constants on the [`MacroParams`]
11500        // algebra. A regression that renames the `&'static str` (e.g.
11501        // to Racket's `"#!rest"` keyword-args) OR the `char` (e.g. to
11502        // `'#'` for the `#!` shebang lead) without updating the other
11503        // fails HERE. Sibling-shape pin to
11504        // `atom_keyword_marker_lead_prefixes_keyword_marker` on the
11505        // peer `Atom`-algebra Keyword-prefix LEAD-byte axis.
11506        assert!(
11507            MacroParams::REST_MARKER.starts_with(MacroParams::LAMBDA_LIST_KEYWORD_LEAD),
11508            "MacroParams::REST_MARKER `{}` does NOT start with \
11509             MacroParams::LAMBDA_LIST_KEYWORD_LEAD `{:?}` — the two \
11510             typed constants have drifted apart on the [`MacroParams`] \
11511             algebra; the CL lambda-list-keyword family disjointness \
11512             contract can no longer bind to ONE shared LEAD byte.",
11513            MacroParams::REST_MARKER,
11514            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11515        );
11516    }
11517
11518    #[test]
11519    fn macro_params_optional_marker_prefixed_by_lambda_list_keyword_lead() {
11520        // STRUCTURAL ROUND-TRIP CONTRACT: peer to
11521        // `macro_params_rest_marker_prefixed_by_lambda_list_keyword_lead`
11522        // on the `&optional` axis. Both `&'static str` markers of the
11523        // CL lambda-list-keyword family MUST share the canonical `char`
11524        // LEAD byte so the typed-marker disjointness contract can bind
11525        // to ONE shared LEAD byte on the [`MacroParams`] algebra.
11526        assert!(
11527            MacroParams::OPTIONAL_MARKER.starts_with(MacroParams::LAMBDA_LIST_KEYWORD_LEAD),
11528            "MacroParams::OPTIONAL_MARKER `{}` does NOT start with \
11529             MacroParams::LAMBDA_LIST_KEYWORD_LEAD `{:?}` — the two \
11530             typed constants have drifted apart on the [`MacroParams`] \
11531             algebra; the CL lambda-list-keyword family disjointness \
11532             contract can no longer bind to ONE shared LEAD byte.",
11533            MacroParams::OPTIONAL_MARKER,
11534            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11535        );
11536    }
11537
11538    #[test]
11539    fn macro_params_rest_and_optional_markers_pairwise_disjoint() {
11540        // PAIRWISE DISJOINTNESS PIN: the two `&'static str` markers on
11541        // the CL lambda-list-keyword algebra MUST differ so the
11542        // parser's typed dispatch cascade at `parse_params` (which
11543        // tests `REST_MARKER` FIRST, then `OPTIONAL_MARKER`) cannot
11544        // silently route both dispatches through the same arm. A
11545        // regression that aliases the two markers (e.g. both to
11546        // `"&rest"` after a typo) would silently drop the optional
11547        // section's structural distinction — every `&optional` name
11548        // would misclassify as a rest-slot marker.
11549        assert_ne!(
11550            MacroParams::REST_MARKER,
11551            MacroParams::OPTIONAL_MARKER,
11552            "REST_MARKER and OPTIONAL_MARKER collide — the parser's \
11553             typed dispatch cascade at `parse_params` can no longer \
11554             distinguish the rest-slot boundary from the optional- \
11555             section boundary.",
11556        );
11557    }
11558
11559    #[test]
11560    fn macro_params_lambda_list_keyword_lead_distinct_from_every_other_algebra_marker() {
11561        // CROSS-AXIS DISJOINTNESS PIN: `MacroParams::LAMBDA_LIST_KEYWORD_LEAD`
11562        // MUST NOT alias any sibling outer-marker `char` on the
11563        // substrate's other closed-set algebras — the Atom-payload
11564        // markers (`STR_DELIMITER`, `STR_ESCAPE_LEAD`,
11565        // `KEYWORD_MARKER_LEAD`, `BOOL_LITERAL_LEAD`), the paired list
11566        // delimiters (`Sexp::LIST_OPEN` / `Sexp::LIST_CLOSE`), the
11567        // paired line-comment delimiters (`Sexp::COMMENT_LEAD` /
11568        // `Sexp::COMMENT_TERM`), every `QuoteForm::lead_char`
11569        // projection, AND `QuoteForm::SPLICE_DISCRIMINATOR`. A
11570        // collision would silently break the reader's outer dispatch:
11571        // an `&`-prefixed bare atom `&rest` / `&optional` would collide
11572        // with whichever marker it aliased. Sibling-shape pin to
11573        // `atom_keyword_marker_lead_distinct_from_every_other_algebra_marker`
11574        // on the peer `Atom`-algebra Keyword-prefix LEAD-byte axis —
11575        // pins the SAME shape on the CL lambda-list-keyword LEAD-byte
11576        // axis. A future outer-marker extension that collided with
11577        // `'&'` fails HERE at the cross-axis enumeration.
11578        use crate::ast::{Atom, QuoteForm, Sexp};
11579
11580        assert_ne!(
11581            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11582            Atom::STR_DELIMITER,
11583            "LAMBDA_LIST_KEYWORD_LEAD collides with STR_DELIMITER — a \
11584             bare `&rest` at a param-list position would ambiguously \
11585             begin a lambda-list keyword AND open a string.",
11586        );
11587        assert_ne!(
11588            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11589            Atom::STR_ESCAPE_LEAD,
11590            "LAMBDA_LIST_KEYWORD_LEAD collides with STR_ESCAPE_LEAD — \
11591             the reader's Str-escape lead byte would alias the CL \
11592             lambda-list-keyword LEAD byte.",
11593        );
11594        assert_ne!(
11595            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11596            Atom::KEYWORD_MARKER_LEAD,
11597            "LAMBDA_LIST_KEYWORD_LEAD collides with KEYWORD_MARKER_LEAD \
11598             — a bare `&rest` at a param-list position would \
11599             ambiguously begin a lambda-list keyword AND begin an \
11600             `:foo` keyword.",
11601        );
11602        assert_ne!(
11603            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11604            Atom::BOOL_LITERAL_LEAD,
11605            "LAMBDA_LIST_KEYWORD_LEAD collides with BOOL_LITERAL_LEAD — \
11606             a bare `&rest` at a param-list position would ambiguously \
11607             begin a lambda-list keyword AND classify as a Bool prefix.",
11608        );
11609        assert_ne!(
11610            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11611            Sexp::LIST_OPEN,
11612            "LAMBDA_LIST_KEYWORD_LEAD collides with LIST_OPEN — a bare \
11613             `&rest` at a param-list position would ambiguously begin \
11614             a lambda-list keyword AND open a list.",
11615        );
11616        assert_ne!(
11617            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11618            Sexp::LIST_CLOSE,
11619            "LAMBDA_LIST_KEYWORD_LEAD collides with LIST_CLOSE — a bare \
11620             `&rest` at a param-list position would ambiguously begin \
11621             a lambda-list keyword AND close a list.",
11622        );
11623        assert_ne!(
11624            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11625            Sexp::COMMENT_LEAD,
11626            "LAMBDA_LIST_KEYWORD_LEAD collides with COMMENT_LEAD — a \
11627             bare `&rest` at a param-list position would ambiguously \
11628             begin a lambda-list keyword AND begin a comment.",
11629        );
11630        assert_ne!(
11631            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11632            Sexp::COMMENT_TERM,
11633            "LAMBDA_LIST_KEYWORD_LEAD collides with COMMENT_TERM — the \
11634             reader's line-comment discard loop would terminate on the \
11635             SAME byte the parser's lambda-list-keyword LEAD dispatch \
11636             binds to.",
11637        );
11638        for qf in QuoteForm::ALL {
11639            assert_ne!(
11640                MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11641                qf.lead_char(),
11642                "LAMBDA_LIST_KEYWORD_LEAD collides with \
11643                 QuoteForm::{qf:?}'s lead_char — a bare `&rest` at a \
11644                 param-list position would ambiguously begin a lambda- \
11645                 list keyword AND begin a quote-family prefix.",
11646            );
11647        }
11648        assert_ne!(
11649            MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11650            QuoteForm::SPLICE_DISCRIMINATOR,
11651            "LAMBDA_LIST_KEYWORD_LEAD collides with \
11652             SPLICE_DISCRIMINATOR — the reader's `,@` splice-promotion \
11653             peek byte would alias the CL lambda-list-keyword LEAD \
11654             byte.",
11655        );
11656    }
11657
11658    #[test]
11659    fn parse_params_recognizes_rest_marker_via_typed_constant() {
11660        // PATH-UNIFORMITY PIN: the parser's rest-slot dispatch at
11661        // `parse_params` MUST classify the typed constant
11662        // `MacroParams::REST_MARKER` as the rest-slot boundary —
11663        // authoring surfaces that assemble a param list through the
11664        // typed constant (rather than through an inline string literal
11665        // read from user source) must reach the SAME rest-slot binding
11666        // the reader-driven path does.
11667        //
11668        // The `read(REST_MARKER)` composition here is the load-bearing
11669        // structural link: we build the param-list source through the
11670        // typed constant, then parse it through the reader, then the
11671        // parser MUST bind `xs` at the `rest` slot. A regression that
11672        // drifts the parser's dispatch away from the typed constant
11673        // (e.g. re-inlines a string literal that goes stale against
11674        // the constant) fails HERE.
11675        let src = format!("a {} xs", MacroParams::REST_MARKER);
11676        let params = parse_params(&read(&src).unwrap()).unwrap();
11677        assert_eq!(
11678            params,
11679            MacroParams {
11680                required: vec!["a".into()],
11681                optional: Vec::new(),
11682                rest: Some("xs".into()),
11683            },
11684            "parse_params dispatch drifted away from REST_MARKER — the \
11685             typed constant no longer routes to the rest-slot arm.",
11686        );
11687    }
11688
11689    #[test]
11690    fn parse_params_recognizes_optional_marker_via_typed_constant() {
11691        // PATH-UNIFORMITY PIN: peer to
11692        // `parse_params_recognizes_rest_marker_via_typed_constant` on
11693        // the `&optional` axis. The parser's optional-section dispatch
11694        // at `parse_params` MUST classify the typed constant
11695        // `MacroParams::OPTIONAL_MARKER` as the section-switch
11696        // boundary that reroutes subsequent bare-symbol names from the
11697        // `required` bin to the `optional` bin.
11698        let src = format!("a {} b c", MacroParams::OPTIONAL_MARKER);
11699        let params = parse_params(&read(&src).unwrap()).unwrap();
11700        assert_eq!(
11701            params,
11702            MacroParams {
11703                required: vec!["a".into()],
11704                optional: vec![OptionalParam::bare("b"), OptionalParam::bare("c")],
11705                rest: None,
11706            },
11707            "parse_params dispatch drifted away from OPTIONAL_MARKER — \
11708             the typed constant no longer routes to the optional- \
11709             section arm.",
11710        );
11711    }
11712
11713    // ── `MacroParams::LAMBDA_LIST_KEYWORDS` + `is_lambda_list_keyword` —
11714    // the closed-set forced-arity ALL array + membership gate that closes
11715    // the CL lambda-list-keyword family at the typed [`MacroParams`]
11716    // algebra. Sibling posture to the closed set of `pub const ALL: [Self;
11717    // N]` forced-arity arrays across the substrate's other closed-set
11718    // outer algebras (`AtomKind::ALL`, `QuoteForm::ALL`, `SexpShape::ALL`,
11719    // `UnquoteForm::ALL`, `MacroDefHead::ALL`); a future third marker
11720    // (`&key`, `&aux`, `&body`) extends the array's arity + adds ONE `pub
11721    // const` for the marker and every downstream family-wide contract
11722    // sweep (LEAD-byte round-trip, pairwise disjointness, membership
11723    // gate) picks up the extension at ONE structural site.
11724    //
11725    // These pins cover: (a) the cardinality contract (the ALL array's
11726    // length matches the pre-lift per-role marker count), (b) the
11727    // ordering contract (each ALL entry matches its corresponding per-
11728    // role `pub const` by-index), (c) the family-wide structural round-
11729    // trip (every ALL element starts_with LEAD), (d) the family-wide
11730    // pairwise disjointness (every distinct ALL index pair yields
11731    // distinct markers), (e) the membership-gate acceptance side (every
11732    // ALL element classifies as `true`), and (f) the membership-gate
11733    // rejection side (the bare LEAD byte + unrecognised `&`-prefixed
11734    // names classify as `false`).
11735    //
11736    // Fail-before/pass-after: every test below references
11737    // `MacroParams::LAMBDA_LIST_KEYWORDS` or
11738    // `MacroParams::is_lambda_list_keyword`, which simply did not exist
11739    // on `MacroParams` before this lift — every assertion was a
11740    // compile-time error against the prior surface.
11741
11742    #[test]
11743    fn macro_params_lambda_list_keywords_has_expected_cardinality() {
11744        // CARDINALITY PIN: the ALL array closes the family at exactly TWO
11745        // entries — the two `&'static str` markers the parser's typed
11746        // dispatch specialises on today (`&rest` + `&optional`). A future
11747        // third marker (`&key`, `&aux`, `&body`) extends this arity to
11748        // 3 AND every downstream family-wide contract sweep picks up the
11749        // extension at ONE structural site. A regression that drops a
11750        // marker from the ALL array (silently narrowing the family) OR
11751        // aliases two markers to the same index (silently narrowing the
11752        // typed dispatch) fails HERE at the cardinality contract before
11753        // the structural round-trip / pairwise-disjointness contracts
11754        // even fire.
11755        assert_eq!(
11756            MacroParams::LAMBDA_LIST_KEYWORDS.len(),
11757            2,
11758            "LAMBDA_LIST_KEYWORDS cardinality drifted from 2 — the CL \
11759             lambda-list-keyword family closure now names a different \
11760             number of markers than the two the parser's typed dispatch \
11761             specialises on.",
11762        );
11763    }
11764
11765    #[test]
11766    fn macro_params_lambda_list_keywords_binds_per_role_markers_by_index() {
11767        // ORDERING PIN: each ALL entry matches its corresponding per-role
11768        // `pub const` by-index. A regression that swaps the ordering
11769        // (`[OPTIONAL_MARKER, REST_MARKER]` after a rebase) would keep
11770        // the cardinality + membership contracts intact but silently
11771        // reorder downstream consumers that iterate `LAMBDA_LIST_KEYWORDS`
11772        // in canonical order (a future authoring surface that renders
11773        // "supported CL lambda-list keywords: &rest, &optional" would
11774        // silently render "supported CL lambda-list keywords: &optional,
11775        // &rest" instead). Pins the array's declaration-order binding
11776        // structurally so a reorder fails HERE at each element rather
11777        // than only at consumer sites downstream.
11778        assert_eq!(
11779            MacroParams::LAMBDA_LIST_KEYWORDS[0],
11780            MacroParams::REST_MARKER,
11781            "LAMBDA_LIST_KEYWORDS[0] drifted from REST_MARKER — the ALL \
11782             array's declaration-order binding to the per-role `pub \
11783             const` broke at the rest-slot marker slot.",
11784        );
11785        assert_eq!(
11786            MacroParams::LAMBDA_LIST_KEYWORDS[1],
11787            MacroParams::OPTIONAL_MARKER,
11788            "LAMBDA_LIST_KEYWORDS[1] drifted from OPTIONAL_MARKER — the \
11789             ALL array's declaration-order binding to the per-role \
11790             `pub const` broke at the optional-section marker slot.",
11791        );
11792    }
11793
11794    #[test]
11795    fn macro_params_every_lambda_list_keyword_prefixed_by_lambda_list_keyword_lead() {
11796        // FAMILY-WIDE STRUCTURAL ROUND-TRIP PIN: every element of the ALL
11797        // array MUST start with the canonical LEAD byte. Where the two
11798        // per-marker `_prefixed_by_lambda_list_keyword_lead` pins each
11799        // named ONE marker inline as a duplicate 3-line
11800        // `assert!(A.starts_with(LEAD), ...)` shape, this family-wide
11801        // sweep routes the SAME contract through the closed-set ALL
11802        // array — a future third marker (`&key`, `&aux`, `&body`)
11803        // extends the array AND is automatically covered by this sweep
11804        // without adding a third `_prefixed_by_...` per-marker pin. The
11805        // per-marker pins stay as fine-grained fail-loud sites; this
11806        // pin is the algebra-wide closure the family-extending refactor
11807        // routes through.
11808        for m in MacroParams::LAMBDA_LIST_KEYWORDS {
11809            assert!(
11810                m.starts_with(MacroParams::LAMBDA_LIST_KEYWORD_LEAD),
11811                "LAMBDA_LIST_KEYWORDS element `{m}` does NOT start with \
11812                 LAMBDA_LIST_KEYWORD_LEAD `{lead:?}` — the CL lambda- \
11813                 list-keyword family's structural round-trip contract \
11814                 no longer binds every marker to its shared LEAD byte.",
11815                lead = MacroParams::LAMBDA_LIST_KEYWORD_LEAD,
11816            );
11817        }
11818    }
11819
11820    #[test]
11821    fn macro_params_lambda_list_keywords_pairwise_distinct() {
11822        // FAMILY-WIDE PAIRWISE DISJOINTNESS PIN: every distinct index pair
11823        // `(i, j)` in the ALL array yields distinct markers. Where the
11824        // pre-lift `_rest_and_optional_markers_pairwise_disjoint` pin
11825        // named the TWO markers as a hand-rolled `assert_ne!` pair, this
11826        // family-wide sweep routes the SAME contract through the closed-
11827        // set ALL array so a future third marker automatically extends
11828        // the sweep to `(3 * 2) / 2 == 3` distinct pairs without re-
11829        // deriving per-marker `assert_ne!` calls. The pre-lift pin
11830        // stays as the fine-grained fail-loud site; this pin is the
11831        // family-wide closure the algebra-extending refactor routes
11832        // through.
11833        for (i, a) in MacroParams::LAMBDA_LIST_KEYWORDS.iter().enumerate() {
11834            for (j, b) in MacroParams::LAMBDA_LIST_KEYWORDS.iter().enumerate() {
11835                if i == j {
11836                    continue;
11837                }
11838                assert_ne!(
11839                    a, b,
11840                    "LAMBDA_LIST_KEYWORDS[{i}] `{a}` collides with \
11841                     LAMBDA_LIST_KEYWORDS[{j}] `{b}` — the CL lambda- \
11842                     list-keyword family's pairwise disjointness \
11843                     contract no longer binds distinct index pairs \
11844                     to distinct markers.",
11845                );
11846            }
11847        }
11848    }
11849
11850    #[test]
11851    fn macro_params_is_lambda_list_keyword_accepts_every_marker() {
11852        // ACCEPTANCE-SIDE MEMBERSHIP PIN: every element of the ALL array
11853        // MUST classify as `true` through the typed membership gate. The
11854        // gate is defined as `LAMBDA_LIST_KEYWORDS.contains(&s)`, so this
11855        // pin is the structural closure binding the gate's return value
11856        // to the ALL array's element set. A regression that specialises
11857        // the gate to a subset of the array (e.g. a `matches!(s,
11858        // "&rest")` inline that silently drops the `&optional` branch)
11859        // fails HERE at the ALL sweep. Sibling-shape acceptance sweep to
11860        // the closed-set `ClosedSet::parse_label` roundtrip pin — every
11861        // element of `Self::ALL` decodes through the projection back to
11862        // itself.
11863        for m in MacroParams::LAMBDA_LIST_KEYWORDS {
11864            assert!(
11865                MacroParams::is_lambda_list_keyword(m),
11866                "is_lambda_list_keyword rejected LAMBDA_LIST_KEYWORDS \
11867                 element `{m}` — the closed-set membership gate's \
11868                 acceptance side drifted from the ALL array.",
11869            );
11870        }
11871    }
11872
11873    #[test]
11874    fn macro_params_is_lambda_list_keyword_rejects_bare_lead_byte() {
11875        // REJECTION-SIDE MEMBERSHIP PIN (LEAD byte alone): the bare LEAD
11876        // byte `"&"` MUST classify as `false`. A future Clojure-compat
11877        // port might land a bare `&` marker — but until such an
11878        // extension explicitly lands on the ALL array, the substrate
11879        // MUST NOT silently classify the LEAD byte alone as a
11880        // recognised CL lambda-list keyword. Pins the gate's
11881        // rejection-side contract against a plausible future
11882        // near-miss extension.
11883        let bare_lead: String = MacroParams::LAMBDA_LIST_KEYWORD_LEAD.to_string();
11884        assert!(
11885            !MacroParams::is_lambda_list_keyword(&bare_lead),
11886            "is_lambda_list_keyword accepted the bare LEAD byte `{bare_lead}` — \
11887             the closed-set membership gate silently classifies the bare `&` \
11888             LEAD byte as a recognised CL lambda-list keyword despite the ALL \
11889             array containing only the two suffixed markers.",
11890        );
11891    }
11892
11893    #[test]
11894    fn macro_params_is_lambda_list_keyword_rejects_unrecognised_ampersand_prefixed_names() {
11895        // REJECTION-SIDE MEMBERSHIP PIN (unrecognised near-misses): the
11896        // three plausible future CL lambda-list keywords (`&key` for
11897        // keyword-args, `&aux` for auxiliaries, `&body` for the
11898        // docstring-carrying tail of `defmacro` bodies) MUST classify as
11899        // `false` UNTIL they explicitly land on the ALL array. Pins the
11900        // gate's conservative rejection contract — the closed-set
11901        // membership gate is not a "starts with `&`" heuristic; it is a
11902        // typed enumeration over the currently-recognised markers. A
11903        // regression that silently loosens the gate to a `starts_with`
11904        // check (dropping the closed-set discipline) fails HERE.
11905        for candidate in ["&key", "&aux", "&body"] {
11906            assert!(
11907                !MacroParams::is_lambda_list_keyword(candidate),
11908                "is_lambda_list_keyword accepted the unrecognised \
11909                 `&`-prefixed name `{candidate}` — the closed-set \
11910                 membership gate silently loosened its acceptance beyond \
11911                 the two markers the ALL array names today.",
11912            );
11913        }
11914    }
11915
11916    #[test]
11917    fn macro_params_is_lambda_list_keyword_rejects_bare_identifiers_and_empty_string() {
11918        // REJECTION-SIDE MEMBERSHIP PIN (bare identifiers + empty): a
11919        // bare identifier (`"a"`, `"xs"`, `"foo"`) and the empty string
11920        // MUST classify as `false`. These are the shapes the parser's
11921        // bare-symbol dispatch cascade sees at `parse_params` when
11922        // walking a param list, so pinning them here binds the
11923        // membership gate's rejection contract to the parser's most
11924        // common non-marker input shapes.
11925        for candidate in ["a", "xs", "foo", ""] {
11926            assert!(
11927                !MacroParams::is_lambda_list_keyword(candidate),
11928                "is_lambda_list_keyword accepted the bare non-marker \
11929                 input `{candidate}` — the closed-set membership gate's \
11930                 rejection side no longer excludes bare identifiers \
11931                 the parser routes through the fall-through cascade.",
11932            );
11933        }
11934    }
11935
11936    // ── fixed_arity: the rest-start / rest-less max-arity primitive ─────
11937    //
11938    // `fixed_arity()` lifts the `self.required.len() + self.optional.len()`
11939    // arithmetic that recurred three times inside `MacroParams::bind` — at
11940    // the `Vec::with_capacity` site (where it adds `usize::from(rest.is_some())`
11941    // to get the bound-values count), at the `rest_start` site (inside the
11942    // `if let Some(rest)` branch), and at the `expected` site (inside the
11943    // rest-less `else`). The latter two sites live in mutually-exclusive
11944    // branches yet name ONE structural concept; lifting them collapses the
11945    // arithmetic to one named primitive. These tests pin the primitive's
11946    // contract directly; the existing `bind_*` tests are the path-uniformity
11947    // guards proving `bind`'s sites route through the same value without
11948    // behavior drift.
11949    //
11950    // Fail-before/pass-after: every test below references
11951    // `params.fixed_arity()`, which simply did not exist on `MacroParams`
11952    // before this lift — every assertion's `expect: ___ == params.fixed_arity()`
11953    // line was a compile-time error against the prior surface.
11954
11955    #[test]
11956    fn fixed_arity_is_zero_for_the_empty_param_list() {
11957        // `()` — a nullary macro has fixed arity 0, the rest-less binder
11958        // boundary at which the FIRST surplus arg already rejects.
11959        let params = MacroParams::default();
11960        assert_eq!(params.fixed_arity(), 0);
11961    }
11962
11963    #[test]
11964    fn fixed_arity_counts_required_only_when_no_optional_or_rest() {
11965        // `(a b c)` — three required, no optional, no rest. fixed_arity is
11966        // exactly the required length.
11967        let params = MacroParams {
11968            required: vec!["a".into(), "b".into(), "c".into()],
11969            optional: Vec::new(),
11970            rest: None,
11971        };
11972        assert_eq!(params.fixed_arity(), 3);
11973    }
11974
11975    #[test]
11976    fn fixed_arity_counts_optional_only_when_no_required_or_rest() {
11977        // `(&optional x y)` — two optional, no required. fixed_arity is the
11978        // optional length; the optional section participates in the fixed
11979        // run because supplied positional args bind to it.
11980        let params = MacroParams {
11981            required: Vec::new(),
11982            optional: vec![OptionalParam::bare("x"), OptionalParam::bare("y")],
11983            rest: None,
11984        };
11985        assert_eq!(params.fixed_arity(), 2);
11986    }
11987
11988    #[test]
11989    fn fixed_arity_sums_required_and_optional_in_canonical_lambda_order() {
11990        // `(a b &optional c d e)` — two required + three optional, no rest.
11991        // fixed_arity is 5: the maximum arity a rest-less call can supply.
11992        let params = MacroParams {
11993            required: vec!["a".into(), "b".into()],
11994            optional: vec![
11995                OptionalParam::bare("c"),
11996                OptionalParam::bare("d"),
11997                OptionalParam::bare("e"),
11998            ],
11999            rest: None,
12000        };
12001        assert_eq!(params.fixed_arity(), 5);
12002    }
12003
12004    #[test]
12005    fn fixed_arity_ignores_rest_slot_by_construction() {
12006        // `(a &optional b &rest r)` and `(a &optional b)` — identical fixed
12007        // arity (2). The `&rest` slot has NO maximum and is structurally
12008        // excluded from `fixed_arity`. Naming this invariant pins that a
12009        // regression that drifts the primitive to "required + optional +
12010        // rest.is_some() as usize" fails loudly here — that drift would
12011        // collapse `fixed_arity` into `names().len()`, losing the rest-start
12012        // vs total-bound-values distinction the typed shape relies on.
12013        let with_rest = MacroParams {
12014            required: vec!["a".into()],
12015            optional: vec![OptionalParam::bare("b")],
12016            rest: Some("r".into()),
12017        };
12018        let without_rest = MacroParams {
12019            required: vec!["a".into()],
12020            optional: vec![OptionalParam::bare("b")],
12021            rest: None,
12022        };
12023        assert_eq!(with_rest.fixed_arity(), without_rest.fixed_arity());
12024        assert_eq!(with_rest.fixed_arity(), 2);
12025    }
12026
12027    #[test]
12028    fn fixed_arity_is_the_rest_start_index_in_names_when_rest_present() {
12029        // When `rest` is `Some`, `names()[fixed_arity()]` IS the rest name
12030        // — the rest-start reading of the primitive. Same arithmetic the
12031        // bytecode index would hit (`Subst(fixed_arity())` resolves to the
12032        // rest-bound `Sexp::List`).
12033        let params = MacroParams {
12034            required: vec!["a".into(), "b".into()],
12035            optional: vec![OptionalParam::bare("c")],
12036            rest: Some("r".into()),
12037        };
12038        assert_eq!(params.fixed_arity(), 3);
12039        assert_eq!(params.names()[params.fixed_arity()], "r");
12040    }
12041
12042    #[test]
12043    fn fixed_arity_equals_names_length_when_rest_is_absent() {
12044        // When `rest` is `None`, `names().len() == fixed_arity()` — there
12045        // is no rest-name slot to extend the flat run past the fixed
12046        // boundary. Pins the structural identity
12047        // `names().len() == fixed_arity() + usize::from(rest.is_some())`
12048        // for the rest-less case; the rest-present case is pinned by the
12049        // sibling test above (where the boundary is the rest-name index,
12050        // i.e. one short of `names().len()`).
12051        let params = MacroParams {
12052            required: vec!["a".into(), "b".into()],
12053            optional: vec![OptionalParam::bare("c")],
12054            rest: None,
12055        };
12056        assert_eq!(params.names().len(), params.fixed_arity());
12057        assert_eq!(params.names().len(), 3);
12058    }
12059
12060    #[test]
12061    fn fixed_arity_is_the_rest_less_surplus_rejection_boundary() {
12062        // The `expected` field of `TooManyMacroArgs` IS `fixed_arity()` —
12063        // the rest-less binder rejects iff `args.len() > fixed_arity()`.
12064        // This pin is the path-uniformity guard binding the typed primitive
12065        // to the binder's rejection contract: a regression that drifts
12066        // `bind`'s `expected` arithmetic from `fixed_arity()` would silently
12067        // surface a different boundary in the diagnostic without touching
12068        // the primitive — and this assertion fails loudly. Mirror of the
12069        // sibling rest-less surplus pin (`bind_rest_less_params_reject_
12070        // surplus_args`); this test pins WHAT the `expected` slot's value
12071        // structurally IS, that pin checks the variant SHAPE.
12072        let params = MacroParams {
12073            required: vec!["a".into(), "b".into()],
12074            optional: vec![OptionalParam::bare("c")],
12075            rest: None,
12076        };
12077        assert_eq!(params.fixed_arity(), 3);
12078        let err = params
12079            .bind(
12080                "m",
12081                &[Sexp::int(1), Sexp::int(2), Sexp::int(3), Sexp::int(4)],
12082            )
12083            .expect_err("4 args against fixed_arity 3 must reject");
12084        match err {
12085            LispError::TooManyMacroArgs {
12086                expected,
12087                got,
12088                macro_name,
12089            } => {
12090                assert_eq!(expected, params.fixed_arity());
12091                assert_eq!(got, 4);
12092                assert_eq!(macro_name, "m");
12093            }
12094            other => panic!("expected TooManyMacroArgs, got {other:?}"),
12095        }
12096    }
12097
12098    #[test]
12099    fn fixed_arity_is_the_rest_start_index_consumed_by_bind() {
12100        // When `rest` is `Some`, `bind` collects `args[fixed_arity()..]`
12101        // into the rest's `Sexp::List`. Pin that the rest list contents
12102        // are exactly the suffix beginning at `fixed_arity()` — the
12103        // primitive's rest-start reading IS the slice index the binder
12104        // consumes. A regression that drifts `bind`'s rest-collection
12105        // slice from `fixed_arity()` would surface as a misaligned rest
12106        // list (off-by-one in either direction) and this assertion fails
12107        // loudly. Sibling of `fixed_arity_is_the_rest_less_surplus_
12108        // rejection_boundary` on the rest-PRESENT branch.
12109        let params = MacroParams {
12110            required: vec!["a".into()],
12111            optional: vec![OptionalParam::bare("b")],
12112            rest: Some("r".into()),
12113        };
12114        assert_eq!(params.fixed_arity(), 2);
12115        let args = [Sexp::int(1), Sexp::int(2), Sexp::int(3), Sexp::int(4)];
12116        let vals = params.bind("m", &args).unwrap();
12117        // Bound vec: [a=1, b=2, r=(3 4)] — the rest list IS args[fixed_arity()..].
12118        let rest_expected: Vec<Sexp> = args[params.fixed_arity()..].to_vec();
12119        assert_eq!(vals.last().unwrap(), &Sexp::List(rest_expected));
12120    }
12121
12122    #[test]
12123    fn bind_rest_present_at_exact_fixed_arity_yields_empty_rest_list() {
12124        // Exactly-saturated rest-present call: `args.len() == fixed_arity()`.
12125        // The rest slot collects the empty slice; bind succeeds, never
12126        // misaligning to an off-by-one underflow. Pin the boundary on the
12127        // rest-PRESENT path — the rest-less mirror is
12128        // `too_many_macro_args_does_not_fire_at_exact_max_arity` above.
12129        let params = MacroParams {
12130            required: vec!["a".into()],
12131            optional: vec![OptionalParam::bare("b")],
12132            rest: Some("r".into()),
12133        };
12134        assert_eq!(params.fixed_arity(), 2);
12135        let vals = params
12136            .bind("m", &[Sexp::int(1), Sexp::int(2)])
12137            .expect("rest-present at exact fixed_arity must bind cleanly");
12138        assert_eq!(vals, vec![Sexp::int(1), Sexp::int(2), Sexp::List(vec![])]);
12139    }
12140
12141    #[test]
12142    fn bind_threads_required_positionally_and_collects_rest_as_list() {
12143        // `(a b &rest c)` bound to `1 2 3 4`: a=1, b=2, c=(3 4). The bound
12144        // vec is parallel to `names()`, so the rest list sits at the rest's
12145        // flat index.
12146        let params = MacroParams {
12147            required: vec!["a".into(), "b".into()],
12148            optional: Vec::new(),
12149            rest: Some("c".into()),
12150        };
12151        let vals = params
12152            .bind(
12153                "m",
12154                &[Sexp::int(1), Sexp::int(2), Sexp::int(3), Sexp::int(4)],
12155            )
12156            .unwrap();
12157        assert_eq!(
12158            vals,
12159            vec![
12160                Sexp::int(1),
12161                Sexp::int(2),
12162                Sexp::List(vec![Sexp::int(3), Sexp::int(4)]),
12163            ]
12164        );
12165    }
12166
12167    #[test]
12168    fn bind_supplied_optional_takes_its_positional_arg() {
12169        // `(a &optional b)` bound to `1 2`: a=1, b=2. A supplied optional
12170        // behaves exactly like a positional — only its ABSENCE differs.
12171        let params = MacroParams {
12172            required: vec!["a".into()],
12173            optional: vec![OptionalParam::bare("b")],
12174            rest: None,
12175        };
12176        let vals = params.bind("m", &[Sexp::int(1), Sexp::int(2)]).unwrap();
12177        assert_eq!(vals, vec![Sexp::int(1), Sexp::int(2)]);
12178    }
12179
12180    #[test]
12181    fn bind_unsupplied_optional_defaults_to_nil() {
12182        // `(a &optional b c)` bound to just `1`: a=1, then b and c run out of
12183        // args and bind to `Sexp::Nil` — CL's default for an `&optional` with
12184        // no supplied default-form. The bound vec is still parallel to
12185        // `names()`, so the template's `,b` / `,c` resolve to nil, not a
12186        // missing-arg error.
12187        let params = MacroParams {
12188            required: vec!["a".into()],
12189            optional: vec![OptionalParam::bare("b"), OptionalParam::bare("c")],
12190            rest: None,
12191        };
12192        let vals = params.bind("m", &[Sexp::int(1)]).unwrap();
12193        assert_eq!(vals, vec![Sexp::int(1), Sexp::Nil, Sexp::Nil]);
12194    }
12195
12196    #[test]
12197    fn bind_rest_collects_args_beyond_required_and_optional() {
12198        // `(a &optional b &rest c)` bound to `1 2 3 4`: a=1, b=2 (supplied),
12199        // c=(3 4). The rest starts AFTER the required+optional run, so the
12200        // optional's supplied arg is not swept into the rest.
12201        let params = MacroParams {
12202            required: vec!["a".into()],
12203            optional: vec![OptionalParam::bare("b")],
12204            rest: Some("c".into()),
12205        };
12206        let vals = params
12207            .bind(
12208                "m",
12209                &[Sexp::int(1), Sexp::int(2), Sexp::int(3), Sexp::int(4)],
12210            )
12211            .unwrap();
12212        assert_eq!(
12213            vals,
12214            vec![
12215                Sexp::int(1),
12216                Sexp::int(2),
12217                Sexp::List(vec![Sexp::int(3), Sexp::int(4)]),
12218            ]
12219        );
12220    }
12221
12222    #[test]
12223    fn bind_unsupplied_optional_then_empty_rest() {
12224        // `(a &optional b &rest c)` bound to just `1`: a=1, b=nil (absent),
12225        // c=() (nothing left). Both the optional default AND the empty-rest
12226        // contract hold in the same bind.
12227        let params = MacroParams {
12228            required: vec!["a".into()],
12229            optional: vec![OptionalParam::bare("b")],
12230            rest: Some("c".into()),
12231        };
12232        let vals = params.bind("m", &[Sexp::int(1)]).unwrap();
12233        assert_eq!(vals, vec![Sexp::int(1), Sexp::Nil, Sexp::List(vec![])]);
12234    }
12235
12236    #[test]
12237    fn bind_rest_with_no_remaining_args_is_the_empty_list() {
12238        // Exactly-saturated required args + a rest that captures nothing →
12239        // the rest binds to the empty list, never errors. Mirrors the
12240        // splice contract `,@()` contributes nothing.
12241        let params = MacroParams {
12242            required: vec!["a".into()],
12243            optional: Vec::new(),
12244            rest: Some("c".into()),
12245        };
12246        let vals = params.bind("m", &[Sexp::int(1)]).unwrap();
12247        assert_eq!(vals, vec![Sexp::int(1), Sexp::List(vec![])]);
12248    }
12249
12250    #[test]
12251    fn bind_missing_required_errors_before_any_rest_collection() {
12252        // A required name with no arg at its position is a
12253        // `MissingMacroArg` — the gate fires during the required walk,
12254        // before the rest is ever collected.
12255        let params = MacroParams {
12256            required: vec!["a".into(), "b".into()],
12257            optional: Vec::new(),
12258            rest: Some("c".into()),
12259        };
12260        let err = params
12261            .bind("m", &[Sexp::int(1)])
12262            .expect_err("missing required `b` must error");
12263        assert!(
12264            matches!(err, LispError::MissingMacroArg { .. }),
12265            "expected MissingMacroArg, got: {err:?}"
12266        );
12267    }
12268
12269    #[test]
12270    fn bind_missing_required_errors_even_with_optional_present() {
12271        // An absent REQUIRED arg errors even when the param list has an
12272        // optional section: the required walk fires `MissingMacroArg` before
12273        // the optional arm (which would otherwise default to nil) is reached.
12274        // Required absence is an error; optional absence is a nil default.
12275        let params = MacroParams {
12276            required: vec!["a".into(), "b".into()],
12277            optional: vec![OptionalParam::bare("c")],
12278            rest: None,
12279        };
12280        let err = params
12281            .bind("m", &[Sexp::int(1)])
12282            .expect_err("missing required `b` must error before optional defaulting");
12283        assert!(
12284            matches!(err, LispError::MissingMacroArg { .. }),
12285            "expected MissingMacroArg, got: {err:?}"
12286        );
12287    }
12288
12289    #[test]
12290    fn bind_rest_less_params_reject_surplus_args() {
12291        // The rest-less binder REJECTS surplus call args via the
12292        // structural `TooManyMacroArgs { macro_name, expected, got }`
12293        // rejection — the call-site mirror of `RestParamTrailingTokens`
12294        // (the definition-site rejection lifted at the parse_params
12295        // boundary). Closes the asymmetry where the typed-entry
12296        // macro-call-gate rejected too-few-args loudly
12297        // (`MissingMacroArg`) but silently truncated too-many. `expected`
12298        // is the rest-less binder's fixed maximum arity
12299        // (`required.len() + optional.len()`); `got` is the actual
12300        // call-site arg count.
12301        let params = MacroParams {
12302            required: vec!["a".into()],
12303            optional: Vec::new(),
12304            rest: None,
12305        };
12306        let err = params
12307            .bind("m", &[Sexp::int(1), Sexp::int(2)])
12308            .expect_err("rest-less surplus must error");
12309        match err {
12310            LispError::TooManyMacroArgs {
12311                macro_name,
12312                expected,
12313                got,
12314            } => {
12315                assert_eq!(macro_name, "m");
12316                assert_eq!(expected, 1);
12317                assert_eq!(got, 2);
12318            }
12319            other => panic!("expected TooManyMacroArgs, got: {other:?}"),
12320        }
12321    }
12322
12323    // ── OptionalParam: per-param default forms — `&optional (x DEFAULT)` ──
12324    //
12325    // The `&optional` section now admits both bare-symbol entries (`x`) AND
12326    // list-form entries (`(x DEFAULT)`). The typed `OptionalParam.default:
12327    // Option<Sexp>` slot makes the per-param default a FIELD on each
12328    // optional entry, not a discipline a sibling `Vec<Sexp>` would have had
12329    // to maintain in lock-step with `Vec<String>`. These tests pin: the
12330    // parser admits both shapes side-by-side; the four malformed list-spec
12331    // shapes (empty / missing-default / extra-elements / non-symbol-name)
12332    // are rejected via `OptionalParamMalformed` with the typed
12333    // `OptionalParamMalformedReason`; the binder consults the default form
12334    // when the arg is absent and ignores it when supplied; and the end-to-
12335    // end expansion agrees between the bytecode and substitute strategies
12336    // (invariant 2 — free middle).
12337
12338    #[test]
12339    fn parse_params_admits_optional_list_spec_with_default() {
12340        // `(a &optional (b 5))` — one bare optional becomes
12341        // `OptionalParam { name: "b", default: Some(Int(5)) }`. The
12342        // surrounding `MacroParams` shape is otherwise identical.
12343        let params = parse_params(&read("a &optional (b 5)").unwrap()).unwrap();
12344        assert_eq!(
12345            params,
12346            MacroParams {
12347                required: vec!["a".into()],
12348                optional: vec![OptionalParam::with_default("b", Sexp::int(5))],
12349                rest: None,
12350            }
12351        );
12352    }
12353
12354    #[test]
12355    fn parse_params_mixes_bare_and_list_optional_specs_side_by_side() {
12356        // `(a &optional b (c "x") d (e 9) &rest r)` — the optional section
12357        // interleaves bare and list-form specs. Each lands in its own
12358        // `OptionalParam` entry; `names()` still yields the flat
12359        // required-then-optional-then-rest order.
12360        let params =
12361            parse_params(&read("a &optional b (c \"x\") d (e 9) &rest r").unwrap()).unwrap();
12362        assert_eq!(
12363            params,
12364            MacroParams {
12365                required: vec!["a".into()],
12366                optional: vec![
12367                    OptionalParam::bare("b"),
12368                    OptionalParam::with_default("c", Sexp::string("x")),
12369                    OptionalParam::bare("d"),
12370                    OptionalParam::with_default("e", Sexp::int(9)),
12371                ],
12372                rest: Some("r".into()),
12373            }
12374        );
12375        assert_eq!(params.names(), vec!["a", "b", "c", "d", "e", "r"]);
12376    }
12377
12378    #[test]
12379    fn parse_params_admits_arbitrary_sexp_as_optional_default_form() {
12380        // `(&optional (x (list 1 2)))` — the default form is itself a list.
12381        // Without an evaluator, the literal Sexp is parked verbatim into
12382        // `default`; the binder produces it for any absent call.
12383        let params = parse_params(&read("&optional (x (list 1 2))").unwrap()).unwrap();
12384        let want_default = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(1), Sexp::int(2)]);
12385        assert_eq!(
12386            params,
12387            MacroParams {
12388                required: Vec::new(),
12389                optional: vec![OptionalParam::with_default("x", want_default)],
12390                rest: None,
12391            }
12392        );
12393    }
12394
12395    #[test]
12396    fn parse_params_rejects_empty_list_optional_spec() {
12397        // `(&optional ())` — a zero-element list is the empty-list rejection.
12398        // Without the gate the loop would `as_symbol()` on a `Sexp::List` and
12399        // fall through to `NonSymbolParam`, which mis-classifies the failure
12400        // (this is a malformed DEFAULT-FORM spec, not a "param must be a
12401        // symbol" rejection).
12402        let err = parse_params(&read("&optional ()").unwrap())
12403            .expect_err("empty list optional spec must error");
12404        assert!(
12405            matches!(
12406                err,
12407                LispError::OptionalParamMalformed {
12408                    position: 1,
12409                    reason: crate::error::OptionalParamMalformedReason::EmptyList,
12410                    ..
12411                }
12412            ),
12413            "expected OptionalParamMalformed{{EmptyList, position: 1}}, got: {err:?}"
12414        );
12415    }
12416
12417    #[test]
12418    fn parse_params_rejects_one_element_optional_list_as_missing_default() {
12419        // `(&optional (x))` — a one-element list. REJECTED with reason
12420        // `MissingDefault` rather than reinterpreted as `&optional x`, because
12421        // a parenthesized single-element spec is structurally ambiguous and
12422        // the bare-symbol form `x` IS the canonical "no default" shape.
12423        let err = parse_params(&read("&optional (x)").unwrap())
12424            .expect_err("one-element list optional spec must error");
12425        assert!(
12426            matches!(
12427                err,
12428                LispError::OptionalParamMalformed {
12429                    position: 1,
12430                    reason: crate::error::OptionalParamMalformedReason::MissingDefault,
12431                    ..
12432                }
12433            ),
12434            "expected OptionalParamMalformed{{MissingDefault, position: 1}}, got: {err:?}"
12435        );
12436    }
12437
12438    #[test]
12439    fn parse_params_rejects_three_or_more_element_optional_list_as_extra_elements() {
12440        // `(&optional (x 5 6))` — a three-element list. CL's `(name default
12441        // supplied-p)` shape is not yet supported (no evaluator → no
12442        // supplied-p variable binding), so the third element is structurally
12443        // surplus. REJECTED with reason `ExtraElements{length: 3}`.
12444        let err = parse_params(&read("&optional (x 5 6)").unwrap())
12445            .expect_err("three-element list optional spec must error");
12446        assert!(
12447            matches!(
12448                err,
12449                LispError::OptionalParamMalformed {
12450                    position: 1,
12451                    reason: crate::error::OptionalParamMalformedReason::ExtraElements { length: 3 },
12452                    ..
12453                }
12454            ),
12455            "expected OptionalParamMalformed{{ExtraElements{{3}}, position: 1}}, got: {err:?}"
12456        );
12457    }
12458
12459    #[test]
12460    fn parse_params_rejects_non_symbol_name_in_optional_list_spec() {
12461        // `(&optional (5 default))` — the name slot must be a symbol; a
12462        // numeric literal is REJECTED with reason `NonSymbolName`. Without
12463        // this branch the gate would silently populate
12464        // `OptionalParam.name` from a stringified non-symbol value (`"5"`),
12465        // breaking the invariant that param names are symbols.
12466        let err = parse_params(&read("&optional (5 default)").unwrap())
12467            .expect_err("non-symbol-name optional spec must error");
12468        assert!(
12469            matches!(
12470                err,
12471                LispError::OptionalParamMalformed {
12472                    position: 1,
12473                    reason: crate::error::OptionalParamMalformedReason::NonSymbolName,
12474                    ..
12475                }
12476            ),
12477            "expected OptionalParamMalformed{{NonSymbolName, position: 1}}, got: {err:?}"
12478        );
12479    }
12480
12481    #[test]
12482    fn parse_params_rejects_list_in_required_section_as_non_symbol_param() {
12483        // `((a 5))` — a list in the REQUIRED section is NOT a default-form
12484        // spec; default forms are an optional-section affordance. The gate
12485        // must fall through to `NonSymbolParam` (parity with the prior
12486        // behavior on lists in the required section), not silently admit
12487        // the list as a default-form spec.
12488        let err =
12489            parse_params(&read("(a 5)").unwrap()).expect_err("list in required section must error");
12490        assert!(
12491            matches!(err, LispError::NonSymbolParam { position: 0, .. }),
12492            "expected NonSymbolParam{{position: 0}}, got: {err:?}"
12493        );
12494    }
12495
12496    #[test]
12497    fn bind_unsupplied_optional_with_default_takes_the_default() {
12498        // `(a &optional (b 5))` bound to just `1`: a=1, b=5 (the declared
12499        // default), not nil. The default form is consulted ONLY when the
12500        // call ran out of args.
12501        let params = MacroParams {
12502            required: vec!["a".into()],
12503            optional: vec![OptionalParam::with_default("b", Sexp::int(5))],
12504            rest: None,
12505        };
12506        let vals = params.bind("m", &[Sexp::int(1)]).unwrap();
12507        assert_eq!(vals, vec![Sexp::int(1), Sexp::int(5)]);
12508    }
12509
12510    #[test]
12511    fn bind_supplied_optional_with_default_takes_the_arg_not_the_default() {
12512        // `(&optional (b 5))` bound to `42`: b=42, NOT the default. A
12513        // supplied optional ALWAYS takes its arg; the default is the
12514        // absence-only fallback. Pins that the default form does not
12515        // shadow a supplied call arg.
12516        let params = MacroParams {
12517            required: Vec::new(),
12518            optional: vec![OptionalParam::with_default("b", Sexp::int(5))],
12519            rest: None,
12520        };
12521        let vals = params.bind("m", &[Sexp::int(42)]).unwrap();
12522        assert_eq!(vals, vec![Sexp::int(42)]);
12523    }
12524
12525    #[test]
12526    fn bind_mixes_supplied_unsupplied_default_and_nil_floor() {
12527        // `(a &optional (b 5) c (d "z"))` bound to just `1`: a=1, b=5
12528        // (default), c=nil (bare floor), d="z" (default). The three
12529        // absence cases coexist in one bind: per-default fill, nil floor,
12530        // and a tail with a literal-string default.
12531        let params = MacroParams {
12532            required: vec!["a".into()],
12533            optional: vec![
12534                OptionalParam::with_default("b", Sexp::int(5)),
12535                OptionalParam::bare("c"),
12536                OptionalParam::with_default("d", Sexp::string("z")),
12537            ],
12538            rest: None,
12539        };
12540        let vals = params.bind("m", &[Sexp::int(1)]).unwrap();
12541        assert_eq!(
12542            vals,
12543            vec![Sexp::int(1), Sexp::int(5), Sexp::Nil, Sexp::string("z")]
12544        );
12545    }
12546
12547    // ── OptionalParam::resolved_default: the absent-call binder accessor ──
12548    //
12549    // `resolved_default` lifts the `param.default.clone().unwrap_or(Sexp::Nil)`
12550    // two-arm fallback that previously inlined at `MacroParams::bind`'s
12551    // optional arm into ONE named accessor on the typed `OptionalParam`.
12552    // The constructor pair `bare` / `with_default` defines the typed
12553    // shapes of the `default` slot; this accessor names the symmetric
12554    // bound-value projection both shapes yield at the absence boundary.
12555    // Tests pin: (a) `bare(name).resolved_default()` is the `Sexp::Nil`
12556    // no-default floor; (b) `with_default(name, d).resolved_default()` is
12557    // `d.clone()`; (c) the projection is `Clone`-stable across repeated
12558    // calls (the typed `default` field is not consumed); (d) path-
12559    // uniformity at the binder — `bind`'s optional arm routes through
12560    // `resolved_default` for both shapes; (e) end-to-end through both
12561    // expansion strategies, the absent-call binding agrees.
12562
12563    #[test]
12564    fn resolved_default_is_nil_for_bare_optional() {
12565        // `OptionalParam::bare(name).default` is `None`, so
12566        // `resolved_default()` projects to `Sexp::Nil` — the CL
12567        // `&optional` no-default-form floor. Fail-before/pass-after: this
12568        // assert is meaningless pre-lift because the helper does not
12569        // exist; post-lift it pins the typed accessor's `Sexp::Nil` arm
12570        // at the named primitive. Sibling of `bare` itself: the
12571        // constructor defines the shape (`default: None`); the accessor
12572        // names the bound-value projection of that shape.
12573        let p = OptionalParam::bare("x");
12574        assert_eq!(p.resolved_default(), Sexp::Nil);
12575    }
12576
12577    #[test]
12578    fn resolved_default_clones_declared_default_for_with_default_optional() {
12579        // `OptionalParam::with_default(name, d).default` is `Some(d)`, so
12580        // `resolved_default()` projects to `d.clone()` — the declared
12581        // default form. Sibling of the bare-floor pin: the closed-set
12582        // `default: Option<Sexp>` slot's two shapes correspond 1:1 with
12583        // the two arms of `resolved_default`. Pins the closed-set
12584        // exhaustive coverage of `Option<Sexp>` × `{Some, None}`.
12585        let p = OptionalParam::with_default("x", Sexp::int(5));
12586        assert_eq!(p.resolved_default(), Sexp::int(5));
12587    }
12588
12589    #[test]
12590    fn resolved_default_clones_arbitrary_sexp_default_form() {
12591        // The declared default can be any `Sexp` — a literal list, a
12592        // keyword, a string, a quasi-quoted form — because v0 has no
12593        // evaluator and the typed slot parks the literal verbatim. Pin
12594        // that `resolved_default()` is faithful to the parked literal
12595        // regardless of shape: a regression that special-cases an arm
12596        // (e.g., projecting `Sexp::List(_)` to `Sexp::Nil`, or "normalizing"
12597        // a `Sexp::Quote`) fails here. The accessor is exactly
12598        // `default.clone()` for the `Some` arm — no shape rewriting.
12599        let arbitrary = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(1), Sexp::int(2)]);
12600        let p = OptionalParam::with_default("x", arbitrary.clone());
12601        assert_eq!(p.resolved_default(), arbitrary);
12602    }
12603
12604    #[test]
12605    fn resolved_default_is_clone_stable_across_repeated_calls() {
12606        // The accessor takes `&self` and projects through `Clone`, so
12607        // repeated calls yield IDENTICAL values — the typed `default`
12608        // field is not consumed. Pins that the accessor is idempotent
12609        // for the same `OptionalParam`, which is the contract the binder
12610        // relies on across multiple `bind` invocations of the same
12611        // macro: every call that leaves the optional unfilled yields
12612        // the SAME bound value, never a partially-consumed shape. A
12613        // regression that converted the accessor to `self.default.take()`
12614        // (consuming the field) would still type-check at the call site
12615        // but would silently desync repeated absent-call bindings; this
12616        // test catches that drift.
12617        let p = OptionalParam::with_default("x", Sexp::string("hi"));
12618        let first = p.resolved_default();
12619        let second = p.resolved_default();
12620        assert_eq!(first, second);
12621        assert_eq!(first, Sexp::string("hi"));
12622    }
12623
12624    #[test]
12625    fn resolved_default_is_the_binders_absent_optional_projection() {
12626        // Path-uniformity pin at the binder boundary: `MacroParams::bind`'s
12627        // optional arm consults `param.resolved_default()` for any
12628        // absent slot. Two-arm coverage: a bare optional (`b`) binds to
12629        // `Sexp::Nil` via the `None` arm; a with-default optional
12630        // (`(c 5)`) binds to `Sexp::int(5)` via the `Some` arm. The
12631        // single bind call exercises both arms in one walk, and the
12632        // bound values vec is parallel to `names()` so position
12633        // checking pins the arm-to-slot mapping. A regression that
12634        // re-inlines the two-arm fallback at the binder, drifting it
12635        // independently from the accessor, would still type-check but
12636        // a future shape change to `resolved_default` (e.g., adding a
12637        // typed `&supplied-p` companion slot) would silently desync
12638        // the binder from the accessor — this test catches that drift.
12639        let params = MacroParams {
12640            required: Vec::new(),
12641            optional: vec![
12642                OptionalParam::bare("b"),
12643                OptionalParam::with_default("c", Sexp::int(5)),
12644            ],
12645            rest: None,
12646        };
12647        // Empty args → both optionals are absent → both arms fire.
12648        let vals = params.bind("m", &[]).unwrap();
12649        assert_eq!(vals.len(), 2);
12650        assert_eq!(vals[0], OptionalParam::bare("b").resolved_default());
12651        assert_eq!(
12652            vals[1],
12653            OptionalParam::with_default("c", Sexp::int(5)).resolved_default()
12654        );
12655        // And the absolute identities pin the projection's arms:
12656        assert_eq!(vals[0], Sexp::Nil);
12657        assert_eq!(vals[1], Sexp::int(5));
12658    }
12659
12660    #[test]
12661    fn resolved_default_is_path_uniform_across_bytecode_and_substitute() {
12662        // End-to-end path-uniformity: a macro with both an `&optional
12663        // (g "hi")` (with-default) and an `&optional h` (bare) param
12664        // expands the same way under bytecode AND substitute
12665        // strategies, because both strategies route through the SHARED
12666        // `MacroParams::bind` which now consults `resolved_default`
12667        // for absent slots. Pins that the accessor's contract is
12668        // structurally observable at the strategy boundary — a
12669        // regression that bifurcated the accessor's behavior between
12670        // the two paths (impossible, since they share `bind`) would
12671        // surface here.
12672        let src = r#"
12673            (defmacro greet (n &optional (g "hi") h)
12674              `(list ,g ,n ,h))
12675            (greet world)
12676        "#;
12677        let expected = vec![Sexp::List(vec![
12678            Sexp::symbol("list"),
12679            Sexp::string("hi"),
12680            Sexp::symbol("world"),
12681            Sexp::Nil,
12682        ])];
12683        let bytecode = Expander::new().expand_program(read(src).unwrap()).unwrap();
12684        let substitute = Expander::new_substitute_only()
12685            .expand_program(read(src).unwrap())
12686            .unwrap();
12687        assert_eq!(
12688            bytecode, expected,
12689            "bytecode resolved_default expansion drifted"
12690        );
12691        assert_eq!(
12692            substitute, expected,
12693            "substitute resolved_default expansion drifted"
12694        );
12695        assert_eq!(
12696            bytecode, substitute,
12697            "the two strategies disagree on resolved_default expansion"
12698        );
12699    }
12700
12701    #[test]
12702    fn resolved_default_supplied_optional_does_not_consult_accessor() {
12703        // A SUPPLIED optional binds to its CALL ARG, never to the
12704        // accessor's projection. Pins the contract: `resolved_default`
12705        // is the absence-only fallback; a present arg shadows the
12706        // accessor at the binder. Sibling negative control to
12707        // `resolved_default_is_the_binders_absent_optional_projection`:
12708        // that test exercises the absence arm at every slot; this test
12709        // exercises the presence arm at every slot and proves the
12710        // accessor's `Sexp::int(5)` projection is NOT consulted when
12711        // the optional is supplied with `Sexp::int(42)`. A regression
12712        // that wired the binder to always consult the accessor (the
12713        // wrong direction — the default would shadow supplied args) is
12714        // caught here.
12715        let params = MacroParams {
12716            required: Vec::new(),
12717            optional: vec![OptionalParam::with_default("b", Sexp::int(5))],
12718            rest: None,
12719        };
12720        let vals = params.bind("m", &[Sexp::int(42)]).unwrap();
12721        assert_eq!(vals, vec![Sexp::int(42)]);
12722        // And the accessor's would-be projection IS NOT the bound value:
12723        let p = OptionalParam::with_default("b", Sexp::int(5));
12724        assert_ne!(vals[0], p.resolved_default());
12725    }
12726
12727    #[test]
12728    fn optional_default_macro_expands_end_to_end_under_both_strategies() {
12729        // The end-to-end path: a macro with `&optional (g "hi")` expands to
12730        // the default literal when unsupplied, and to the supplied arg when
12731        // present. Both the bytecode and substitute strategies must agree
12732        // (invariant 2 — free middle); they share `MacroParams::bind`, so
12733        // the default arm lands once in `bind` and both strategies inherit
12734        // it unable to drift. This is the test the prior run (611a682)
12735        // signposted as the next-change-that-benefits.
12736        let src = r#"
12737            (defmacro greet (n &optional (g "hi"))
12738              `(list ,g ,n))
12739            (greet world)
12740            (greet world there)
12741        "#;
12742        let expected = vec![
12743            Sexp::List(vec![
12744                Sexp::symbol("list"),
12745                Sexp::string("hi"),
12746                Sexp::symbol("world"),
12747            ]),
12748            Sexp::List(vec![
12749                Sexp::symbol("list"),
12750                Sexp::symbol("there"),
12751                Sexp::symbol("world"),
12752            ]),
12753        ];
12754        let bytecode = Expander::new().expand_program(read(src).unwrap()).unwrap();
12755        let substitute = Expander::new_substitute_only()
12756            .expand_program(read(src).unwrap())
12757            .unwrap();
12758        assert_eq!(
12759            bytecode, expected,
12760            "bytecode optional-default expansion drifted"
12761        );
12762        assert_eq!(
12763            substitute, expected,
12764            "substitute optional-default expansion drifted"
12765        );
12766        assert_eq!(
12767            bytecode, substitute,
12768            "the two strategies disagree on optional-default expansion"
12769        );
12770    }
12771
12772    #[test]
12773    fn optional_macro_expands_end_to_end_under_both_strategies() {
12774        // The end-to-end path: a macro with an `&optional` param expands
12775        // correctly whether the optional is supplied or defaulted, and the
12776        // bytecode and substitute strategies agree (invariant 2 — free
12777        // middle). `,b` resolves to the supplied arg when present, to
12778        // `Sexp::Nil` when absent (CL's `&optional` default).
12779        let src = "(defmacro pair (a &optional b) `(cons ,a ,b)) (pair 1 2) (pair 3)";
12780        // (cons 1 2) — optional supplied; (cons 3 <Nil>) — optional defaulted.
12781        // The defaulted slot is the canonical `Sexp::Nil`, distinct in the AST
12782        // from a reader-produced empty list `()` even though both Display as
12783        // `()`.
12784        let expected = vec![
12785            Sexp::List(vec![Sexp::symbol("cons"), Sexp::int(1), Sexp::int(2)]),
12786            Sexp::List(vec![Sexp::symbol("cons"), Sexp::int(3), Sexp::Nil]),
12787        ];
12788        let bytecode = Expander::new().expand_program(read(src).unwrap()).unwrap();
12789        let substitute = Expander::new_substitute_only()
12790            .expand_program(read(src).unwrap())
12791            .unwrap();
12792        assert_eq!(bytecode, expected, "bytecode optional expansion drifted");
12793        assert_eq!(
12794            substitute, expected,
12795            "substitute optional expansion drifted"
12796        );
12797        assert_eq!(
12798            bytecode, substitute,
12799            "the two strategies disagree on optional expansion"
12800        );
12801    }
12802
12803    // ── MacroDef::template_body: the shared body-projection primitive ──
12804    //
12805    // `template_body` lifts the `match &def.body { Sexp::Quasiquote(inner)
12806    // => inner.as_ref(), other => other }` inline peel — present
12807    // byte-identically at the bytecode (`compile_template`) AND substitute
12808    // (`apply`'s fallback) path entries — into ONE named projection both
12809    // strategies share. The existing `compiled_template_matches_substitute
12810    // _path` and `expansion_layers_agree_on_output_and_cache_wins` tests
12811    // are the path-uniformity guards covering the SHARED-PROJECTION shape;
12812    // the four tests below pin the projection's contract DIRECTLY:
12813    // (a) a quasi-quoted body unwraps to the inner; (b) a non-quasi-quoted
12814    // body returns the body verbatim; (c) the borrow is rooted in the
12815    // body field (single-level peel); (d) the projection is the same
12816    // `&Sexp` both strategies route on (so a regression that drifts the
12817    // body-peel from one strategy to the other becomes a type-level
12818    // change at this helper, not a silent two-site divergence).
12819
12820    #[test]
12821    fn template_body_unwraps_outer_quasiquote_to_inner() {
12822        // The canonical authoring shape: `(defmacro f (a) `(list ,a))` —
12823        // the reader wraps the `` ` `` form into `Sexp::Quasiquote(inner)`,
12824        // and `template_body` peels the outer marker. The returned `&Sexp`
12825        // is the inner walker-payload both expansion strategies consume.
12826        let inner = Sexp::List(vec![
12827            Sexp::symbol("list"),
12828            Sexp::Unquote(Box::new(Sexp::symbol("a"))),
12829        ]);
12830        let def = MacroDef {
12831            name: "f".into(),
12832            params: MacroParams::default(),
12833            body: Sexp::Quasiquote(Box::new(inner.clone())),
12834        };
12835        assert_eq!(def.template_body(), &inner);
12836    }
12837
12838    #[test]
12839    fn template_body_returns_non_quasiquote_body_verbatim() {
12840        // A body authored WITHOUT the outer `` ` `` affordance — a bare
12841        // `Sexp::List` body — returns verbatim. The "other" arm of the
12842        // legacy match. Pin parity with the pre-lift code path so a
12843        // regression that drifts the body-peel into "always peel
12844        // something" (which would break literal-body macros) fails here.
12845        let body = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(1)]);
12846        let def = MacroDef {
12847            name: "f".into(),
12848            params: MacroParams::default(),
12849            body: body.clone(),
12850        };
12851        assert_eq!(def.template_body(), &body);
12852        // Atom bodies too — the projection is a single-arm match, not a
12853        // recursive descent. A `Sexp::Atom` body is its own template payload.
12854        let atom_def = MacroDef {
12855            name: "g".into(),
12856            params: MacroParams::default(),
12857            body: Sexp::symbol("nil-template"),
12858        };
12859        assert_eq!(atom_def.template_body(), &Sexp::symbol("nil-template"));
12860    }
12861
12862    #[test]
12863    fn template_body_peels_single_level_only() {
12864        // A nested `` ``form `` body — `Sexp::Quasiquote(Box::new(
12865        // Sexp::Quasiquote(...)))` — unwraps ONE outer quasi-quote and
12866        // returns the inner `Sexp::Quasiquote(...)` as-is. The v0 module
12867        // preamble declares "Nested quasi-quotes: Not yet supported"; the
12868        // single-level peel matches the legacy inline match's posture
12869        // (which only matched ONE outer `Sexp::Quasiquote(_)` arm, not a
12870        // recursive loop). A regression that drifts to a recursive peel
12871        // would project too far and the inner `Sexp::Quasiquote` marker —
12872        // which the substitute walker treats as an atomic leaf returned
12873        // verbatim (line ~830, `Sexp::Quote(_) | Sexp::Quasiquote(_)
12874        // => Ok(form.clone())`) — would silently disappear from the
12875        // expansion's emitted form. Pin the single-level contract here.
12876        let inner_payload = Sexp::List(vec![Sexp::symbol("list"), Sexp::int(7)]);
12877        let inner_qq = Sexp::Quasiquote(Box::new(inner_payload.clone()));
12878        let def = MacroDef {
12879            name: "nested".into(),
12880            params: MacroParams::default(),
12881            body: Sexp::Quasiquote(Box::new(inner_qq.clone())),
12882        };
12883        // Outer peel returns the INNER quasi-quote, NOT its inner payload.
12884        assert_eq!(def.template_body(), &inner_qq);
12885        assert_ne!(def.template_body(), &inner_payload);
12886    }
12887
12888    #[test]
12889    fn template_body_returns_quote_form_verbatim_distinct_from_quasiquote() {
12890        // A `Sexp::Quote(_)` body — not a `Sexp::Quasiquote(_)` — returns
12891        // verbatim through the "other" arm. The two close-cousin shapes
12892        // share an outer-marker character (`'` vs `` ` ``) at the reader
12893        // boundary but differ semantically: a `Quote` body is a literal
12894        // template (no substitution semantics), a `Quasiquote` body is a
12895        // substitution-walker entry. A regression that conflated the two
12896        // — peeling Quote as if it were Quasiquote — would silently turn
12897        // every quoted-body macro into a template-walked macro. Pin the
12898        // discrimination.
12899        let inner = Sexp::List(vec![Sexp::symbol("opaque"), Sexp::int(42)]);
12900        let body = Sexp::Quote(Box::new(inner.clone()));
12901        let def = MacroDef {
12902            name: "quoted".into(),
12903            params: MacroParams::default(),
12904            body: body.clone(),
12905        };
12906        assert_eq!(def.template_body(), &body);
12907        // The inner is NOT what comes back — only Quasiquote-bodied macros
12908        // would peel to the inner.
12909        assert_ne!(def.template_body(), &inner);
12910    }
12911
12912    #[test]
12913    fn template_body_is_the_shared_projection_both_strategies_walk() {
12914        // End-to-end path-uniformity at the projection boundary: a macro
12915        // authored with the canonical quasi-quoted body expands
12916        // IDENTICALLY under bytecode and substitute strategies because
12917        // both route their walker's body through `template_body()` — the
12918        // SAME `&Sexp` projection. Sibling of
12919        // `compiled_template_matches_substitute_path` (which observes
12920        // agreement on the EMITTED form); this test pins agreement on
12921        // the projection ENTRY: `compile_template` and the substitute
12922        // fallback now consume the same `&Sexp` (`def.template_body()`),
12923        // never a divergent inline match the two paths could regress
12924        // independently.
12925        let src = "(defmacro wrap (x) `(list ,x ,x)) (wrap 5)";
12926        let expected = vec![Sexp::List(vec![
12927            Sexp::symbol("list"),
12928            Sexp::int(5),
12929            Sexp::int(5),
12930        ])];
12931        let bytecode = Expander::new().expand_program(read(src).unwrap()).unwrap();
12932        let substitute = Expander::new_substitute_only()
12933            .expand_program(read(src).unwrap())
12934            .unwrap();
12935        assert_eq!(bytecode, expected, "bytecode body-projection drifted");
12936        assert_eq!(substitute, expected, "substitute body-projection drifted");
12937        assert_eq!(
12938            bytecode, substitute,
12939            "the two strategies disagree on the body-projection's emission"
12940        );
12941    }
12942
12943    // ── Expander::expand: macro-call dispatch routes through `as_call_to_any` ──
12944    //
12945    // `expand` lifts its macro-call recognition to route through the
12946    // substrate's typed-decoded call decomposition: `as_call_to_any(|h|
12947    // self.macros.get(h))` answers "is this form an invocation of any
12948    // registered macro?" in ONE structural query on the Sexp algebra,
12949    // and a HashMap-backed lookup as its classifier. Sibling consumer to
12950    // `macro_def_from` (the typed-macro-definition dispatcher already
12951    // routing through `as_call_to_any(MacroDefHead::from_keyword)` with
12952    // a closed-set enum classifier). With both in place, BOTH dispatch
12953    // sites in the macro expander project through the SAME family
12954    // primitive — each binding the classifier that fits its candidate
12955    // set. The tests below pin the consumer's path-uniformity contract
12956    // at the new boundary: a hand-rolled `as_call_to_any(|h| macros.get
12957    // (h))` dispatch observes the SAME `(def, args)` decomposition the
12958    // `Expander::expand` consumer routes through.
12959
12960    #[test]
12961    fn expand_routes_macro_call_dispatch_observably_through_as_call_to_any() {
12962        // Structural identity: on a registered-macro call, the consumer's
12963        // expansion is observably equivalent to: classify the form via
12964        // `as_call_to_any(|h| macros.get(h))` → some `(def, args)` →
12965        // apply the def to args. Pin path-uniformity: a hand-rolled
12966        // `as_call_to_any` lookup against the same registry the expander
12967        // walks produces the SAME `MacroDef` reference for the SAME
12968        // input form. A regression that drifts the consumer back to an
12969        // inline `as_list + as_call + macros.get` chain (which would
12970        // fragment the family adoption) is caught structurally — the
12971        // hand-rolled `as_call_to_any` and the consumer's dispatch must
12972        // observe the same decomposition.
12973        let mut e = Expander::new();
12974        e.expand_program(read("(defmacro wrap (x) `(list ,x ,x))").unwrap())
12975            .unwrap();
12976        let call_form = parse("(wrap 42)");
12977
12978        // Hand-rolled family-primitive lookup mirrors the lifted consumer.
12979        let (def_via_family, args_via_family) = call_form
12980            .as_call_to_any(|h| e.macros.get(h))
12981            .expect("registered macro call must decompose via as_call_to_any");
12982        assert_eq!(def_via_family.name, "wrap");
12983        assert_eq!(args_via_family, &[Sexp::int(42)]);
12984
12985        // Consumer's expand observes the SAME decomposition: the expanded
12986        // form is `(list 42 42)`, derived from the SAME def + args the
12987        // hand-rolled lookup found. Path-uniform with the family
12988        // primitive at the dispatch boundary.
12989        let expanded = e.expand(&call_form).unwrap();
12990        assert_eq!(
12991            expanded,
12992            Sexp::List(vec![Sexp::symbol("list"), Sexp::int(42), Sexp::int(42)])
12993        );
12994    }
12995
12996    #[test]
12997    fn expand_skips_non_macro_call_into_children_walk_via_family_primitive_none() {
12998        // Path-uniformity for the non-registered-head path: `as_call_to_any
12999        // (|h| macros.get(h))` returns `None` for a call whose head ISN'T
13000        // a registered macro, and the consumer falls through to the
13001        // children-walk (which expands any nested macro calls). Pin
13002        // both halves: the hand-rolled lookup returns `None`, AND the
13003        // consumer's expand walks into the children. A regression that
13004        // accidentally short-circuits the children-walk for non-macro
13005        // calls (e.g. by treating `as_call_to_any` = `None` as
13006        // "non-expandable" globally) would fail here.
13007        let mut e = Expander::new();
13008        e.expand_program(read("(defmacro wrap (x) `(list ,x ,x))").unwrap())
13009            .unwrap();
13010        let outer = parse("(foo (wrap 5))");
13011
13012        // Hand-rolled family-primitive lookup rejects the outer head.
13013        assert!(outer.as_call_to_any(|h| e.macros.get(h)).is_none());
13014
13015        // Consumer walks children — the inner `(wrap 5)` IS a macro call
13016        // and expands to `(list 5 5)`; the outer `foo` head is preserved.
13017        let expanded = e.expand(&outer).unwrap();
13018        assert_eq!(
13019            expanded,
13020            Sexp::List(vec![
13021                Sexp::symbol("foo"),
13022                Sexp::List(vec![Sexp::symbol("list"), Sexp::int(5), Sexp::int(5)]),
13023            ])
13024        );
13025    }
13026
13027    #[test]
13028    fn expand_non_call_shapes_route_past_family_primitive_into_fallthrough_clone() {
13029        // Path-uniformity for the non-call path: every shape `as_call`
13030        // rejects (atoms across all 6 kinds, Nil, Quote-family wrappers)
13031        // ALSO routes past `as_call_to_any` into the `as_list()`
13032        // fallthrough, where the not-a-list arm returns `form.clone()`
13033        // verbatim. Pin both halves: the hand-rolled lookup rejects
13034        // every non-call shape regardless of decoder, AND the consumer
13035        // preserves each shape unchanged. A regression that drifts the
13036        // consumer's dispatch order (e.g. checking `as_list()` BEFORE
13037        // `as_call_to_any` in a way that mis-handles Quote-family
13038        // wrappers) would fail here.
13039        let e = Expander::new();
13040        let shapes = [
13041            Sexp::symbol("foo"),
13042            Sexp::int(5),
13043            Sexp::keyword("k"),
13044            Sexp::string("s"),
13045            Sexp::boolean(true),
13046            Sexp::float(1.5),
13047            Sexp::Nil,
13048            Sexp::Quote(Box::new(Sexp::symbol("x"))),
13049            Sexp::Quasiquote(Box::new(Sexp::symbol("x"))),
13050            Sexp::Unquote(Box::new(Sexp::symbol("x"))),
13051            Sexp::UnquoteSplice(Box::new(Sexp::symbol("x"))),
13052        ];
13053        for s in &shapes {
13054            // Hand-rolled family-primitive lookup rejects non-call shapes
13055            // even for a promiscuous decoder — the call-shape gate fires
13056            // BEFORE the decoder runs.
13057            assert!(
13058                s.as_call_to_any(|_h: &str| Some(0_u8)).is_none(),
13059                "non-call shape must yield None for as_call_to_any: {s}"
13060            );
13061            // Consumer preserves the shape verbatim — the not-a-list
13062            // arm at the fallthrough returns `form.clone()`.
13063            assert_eq!(
13064                e.expand(s).unwrap(),
13065                s.clone(),
13066                "non-call shape must round-trip unchanged through expand: {s}"
13067            );
13068        }
13069    }
13070
13071    #[test]
13072    fn expand_empty_list_routes_past_family_primitive_into_children_walk() {
13073        // The empty list `()` has no operator and no children. Pin that
13074        // `as_call_to_any` rejects it (no head to feed the decoder), the
13075        // consumer falls through to `as_list()` which returns `Some(&[])`,
13076        // and the children-walk emits `Sexp::List(vec![])` (an empty
13077        // list, not `form.clone()` of `Sexp::List(vec![])` — both happen
13078        // to be observationally identical, but the path is the
13079        // children-walk arm, NOT the not-a-list arm). Path-uniformity
13080        // gate for the singleton-list edge case the `compile_named_from_
13081        // forms` rejection chain relies on `as_call_to(KEYWORD)` to yield
13082        // `Some(&[])` for — same posture, different family member.
13083        let e = Expander::new();
13084        let empty = Sexp::List(vec![]);
13085
13086        // Hand-rolled family-primitive lookup rejects the empty list.
13087        assert!(empty.as_call_to_any(|_h: &str| Some(())).is_none());
13088
13089        // Consumer walks children (zero of them) — output is the empty
13090        // list, same as input.
13091        assert_eq!(e.expand(&empty).unwrap(), Sexp::List(vec![]));
13092    }
13093
13094    // ── Expander::expand_and_collect_calls_to: program-level walk ───────
13095    //
13096    // `expand_and_collect_calls_to(forms, keyword, project)` composes the
13097    // expander's program-level expansion (`expand_program`) with the
13098    // substrate's slice-side typed-keyword projection (`iter_calls_to`)
13099    // and a caller-supplied per-form mapper into ONE method on the
13100    // `Expander` surface. Both `compile_typed::<T>` and
13101    // `compile_named_from_forms::<T>` (compile.rs) route through it; the
13102    // tests below pin its contract directly. The existing compile.rs
13103    // dispatch tests are the path-uniformity guards proving the two
13104    // production sites route through it without behavior drift.
13105
13106    #[test]
13107    fn expand_and_collect_calls_to_yields_projection_for_every_matching_form_in_source_order() {
13108        // Three forms — two match `defmonitor`, one matches `defalert`.
13109        // The mapper records `args.len()` from each matching form's tail.
13110        // Pin: only the two `defmonitor` matches flow through `project`,
13111        // in source order (so the recorded lengths are 2, 1).
13112        let forms = vec![
13113            Sexp::List(vec![
13114                Sexp::symbol("defmonitor"),
13115                Sexp::keyword("name"),
13116                Sexp::string("first"),
13117            ]),
13118            Sexp::List(vec![
13119                Sexp::symbol("defalert"),
13120                Sexp::keyword("name"),
13121                Sexp::string("not-a-match"),
13122            ]),
13123            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::keyword("solo")]),
13124        ];
13125        let mut e = Expander::new();
13126        let lengths: Vec<usize> = e
13127            .expand_and_collect_calls_to(forms, "defmonitor", |args| Ok(args.len()))
13128            .expect("matching forms must compose");
13129        assert_eq!(lengths, vec![2, 1]);
13130    }
13131
13132    #[test]
13133    fn expand_and_collect_calls_to_skips_non_matching_forms_without_invoking_project() {
13134        // Path-uniformity for the soft-projection posture inherited from
13135        // `iter_calls_to`: non-matching forms are skipped silently, and
13136        // `project` is never invoked on them. Pin via a counter the
13137        // closure increments per invocation — the counter is the
13138        // observable that distinguishes "skipped silently" from
13139        // "projected to a no-op".
13140        let forms = vec![
13141            Sexp::symbol("bare-atom"),
13142            Sexp::List(vec![Sexp::symbol("defalert"), Sexp::int(1)]),
13143            Sexp::List(vec![Sexp::int(5), Sexp::symbol("not-symbol-head")]),
13144            Sexp::List(vec![]),
13145            Sexp::Nil,
13146        ];
13147        let mut count = 0usize;
13148        let mut e = Expander::new();
13149        let out: Vec<()> = e
13150            .expand_and_collect_calls_to(forms, "defmonitor", |_args| {
13151                count += 1;
13152                Ok(())
13153            })
13154            .expect("non-matching-only slice must collect to empty Vec");
13155        assert!(out.is_empty(), "no matching forms — empty Vec, got {out:?}");
13156        assert_eq!(count, 0, "project must never run for non-matching forms");
13157    }
13158
13159    #[test]
13160    fn expand_and_collect_calls_to_short_circuits_on_project_error_at_first_failure() {
13161        // Three matching forms; the closure errors on the SECOND. Pin: the
13162        // collect's `Result<Vec<_>, _>` short-circuit fires at the second
13163        // form's error, the third form's `project` is NEVER invoked, and
13164        // the returned error is exactly the one the closure raised. This
13165        // mirrors `compile_named_from_forms`'s short-circuit on the first
13166        // `NamedFormMissingName` / `NamedFormNonSymbolName` / typed-entry
13167        // kwargs rejection.
13168        let forms = vec![
13169            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::int(1)]),
13170            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::int(2)]),
13171            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::int(3)]),
13172        ];
13173        let mut seen = Vec::new();
13174        let mut e = Expander::new();
13175        let err = e
13176            .expand_and_collect_calls_to::<(), _>(forms, "defmonitor", |args| {
13177                let n = args[0].as_int().expect("test args are ints");
13178                seen.push(n);
13179                if n == 2 {
13180                    Err(LispError::Compile {
13181                        form: "test".to_string(),
13182                        message: "stop at two".to_string(),
13183                    })
13184                } else {
13185                    Ok(())
13186                }
13187            })
13188            .expect_err("project's error must short-circuit collect");
13189        // Only the first two forms were ever projected — the third
13190        // never reached `project`.
13191        assert_eq!(seen, vec![1, 2]);
13192        assert!(
13193            matches!(err, LispError::Compile { ref message, .. } if message == "stop at two"),
13194            "short-circuit must propagate the project's error verbatim, got {err:?}"
13195        );
13196    }
13197
13198    #[test]
13199    fn expand_and_collect_calls_to_short_circuits_on_expand_program_error_before_project_runs() {
13200        // The `expand_program` step runs BEFORE `iter_calls_to` walks
13201        // anything — so an `expand_program` error (e.g., a malformed
13202        // `defmacro` head) must short-circuit the entire composition
13203        // and `project` must NEVER run. Pin via the closure-running
13204        // counter: an error from the FIRST step never invokes `project`.
13205        // `(defmacro 5 (x) `,x)` — the macro NAME slot is an int, not a
13206        // symbol; `macro_def_from`'s `defmacro_non_symbol_name` rejection
13207        // fires during `expand_program`.
13208        let forms = read("(defmacro 5 (x) `,x) (defmonitor :name \"x\")").unwrap();
13209        let mut count = 0usize;
13210        let mut e = Expander::new();
13211        let err = e
13212            .expand_and_collect_calls_to::<(), _>(forms, "defmonitor", |_args| {
13213                count += 1;
13214                Ok(())
13215            })
13216            .expect_err("expand_program error must short-circuit before project");
13217        assert_eq!(
13218            count, 0,
13219            "project must never run when expand_program errors"
13220        );
13221        // Sanity: the error IS the expand_program-stage rejection, NOT
13222        // a project-stage error (path-uniformity for the ordering).
13223        let rendered = format!("{err}");
13224        assert!(
13225            rendered.contains("NAME") || rendered.contains("symbol"),
13226            "error must be the defmacro-NAME-not-a-symbol rejection, got: {rendered}"
13227        );
13228    }
13229
13230    #[test]
13231    fn expand_and_collect_calls_to_yields_empty_vec_for_empty_forms_input() {
13232        // Boundary: empty forms slice yields zero items regardless of
13233        // keyword. Pin the degenerate boundary — empty in, empty out —
13234        // and the closure is never invoked. Sibling of
13235        // `iter_calls_to_yields_nothing_for_empty_slice` in ast.rs,
13236        // one level up the composition: the program-level walk is
13237        // fused-empty when `expand_program` yields an empty `Vec<Sexp>`.
13238        let mut count = 0usize;
13239        let mut e = Expander::new();
13240        let out: Vec<()> = e
13241            .expand_and_collect_calls_to(Vec::new(), "anything", |_args| {
13242                count += 1;
13243                Ok(())
13244            })
13245            .expect("empty forms is not an error");
13246        assert!(out.is_empty());
13247        assert_eq!(count, 0);
13248    }
13249
13250    #[test]
13251    fn expand_and_collect_calls_to_expands_macros_before_filtering_by_keyword() {
13252        // Critical ordering: `expand_program` runs BEFORE the keyword
13253        // filter, so a `defmacro` whose expansion produces a form with
13254        // a matching head IS visible to `project`. Pin the pipeline
13255        // order: `(defmacro emit-monitor (n) `(defmonitor :name ,n))
13256        //         (emit-monitor "alpha") (defmonitor :name "beta")`
13257        // expands the macro call to `(defmonitor :name "alpha")` and
13258        // the keyword walk then sees TWO `defmonitor` forms (the
13259        // macro-emitted one AND the directly-authored one). A
13260        // pre-expansion filter would miss the macro-emitted form.
13261        let forms = read(
13262            "(defmacro emit-monitor (n) `(defmonitor :name ,n))
13263             (emit-monitor \"alpha\")
13264             (defmonitor :name \"beta\")",
13265        )
13266        .unwrap();
13267        let mut e = Expander::new();
13268        let names: Vec<String> = e
13269            .expand_and_collect_calls_to(forms, "defmonitor", |args| {
13270                // Each defmonitor form's tail is `(:name "X")`; project
13271                // the second element's string to capture the name.
13272                Ok(args[1].as_string().unwrap().to_string())
13273            })
13274            .expect("macroexpanded + directly-authored forms must both flow");
13275        assert_eq!(names, vec!["alpha".to_string(), "beta".to_string()]);
13276    }
13277
13278    #[test]
13279    fn expand_and_collect_calls_to_threads_keyword_argument_verbatim_into_filter() {
13280        // The `keyword` argument flows straight into `iter_calls_to`'s
13281        // head-comparison gate — a regression that drifts the keyword
13282        // (e.g., uses `T::KEYWORD` from a different `T` at the helper,
13283        // or lowercases / trims the keyword en route) fails here. Pin
13284        // by passing TWO different keywords against the SAME forms
13285        // input and asserting each picks up only its matching forms.
13286        let forms = vec![
13287            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::int(1)]),
13288            Sexp::List(vec![Sexp::symbol("defalert"), Sexp::int(2)]),
13289            Sexp::List(vec![Sexp::symbol("defmonitor"), Sexp::int(3)]),
13290            Sexp::List(vec![Sexp::symbol("defnotify"), Sexp::int(4)]),
13291        ];
13292
13293        let mut e = Expander::new();
13294        let monitors: Vec<i64> = e
13295            .expand_and_collect_calls_to(forms.clone(), "defmonitor", |args| {
13296                Ok(args[0].as_int().unwrap())
13297            })
13298            .unwrap();
13299        assert_eq!(monitors, vec![1, 3]);
13300
13301        let mut e2 = Expander::new();
13302        let alerts: Vec<i64> = e2
13303            .expand_and_collect_calls_to(forms.clone(), "defalert", |args| {
13304                Ok(args[0].as_int().unwrap())
13305            })
13306            .unwrap();
13307        assert_eq!(alerts, vec![2]);
13308
13309        // A keyword nothing matches collects to the empty Vec — same
13310        // shape as the empty-forms case at the slice level.
13311        let mut e3 = Expander::new();
13312        let none: Vec<i64> = e3
13313            .expand_and_collect_calls_to(forms, "missing-keyword", |args| {
13314                Ok(args[0].as_int().unwrap())
13315            })
13316            .unwrap();
13317        assert!(none.is_empty());
13318    }
13319
13320    #[test]
13321    fn expand_and_collect_calls_to_matches_inlined_expand_program_plus_iter_calls_to_path() {
13322        // Structural identity: for any (forms, keyword, project) triple
13323        // whose pieces succeed, the method's result equals the inlined
13324        // pre-lift pipeline `expand_program + iter_calls_to + map +
13325        // collect`. Pin shape AND ordering on a mixed forms input
13326        // (matching + non-matching + macroexpand-emitted matches) so a
13327        // regression that drifts the method's pipeline from the inlined
13328        // closed-form fails here. This is the lift's load-bearing
13329        // path-uniformity gate — the SAME assertion that holds for
13330        // both production consumers (compile.rs) holds at the
13331        // primitive's contract level.
13332        let src = "(defmacro emit-foo (n) `(foo :idx ,n))
13333                   (foo :idx 1)
13334                   (emit-foo 2)
13335                   (bar :idx 99)
13336                   (foo :idx 3)";
13337        let forms = read(src).unwrap();
13338
13339        // Inlined pre-lift pipeline.
13340        let mut exp_inline = Expander::new();
13341        let expanded = exp_inline.expand_program(forms.clone()).unwrap();
13342        let via_inline: Vec<i64> = crate::ast::iter_calls_to(&expanded, "foo")
13343            .map(|args| -> Result<i64> { Ok(args[1].as_int().unwrap()) })
13344            .collect::<Result<Vec<_>>>()
13345            .unwrap();
13346
13347        // Method pipeline.
13348        let mut exp_method = Expander::new();
13349        let via_method: Vec<i64> = exp_method
13350            .expand_and_collect_calls_to(forms, "foo", |args| Ok(args[1].as_int().unwrap()))
13351            .unwrap();
13352
13353        assert_eq!(via_inline, via_method);
13354        assert_eq!(via_inline, vec![1, 2, 3]);
13355    }
13356
13357    // ── Expander::expand_and_collect_calls_to_any: typed-decoded sibling ──
13358    //
13359    // The typed-decoded classifier sibling of `expand_and_collect_calls_to`:
13360    // composes `expand_program` with `iter_calls_to_any` and a per-form
13361    // mapper that receives BOTH the typed witness AND the args tail.
13362    // Closes the (keyword, classifier) 2×2 of expander-surface
13363    // compose-on-iter projections at the typed-decoded corner the prior
13364    // runs' slice-side `iter_calls_to_any` lift left open. The keyword
13365    // sibling now ROUTES through this primitive via a constant-classifier
13366    // composition — the tests below pin both the typed-decoded primitive's
13367    // contract directly AND the routing identity binding the keyword
13368    // sibling to it.
13369    //
13370    // The existing `expand_and_collect_calls_to_*` tests above remain
13371    // load-bearing — they pin the keyword sibling's contract through
13372    // its ROUTED implementation, so a regression that drifts the routing
13373    // composition surfaces at the existing keyword tests too.
13374
13375    #[test]
13376    fn expand_and_collect_calls_to_any_yields_decoded_pair_for_every_matching_form_in_source_order()
13377    {
13378        // The typed-decoded primitive's happy path: a closed-set
13379        // classifier decodes head symbols to a typed enum, and the
13380        // per-form projection receives `(decoded, args)` for every
13381        // matched form in source order. Sweep across THREE distinct
13382        // classifier outcomes — `Foo` / `Bar` / `Baz` — interleaved with
13383        // a non-matching form to pin both the typed-witness threading
13384        // AND the source-order yield AND the rejection of non-classifier
13385        // forms in ONE assertion. The decoder runs once per call form
13386        // (`FnMut`); the projection runs once per matched form.
13387        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
13388        enum Op {
13389            Foo,
13390            Bar,
13391            Baz,
13392        }
13393        let src = "(foo 1)
13394                   (bar 2)
13395                   (other 99)
13396                   (baz 3)
13397                   (foo 4)";
13398        let forms = read(src).unwrap();
13399        let mut e = Expander::new();
13400        let pairs: Vec<(Op, i64)> = e
13401            .expand_and_collect_calls_to_any(
13402                forms,
13403                |h| match h {
13404                    "foo" => Some(Op::Foo),
13405                    "bar" => Some(Op::Bar),
13406                    "baz" => Some(Op::Baz),
13407                    _ => None,
13408                },
13409                |op, args| Ok((op, args[0].as_int().unwrap())),
13410            )
13411            .unwrap();
13412        assert_eq!(
13413            pairs,
13414            vec![(Op::Foo, 1), (Op::Bar, 2), (Op::Baz, 3), (Op::Foo, 4),],
13415        );
13416    }
13417
13418    #[test]
13419    fn expand_and_collect_calls_to_any_skips_non_matching_forms_without_invoking_project() {
13420        // The soft-projection contract — every shape `iter_calls_to_any`
13421        // rejects (non-list, empty list, list whose head is not a
13422        // symbol, list whose head decodes through the classifier to
13423        // `None`) skips the projection silently. Pin a deliberately-
13424        // panicking projection so the assertion fires loudly if a
13425        // non-matching form reaches it. The decoder accepts only
13426        // `"defmonitor"`; every other shape (atom, list whose head is a
13427        // keyword, list whose head is a symbol the decoder rejects)
13428        // short-circuits before the projection runs.
13429        let src = r#":bare-keyword
13430                     "bare-string"
13431                     42
13432                     ()
13433                     (foo bar)
13434                     (defmonitor :name "matches")"#;
13435        let forms = read(src).unwrap();
13436        let mut e = Expander::new();
13437        let lengths: Vec<usize> = e
13438            .expand_and_collect_calls_to_any(
13439                forms,
13440                |h| (h == "defmonitor").then_some(()),
13441                |(), args| {
13442                    // The projection must run EXACTLY once — for the
13443                    // single matched form. Any non-matching form that
13444                    // reaches the projection panics here.
13445                    assert_eq!(args.len(), 2, "projection ran on non-matching form");
13446                    Ok(args.len())
13447                },
13448            )
13449            .unwrap();
13450        assert_eq!(lengths, vec![2]);
13451    }
13452
13453    #[test]
13454    fn expand_and_collect_calls_to_any_short_circuits_on_project_error_at_first_failure() {
13455        // The Result projection's short-circuit contract — when the
13456        // projection returns `Err` on a matched form, the walk
13457        // short-circuits and subsequent matched forms are NOT projected.
13458        // Pin the source-order short-circuit via a counter the
13459        // projection increments before deciding to fail: the counter
13460        // sits at exactly the index of the failing form (the second
13461        // match), proving the third match never reached the projection.
13462        let src = "(foo 1) (foo 2) (foo 3)";
13463        let forms = read(src).unwrap();
13464        let mut count = 0usize;
13465        let mut e = Expander::new();
13466        let err = e
13467            .expand_and_collect_calls_to_any::<i64, _, _, ()>(
13468                forms,
13469                |h| (h == "foo").then_some(()),
13470                |(), args| {
13471                    count += 1;
13472                    let v = args[0].as_int().unwrap();
13473                    if v == 2 {
13474                        return Err(crate::error::LispError::Missing("test-failure"));
13475                    }
13476                    Ok(v)
13477                },
13478            )
13479            .expect_err("projection must short-circuit on first Err");
13480        assert_eq!(
13481            count, 2,
13482            "projection must have run on first AND failing form, then stopped",
13483        );
13484        // Sanity: the error is the project-stage rejection — the same
13485        // typed `LispError` the projection returned, propagated verbatim.
13486        assert!(
13487            matches!(err, crate::error::LispError::Missing("test-failure")),
13488            "expected the projection's typed Err verbatim, got {err:?}",
13489        );
13490    }
13491
13492    #[test]
13493    fn expand_and_collect_calls_to_any_expands_macros_before_filtering_by_classifier() {
13494        // Ordering contract — `expand_program` runs BEFORE
13495        // `iter_calls_to_any` walks the slice. A `(defmacro …)` form
13496        // that emits a classifier-decoded body must have its expanded
13497        // body decoded by the classifier (not the unexpanded macro-call
13498        // head). Pin the ordering with a macro `(emit-foo n)` that
13499        // expands to `(foo :idx n)` — the classifier sees the expanded
13500        // `foo` head, not `emit-foo`. Mirrors the parallel keyword test
13501        // `expand_and_collect_calls_to_expands_macros_before_filtering_by_keyword`
13502        // — the SAME composition contract holds on the typed-decoded
13503        // sibling.
13504        let src = "(defmacro emit-foo (n) `(foo :idx ,n))
13505                   (foo :idx 1)
13506                   (emit-foo 2)
13507                   (bar :idx 99)
13508                   (foo :idx 3)";
13509        let forms = read(src).unwrap();
13510        let mut e = Expander::new();
13511        // Classifier returns `()` for "foo" only; the keyword sibling's
13512        // routing identity is the same composition we exercise here
13513        // directly.
13514        let idxs: Vec<i64> = e
13515            .expand_and_collect_calls_to_any(
13516                forms,
13517                |h| (h == "foo").then_some(()),
13518                |(), args| Ok(args[1].as_int().unwrap()),
13519            )
13520            .unwrap();
13521        // 1 from the literal call, 2 from the macro-emitted call, 3 from
13522        // the final literal call. The macro-emitted `(foo :idx 2)` is
13523        // present BECAUSE `expand_program` ran first.
13524        assert_eq!(idxs, vec![1, 2, 3]);
13525    }
13526
13527    #[test]
13528    fn expand_and_collect_calls_to_any_admits_fnmut_classifier_maintaining_state_across_walk() {
13529        // The `FnMut` constraint — the slice walk calls the classifier
13530        // once per call form, and a decoder that captures mutable state
13531        // (a counter, a registry cache, a visited-set) maintains that
13532        // state across the walk. Pin the `FnMut` contract with a counter
13533        // closure that increments per shape-gate-passing form (calls
13534        // whose head is a symbol), asserting the counter equals the
13535        // total call count regardless of whether the classifier accepts
13536        // each form. Mirrors the parallel slice-side test
13537        // `iter_calls_to_any_admits_fnmut_classifier_maintaining_state_across_batch_walk`
13538        // — the SAME `FnMut` contract holds on the expander surface.
13539        let src = "(foo 1) (bar 2) (foo 3) (bar 4) (foo 5)";
13540        let forms = read(src).unwrap();
13541        let mut e = Expander::new();
13542        let mut classifier_calls = 0usize;
13543        let projected: Vec<i64> = e
13544            .expand_and_collect_calls_to_any(
13545                forms,
13546                |h| {
13547                    classifier_calls += 1;
13548                    (h == "foo").then_some(())
13549                },
13550                |(), args| Ok(args[0].as_int().unwrap()),
13551            )
13552            .unwrap();
13553        assert_eq!(
13554            classifier_calls, 5,
13555            "classifier must run once per call form (5 forms, all calls with symbol heads)",
13556        );
13557        assert_eq!(
13558            projected,
13559            vec![1, 3, 5],
13560            "projection must run only for classifier-accepted forms in source order",
13561        );
13562    }
13563
13564    #[test]
13565    fn expand_and_collect_calls_to_routes_through_expand_and_collect_calls_to_any_via_constant_classifier_composition(
13566    ) {
13567        // Structural identity: the keyword sibling
13568        // `expand_and_collect_calls_to` ROUTES through the typed-decoded
13569        // primitive via a constant-classifier composition — the
13570        // post-lift composition law binding the two siblings:
13571        //
13572        //   expand_and_collect_calls_to(forms, k, p) ==
13573        //       expand_and_collect_calls_to_any(forms,
13574        //           |h| (h == k).then_some(()),
13575        //           |(), args| p(args))
13576        //
13577        // Pin shape AND ordering across THREE representative keyword
13578        // values: one that matches multiple forms, one that matches
13579        // none, one that matches exactly one form. Same mixed input
13580        // (literal + macroexpand-emitted + non-matching) the keyword
13581        // path-uniformity test exercises, so the routing identity holds
13582        // on the SAME input shape the keyword sibling's contract pins.
13583        let src = "(defmacro emit-foo (n) `(foo :idx ,n))
13584                   (foo :idx 1)
13585                   (emit-foo 2)
13586                   (bar :idx 99)
13587                   (foo :idx 3)";
13588        let forms = read(src).unwrap();
13589
13590        for keyword in ["foo", "bar", "absent"] {
13591            let mut exp_keyword = Expander::new();
13592            let via_keyword: Vec<i64> = exp_keyword
13593                .expand_and_collect_calls_to(forms.clone(), keyword, |args| {
13594                    Ok(args[1].as_int().unwrap())
13595                })
13596                .unwrap();
13597            let mut exp_classifier = Expander::new();
13598            let via_classifier: Vec<i64> = exp_classifier
13599                .expand_and_collect_calls_to_any(
13600                    forms.clone(),
13601                    |h| (h == keyword).then_some(()),
13602                    |(), args| Ok(args[1].as_int().unwrap()),
13603                )
13604                .unwrap();
13605            assert_eq!(
13606                via_keyword, via_classifier,
13607                "routing identity drifted for keyword {keyword:?}",
13608            );
13609        }
13610    }
13611
13612    #[test]
13613    fn expand_and_collect_calls_to_any_short_circuits_on_expand_program_error_before_project_runs()
13614    {
13615        // The expand_program-stage short-circuit contract — when
13616        // `expand_program` rejects (e.g. a `(defmacro …)` form whose
13617        // param list is malformed), the walk short-circuits BEFORE the
13618        // classifier or projection runs. Pin the ordering with a
13619        // deliberately-panicking classifier AND projection: any
13620        // post-expand-program execution fires the panic. Mirrors the
13621        // parallel keyword test
13622        // `expand_and_collect_calls_to_short_circuits_on_expand_program_error_before_project_runs`
13623        // — the SAME composition contract holds on the typed-decoded
13624        // sibling.
13625        // `(defmacro 5 (x) `,x)` — macro NAME slot is an int, not a
13626        // symbol; `macro_def_from`'s `defmacro_non_symbol_name` rejection
13627        // fires during `expand_program`. Same rejection shape the
13628        // parallel keyword test uses, so the ordering contract observed
13629        // here is structurally identical to the keyword sibling's.
13630        let forms = read("(defmacro 5 (x) `,x) (foo :idx 1)").unwrap();
13631        let mut e = Expander::new();
13632        let err = e
13633            .expand_and_collect_calls_to_any::<(), _, _, ()>(
13634                forms,
13635                |_h| -> Option<()> {
13636                    panic!("classifier must not run when expand_program errors");
13637                },
13638                |(), _args| {
13639                    panic!("project must not run when expand_program errors");
13640                },
13641            )
13642            .expect_err("expand_program error must short-circuit before classifier or project");
13643        // Sanity: the error IS an expand_program-stage rejection — not a
13644        // classifier or projection error — so the ordering is observable.
13645        let rendered = format!("{err}");
13646        assert!(
13647            rendered.contains("NAME") || rendered.contains("symbol"),
13648            "expected expand_program-stage `defmacro-NAME-not-a-symbol` rejection, got {rendered:?}",
13649        );
13650    }
13651
13652    // ── Expander::expand_source_and_collect_calls_to: from-source walk ──
13653    //
13654    // The from-source sibling of `expand_and_collect_calls_to`: composes
13655    // `crate::reader::read(src)?` with the from-forms primitive in ONE
13656    // method on the `Expander` surface. All four typed-program dispatchers
13657    // (free-function `compile_typed` / `compile_named`, preloaded-expander
13658    // `RealizedCompiler::compile_typed` / `compile_named`) route through
13659    // it; the tests below pin its contract directly. The existing
13660    // compile.rs + compiler_spec.rs dispatch tests are the path-uniformity
13661    // guards proving the four production sites route through it without
13662    // behavior drift.
13663
13664    #[test]
13665    fn expand_source_and_collect_calls_to_routes_through_reader_then_expand_and_collect() {
13666        // Happy path: a source string with three top-level forms (two
13667        // matching `defmonitor`, one matching `defalert`) flows through
13668        // `read` then through the from-forms primitive. Pin the SAME
13669        // emission shape the from-forms test pins for the matching
13670        // sub-set, sourced from a `&str` rather than a `Vec<Sexp>`.
13671        let src = r#"(defmonitor :name "first")
13672                     (defalert :name "not-a-match")
13673                     (defmonitor :solo)"#;
13674        let mut e = Expander::new();
13675        let lengths: Vec<usize> = e
13676            .expand_source_and_collect_calls_to(src, "defmonitor", |args| Ok(args.len()))
13677            .expect("matching forms must compose");
13678        assert_eq!(lengths, vec![2, 1]);
13679    }
13680
13681    #[test]
13682    fn expand_source_and_collect_calls_to_short_circuits_on_reader_error_before_expand_program() {
13683        // The reader runs BEFORE `expand_program` — so a reader error (an
13684        // unterminated string, an unbalanced paren, an unknown escape)
13685        // must short-circuit the entire composition and `expand_program`
13686        // / the keyword filter / `project` must NEVER run. Pin the
13687        // ordering: an unbalanced open-paren is rejected by the reader
13688        // at parse time, never reaches the from-forms primitive.
13689        let mut count = 0usize;
13690        let mut e = Expander::new();
13691        let err = e
13692            .expand_source_and_collect_calls_to::<(), _>(
13693                "(defmonitor :name \"unbalanced",
13694                "defmonitor",
13695                |_args| {
13696                    count += 1;
13697                    Ok(())
13698                },
13699            )
13700            .expect_err("reader error must short-circuit before expand_program");
13701        assert_eq!(
13702            count, 0,
13703            "project must never run when reader errors at parse time"
13704        );
13705        // Sanity: the error IS a reader-stage rejection — the rendered
13706        // diagnostic mentions the lexer's gate, not the expander's or
13707        // projector's (path-uniformity for the read-then-walk ordering).
13708        let rendered = format!("{err}");
13709        assert!(
13710            rendered.to_lowercase().contains("string")
13711                || rendered.to_lowercase().contains("paren")
13712                || rendered.to_lowercase().contains("eof")
13713                || rendered.to_lowercase().contains("unexpected")
13714                || rendered.to_lowercase().contains("unterminated")
13715                || rendered.to_lowercase().contains("unclosed"),
13716            "error must be the reader-stage rejection, got: {rendered}"
13717        );
13718    }
13719
13720    #[test]
13721    fn expand_source_and_collect_calls_to_short_circuits_on_expand_program_error_before_project_runs(
13722    ) {
13723        // Reader succeeds, but `expand_program` rejects a malformed
13724        // `defmacro` head (NAME slot is an int, not a symbol). Pin the
13725        // ordering: `expand_program` rejects BEFORE the keyword filter
13726        // walks anything, `project` must NEVER run. Sibling of the
13727        // from-forms test of the same name — one level up the
13728        // composition (sourced from `&str` rather than `Vec<Sexp>`).
13729        let mut count = 0usize;
13730        let mut e = Expander::new();
13731        let err = e
13732            .expand_source_and_collect_calls_to::<(), _>(
13733                "(defmacro 5 (x) `,x) (defmonitor :name \"x\")",
13734                "defmonitor",
13735                |_args| {
13736                    count += 1;
13737                    Ok(())
13738                },
13739            )
13740            .expect_err("expand_program error must short-circuit before project");
13741        assert_eq!(
13742            count, 0,
13743            "project must never run when expand_program errors"
13744        );
13745        let rendered = format!("{err}");
13746        assert!(
13747            rendered.contains("NAME") || rendered.contains("symbol"),
13748            "error must be the defmacro-NAME-not-a-symbol rejection, got: {rendered}"
13749        );
13750    }
13751
13752    #[test]
13753    fn expand_source_and_collect_calls_to_short_circuits_on_project_error_at_first_failure() {
13754        // Reader + `expand_program` both succeed; the per-form `project`
13755        // errors on the SECOND matched form. Pin: the collect's short-
13756        // circuit fires at the second match's error, the third match's
13757        // `project` is NEVER invoked, and the returned error is exactly
13758        // the one the closure raised. Mirrors `compile_named`'s
13759        // short-circuit on the first `NamedFormMissingName` /
13760        // `NamedFormNonSymbolName` / typed-entry rejection — sourced
13761        // from `&str`.
13762        let src = "(defmonitor :idx 1) (defmonitor :idx 2) (defmonitor :idx 3)";
13763        let mut seen = Vec::new();
13764        let mut e = Expander::new();
13765        let err = e
13766            .expand_source_and_collect_calls_to::<(), _>(src, "defmonitor", |args| {
13767                let n = args[1].as_int().expect("test args are ints");
13768                seen.push(n);
13769                if n == 2 {
13770                    Err(LispError::Compile {
13771                        form: "test".to_string(),
13772                        message: "stop at two".to_string(),
13773                    })
13774                } else {
13775                    Ok(())
13776                }
13777            })
13778            .expect_err("project's error must short-circuit collect");
13779        assert_eq!(seen, vec![1, 2]);
13780        assert!(
13781            matches!(err, LispError::Compile { ref message, .. } if message == "stop at two"),
13782            "short-circuit must propagate the project's error verbatim, got {err:?}"
13783        );
13784    }
13785
13786    #[test]
13787    fn expand_source_and_collect_calls_to_yields_empty_vec_for_empty_source() {
13788        // Boundary: empty source string yields zero items regardless of
13789        // keyword. Pin the degenerate boundary — empty in, empty out —
13790        // and the closure is never invoked. Sibling of the from-forms
13791        // test of the same name, one level up the composition: the
13792        // from-source posture is fused-empty when `read` yields an
13793        // empty `Vec<Sexp>` (which, fed into `expand_program`, yields
13794        // an empty `Vec<Sexp>`, which iter_calls_to walks to zero
13795        // matches).
13796        let mut count = 0usize;
13797        let mut e = Expander::new();
13798        let out: Vec<()> = e
13799            .expand_source_and_collect_calls_to("", "anything", |_args| {
13800                count += 1;
13801                Ok(())
13802            })
13803            .expect("empty source is not an error");
13804        assert!(out.is_empty());
13805        assert_eq!(count, 0);
13806    }
13807
13808    #[test]
13809    fn expand_source_and_collect_calls_to_expands_macros_before_filtering_by_keyword() {
13810        // Critical ordering preserved across the from-source posture:
13811        // `defmacro` source registers in `expand_program`, its call
13812        // sites expand to the matching keyword, AND the keyword walk
13813        // sees both the macro-emitted form and any directly-authored
13814        // matches in source order. Sibling of the from-forms test of
13815        // the same name, sourced from `&str`.
13816        let src = "(defmacro emit-monitor (n) `(defmonitor :name ,n))
13817                   (emit-monitor \"alpha\")
13818                   (defmonitor :name \"beta\")";
13819        let mut e = Expander::new();
13820        let names: Vec<String> = e
13821            .expand_source_and_collect_calls_to(src, "defmonitor", |args| {
13822                Ok(args[1].as_string().unwrap().to_string())
13823            })
13824            .expect("macroexpanded + directly-authored forms must both flow");
13825        assert_eq!(names, vec!["alpha".to_string(), "beta".to_string()]);
13826    }
13827
13828    #[test]
13829    fn expand_source_and_collect_calls_to_matches_inlined_read_plus_expand_and_collect_path() {
13830        // Structural identity: for any (src, keyword, project) triple
13831        // whose pieces succeed, the from-source method's result equals
13832        // the inlined pre-lift pipeline `read + expand_and_collect_
13833        // calls_to`. Pin shape AND ordering on a mixed source
13834        // (matching + non-matching + macroexpand-emitted matches) so a
13835        // regression that drifts the method's pipeline from the
13836        // inlined closed-form fails here. This is the lift's
13837        // load-bearing path-uniformity gate — the SAME assertion that
13838        // holds for all four production consumers (free-function
13839        // `compile_typed`/`compile_named` + `RealizedCompiler::compile_typed`/`compile_named`)
13840        // holds at the primitive's contract level.
13841        let src = "(defmacro emit-foo (n) `(foo :idx ,n))
13842                   (foo :idx 1)
13843                   (emit-foo 2)
13844                   (bar :idx 99)
13845                   (foo :idx 3)";
13846
13847        // Inlined pre-lift pipeline.
13848        let mut exp_inline = Expander::new();
13849        let inline_forms = read(src).unwrap();
13850        let via_inline: Vec<i64> = exp_inline
13851            .expand_and_collect_calls_to(inline_forms, "foo", |args| Ok(args[1].as_int().unwrap()))
13852            .unwrap();
13853
13854        // From-source method pipeline.
13855        let mut exp_method = Expander::new();
13856        let via_method: Vec<i64> = exp_method
13857            .expand_source_and_collect_calls_to(src, "foo", |args| Ok(args[1].as_int().unwrap()))
13858            .unwrap();
13859
13860        assert_eq!(via_inline, via_method);
13861        assert_eq!(via_inline, vec![1, 2, 3]);
13862    }
13863
13864    #[test]
13865    fn expand_source_and_collect_calls_to_threads_keyword_argument_verbatim_into_filter() {
13866        // The `keyword` argument flows straight into the from-forms
13867        // primitive's head-comparison gate, which inherits from
13868        // `iter_calls_to` — a regression that drifts the keyword (e.g.,
13869        // lowercases / trims it en route through the read step) fails
13870        // here. Pin by passing TWO different keywords against the SAME
13871        // source and asserting each picks up only its matching forms.
13872        let src = "(defmonitor :idx 1) (defalert :idx 2) (defmonitor :idx 3) (defnotify :idx 4)";
13873
13874        let mut e1 = Expander::new();
13875        let monitors: Vec<i64> = e1
13876            .expand_source_and_collect_calls_to(src, "defmonitor", |args| {
13877                Ok(args[1].as_int().unwrap())
13878            })
13879            .unwrap();
13880        assert_eq!(monitors, vec![1, 3]);
13881
13882        let mut e2 = Expander::new();
13883        let alerts: Vec<i64> = e2
13884            .expand_source_and_collect_calls_to(src, "defalert", |args| {
13885                Ok(args[1].as_int().unwrap())
13886            })
13887            .unwrap();
13888        assert_eq!(alerts, vec![2]);
13889
13890        let mut e3 = Expander::new();
13891        let none: Vec<i64> = e3
13892            .expand_source_and_collect_calls_to(src, "missing-keyword", |args| {
13893                Ok(args[1].as_int().unwrap())
13894            })
13895            .unwrap();
13896        assert!(none.is_empty());
13897    }
13898
13899    #[test]
13900    fn expand_source_and_collect_calls_to_skips_non_matching_forms_without_invoking_project() {
13901        // Path-uniformity for the soft-projection posture inherited
13902        // from `iter_calls_to`: non-matching forms in the source are
13903        // skipped silently, and `project` is never invoked on them.
13904        // Sibling of the from-forms test of the same name, sourced
13905        // from `&str` with a mix of atoms-and-lists that the reader
13906        // emits at the top level.
13907        let src = "bare-atom (defalert :idx 1) (defnotify :idx 2)";
13908        let mut count = 0usize;
13909        let mut e = Expander::new();
13910        let out: Vec<()> = e
13911            .expand_source_and_collect_calls_to(src, "defmonitor", |_args| {
13912                count += 1;
13913                Ok(())
13914            })
13915            .expect("non-matching-only source must collect to empty Vec");
13916        assert!(out.is_empty(), "no matching forms — empty Vec, got {out:?}");
13917        assert_eq!(count, 0, "project must never run for non-matching forms");
13918    }
13919
13920    // ── Expander::expand_source_and_collect_calls_to_any — from-source × classifier ─
13921    //
13922    // The from-source posture of the typed-decoded classifier sibling on
13923    // the `Expander` surface — closes the (from-forms, from-source) ×
13924    // (keyword, classifier) 2×2 of compose-on-iter projections at the
13925    // from-source classifier corner the prior runs left open. The
13926    // pre-existing `expand_source_and_collect_calls_to` (from-source ×
13927    // keyword) is the constant-classifier projection of this primitive:
13928    // its body composes this function with a `|h| (h == keyword).
13929    // then_some(())` decoder, so the from-source pipeline lives at ONE
13930    // site. These tests pin both the typed-decoded primitive's contract
13931    // directly AND the post-lift routing of
13932    // `expand_source_and_collect_calls_to` through it.
13933
13934    #[test]
13935    fn expand_source_and_collect_calls_to_any_yields_decoded_pair_for_every_matching_form_in_source_order(
13936    ) {
13937        // Happy path: a source string with four top-level forms (two
13938        // matching "foo", one matching "bar", one rejected-head
13939        // "defmonitor") flows through `read` → `expand_program` → the
13940        // typed-decoded classifier walk. The closed-set classifier
13941        // `Op::from_keyword` admits "foo" and "bar" but rejects
13942        // "defmonitor". Pin the typed-decoded yield shape (decoded
13943        // witness alongside args tail) AND source-order preservation
13944        // sourced from `&str` rather than a pre-parsed `Vec<Sexp>`.
13945        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
13946        enum Op {
13947            Foo,
13948            Bar,
13949        }
13950        fn op_from_keyword(head: &str) -> Option<Op> {
13951            match head {
13952                "foo" => Some(Op::Foo),
13953                "bar" => Some(Op::Bar),
13954                _ => None,
13955            }
13956        }
13957        let src = r#"(foo :idx 1)
13958                     (defmonitor :idx 99)
13959                     (bar :idx 2)
13960                     (foo :idx 3)"#;
13961        let mut e = Expander::new();
13962        let yielded: Vec<(Op, i64)> = e
13963            .expand_source_and_collect_calls_to_any(src, op_from_keyword, |op, args| {
13964                Ok((op, args[1].as_int().expect("test args are ints")))
13965            })
13966            .expect("matching forms must compose through from-source classifier walk");
13967        assert_eq!(
13968            yielded,
13969            vec![(Op::Foo, 1), (Op::Bar, 2), (Op::Foo, 3)],
13970            "yields must be in source order, decoded witness paired with per-form mapper output"
13971        );
13972    }
13973
13974    #[test]
13975    fn expand_source_and_collect_calls_to_any_short_circuits_on_reader_error_before_classifier_runs(
13976    ) {
13977        // The reader runs BEFORE `expand_program` AND BEFORE the
13978        // classifier — so a reader error (an unterminated string)
13979        // short-circuits the entire composition and both the classifier
13980        // AND `project` must NEVER run. Pin the ordering with BOTH
13981        // decoder and projection explicitly panicking — any post-reader
13982        // execution fires the panic. Sibling of
13983        // `expand_source_and_collect_calls_to_short_circuits_on_reader_error_before_expand_program`
13984        // one corner over in the 2×2 (the classifier version of the
13985        // same ordering pin).
13986        let mut e = Expander::new();
13987        let err = e
13988            .expand_source_and_collect_calls_to_any::<(), _, _, ()>(
13989                "(defmonitor :name \"unbalanced",
13990                |_h: &str| -> Option<()> {
13991                    panic!("classifier must not run when reader errors at parse time")
13992                },
13993                |(), _args| -> Result<()> { panic!("project must not run when reader errors") },
13994            )
13995            .expect_err("reader error must short-circuit before classifier");
13996        // Sanity: the error is a reader-stage rejection.
13997        let rendered = format!("{err}");
13998        assert!(
13999            rendered.to_lowercase().contains("string")
14000                || rendered.to_lowercase().contains("paren")
14001                || rendered.to_lowercase().contains("eof")
14002                || rendered.to_lowercase().contains("unexpected")
14003                || rendered.to_lowercase().contains("unterminated")
14004                || rendered.to_lowercase().contains("unclosed"),
14005            "error must be the reader-stage rejection, got: {rendered}"
14006        );
14007    }
14008
14009    #[test]
14010    fn expand_source_and_collect_calls_to_any_short_circuits_on_expand_program_error_before_classifier_runs(
14011    ) {
14012        // Reader succeeds, but `expand_program` rejects a malformed
14013        // `defmacro` head (NAME slot is an int, not a symbol). Pin the
14014        // ordering: `expand_program` rejects BEFORE the classifier walks
14015        // anything, both decoder AND `project` must NEVER run. Sibling
14016        // of the keyword-from-source variant — one corner over in the
14017        // 2×2 with both classifier and projection explicitly panicking.
14018        let mut e = Expander::new();
14019        let err = e
14020            .expand_source_and_collect_calls_to_any::<(), _, _, ()>(
14021                "(defmacro 5 (x) `,x) (defmonitor :name \"x\")",
14022                |_h: &str| -> Option<()> {
14023                    panic!("classifier must not run when expand_program errors")
14024                },
14025                |(), _args| -> Result<()> {
14026                    panic!("project must not run when expand_program errors")
14027                },
14028            )
14029            .expect_err("expand_program error must short-circuit before classifier");
14030        let rendered = format!("{err}");
14031        assert!(
14032            rendered.contains("NAME") || rendered.contains("symbol"),
14033            "error must be the defmacro-NAME-not-a-symbol rejection, got: {rendered}"
14034        );
14035    }
14036
14037    #[test]
14038    fn expand_source_and_collect_calls_to_any_skips_non_matching_forms_without_invoking_project() {
14039        // Path-uniformity for the soft-projection posture inherited
14040        // from `iter_calls_to_any`: forms whose head the classifier
14041        // rejects are skipped silently, and `project` is never invoked
14042        // on them. Pin via an explicitly-panicking projection across a
14043        // mix of forms (atom, list-with-non-symbol-head, list with
14044        // unrecognized-by-classifier symbol head). A regression that
14045        // wrongly invokes `project` on a rejected form fires the panic.
14046        let src = r#"bare-atom
14047                     (5 :not-a-symbol-head)
14048                     (defmonitor :name "decoder-rejects-me")"#;
14049        let mut e = Expander::new();
14050        // The closed-set classifier admits NOTHING in this source.
14051        let out: Vec<()> = e
14052            .expand_source_and_collect_calls_to_any::<(), _, _, ()>(
14053                src,
14054                |_h: &str| -> Option<()> { None },
14055                |(), _args| -> Result<()> {
14056                    panic!("project must not run for classifier-rejected forms")
14057                },
14058            )
14059            .expect("classifier-rejects-all source must collect to empty Vec");
14060        assert!(
14061            out.is_empty(),
14062            "no classifier-accepted forms — empty Vec, got {out:?}"
14063        );
14064    }
14065
14066    #[test]
14067    fn expand_source_and_collect_calls_to_any_short_circuits_on_project_error_at_first_failure() {
14068        // Reader + `expand_program` + classifier all succeed; the
14069        // per-form `project` errors on the SECOND matched form. Pin: the
14070        // collect's short-circuit fires at the second match's error, the
14071        // third match's `project` is NEVER invoked, and the returned
14072        // error is exactly the one the closure raised. Sibling of the
14073        // keyword-from-source variant — one corner over in the 2×2.
14074        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14075        enum Op {
14076            Foo,
14077        }
14078        fn op_from_keyword(head: &str) -> Option<Op> {
14079            (head == "foo").then_some(Op::Foo)
14080        }
14081        let src = "(foo :idx 1) (foo :idx 2) (foo :idx 3)";
14082        let mut seen = Vec::new();
14083        let mut e = Expander::new();
14084        let err = e
14085            .expand_source_and_collect_calls_to_any::<(), _, _, _>(
14086                src,
14087                op_from_keyword,
14088                |_op: Op, args: &[Sexp]| -> Result<()> {
14089                    let n = args[1].as_int().expect("test args are ints");
14090                    seen.push(n);
14091                    if n == 2 {
14092                        Err(LispError::Compile {
14093                            form: "test".to_string(),
14094                            message: "stop at two".to_string(),
14095                        })
14096                    } else {
14097                        Ok(())
14098                    }
14099                },
14100            )
14101            .expect_err("project's error must short-circuit collect");
14102        assert_eq!(seen, vec![1, 2], "third match's project must never run");
14103        assert!(
14104            matches!(err, LispError::Compile { ref message, .. } if message == "stop at two"),
14105            "short-circuit must propagate the project's error verbatim, got {err:?}"
14106        );
14107    }
14108
14109    #[test]
14110    fn expand_source_and_collect_calls_to_routes_through_expand_source_and_collect_calls_to_any_via_constant_classifier_composition(
14111    ) {
14112        // The post-lift composition law binding the keyword-from-source
14113        // sibling to the typed-decoded primitive:
14114        //   `expand_source_and_collect_calls_to(src, k, project) ==
14115        //    expand_source_and_collect_calls_to_any(src,
14116        //        |h| (h == k).then_some(()),
14117        //        |(), args| project(args))`
14118        // Pin shape AND ordering across THREE representative keywords
14119        // (matching some, matching exactly one, matching none) on the
14120        // SAME mixed source the keyword path-uniformity test exercises.
14121        // A regression that re-implements the keyword-from-source filter
14122        // as a parallel `read + expand_and_collect_calls_to` pipeline
14123        // (rather than routing through the typed-decoded primitive)
14124        // fails the value pin.
14125        let src = "(defmacro emit-foo (n) `(foo :idx ,n))
14126                   (foo :idx 1)
14127                   (emit-foo 2)
14128                   (bar :idx 99)
14129                   (foo :idx 3)";
14130        for k in ["foo", "bar", "missing"] {
14131            let mut e_keyword = Expander::new();
14132            let via_keyword: Vec<i64> = e_keyword
14133                .expand_source_and_collect_calls_to(src, k, |args| Ok(args[1].as_int().unwrap()))
14134                .unwrap();
14135
14136            let mut e_classifier = Expander::new();
14137            let via_classifier: Vec<i64> = e_classifier
14138                .expand_source_and_collect_calls_to_any(
14139                    src,
14140                    |h: &str| (h == k).then_some(()),
14141                    |(), args: &[Sexp]| Ok(args[1].as_int().unwrap()),
14142                )
14143                .unwrap();
14144
14145            assert_eq!(
14146                via_keyword, via_classifier,
14147                "keyword from-source path must equal classifier from-source path for {k:?}"
14148            );
14149        }
14150    }
14151
14152    #[test]
14153    fn expand_source_and_collect_calls_to_any_expands_macros_before_filtering_by_classifier() {
14154        // Critical ordering preserved across the typed-decoded
14155        // from-source posture: `defmacro` source registers in
14156        // `expand_program`, its call sites expand to a classifier-
14157        // accepted form, AND the classifier walk sees both the
14158        // macro-emitted form and any directly-authored matches in
14159        // source order. Sibling of the keyword-from-source ordering
14160        // test, one corner over in the 2×2.
14161        #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14162        enum Op {
14163            Foo,
14164        }
14165        fn op_from_keyword(head: &str) -> Option<Op> {
14166            (head == "foo").then_some(Op::Foo)
14167        }
14168        let src = "(defmacro emit-foo (n) `(foo :name ,n))
14169                   (emit-foo \"alpha\")
14170                   (foo :name \"beta\")";
14171        let mut e = Expander::new();
14172        let names: Vec<(Op, String)> = e
14173            .expand_source_and_collect_calls_to_any(src, op_from_keyword, |op, args| {
14174                Ok((op, args[1].as_string().unwrap().to_string()))
14175            })
14176            .expect("macroexpanded + directly-authored forms must both flow through classifier");
14177        assert_eq!(
14178            names,
14179            vec![
14180                (Op::Foo, "alpha".to_string()),
14181                (Op::Foo, "beta".to_string()),
14182            ]
14183        );
14184    }
14185
14186    // ── expand_source_program — from-source × yield-all primitive ───
14187    //
14188    // Closes the 2×2 program-level walk family on the `Expander` surface:
14189    // from-forms × yield-all (`expand_program`), from-forms × keyword-
14190    // projected (`expand_and_collect_calls_to`), from-source × keyword-
14191    // projected (`expand_source_and_collect_calls_to`), AND now
14192    // from-source × yield-all (`expand_source_program`). Pins (a) the
14193    // happy path (mixed forms flow through reader then expand_program),
14194    // (b) reader-stage short-circuit (no expand_program side-effects),
14195    // (c) expand_program-stage short-circuit (defmacro-NAME-not-symbol
14196    // is rejected at its offending form), (d) defmacro absorption (the
14197    // primitive's side-effect on `self.macros` matches the from-forms
14198    // posture's side-effect verbatim), (e) the empty-source boundary,
14199    // and (f) structural identity vs. the inlined pre-lift `read +
14200    // expand_program` pipeline (path-uniformity gate).
14201
14202    #[test]
14203    fn expand_source_program_routes_through_reader_then_expand_program() {
14204        // Happy path: a source string with mixed forms (a `defmacro`
14205        // definition, a `defmonitor` form, a plain symbol literal) flows
14206        // through `read` then through the from-forms primitive. Pin the
14207        // SAME emission shape the from-forms test pins, sourced from a
14208        // `&str` rather than a `Vec<Sexp>`.
14209        let src = r#"(defmacro id (x) `,x)
14210                     (defmonitor :name "alpha")
14211                     bare-symbol"#;
14212        let mut e = Expander::new();
14213        let out = e
14214            .expand_source_program(src)
14215            .expect("mixed forms must compose");
14216        // `(defmacro id …)` is consumed as a side-effect (registers `id`
14217        // in `e.macros`), so the returned `Vec<Sexp>` contains the
14218        // remaining two top-level forms in source order.
14219        assert_eq!(out.len(), 2);
14220        assert_eq!(
14221            out[0].as_call_to("defmonitor").map(<[_]>::len),
14222            Some(2),
14223            "first surviving form is the defmonitor with two args, got: {:?}",
14224            out[0]
14225        );
14226        assert_eq!(
14227            out[1].as_symbol(),
14228            Some("bare-symbol"),
14229            "second surviving form is the bare symbol literal, got: {:?}",
14230            out[1]
14231        );
14232        // The defmacro side-effect landed in the expander's macro table.
14233        assert!(
14234            e.has("id"),
14235            "defmacro must register `id` in the expander's macro table"
14236        );
14237    }
14238
14239    #[test]
14240    fn expand_source_program_short_circuits_on_reader_error_before_expand_program() {
14241        // The reader runs BEFORE `expand_program` — so a reader error (an
14242        // unterminated string, an unbalanced paren) must short-circuit
14243        // the entire composition and `expand_program` must NEVER run.
14244        // Pin the ordering: a `defmacro` BEFORE the unterminated string
14245        // is NOT registered, because the reader fails at parse time
14246        // before `expand_program` sees any form.
14247        let mut e = Expander::new();
14248        let err = e
14249            .expand_source_program(r#"(defmacro should-not-register (x) `,x) "unterminated"#)
14250            .expect_err("reader error must short-circuit before expand_program");
14251        // Sanity: the error IS a reader-stage rejection.
14252        let rendered = format!("{err}").to_lowercase();
14253        assert!(
14254            rendered.contains("string")
14255                || rendered.contains("paren")
14256                || rendered.contains("eof")
14257                || rendered.contains("unexpected")
14258                || rendered.contains("unterminated")
14259                || rendered.contains("unclosed"),
14260            "error must be the reader-stage rejection, got: {err}"
14261        );
14262        // Path-uniformity gate: the `defmacro` lexically BEFORE the
14263        // unterminated string must NOT have been absorbed — `read`'s
14264        // failure short-circuits the entire pipeline before
14265        // `expand_program` walks any forms.
14266        assert!(
14267            !e.has("should-not-register"),
14268            "reader-stage rejection must short-circuit BEFORE expand_program registers any defmacro"
14269        );
14270    }
14271
14272    #[test]
14273    fn expand_source_program_short_circuits_on_expand_program_error() {
14274        // Reader succeeds, but `expand_program` rejects a malformed
14275        // `defmacro` head (NAME slot is an int, not a symbol). Pin the
14276        // ordering: the expand_program-stage rejection bubbles up
14277        // verbatim, sourced from `&str` rather than `Vec<Sexp>`.
14278        let mut e = Expander::new();
14279        let err = e
14280            .expand_source_program("(defmacro 5 (x) `,x)")
14281            .expect_err("expand_program error must propagate");
14282        let rendered = format!("{err}");
14283        assert!(
14284            rendered.contains("NAME") || rendered.contains("symbol"),
14285            "error must be the defmacro-NAME-not-a-symbol rejection, got: {rendered}"
14286        );
14287    }
14288
14289    #[test]
14290    fn expand_source_program_yields_empty_vec_for_empty_source() {
14291        // Boundary: empty source string yields zero items. Pin the
14292        // degenerate boundary — empty in, empty out — sibling of the
14293        // from-source × keyword-projected test of the same name, one
14294        // level down the composition (no keyword filter).
14295        let mut e = Expander::new();
14296        let out = e
14297            .expand_source_program("")
14298            .expect("empty source is not an error");
14299        assert!(
14300            out.is_empty(),
14301            "empty source must yield empty Vec, got: {out:?}"
14302        );
14303    }
14304
14305    #[test]
14306    fn expand_source_program_absorbs_defmacro_and_expands_subsequent_calls() {
14307        // Critical pipeline order: a `defmacro` in the source registers
14308        // AND a subsequent call to it expands AND the expanded form
14309        // surfaces in the returned Vec. Sourced from `&str`, the
14310        // from-source posture preserves the side-effect-then-expansion
14311        // semantics of the from-forms primitive verbatim.
14312        let src = r#"(defmacro emit-monitor (n) `(defmonitor :name ,n))
14313                     (emit-monitor "alpha")
14314                     (emit-monitor "beta")"#;
14315        let mut e = Expander::new();
14316        let out = e
14317            .expand_source_program(src)
14318            .expect("defmacro absorption then expansion must compose");
14319        // Two surviving forms (the `defmacro` itself is consumed as a
14320        // side-effect), each expanded to a `(defmonitor :name "<n>")`.
14321        assert_eq!(out.len(), 2, "expected two expanded forms, got: {out:?}");
14322        for (i, expected_name) in [(0, "alpha"), (1, "beta")] {
14323            let args = out[i]
14324                .as_call_to("defmonitor")
14325                .unwrap_or_else(|| panic!("form {i} must be defmonitor, got: {:?}", out[i]));
14326            assert_eq!(args[0].as_keyword(), Some("name"));
14327            assert_eq!(args[1].as_string(), Some(expected_name));
14328        }
14329        // The macro is now in the table — a subsequent call would
14330        // expand against this SAME expander.
14331        assert!(e.has("emit-monitor"));
14332    }
14333
14334    #[test]
14335    fn expand_source_program_matches_inlined_read_plus_expand_program_path() {
14336        // Structural identity: for any `src` whose pieces succeed, the
14337        // from-source method's result equals the inlined pre-lift
14338        // pipeline `read + expand_program`. Pin shape AND ordering on a
14339        // mixed source (defmacro definition + macro call + plain form)
14340        // so a regression that drifts the method's pipeline from the
14341        // inlined closed-form fails here. This is the lift's
14342        // load-bearing path-uniformity gate — the SAME assertion holds
14343        // for both production consumers (`RealizedCompiler::compile` +
14344        // `realize_in_memory`'s `:macros` loop) at the primitive's
14345        // contract level.
14346        let src = r#"(defmacro id (x) `,x)
14347                     (id (foo 1 2))
14348                     (bar)"#;
14349
14350        // Inlined pre-lift pipeline.
14351        let mut e_inline = Expander::new();
14352        let inline_forms = read(src).unwrap();
14353        let via_inline = e_inline
14354            .expand_program(inline_forms)
14355            .expect("inlined pipeline must succeed");
14356
14357        // From-source method pipeline.
14358        let mut e_method = Expander::new();
14359        let via_method = e_method
14360            .expand_source_program(src)
14361            .expect("from-source method pipeline must succeed");
14362
14363        assert_eq!(
14364            via_inline, via_method,
14365            "from-source method must emit byte-identical result to inlined read+expand_program"
14366        );
14367        // Both pipelines registered the same defmacro side-effect.
14368        assert_eq!(e_inline.has("id"), e_method.has("id"));
14369        assert!(e_method.has("id"));
14370    }
14371
14372    #[test]
14373    fn expand_source_program_preserves_defmacro_absorption_across_repeated_calls() {
14374        // Per-`&mut self` absorption posture pin: a `defmacro` registered
14375        // in call 1 SURVIVES into call 2 on the SAME `Expander`. This is
14376        // the load-bearing semantic `realize_in_memory`'s per-spec-macro
14377        // build-up depends on — every iteration's `expand_source_program(
14378        // macro_src)?` lands its `defmacro` heads into the SAME mutable
14379        // `preloaded` expander, so a follow-up `:macros` source can
14380        // invoke macros defined in earlier ones.
14381        let mut e = Expander::new();
14382        let _ = e
14383            .expand_source_program("(defmacro outer (n) `(inner ,n))")
14384            .unwrap();
14385        assert!(e.has("outer"));
14386        // Call 2: defines `inner` AND invokes `outer`, which expands to
14387        // `(inner <n>)`. The expansion proves call 1's `outer` survived
14388        // into call 2's expansion.
14389        let out = e
14390            .expand_source_program("(defmacro inner (x) `(wrapped ,x)) (outer 42)")
14391            .unwrap();
14392        assert_eq!(out.len(), 1, "call 2 yields one expanded form");
14393        // (outer 42) → (inner 42) → (wrapped 42)
14394        let args = out[0]
14395            .as_call_to("wrapped")
14396            .expect("nested expansion must reach `wrapped`");
14397        assert_eq!(args[0].as_int(), Some(42));
14398    }
14399
14400    // ── Expander::register_macro_def: the macro-registration primitive ──
14401    //
14402    // `register_macro_def(&mut self, def: MacroDef) -> Result<()>` lifts
14403    // the byte-identical two-step block —
14404    //
14405    //   if self.compile_templates {
14406    //       self.templates.insert(def.name.clone(), compile_template(&def)?);
14407    //   }
14408    //   self.macros.insert(def.name.clone(), def);
14409    //
14410    // — that lived inline at `with_macros` (the bulk-from-iterator
14411    // constructor) AND `expand_program`'s `(defmacro …)`-head arm (the
14412    // program-walk-time registration site) into ONE named method on the
14413    // `Expander` surface. The tests below pin:
14414    //
14415    //   (a) the bytecode-default expander (`Expander::new()`) populates
14416    //       BOTH `macros` AND `templates` keyed by `def.name`,
14417    //   (b) the substitute-only expander (`Expander::new_substitute_only()`)
14418    //       populates `macros` but SKIPS `templates` — the
14419    //       `compile_templates: false` gate fires structurally,
14420    //   (c) a template that fails to compile (`,foo` against `params:
14421    //       []`) short-circuits BEFORE `self.macros.insert` runs, so the
14422    //       failed registration leaves both tables untouched — no
14423    //       partial-write window in which `self.macros.has(name)` is
14424    //       true but `self.templates.has(name)` is missing,
14425    //   (d) `with_macros([def])` and `register_macro_def(def)` on a fresh
14426    //       expander produce structurally identical state (both tables'
14427    //       sets of keys + the same `MacroDef` body under each key),
14428    //   (e) `expand_program` of one `(defmacro …)` form and
14429    //       `register_macro_def` of the parsed `MacroDef` produce
14430    //       structurally identical state — closing path-uniformity
14431    //       across the lift's two consumers AT the registration site.
14432    //
14433    // The tests bind on `Expander::has(name)` / `Expander::len()` for
14434    // the macros table and on bytecode-vs-substitute output equivalence
14435    // (a registered macro must expand to the SAME result regardless of
14436    // whether the bytecode path's `self.templates` entry exists) for the
14437    // templates table.
14438
14439    fn macro_def_id() -> MacroDef {
14440        // `(defmacro id (x) `,x)` — the simplest well-formed MacroDef:
14441        // one required param, a single-symbol unquote body. Compiles to
14442        // a valid `CompiledTemplate` (no unbound vars), so
14443        // `register_macro_def` succeeds on every Expander posture.
14444        MacroDef {
14445            name: "id".into(),
14446            params: MacroParams {
14447                required: vec!["x".into()],
14448                optional: vec![],
14449                rest: None,
14450            },
14451            // ` ` quasi-quoted `,x` — `Quasiquote(Unquote(Symbol("x")))`.
14452            body: Sexp::Quasiquote(Box::new(Sexp::Unquote(Box::new(Sexp::symbol("x"))))),
14453        }
14454    }
14455
14456    fn macro_def_bad_template() -> MacroDef {
14457        // `(defmacro bad () `,unbound)` — a quasi-quote body with `,unbound`
14458        // against an EMPTY required-params list. `compile_template`
14459        // rejects it with `UnboundTemplateVar` because `unbound` is not
14460        // in `params.names()`. Used to exercise the
14461        // `compile_template`-failed-but-self.macros-still-pristine
14462        // path-uniformity pin.
14463        MacroDef {
14464            name: "bad".into(),
14465            params: MacroParams::default(),
14466            body: Sexp::Quasiquote(Box::new(Sexp::Unquote(Box::new(Sexp::symbol("unbound"))))),
14467        }
14468    }
14469
14470    #[test]
14471    fn register_macro_def_bytecode_default_populates_macros_and_templates() {
14472        // The bytecode-default `Expander::new()` carries
14473        // `compile_templates: true`; `register_macro_def` MUST populate
14474        // BOTH `self.macros` (the substitute path's registry) AND
14475        // `self.templates` (the bytecode path's pre-compiled bytecode
14476        // index) keyed by `def.name`. Fail-before-pass-after: this
14477        // assert requires the new method to exist AND to compose the
14478        // two side-effects in canonical order on a single-call
14479        // registration — pre-lift `register_macro_def` did not exist.
14480        let mut e = Expander::new();
14481        e.register_macro_def(macro_def_id())
14482            .expect("well-formed MacroDef must register");
14483        assert!(
14484            e.has("id"),
14485            "self.macros must carry the registered name after register_macro_def"
14486        );
14487        assert!(
14488            e.templates.contains_key("id"),
14489            "self.templates must carry the compiled bytecode under the bytecode-default posture"
14490        );
14491        // The registered macro must expand correctly through the
14492        // bytecode strategy — proves the inserted template is the right
14493        // bytecode for the body.
14494        let out = e
14495            .expand_program(read("(id 42)").unwrap())
14496            .expect("registered macro must expand");
14497        assert_eq!(out.len(), 1);
14498        assert_eq!(out[0], Sexp::int(42));
14499    }
14500
14501    #[test]
14502    fn register_macro_def_substitute_only_skips_templates() {
14503        // `Expander::new_substitute_only()` carries `compile_templates:
14504        // false`; `register_macro_def` MUST populate `self.macros` for
14505        // the substitute path but SKIP `self.templates` (no bytecode
14506        // pre-compile). Pin the `compile_templates: false` gate fires
14507        // structurally — a regression that drifts the conditional from
14508        // the registration primitive (e.g. a future emitter that
14509        // unconditionally pre-compiles) would silently double the
14510        // benchmark baseline's allocation footprint.
14511        let mut e = Expander::new_substitute_only();
14512        e.register_macro_def(macro_def_id())
14513            .expect("well-formed MacroDef must register under substitute-only");
14514        assert!(
14515            e.has("id"),
14516            "self.macros must carry the registered name even under substitute-only"
14517        );
14518        assert!(
14519            !e.templates.contains_key("id"),
14520            "self.templates MUST be empty under compile_templates: false — the gate fires"
14521        );
14522        // The registered macro must still expand correctly through the
14523        // substitute strategy — proves the inserted MacroDef is the
14524        // right body for substitute-walking.
14525        let out = e
14526            .expand_program(read("(id 42)").unwrap())
14527            .expect("registered macro must expand via substitute path");
14528        assert_eq!(out.len(), 1);
14529        assert_eq!(out[0], Sexp::int(42));
14530    }
14531
14532    #[test]
14533    fn register_macro_def_template_compile_failure_leaves_both_tables_pristine() {
14534        // The structural ordering pin: when `compile_template(&def)?`
14535        // rejects (e.g. `,unbound` against empty params), the `?` MUST
14536        // short-circuit BEFORE `self.macros.insert(def.name.clone(),
14537        // def)` runs, so a failed registration leaves BOTH tables
14538        // exactly as they were. Without this ordering a future
14539        // bytecode-strategy lookup would resolve `self.macros.get(name)`
14540        // (the body was inserted) AND find `self.templates.get(name)`
14541        // returning `None` (the pre-compile failed), silently coercing
14542        // the macro onto the substitute fallback path despite
14543        // `compile_templates: true`. The lift preserves the pre-lift
14544        // ordering (`compile_template` precedes `self.macros.insert`)
14545        // structurally — the test pins it across the lift.
14546        let mut e = Expander::new();
14547        let err = e
14548            .register_macro_def(macro_def_bad_template())
14549            .expect_err("unbound-template body must reject");
14550        // The rejection is the structural variant the bytecode
14551        // strategy's template-gate emits.
14552        assert!(
14553            matches!(err, LispError::UnboundTemplateVar { .. }),
14554            "expected UnboundTemplateVar, got: {err:?}"
14555        );
14556        // Both tables MUST be pristine — no partial write.
14557        assert!(
14558            !e.has("bad"),
14559            "self.macros must be untouched after compile_template failure"
14560        );
14561        assert!(
14562            !e.templates.contains_key("bad"),
14563            "self.templates must be untouched after compile_template failure"
14564        );
14565    }
14566
14567    #[test]
14568    fn with_macros_routes_through_register_macro_def_path_uniformity() {
14569        // Path-uniformity pin across the bulk-constructor consumer:
14570        // `with_macros([def])` MUST produce the same final state as
14571        // `Expander::new()` + `register_macro_def(def)`. A regression
14572        // that drifts the constructor's per-MacroDef inline block from
14573        // the registration primitive (e.g. a future emitter that
14574        // re-inlines the two-step block at `with_macros` rather than
14575        // delegating) would fail loudly here.
14576        let mut via_register = Expander::new();
14577        via_register
14578            .register_macro_def(macro_def_id())
14579            .expect("register must succeed");
14580        let mut via_with_macros =
14581            Expander::with_macros([macro_def_id()]).expect("with_macros must succeed");
14582        assert_eq!(via_register.len(), via_with_macros.len());
14583        assert!(via_register.has("id"));
14584        assert!(via_with_macros.has("id"));
14585        // Both tables key on the same set across both postures.
14586        assert_eq!(
14587            via_register.templates.contains_key("id"),
14588            via_with_macros.templates.contains_key("id"),
14589            "self.templates key-presence must agree across with_macros and register_macro_def"
14590        );
14591        // Both registered expanders expand the registered macro
14592        // identically — the strongest behavioral parity.
14593        let out_a = via_register
14594            .expand_program(read("(id 99)").unwrap())
14595            .unwrap();
14596        let out_b = via_with_macros
14597            .expand_program(read("(id 99)").unwrap())
14598            .unwrap();
14599        assert_eq!(out_a, out_b);
14600        assert_eq!(out_a, vec![Sexp::int(99)]);
14601    }
14602
14603    #[test]
14604    fn expand_program_routes_through_register_macro_def_path_uniformity() {
14605        // Path-uniformity pin across the program-walk consumer:
14606        // `expand_program` of `(defmacro id (x) `,x)` MUST produce the
14607        // same final state as a direct
14608        // `register_macro_def(macro_def_id())`. A regression that
14609        // drifts `expand_program`'s `(defmacro …)`-head arm from the
14610        // registration primitive (e.g. a future emitter that re-inlines
14611        // the two-step block at `expand_program` rather than delegating)
14612        // would fail loudly here.
14613        let mut via_register = Expander::new();
14614        via_register
14615            .register_macro_def(macro_def_id())
14616            .expect("register must succeed");
14617        let mut via_expand_program = Expander::new();
14618        let yielded = via_expand_program
14619            .expand_program(read("(defmacro id (x) `,x)").unwrap())
14620            .expect("expand_program of one defmacro must succeed");
14621        // expand_program drops the (defmacro …) form from its returned
14622        // Vec<Sexp> (defmacro is a definition, not a program form), so
14623        // the yielded list is empty — pin the side-effect-only posture.
14624        assert!(
14625            yielded.is_empty(),
14626            "(defmacro …) is a side-effect-only top-level form; expand_program yields nothing"
14627        );
14628        assert!(via_register.has("id"));
14629        assert!(via_expand_program.has("id"));
14630        assert_eq!(via_register.len(), via_expand_program.len());
14631        // Both tables key on the same set across both postures.
14632        assert_eq!(
14633            via_register.templates.contains_key("id"),
14634            via_expand_program.templates.contains_key("id"),
14635            "self.templates key-presence must agree across expand_program and register_macro_def"
14636        );
14637        // Both registered expanders expand the registered macro
14638        // identically — the strongest behavioral parity, closing
14639        // path-uniformity across BOTH consumers (with_macros above,
14640        // expand_program here) at ONE primitive.
14641        let out_a = via_register
14642            .expand_program(read("(id 7)").unwrap())
14643            .unwrap();
14644        let out_b = via_expand_program
14645            .expand_program(read("(id 7)").unwrap())
14646            .unwrap();
14647        assert_eq!(out_a, out_b);
14648        assert_eq!(out_a, vec![Sexp::int(7)]);
14649    }
14650
14651    // ── as_unquote: path-uniformity across the three substitute / compile_node sites ──
14652    //
14653    // The new typed-marker projection `Sexp::as_unquote` lifts the
14654    // (Sexp::Unquote/UnquoteSplice variant, UnquoteForm::Unquote/Splice
14655    // literal) pair into ONE structural query. These tests pin behavioral
14656    // parity end-to-end across BOTH expansion strategies:
14657    //   * `compile_node` — the bytecode-template strategy's typed marker
14658    //     dispatch routes through as_unquote at the compile step.
14659    //   * `substitute` (top-level + list-inner) — the substitute-walker
14660    //     fallback strategy's typed marker dispatch routes through
14661    //     as_unquote at both per-form sites.
14662    // The structural invariant the prior runs' `expansion_layers_agree_on_
14663    // output_and_cache_wins` benchmark observes — bytecode AND substitute
14664    // produce byte-identical output — is anchored here at the macro-template
14665    // level for every distinct unquote-family shape the substitute and
14666    // bytecode strategies discriminate.
14667
14668    #[test]
14669    fn bytecode_and_substitute_agree_on_unquote_substitution_routed_through_as_unquote() {
14670        // A template body whose only marker is a top-level `,x`. Both
14671        // expansion strategies route through `as_unquote` (compile_node for
14672        // bytecode, substitute for the fallback walker), each pairing
14673        // Sexp::Unquote ↔ UnquoteForm::Unquote at ONE typed projection.
14674        // Pin byte-identical output across both strategies — the
14675        // structural-invariant the new projection's lift was designed to
14676        // make load-bearing structural rather than per-site discipline.
14677        let src = "(defmacro id (x) ,x) (id 42)";
14678        let mut bc = Expander::new();
14679        let mut sub = Expander::new_substitute_only();
14680        let out_bc = bc.expand_program(read(src).unwrap()).unwrap();
14681        let out_sub = sub.expand_program(read(src).unwrap()).unwrap();
14682        assert_eq!(out_bc, out_sub, "strategies diverged on `,x` template");
14683        assert_eq!(out_bc, vec![Sexp::int(42)]);
14684    }
14685
14686    #[test]
14687    fn bytecode_and_substitute_agree_on_unquote_splice_routed_through_as_unquote() {
14688        // A template body whose marker is a list-inner `,@xs`. The substitute
14689        // strategy's list-inner Splice arm routes through `as_unquote`
14690        // matching Some((UnquoteForm::Splice, inner)); the bytecode strategy's
14691        // compile_node Splice arm routes through `as_unquote` matching the
14692        // same. Pin byte-identical output across both strategies for the
14693        // splice path — the projection's typed-marker pairing
14694        // Sexp::UnquoteSplice ↔ UnquoteForm::Splice is structurally
14695        // identical at every consumer post-lift.
14696        let src = "(defmacro wrap (xs) (list 0 ,@xs 99)) (wrap (1 2 3))";
14697        let mut bc = Expander::new();
14698        let mut sub = Expander::new_substitute_only();
14699        let out_bc = bc.expand_program(read(src).unwrap()).unwrap();
14700        let out_sub = sub.expand_program(read(src).unwrap()).unwrap();
14701        assert_eq!(out_bc, out_sub, "strategies diverged on `,@xs` template");
14702        let expected = Sexp::List(vec![
14703            Sexp::symbol("list"),
14704            Sexp::int(0),
14705            Sexp::int(1),
14706            Sexp::int(2),
14707            Sexp::int(3),
14708            Sexp::int(99),
14709        ]);
14710        assert_eq!(out_bc, vec![expected]);
14711    }
14712
14713    #[test]
14714    fn substitute_splice_outside_list_routes_through_as_unquote_typed_marker() {
14715        // The substitute strategy's top-level `,@x` arm rejects with
14716        // LispError::SpliceOutsideList (a splice marker with no containing
14717        // list to flatten into). Pre-lift the arm was
14718        // `Sexp::UnquoteSplice(inner) => Err(splice_outside_list(inner))`;
14719        // post-lift the arm routes through `as_unquote` matching
14720        // Some((UnquoteForm::Splice, inner)) and dispatches on the typed
14721        // marker. Pin path-uniformity: the rejection MUST fire identically
14722        // through the new projection. The substitute-only strategy bypasses
14723        // the bytecode path, exposing this gate directly.
14724        //
14725        // `(defmacro bad (xs) ,@xs) (bad (1 2 3))` — the body is a bare
14726        // `,@xs` at top level (NOT wrapped in a containing list), so the
14727        // substitute fallback's top-level Splice arm fires.
14728        let src = "(defmacro bad (xs) ,@xs) (bad (1 2 3))";
14729        let mut sub = Expander::new_substitute_only();
14730        let err = sub.expand_program(read(src).unwrap()).unwrap_err();
14731        assert!(
14732            matches!(err, crate::error::LispError::SpliceOutsideList { .. }),
14733            "expected SpliceOutsideList through as_unquote, got: {err:?}"
14734        );
14735    }
14736
14737    #[test]
14738    fn as_unquote_threads_typed_marker_into_unbound_template_var_rejection() {
14739        // A `,unbound` template body — the inner symbol isn't a param —
14740        // fires gate-2 (must-be-bound-in-scope). The typed marker
14741        // `UnquoteForm::Unquote` MUST thread through `as_unquote` →
14742        // `resolve_unquote_in_params(inner, params, form)` → gate-2's
14743        // rejection-builder. Pre-lift the marker was bound at the per-arm
14744        // literal `UnquoteForm::Unquote`; post-lift it's bound at the
14745        // typed projection. Pin: the rejection's `prefix` slot is
14746        // UnquoteForm::Unquote, structurally derived from the typed
14747        // projection's typed marker, NOT a literal at the arm body.
14748        let src = "(defmacro bad (x) ,unbound)";
14749        let mut bc = Expander::new();
14750        let err = bc.expand_program(read(src).unwrap()).unwrap_err();
14751        match err {
14752            crate::error::LispError::UnboundTemplateVar { prefix, .. } => {
14753                assert_eq!(
14754                    prefix,
14755                    UnquoteForm::Unquote,
14756                    "typed marker drifted from UnquoteForm::Unquote at gate-2"
14757                );
14758            }
14759            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
14760        }
14761        // Sibling negative control: `,@unbound` inside a containing list
14762        // threads UnquoteForm::Splice through the same projection. The
14763        // splice MUST be inside a list — `compile_template` rejects
14764        // top-level `,@X` bodies with SpliceOutsideList before compile_node
14765        // runs, so the typed-marker dispatch on Splice is observable only
14766        // for well-positioned splice bodies. Closes path-uniformity across
14767        // BOTH typed marker variants at the compile path.
14768        let src_splice = "(defmacro bad (x) (foo ,@unbound))";
14769        let mut bc2 = Expander::new();
14770        let err_splice = bc2.expand_program(read(src_splice).unwrap()).unwrap_err();
14771        match err_splice {
14772            crate::error::LispError::UnboundTemplateVar { prefix, .. } => {
14773                assert_eq!(
14774                    prefix,
14775                    UnquoteForm::Splice,
14776                    "typed marker drifted from UnquoteForm::Splice at gate-2"
14777                );
14778            }
14779            other => panic!("expected UnboundTemplateVar, got: {other:?}"),
14780        }
14781    }
14782
14783    // ── compile_template + contains_unquote path-uniformity through as_unquote ──
14784    //
14785    // The prior `as_unquote` projection lift (eb00684) routed `compile_node`'s
14786    // two arms and `substitute`'s top-level + list-inner arms through the
14787    // typed-marker projection but LEFT `compile_template`'s top-level
14788    // splice-outside-list gate and `contains_unquote`'s family check inline
14789    // matching `Sexp::UnquoteSplice(inner)` / `Sexp::Unquote(_) |
14790    // Sexp::UnquoteSplice(_)`. After this lift both production sites route
14791    // through `as_unquote`: `compile_template` matches `Some((UnquoteForm::
14792    // Splice, inner))` (same shape as `substitute`'s list-inner Splice arm),
14793    // `contains_unquote` uses `as_unquote().is_some()` for the family check.
14794    // Every production-site recognizer of an unquote-family wrapper now
14795    // shares ONE typed-marker projection — the (Sexp variant, UnquoteForm
14796    // variant) pairing for `,@-outside-list` is bound at ONE structural
14797    // query across all FOUR reachable splice-recognition sites, and a future
14798    // regression that drifts the marker pairing at any production site
14799    // becomes a type error at the helper boundary rather than a silent
14800    // per-site divergence.
14801
14802    #[test]
14803    fn compile_template_splice_outside_list_routes_through_as_unquote_typed_marker() {
14804        // The bytecode path's `compile_template` gate pre-rejects top-level
14805        // `,@X` bodies via `as_unquote()` matching `Some((UnquoteForm::Splice,
14806        // inner))`. Pre-lift the arm was `if let Sexp::UnquoteSplice(inner) =
14807        // body` — the LAST production-site inline `Sexp::UnquoteSplice(_)`
14808        // match outside the projection itself. Post-lift the (Sexp variant,
14809        // UnquoteForm variant) pairing for the splice-outside-list gate is
14810        // bound at ONE projection function across all three reachable
14811        // emission sites (compile_template top-level, substitute top-level,
14812        // substitute list-inner). Path-uniformity guard at the new boundary:
14813        // the bytecode-path compile-time gate now routes through the same
14814        // shape `substitute`'s list-inner Splice arm uses.
14815        //
14816        // `(defmacro bad (xs) \`,@xs)` — the body's outer quasi-quote is
14817        // peeled by `template_body`, leaving `,@xs` at top level for
14818        // `compile_template` to gate before `compile_node` walks anything.
14819        // The bytecode strategy is default-on (`Expander::new()` sets
14820        // `compile_templates: true`), so the gate fires at
14821        // `register_macro_def` time.
14822        let mut e = Expander::new();
14823        let err = e
14824            .expand_program(read("(defmacro bad (xs) `,@xs)").unwrap())
14825            .expect_err("compile_template must reject top-level ,@X via as_unquote");
14826        assert!(
14827            matches!(err, crate::error::LispError::SpliceOutsideList { .. }),
14828            "expected SpliceOutsideList through as_unquote, got: {err:?}"
14829        );
14830    }
14831
14832    #[test]
14833    fn compile_template_accepts_top_level_unquote_through_as_unquote_typed_marker() {
14834        // Negative control on the typed-marker dispatch at `compile_template`'s
14835        // top-level gate. A top-level `,X` body (Unquote, NOT Splice) is a
14836        // valid template — `compile_node` lowers it to a single `Subst(idx)`
14837        // op. The new `Some((UnquoteForm::Splice, inner))` pattern at the
14838        // gate MUST NOT fire on the Unquote variant — only Splice — so the
14839        // typed marker is observably load-bearing: a regression that drifts
14840        // the pattern from `UnquoteForm::Splice` to the wider
14841        // `Some((_, inner))` would mis-reject `(defmacro id (x) ,x)` here.
14842        //
14843        // `(defmacro id (x) ,x) (id 42)` — `id`'s body is bare `,x` at top
14844        // level; `compile_template` admits it via the typed-marker gate,
14845        // `compile_node` emits `Subst(0)`, and the call expands to `42`.
14846        let src = "(defmacro id (x) ,x) (id 42)";
14847        let mut e = Expander::new();
14848        let expanded = e
14849            .expand_program(read(src).unwrap())
14850            .expect("top-level ,X body must compile through as_unquote-typed gate");
14851        assert_eq!(expanded, vec![Sexp::int(42)]);
14852    }
14853
14854    #[test]
14855    fn contains_unquote_routes_through_as_unquote_for_unquote_family_recognition() {
14856        // The fast-path optimizer in `compile_node` short-circuits on
14857        // `contains_unquote(node)` — true iff the subtree carries an
14858        // unquote-family wrapper. Pre-lift the family check inlined
14859        // `Sexp::Unquote(_) | Sexp::UnquoteSplice(_) => true`; post-lift it
14860        // routes through `as_unquote().is_some()`. Pin path-uniformity at
14861        // the family-recognition boundary: every production-site unquote
14862        // recognizer (compile_template's gate, compile_node's per-arm
14863        // dispatch, substitute's top-level + list-inner arms, AND this
14864        // fast-path predicate) now shares ONE typed-marker projection.
14865        //
14866        // Both variants must trigger the fast-path bail — the optimizer's
14867        // gate keys on the family, not the inner. The recursion into
14868        // Quote/Quasiquote/List subtrees ALSO observes the same family
14869        // gate at every level, so a `,@xs` buried under a `\`...` outer
14870        // wrapper still fires it.
14871        let bare_unquote = Sexp::Unquote(Box::new(Sexp::symbol("x")));
14872        let bare_splice = Sexp::UnquoteSplice(Box::new(Sexp::symbol("xs")));
14873        let nested = Sexp::Quasiquote(Box::new(Sexp::List(vec![
14874            Sexp::symbol("foo"),
14875            Sexp::UnquoteSplice(Box::new(Sexp::symbol("xs"))),
14876        ])));
14877        // The projection's `is_some()` face MUST agree with the pre-lift
14878        // `matches!()` discriminant on every variant — closed-set guarantee
14879        // shared with `as_unquote`'s contract pin in `ast.rs`.
14880        assert!(super::contains_unquote(&bare_unquote));
14881        assert!(super::contains_unquote(&bare_splice));
14882        assert!(super::contains_unquote(&nested));
14883        // Negative control: shapes the projection rejects (Nil, Atom,
14884        // bare List, Quote-family without inner unquote) must NOT trigger
14885        // the fast-path bail — the projection's None face stays observably
14886        // distinct from its Some face.
14887        assert!(!super::contains_unquote(&Sexp::Nil));
14888        assert!(!super::contains_unquote(&Sexp::symbol("plain")));
14889        assert!(!super::contains_unquote(&Sexp::int(5)));
14890        assert!(!super::contains_unquote(&Sexp::List(vec![
14891            Sexp::symbol("plain"),
14892            Sexp::int(1),
14893        ])));
14894        assert!(!super::contains_unquote(&Sexp::Quote(Box::new(
14895            Sexp::symbol("inert")
14896        ))));
14897    }
14898
14899    #[test]
14900    fn contains_unquote_routes_quote_family_through_as_quote_form_typed_marker() {
14901        // Pin the lift: `contains_unquote`'s quote-family recognition
14902        // now routes through `Sexp::as_quote_form` (the 4-of-4 wrapper
14903        // projection) AND `QuoteForm::as_unquote_form` (the 2-of-4
14904        // substitution-subset gate) at ONE site — not through the pair
14905        // of `as_unquote().is_some()` + inline
14906        // `Sexp::Quote(inner) | Sexp::Quasiquote(inner)` arm. The
14907        // path-uniformity assertion: for every quote-family wrapper, the
14908        // function's behavior agrees with the manual composition through
14909        // the two typed-marker projections. A regression that re-inlines
14910        // either arm (e.g. drops the `as_unquote_form().is_some()` gate
14911        // and just returns true for any `as_quote_form().is_some()`, or
14912        // restores the per-variant `Quote | Quasiquote` arm) drifts from
14913        // this composition and surfaces here.
14914        use crate::ast::QuoteForm;
14915
14916        // Sweep every QuoteForm variant under both axes:
14917        //   * unquote-only-inner (inner = symbol):
14918        //       Unquote(s)        → true via `as_unquote_form() == Some`
14919        //       UnquoteSplice(s)  → true via `as_unquote_form() == Some`
14920        //       Quote(s)          → false (inner has no unquote, recurses to false)
14921        //       Quasiquote(s)     → false (same)
14922        //   * unquote-inner-inside-quote-wrapper:
14923        //       Quote(Unquote(s)) → true (Quote arm recurses via `as_quote_form`,
14924        //                                 inner `Unquote` returns true through the
14925        //                                 SAME projection — both arms share the
14926        //                                 ONE typed-marker site post-lift)
14927        let inner_plain = Sexp::symbol("x");
14928        let inner_unquote = Sexp::Unquote(Box::new(Sexp::symbol("x")));
14929
14930        for qf in QuoteForm::ALL {
14931            let wrapped_plain = match qf {
14932                QuoteForm::Quote => Sexp::Quote(Box::new(inner_plain.clone())),
14933                QuoteForm::Quasiquote => Sexp::Quasiquote(Box::new(inner_plain.clone())),
14934                QuoteForm::Unquote => Sexp::Unquote(Box::new(inner_plain.clone())),
14935                QuoteForm::UnquoteSplice => Sexp::UnquoteSplice(Box::new(inner_plain.clone())),
14936            };
14937            // Path-uniformity: behavior derives from the manual two-marker
14938            // composition through `as_quote_form` + `as_unquote_form`.
14939            // The substitution-subset gate (`as_unquote_form().is_some()`)
14940            // returns true for Unquote/UnquoteSplice and false for
14941            // Quote/Quasiquote — directly encoding the 2-of-4 partition.
14942            let via_manual =
14943                qf.as_unquote_form().is_some() || super::contains_unquote(&inner_plain);
14944            assert_eq!(
14945                super::contains_unquote(&wrapped_plain),
14946                via_manual,
14947                "contains_unquote drifted from as_quote_form + as_unquote_form composition for {qf:?}"
14948            );
14949
14950            let wrapped_unquote = match qf {
14951                QuoteForm::Quote => Sexp::Quote(Box::new(inner_unquote.clone())),
14952                QuoteForm::Quasiquote => Sexp::Quasiquote(Box::new(inner_unquote.clone())),
14953                QuoteForm::Unquote => Sexp::Unquote(Box::new(inner_unquote.clone())),
14954                QuoteForm::UnquoteSplice => Sexp::UnquoteSplice(Box::new(inner_unquote.clone())),
14955            };
14956            // EVERY quote-family wrapper around an inner `Unquote` must
14957            // contain unquote — either the outer projects as `Some` on
14958            // the substitution subset (Unquote/UnquoteSplice arms), OR
14959            // the outer is Quote/Quasiquote and the recursion into the
14960            // inner re-fires the projection. The pre-lift Quote arm
14961            // recursed via the inline `Sexp::Quote(inner)` match;
14962            // post-lift the SAME recursion fires via `as_quote_form`'s
14963            // typed projection. Both yield `true`.
14964            assert!(
14965                super::contains_unquote(&wrapped_unquote),
14966                "contains_unquote missed an inner Unquote under {qf:?} — \
14967                 the quote-family recursion through as_quote_form drifted"
14968            );
14969        }
14970
14971        // Negative control: a non-quote-family wrapper (List, Atom,
14972        // Nil) must NOT route through `as_quote_form` at all. The
14973        // `if let Some(...) = node.as_quote_form()` gate stays `None`
14974        // and falls through to the List/_  arms. This is the structural
14975        // partition: the projection's `None` face MUST agree with
14976        // `!matches!(node, Sexp::Quote(_) | Sexp::Quasiquote(_) |
14977        // Sexp::Unquote(_) | Sexp::UnquoteSplice(_))`.
14978        let nested_in_list = Sexp::List(vec![
14979            Sexp::symbol("outer"),
14980            Sexp::Quasiquote(Box::new(Sexp::Unquote(Box::new(Sexp::symbol("x"))))),
14981        ]);
14982        assert!(
14983            super::contains_unquote(&nested_in_list),
14984            "contains_unquote failed to descend into a List subtree containing a \
14985             Quasiquote(Unquote(_)) — list recursion arm drifted"
14986        );
14987    }
14988
14989    // ── expand_and_collect_named_calls_to_any (from-forms × named ────
14990    // × typed-decoded classifier) ─────────────────────────────────────
14991    //
14992    // The (named NAME-then-kwargs × typed-decoded classifier) cell of
14993    // the dispatcher matrix on the `Expander` surface — pre-lift the
14994    // matrix had the bare-kwargs row closed (expand_and_collect_calls_to
14995    // + expand_and_collect_calls_to_any) AND the constant-keyword
14996    // column closed on the named axis (expand_to_named +
14997    // expand_source_to_named) but the (named × classifier) cell was
14998    // open — every consumer that wanted to walk a program by typed-
14999    // decoded classifier AND extract the NAME slot per matched form
15000    // had to compose `expand_and_collect_calls_to_any` with
15001    // `split_name_slot` inline. Post-lift the cell is ONE named primitive
15002    // on the `Expander` surface, and the constant-`T::KEYWORD` named
15003    // sibling routes through it as the (constant-classifier × named)
15004    // specialization.
15005    //
15006    // The tests below pin: (a) the happy path yields the decoded triple
15007    // `(T, &str, &[Sexp])` for every matched form in source order; (b)
15008    // non-matching forms are skipped (soft-projection contract inherited
15009    // from the typed-decoded primitive); (c) the `NamedFormMissingName`
15010    // variant fires for matched forms with no NAME slot, threading the
15011    // classifier-supplied `&'static str` keyword; (d) the
15012    // `NamedFormNonSymbolName` variant fires for matched forms with a
15013    // non-symbol-or-string NAME slot, again threading the classifier-
15014    // supplied keyword; (e) the projection's `Err` short-circuits at
15015    // the first failing matched form; (f) `expand_program` runs BEFORE
15016    // the classifier filter walks — a `(defmacro …)` emitting a named
15017    // form is decoded by the classifier on the EXPANDED head, not the
15018    // macro-call head; (g) the from-source sibling agrees with `read +
15019    // from-forms` on the same source.
15020
15021    #[test]
15022    fn expand_and_collect_named_calls_to_any_yields_decoded_triple_for_every_matching_form_in_source_order(
15023    ) {
15024        // The typed-decoded named primitive's happy path: a closed-set
15025        // classifier decodes head symbols to a typed enum PAIRED with
15026        // its canonical static keyword, and the per-form projection
15027        // receives `(decoded, name, spec_args)` for every matched form
15028        // in source order. Sweep across TWO distinct classifier outcomes
15029        // — `Monitor` / `Notify` — interleaved with a non-matching form
15030        // to pin the typed-witness threading AND the NAME-slot
15031        // projection AND the source-order yield AND the rejection of
15032        // non-classifier forms in ONE assertion.
15033        #[derive(Clone, Copy, Debug, PartialEq, Eq)]
15034        enum Kind {
15035            Monitor,
15036            Notify,
15037        }
15038        let src = r#"(defmonitor cpu :threshold 80)
15039                     (other-form 99)
15040                     (defnotify email :to "ops@example.com")
15041                     (defmonitor mem :threshold 90)"#;
15042        let forms = read(src).unwrap();
15043        let mut e = Expander::new();
15044        let rows: Vec<(Kind, String, usize)> = e
15045            .expand_and_collect_named_calls_to_any(
15046                forms,
15047                |h| match h {
15048                    "defmonitor" => Some((Kind::Monitor, "defmonitor")),
15049                    "defnotify" => Some((Kind::Notify, "defnotify")),
15050                    _ => None,
15051                },
15052                |kind, name, spec_args| Ok((kind, name.to_string(), spec_args.len())),
15053            )
15054            .unwrap();
15055        assert_eq!(
15056            rows,
15057            vec![
15058                (Kind::Monitor, "cpu".to_string(), 2),
15059                (Kind::Notify, "email".to_string(), 2),
15060                (Kind::Monitor, "mem".to_string(), 2),
15061            ],
15062        );
15063    }
15064
15065    #[test]
15066    fn expand_and_collect_named_calls_to_any_skips_non_matching_forms_without_invoking_project() {
15067        // Soft-projection contract — every shape the typed-decoded
15068        // primitive rejects (non-list, empty list, list whose head is
15069        // not a symbol, list whose head decodes to `None`) skips the
15070        // projection silently. The named gate inside the wrapper
15071        // projection runs ONLY for classifier-matched forms; a non-
15072        // matching form that reaches the projection panics here.
15073        let src = r#":bare-keyword
15074                     "bare-string"
15075                     42
15076                     ()
15077                     (foo bar)
15078                     (defmonitor cpu :threshold 80)"#;
15079        let forms = read(src).unwrap();
15080        let mut e = Expander::new();
15081        let names: Vec<String> = e
15082            .expand_and_collect_named_calls_to_any(
15083                forms,
15084                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15085                |(), name, _args| Ok(name.to_string()),
15086            )
15087            .unwrap();
15088        assert_eq!(names, vec!["cpu".to_string()]);
15089    }
15090
15091    #[test]
15092    fn expand_and_collect_named_calls_to_any_emits_named_form_missing_name_through_classifier_keyword(
15093    ) {
15094        // `(defmonitor)` matches the classifier but has no NAME slot —
15095        // `split_name_slot` fires `NamedFormMissingName { keyword:
15096        // "defmonitor" }`. The classifier's `&'static str` keyword is
15097        // threaded VERBATIM through the named gate; a regression that
15098        // hardcodes a different keyword (e.g. the matched head text or
15099        // a default) would fail the structural variant identity
15100        // assertion. Fail-before-pass-after: pre-lift the primitive did
15101        // not exist; the inline `expand_and_collect_calls_to_any +
15102        // split_name_slot` composition at the consumer site was the
15103        // only path, with the keyword bound at the consumer's call
15104        // boundary rather than at the named primitive's body.
15105        let forms = read("(defmonitor)").unwrap();
15106        let mut e = Expander::new();
15107        let err = e
15108            .expand_and_collect_named_calls_to_any::<(), _, _, ()>(
15109                forms,
15110                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15111                |(), _name, _args| Ok(()),
15112            )
15113            .unwrap_err();
15114        assert!(
15115            matches!(
15116                err,
15117                crate::error::LispError::NamedFormMissingName {
15118                    keyword: "defmonitor"
15119                }
15120            ),
15121            "expected NamedFormMissingName with classifier-supplied keyword, got: {err:?}"
15122        );
15123    }
15124
15125    #[test]
15126    fn expand_and_collect_named_calls_to_any_emits_named_form_non_symbol_name_through_classifier_keyword(
15127    ) {
15128        // `(defmonitor 42 :threshold 80)` matches the classifier but
15129        // the NAME slot is an int — `split_name_slot` fires
15130        // `NamedFormNonSymbolName { keyword: "defmonitor", got:
15131        // SexpShape::Int }`. The classifier's `&'static str` keyword is
15132        // threaded through the structural rejection variant verbatim,
15133        // and the typed `SexpShape` projection on the offending slot
15134        // is preserved across the named primitive.
15135        let forms = read("(defmonitor 42 :threshold 80)").unwrap();
15136        let mut e = Expander::new();
15137        let err = e
15138            .expand_and_collect_named_calls_to_any::<(), _, _, ()>(
15139                forms,
15140                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15141                |(), _name, _args| Ok(()),
15142            )
15143            .unwrap_err();
15144        assert!(
15145            matches!(
15146                err,
15147                crate::error::LispError::NamedFormNonSymbolName {
15148                    keyword: "defmonitor",
15149                    got: crate::error::SexpShape::Int,
15150                }
15151            ),
15152            "expected NamedFormNonSymbolName with classifier-supplied keyword + SexpShape::Int, got: {err:?}"
15153        );
15154    }
15155
15156    #[test]
15157    fn expand_and_collect_named_calls_to_any_short_circuits_on_project_error_at_first_failure() {
15158        // Result projection's short-circuit contract — when the
15159        // projection returns `Err` on a matched named form, the walk
15160        // stops and subsequent matched forms are NOT projected. Pin
15161        // source-order short-circuit via a counter the projection
15162        // increments before deciding to fail: the counter sits at
15163        // exactly the index of the failing form (second match),
15164        // proving the third match never reached the projection.
15165        let forms = read("(defmon a :x 1) (defmon b :x 2) (defmon c :x 3)").unwrap();
15166        let mut count = 0usize;
15167        let mut e = Expander::new();
15168        let err = e
15169            .expand_and_collect_named_calls_to_any::<String, _, _, ()>(
15170                forms,
15171                |h| (h == "defmon").then_some(((), "defmon")),
15172                |(), name, _args| {
15173                    count += 1;
15174                    if name == "b" {
15175                        return Err(crate::error::LispError::Missing("test-failure"));
15176                    }
15177                    Ok(name.to_string())
15178                },
15179            )
15180            .expect_err("projection must short-circuit on first Err");
15181        assert_eq!(
15182            count, 2,
15183            "projection must have run on first matched form AND the failing form, then stopped"
15184        );
15185        assert!(
15186            matches!(err, crate::error::LispError::Missing("test-failure")),
15187            "expected the projection's typed Err verbatim, got: {err:?}"
15188        );
15189    }
15190
15191    #[test]
15192    fn expand_and_collect_named_calls_to_any_expands_macros_before_filtering_by_classifier() {
15193        // Ordering contract — `expand_program` runs BEFORE the
15194        // typed-decoded named walk. A `(defmacro …)` form that emits a
15195        // classifier-decoded named form must have its EXPANDED head
15196        // decoded by the classifier, not the macro-call head. Pin the
15197        // ordering with a macro `(emit-mon n thr)` expanding to
15198        // `(defmonitor ,n :threshold ,thr)` — the classifier sees the
15199        // expanded `defmonitor` head and the NAME slot's symbol value
15200        // from the macro arg.
15201        let src = r#"(defmacro emit-mon (n thr) `(defmonitor ,n :threshold ,thr))
15202                     (defmonitor cpu :threshold 80)
15203                     (emit-mon mem 90)
15204                     (other-form 99)
15205                     (emit-mon disk 70)"#;
15206        let forms = read(src).unwrap();
15207        let mut e = Expander::new();
15208        let names: Vec<String> = e
15209            .expand_and_collect_named_calls_to_any(
15210                forms,
15211                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15212                |(), name, _args| Ok(name.to_string()),
15213            )
15214            .unwrap();
15215        // `cpu` from the literal call, `mem` from the first macro
15216        // emit, `disk` from the second macro emit. The macro-emitted
15217        // forms are present BECAUSE `expand_program` ran first.
15218        assert_eq!(names, vec!["cpu", "mem", "disk"]);
15219    }
15220
15221    #[test]
15222    fn expand_source_and_collect_named_calls_to_any_matches_inlined_read_plus_from_forms_path() {
15223        // From-source posture parity: feeding source through
15224        // `expand_source_and_collect_named_calls_to_any` is byte-
15225        // identical to feeding `read(src)?` through the from-forms
15226        // sibling on a fresh expander, because the from-source posture
15227        // is `read(src)? + from-forms` by construction. A regression
15228        // that drifts the from-source posture's pipeline from the
15229        // from-forms posture's pipeline would fail here.
15230        let src = r#"(defmonitor cpu :threshold 80)
15231                     (defmonitor mem :threshold 90)"#;
15232        let via_src: Vec<String> = Expander::new()
15233            .expand_source_and_collect_named_calls_to_any(
15234                src,
15235                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15236                |(), name, _args| Ok(name.to_string()),
15237            )
15238            .unwrap();
15239        let forms = read(src).unwrap();
15240        let via_forms: Vec<String> = Expander::new()
15241            .expand_and_collect_named_calls_to_any(
15242                forms,
15243                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15244                |(), name, _args| Ok(name.to_string()),
15245            )
15246            .unwrap();
15247        assert_eq!(via_src, via_forms);
15248        assert_eq!(via_src, vec!["cpu".to_string(), "mem".to_string()]);
15249    }
15250
15251    #[test]
15252    fn expand_source_and_collect_named_calls_to_any_short_circuits_on_reader_error_before_classifier_runs(
15253    ) {
15254        // `?`-routing through `read` short-circuits BEFORE the
15255        // classifier or the named gate runs. Unbalanced paren — reader
15256        // error fires; classifier panic-decoder MUST NOT execute,
15257        // proving the read step gates the entire pipeline.
15258        let mut e = Expander::new();
15259        let err = e
15260            .expand_source_and_collect_named_calls_to_any::<(), _, _, ()>(
15261                "(defmonitor cpu :threshold 80",
15262                |_h| panic!("classifier must not run when reader fails"),
15263                |(), _name, _args| Ok(()),
15264            )
15265            .unwrap_err();
15266        // The named gate's variants MUST NOT fire — the reader error
15267        // is structurally distinct.
15268        assert!(
15269            !matches!(
15270                err,
15271                crate::error::LispError::NamedFormMissingName { .. }
15272                    | crate::error::LispError::NamedFormNonSymbolName { .. }
15273            ),
15274            "expected reader error, not named-gate variant; got: {err:?}"
15275        );
15276    }
15277
15278    // ── expand_and_collect_named_calls_to (from-forms × named × ──────
15279    // constant-keyword × untyped R) ─────────────────────────────────
15280    //
15281    // The (named NAME-then-kwargs × constant-keyword × untyped `R`)
15282    // cell of the `Expander` typed-walk family — pre-lift the cell was
15283    // reachable ONLY through `expand_to_named<T>` with the
15284    // `T: TataraDomain` parameter baking BOTH the `T::KEYWORD` filter
15285    // AND the `T::compile_from_args`-based projection through
15286    // `expand_and_collect_calls_to(forms, T::KEYWORD,
15287    // named_form_projection::<T>)`. Post-lift the cell surfaces as ONE
15288    // method that takes the keyword and the `(name, args) -> R`
15289    // projection as caller-supplied parameters, AND
15290    // `expand_to_named<T>` routes through it as the typed
15291    // `T::KEYWORD`-constant specialization — so the named-form
15292    // `split_name_slot` composition lives at ONE site
15293    // (`expand_and_collect_named_calls_to_any` body) post-lift rather
15294    // than at TWO sites pre-lift (the bare-kwargs path through
15295    // `named_form_projection<T>` AND the classifier path).
15296    //
15297    // The tests below pin: (a) the happy path yields the `(name, args)`
15298    // pair for every matched form in source order; (b) non-matching
15299    // forms are skipped (soft-projection contract inherited from the
15300    // named typed-decoded primitive); (c) the `NamedFormMissingName`
15301    // variant fires for matched forms with no NAME slot, threading the
15302    // primitive's `&'static str` keyword; (d) the
15303    // `NamedFormNonSymbolName` variant fires for matched forms with a
15304    // non-symbol-or-string NAME slot, again threading the primitive's
15305    // keyword; (e) the projection's `Err` short-circuits at the first
15306    // failing matched form; (f) `expand_program` runs BEFORE the
15307    // keyword filter walks — a `(defmacro …)` emitting a named form
15308    // has its EXPANDED head matched by the constant-keyword filter,
15309    // not the macro-call head; (g) the from-source sibling agrees with
15310    // `read + from-forms` on the same source; (h) the constant-
15311    // classifier composition law binds the runtime-keyword cell
15312    // (`expand_and_collect_named_calls_to`) to the typed-decoded
15313    // classifier cell (`expand_and_collect_named_calls_to_any`) via a
15314    // constant-classifier decoder.
15315
15316    #[test]
15317    fn expand_and_collect_named_calls_to_yields_name_and_args_for_every_matching_form_in_source_order(
15318    ) {
15319        // The constant-keyword named primitive's happy path: a runtime
15320        // `&'static str` keyword filters matched forms, and the per-
15321        // form projection receives `(name, spec_args)` for every
15322        // matched form in source order. Three matched forms
15323        // interleaved with a non-matcher pin the source-order yield,
15324        // the NAME-slot projection, AND the rejection of non-keyword
15325        // forms in ONE assertion.
15326        let src = r#"(defmonitor cpu :threshold 80)
15327                     (other-form 99)
15328                     (defmonitor mem :threshold 90 :unit "MiB")
15329                     (defmonitor disk :threshold 70)"#;
15330        let forms = read(src).unwrap();
15331        let mut e = Expander::new();
15332        let rows: Vec<(String, usize)> = e
15333            .expand_and_collect_named_calls_to(forms, "defmonitor", |name, spec_args| {
15334                Ok((name.to_string(), spec_args.len()))
15335            })
15336            .unwrap();
15337        assert_eq!(
15338            rows,
15339            vec![
15340                ("cpu".to_string(), 2),
15341                ("mem".to_string(), 4),
15342                ("disk".to_string(), 2),
15343            ],
15344        );
15345    }
15346
15347    #[test]
15348    fn expand_and_collect_named_calls_to_skips_non_matching_forms_without_invoking_project() {
15349        // Soft-projection contract — every shape the named typed-
15350        // decoded primitive rejects (non-list, empty list, list whose
15351        // head is not a symbol, list whose head doesn't match the
15352        // constant keyword) skips the projection silently. The named
15353        // gate inside the wrapper projection runs ONLY for matched
15354        // forms; a non-matching form that reaches the projection
15355        // panics here.
15356        let src = r#":bare-keyword
15357                     "bare-string"
15358                     42
15359                     ()
15360                     (foo bar)
15361                     (defmonitor cpu :threshold 80)"#;
15362        let forms = read(src).unwrap();
15363        let mut e = Expander::new();
15364        let names: Vec<String> = e
15365            .expand_and_collect_named_calls_to(forms, "defmonitor", |name, _args| {
15366                Ok(name.to_string())
15367            })
15368            .unwrap();
15369        assert_eq!(names, vec!["cpu".to_string()]);
15370    }
15371
15372    #[test]
15373    fn expand_and_collect_named_calls_to_emits_named_form_missing_name_through_primitive_keyword() {
15374        // The named-form gate's missing-NAME variant must thread the
15375        // primitive's `&'static str` keyword (NOT a hardcoded literal,
15376        // NOT the projection's own copy) — the `NamedFormMissingName
15377        // { keyword }` slot inside `split_name_slot` is bound at the
15378        // call site BY THE PRIMITIVE itself, via the constant-
15379        // classifier decoder threading `((), keyword)` into
15380        // `expand_and_collect_named_calls_to_any`.
15381        let src = r#"(defmonitor)"#;
15382        let forms = read(src).unwrap();
15383        let mut e = Expander::new();
15384        let err = e
15385            .expand_and_collect_named_calls_to::<(), _>(forms, "defmonitor", |_name, _args| Ok(()))
15386            .unwrap_err();
15387        assert!(
15388            matches!(
15389                err,
15390                crate::error::LispError::NamedFormMissingName {
15391                    keyword: "defmonitor"
15392                }
15393            ),
15394            "expected NamedFormMissingName threading the primitive's keyword; got: {err:?}",
15395        );
15396    }
15397
15398    #[test]
15399    fn expand_and_collect_named_calls_to_emits_named_form_non_symbol_name_through_primitive_keyword(
15400    ) {
15401        // The named-form gate's non-symbol-NAME variant must thread
15402        // the primitive's `&'static str` keyword AND the typed-shape
15403        // witness (`SexpShape::Int` for an integer NAME slot). The
15404        // shape projection through `split_name_slot`'s
15405        // `as_symbol_or_string()` gate fires on the first matched
15406        // form whose NAME slot is not a symbol or string.
15407        let src = r#"(defmonitor 42 :threshold 80)"#;
15408        let forms = read(src).unwrap();
15409        let mut e = Expander::new();
15410        let err = e
15411            .expand_and_collect_named_calls_to::<(), _>(forms, "defmonitor", |_name, _args| Ok(()))
15412            .unwrap_err();
15413        match err {
15414            crate::error::LispError::NamedFormNonSymbolName { keyword, got } => {
15415                assert_eq!(keyword, "defmonitor");
15416                assert_eq!(got, crate::error::SexpShape::Int);
15417            }
15418            other => panic!(
15419                "expected NamedFormNonSymbolName threading the primitive's keyword + Int shape; got: {other:?}",
15420            ),
15421        }
15422    }
15423
15424    #[test]
15425    fn expand_and_collect_named_calls_to_short_circuits_on_project_error_at_first_failure() {
15426        // `Iterator::collect::<Result<Vec<R>, _>>()` short-circuits at
15427        // the first failing projection. The second matched form's
15428        // projection error MUST fire AND the third matched form's
15429        // projection MUST NOT run. Pin both halves with a counter +
15430        // a fail-on-call sentinel.
15431        let src = r#"(defmonitor cpu :threshold 80)
15432                     (defmonitor mem :threshold 90)
15433                     (defmonitor disk :threshold 70)"#;
15434        let forms = read(src).unwrap();
15435        let mut count: usize = 0;
15436        let mut e = Expander::new();
15437        let err = e
15438            .expand_and_collect_named_calls_to::<String, _>(forms, "defmonitor", |name, _args| {
15439                count += 1;
15440                if name == "mem" {
15441                    Err(crate::error::LispError::Compile {
15442                        form: "test".into(),
15443                        message: format!("rejecting at NAME={name}"),
15444                    })
15445                } else {
15446                    Ok(name.to_string())
15447                }
15448            })
15449            .unwrap_err();
15450        assert_eq!(count, 2, "third matched form must not be projected");
15451        match err {
15452            crate::error::LispError::Compile { message, .. } => {
15453                assert!(message.contains("NAME=mem"));
15454            }
15455            other => panic!("expected projection-driven Compile error; got: {other:?}"),
15456        }
15457    }
15458
15459    #[test]
15460    fn expand_and_collect_named_calls_to_expands_macros_before_filtering_by_keyword() {
15461        // Ordering contract — `expand_program` runs BEFORE the
15462        // constant-keyword named walk. A `(defmacro …)` form that
15463        // emits a matched named form must have its EXPANDED head
15464        // matched by the constant-keyword filter, not the macro-call
15465        // head. Pin the ordering with a macro `(emit-mon n thr)`
15466        // expanding to `(defmonitor ,n :threshold ,thr)` — the
15467        // constant-keyword filter matches the expanded `defmonitor`
15468        // head and the NAME slot projects the symbol-author value.
15469        let src = r#"(defmacro emit-mon (n thr) `(defmonitor ,n :threshold ,thr))
15470                     (defmonitor cpu :threshold 80)
15471                     (emit-mon mem 90)
15472                     (other-form 99)
15473                     (emit-mon disk 70)"#;
15474        let forms = read(src).unwrap();
15475        let mut e = Expander::new();
15476        let names: Vec<String> = e
15477            .expand_and_collect_named_calls_to(forms, "defmonitor", |name, _args| {
15478                Ok(name.to_string())
15479            })
15480            .unwrap();
15481        // `cpu` from the literal call, `mem` from the first macro
15482        // emit, `disk` from the second macro emit. The macro-emitted
15483        // forms are present BECAUSE `expand_program` ran first.
15484        assert_eq!(names, vec!["cpu", "mem", "disk"]);
15485    }
15486
15487    #[test]
15488    fn expand_source_and_collect_named_calls_to_matches_inlined_read_plus_from_forms_path() {
15489        // From-source posture parity: feeding source through
15490        // `expand_source_and_collect_named_calls_to` is byte-identical
15491        // to feeding `read(src)?` through the from-forms sibling on a
15492        // fresh expander, because the from-source posture is
15493        // `read(src)? + from-forms` by construction. A regression that
15494        // drifts the from-source posture's pipeline from the from-
15495        // forms posture's pipeline would fail here.
15496        let src = r#"(defmonitor cpu :threshold 80)
15497                     (defmonitor mem :threshold 90)"#;
15498        let via_src: Vec<String> = Expander::new()
15499            .expand_source_and_collect_named_calls_to(src, "defmonitor", |name, _args| {
15500                Ok(name.to_string())
15501            })
15502            .unwrap();
15503        let forms = read(src).unwrap();
15504        let via_forms: Vec<String> = Expander::new()
15505            .expand_and_collect_named_calls_to(forms, "defmonitor", |name, _args| {
15506                Ok(name.to_string())
15507            })
15508            .unwrap();
15509        assert_eq!(via_src, via_forms);
15510        assert_eq!(via_src, vec!["cpu".to_string(), "mem".to_string()]);
15511    }
15512
15513    #[test]
15514    fn expand_source_and_collect_named_calls_to_short_circuits_on_reader_error_before_named_gate_runs(
15515    ) {
15516        // `?`-routing through `read` short-circuits BEFORE the
15517        // constant-keyword filter, the named gate, or the projection
15518        // runs. Unbalanced paren — reader error fires; the projection
15519        // panic-payload MUST NOT execute, proving the read step gates
15520        // the entire pipeline.
15521        let mut e = Expander::new();
15522        let err = e
15523            .expand_source_and_collect_named_calls_to::<(), _>(
15524                "(defmonitor cpu :threshold 80",
15525                "defmonitor",
15526                |_name, _args| panic!("projection must not run when reader fails"),
15527            )
15528            .unwrap_err();
15529        // The named gate's variants MUST NOT fire — the reader error
15530        // is structurally distinct.
15531        assert!(
15532            !matches!(
15533                err,
15534                crate::error::LispError::NamedFormMissingName { .. }
15535                    | crate::error::LispError::NamedFormNonSymbolName { .. }
15536            ),
15537            "expected reader error, not named-gate variant; got: {err:?}",
15538        );
15539    }
15540
15541    #[test]
15542    fn expand_and_collect_named_calls_to_routes_through_classifier_via_constant_decoder_composition(
15543    ) {
15544        // Composition-identity test pinning the runtime-keyword named
15545        // cell (`expand_and_collect_named_calls_to`) to the typed-
15546        // decoded named-classifier cell
15547        // (`expand_and_collect_named_calls_to_any`) via the constant-
15548        // classifier decoder shape. Post-lift the identity:
15549        //
15550        //   expand_and_collect_named_calls_to(forms, kw, project) ==
15551        //       expand_and_collect_named_calls_to_any(forms,
15552        //           |h| (h == kw).then_some(((), kw)),
15553        //           |(), name, args| project(name, args))
15554        //
15555        // Pinning the identity here makes the typed-decoded named-
15556        // classifier primitive the CANONICAL composition point the
15557        // runtime-keyword sibling routes through — parallel to how
15558        // `expand_and_collect_calls_to` routes through
15559        // `expand_and_collect_calls_to_any` via a `|h| (h == k).
15560        // then_some(())` decoder on the bare-kwargs axis. A future
15561        // regression that drifts ONE cell's NAME-slot rejection chain
15562        // from the other becomes loudly visible at this assertion.
15563        let src = r#"(defmonitor cpu :threshold 80)
15564                     (other-form 99)
15565                     (defmonitor mem :threshold 90)"#;
15566        let forms = read(src).unwrap();
15567        let via_constant_keyword: Vec<(String, usize)> = Expander::new()
15568            .expand_and_collect_named_calls_to(forms.clone(), "defmonitor", |name, args| {
15569                Ok((name.to_string(), args.len()))
15570            })
15571            .unwrap();
15572        let via_classifier: Vec<(String, usize)> = Expander::new()
15573            .expand_and_collect_named_calls_to_any(
15574                forms,
15575                |h| (h == "defmonitor").then_some(((), "defmonitor")),
15576                |(), name, args| Ok((name.to_string(), args.len())),
15577            )
15578            .unwrap();
15579        assert_eq!(via_constant_keyword, via_classifier);
15580        assert_eq!(
15581            via_constant_keyword,
15582            vec![("cpu".to_string(), 2), ("mem".to_string(), 2)],
15583        );
15584    }
15585
15586    #[test]
15587    fn expand_to_named_routes_through_expand_and_collect_named_calls_to_via_constant_keyword_composition(
15588    ) {
15589        // End-to-end path-uniformity: `Expander::expand_to_named<T>`
15590        // (the typed constant-keyword named cell) must yield the same
15591        // payload as `expand_and_collect_named_calls_to(forms,
15592        // T::KEYWORD, |name, args| { T::compile_from_args(args)? +
15593        // NamedDefinition })` — the lift's structural identity.
15594        //
15595        // Pre-lift `expand_to_named<T>` routed through
15596        // `expand_and_collect_calls_to(forms, T::KEYWORD,
15597        // named_form_projection::<T>)` (the bare-kwargs constant
15598        // primitive with `named_form_projection<T>` doing the NAME
15599        // extraction internally). Post-lift `expand_to_named<T>`
15600        // routes through the named constant-keyword primitive (which
15601        // itself routes through the classifier primitive via a
15602        // constant-classifier decoder) — so the `split_name_slot`
15603        // composition lives at ONE site (the `_any` primitive body)
15604        // post-lift rather than at TWO sites.
15605        use crate::compile::NamedDefinition;
15606        use crate::compiler_spec::CompilerSpec;
15607        let src = r#"(defcompiler alpha-compiler :name "x" :dialect "standard")
15608                     (defcompiler beta-compiler  :name "y" :dialect "standard")"#;
15609        let forms = read(src).unwrap();
15610        let via_expand_to_named = Expander::new()
15611            .expand_to_named::<CompilerSpec>(forms.clone())
15612            .unwrap();
15613        let via_named_constant: Vec<NamedDefinition<CompilerSpec>> = Expander::new()
15614            .expand_and_collect_named_calls_to(forms, "defcompiler", |name, spec_args| {
15615                let spec =
15616                    <CompilerSpec as crate::domain::TataraDomain>::compile_from_args(spec_args)?;
15617                Ok(NamedDefinition {
15618                    name: name.to_string(),
15619                    spec,
15620                })
15621            })
15622            .unwrap();
15623        assert_eq!(via_expand_to_named.len(), 2);
15624        assert_eq!(via_expand_to_named.len(), via_named_constant.len());
15625        for (a, b) in via_expand_to_named.iter().zip(via_named_constant.iter()) {
15626            assert_eq!(a.name, b.name, "NAME slot must agree across cells");
15627            assert_eq!(a.spec.name, b.spec.name, ":name spec must agree");
15628        }
15629        assert_eq!(via_expand_to_named[0].name, "alpha-compiler");
15630        assert_eq!(via_expand_to_named[0].spec.name, "x");
15631        assert_eq!(via_expand_to_named[1].name, "beta-compiler");
15632        assert_eq!(via_expand_to_named[1].spec.name, "y");
15633    }
15634
15635    // ── ExpansionDepthExceeded: runaway macro rejected instead of aborting ──
15636
15637    #[test]
15638    fn expander_default_max_expansion_depth_is_the_module_constant() {
15639        // Both constructors seed `max_expansion_depth` from the module
15640        // constant — a regression that drifts one constructor's default
15641        // from the constant (e.g. a hardcoded `256` at ONE site with the
15642        // other left uninitialized after a `Default` derive addition)
15643        // fails this pin.
15644        assert_eq!(
15645            Expander::new().max_expansion_depth(),
15646            DEFAULT_MAX_EXPANSION_DEPTH
15647        );
15648        assert_eq!(
15649            Expander::new_substitute_only().max_expansion_depth(),
15650            DEFAULT_MAX_EXPANSION_DEPTH
15651        );
15652        assert_eq!(DEFAULT_MAX_EXPANSION_DEPTH, 256);
15653    }
15654
15655    #[test]
15656    fn set_max_expansion_depth_takes_effect_on_subsequent_expand() {
15657        let mut e = Expander::new();
15658        e.set_max_expansion_depth(7);
15659        assert_eq!(e.max_expansion_depth(), 7);
15660    }
15661
15662    #[test]
15663    fn expand_recursive_macro_rejects_at_depth_limit_bytecode_path() {
15664        // `(defmacro loop (x) `(loop ,x))` applied to `(loop hello)`
15665        // pre-lift stack-overflowed the process — a below-floor abort.
15666        // Post-lift the expander returns a typed
15667        // `LispError::ExpansionDepthExceeded` at the ceiling with
15668        // `macro_name` populated from the offending call's head.
15669        let mut e = Expander::new();
15670        e.set_max_expansion_depth(4);
15671        let forms = read("(defmacro loop (x) `(loop ,x)) (loop hello)").unwrap();
15672        let err = e.expand_program(forms).unwrap_err();
15673        match err {
15674            LispError::ExpansionDepthExceeded { macro_name, limit } => {
15675                assert_eq!(macro_name, "loop");
15676                assert_eq!(limit, 4);
15677            }
15678            other => panic!("expected ExpansionDepthExceeded, got: {other:?}"),
15679        }
15680    }
15681
15682    #[test]
15683    fn expand_recursive_macro_rejects_at_depth_limit_substitute_path() {
15684        // The substitute strategy shares the same `expand` outer walker
15685        // with the bytecode strategy, so the ceiling fires at the SAME
15686        // gate regardless of which apply-layer is active. The pin makes
15687        // that shared-outer-walker property structural: a regression
15688        // that ever branches the depth guard on strategy (e.g. moves it
15689        // into `apply_compiled` alone) fails this test on the
15690        // substitute expander.
15691        let mut e = Expander::new_substitute_only();
15692        e.set_max_expansion_depth(4);
15693        let forms = read("(defmacro loop (x) `(loop ,x)) (loop hello)").unwrap();
15694        let err = e.expand_program(forms).unwrap_err();
15695        match err {
15696            LispError::ExpansionDepthExceeded { macro_name, limit } => {
15697                assert_eq!(macro_name, "loop");
15698                assert_eq!(limit, 4);
15699            }
15700            other => panic!("expected ExpansionDepthExceeded, got: {other:?}"),
15701        }
15702    }
15703
15704    #[test]
15705    fn expand_lawful_nested_macros_within_ceiling_succeed() {
15706        // Lawful nested macros (`(twice (twice hey))`) accrete depth in
15707        // the low single digits because the tree-child arm doesn't
15708        // count — only the post-apply re-expansion path bumps the
15709        // counter. A ceiling of 3 is enough for a two-deep nested
15710        // expansion. This pin is the fail-before/pass-after negative
15711        // control for the recursive-macro rejection tests above: the
15712        // depth counter must NOT reject lawful macro-nesting within
15713        // the ceiling, and only reject the runaway shape.
15714        let mut e = Expander::new();
15715        e.set_max_expansion_depth(3);
15716        let forms = read(
15717            "(defmacro twice (x) `(list ,x ,x))
15718             (twice (twice hey))",
15719        )
15720        .unwrap();
15721        let out = e.expand_program(forms).unwrap();
15722        assert_eq!(out[0], parse("(list (list hey hey) (list hey hey))"));
15723    }
15724
15725    #[test]
15726    fn expand_depth_ceiling_ignores_lawful_tree_nesting_depth() {
15727        // A macro-free lawful deep tree — five levels of plain nesting —
15728        // is expanded at a ceiling well below the tree's height. The
15729        // tree-child arm does not accrete depth, so tree height is
15730        // orthogonal to the ceiling; only macro re-expansion counts.
15731        // A regression that ever bumps depth on tree-child descent
15732        // fails this pin.
15733        let mut e = Expander::new();
15734        e.set_max_expansion_depth(2);
15735        let forms = read("(a (b (c (d (e f)))))").unwrap();
15736        let out = e.expand_program(forms).unwrap();
15737        assert_eq!(out[0], parse("(a (b (c (d (e f)))))"));
15738    }
15739
15740    #[test]
15741    fn expansion_depth_exceeded_position_is_none() {
15742        // The variant joins the non-positional cohort — the offending
15743        // macro's byte offset is not what a runaway diagnostic anchors
15744        // to; the OFFENDING MACRO NAME (`macro_name`) is. This pin
15745        // keeps the variant in the `position() -> None` cohort so a
15746        // future span-carrying edit lands the field in ONE place
15747        // across every non-positional variant simultaneously.
15748        let err = LispError::ExpansionDepthExceeded {
15749            macro_name: "loop".to_string(),
15750            limit: 256,
15751        };
15752        assert_eq!(err.position(), None);
15753    }
15754
15755    #[test]
15756    fn expansion_depth_exceeded_display_matches_typed_variant_shape() {
15757        // Display projection carries both `macro_name` and `limit`, so
15758        // authoring surfaces that substring-grep the rendered
15759        // diagnostic (`tatara-check`, REPL, LSP) see BOTH halves at
15760        // the variant boundary and never need to substring-parse
15761        // free-form text to recover the identity of the offending
15762        // macro or the ceiling it breached.
15763        let err = LispError::ExpansionDepthExceeded {
15764            macro_name: "loop".to_string(),
15765            limit: 256,
15766        };
15767        let rendered = err.to_string();
15768        assert!(rendered.contains("loop"), "rendered: {rendered}");
15769        assert!(rendered.contains("256"), "rendered: {rendered}");
15770        assert!(
15771            rendered.contains("macro expansion depth exceeded"),
15772            "rendered: {rendered}"
15773        );
15774    }
15775
15776    // ── max_cache_entries: bounded-memoization ceiling ──────────────────
15777
15778    #[test]
15779    fn expander_default_max_cache_entries_is_the_module_constant() {
15780        // Both cache-enabling constructors seed `max_cache_entries` from
15781        // the module constant — a regression that drifts one
15782        // constructor's default from the constant (e.g. a hardcoded
15783        // `8192` at ONE site with the other left uninitialized after a
15784        // `Default` derive addition) fails this pin. The peer
15785        // constructor `new_substitute_only` seeds the same ceiling so
15786        // the field stays initialized under `#[derive(Clone)]` (a
15787        // zero-initialized clone would be a silent bifurcation of the
15788        // cache-ceiling surface).
15789        assert_eq!(
15790            Expander::new().max_cache_entries(),
15791            DEFAULT_MAX_CACHE_ENTRIES
15792        );
15793        assert_eq!(
15794            Expander::new_substitute_only().max_cache_entries(),
15795            DEFAULT_MAX_CACHE_ENTRIES
15796        );
15797        assert_eq!(DEFAULT_MAX_CACHE_ENTRIES, 8192);
15798    }
15799
15800    #[test]
15801    fn set_max_cache_entries_takes_effect_on_subsequent_expand() {
15802        // Setter mirrors accessor — a regression that ever forgets to
15803        // write through to the field, or writes to a shadow field the
15804        // accessor does not read, fails this pin.
15805        let mut e = Expander::new();
15806        e.set_max_cache_entries(7);
15807        assert_eq!(e.max_cache_entries(), 7);
15808    }
15809
15810    #[test]
15811    fn expand_cache_size_is_bounded_by_max_cache_entries() {
15812        // Five distinct `(name, args)` pairs expanded against a cache
15813        // ceiling of `2` populate the cache with EXACTLY the first two
15814        // insertions and skip the remaining three. Every expansion
15815        // still returns the correct result — the cache is a pure
15816        // PERFORMANCE optimization; the ceiling gates memoization,
15817        // never correctness. LOAD-BEARING true-arm catch on the
15818        // bounded-cache contract: a regression that ignored the
15819        // ceiling (unbounded insert) would land `cache_size() == 5`
15820        // here; a regression that skipped insertion entirely (broken
15821        // cache) would still see correct expansion output but would
15822        // fail the interior `cache_size() == 2` pin.
15823        let mut e = Expander::new();
15824        e.set_max_cache_entries(2);
15825        let src = "
15826            (defmacro id (x) `,x)
15827            (id one)
15828            (id two)
15829            (id three)
15830            (id four)
15831            (id five)
15832        ";
15833        let out = e.expand_program(read(src).unwrap()).unwrap();
15834        assert_eq!(out.len(), 5);
15835        assert_eq!(out[0], parse("one"));
15836        assert_eq!(out[1], parse("two"));
15837        assert_eq!(out[2], parse("three"));
15838        assert_eq!(out[3], parse("four"));
15839        assert_eq!(out[4], parse("five"));
15840        assert_eq!(
15841            e.cache_size(),
15842            2,
15843            "cache grew past the max_cache_entries ceiling",
15844        );
15845    }
15846
15847    #[test]
15848    fn expand_zero_cache_ceiling_disables_caching_effectively() {
15849        // Zero ceiling admits every fresh `(name, args)` pair through
15850        // the compute path and never grows the cache — the same
15851        // observable behavior as `set_cache_enabled(false)` but
15852        // reached through the ceiling knob. Correctness is preserved:
15853        // the miss path always recomputes the same value the cache
15854        // would have returned, so an operator that dials the ceiling
15855        // to `0` (e.g. to hunt a cache-related regression) never
15856        // bifurcates the output surface. Peer to
15857        // `set_max_expansion_depth(0)` one RESOURCE axis over — both
15858        // ceilings admit the zero endpoint as a well-defined
15859        // extremum.
15860        let mut e = Expander::new();
15861        e.set_max_cache_entries(0);
15862        let src = "
15863            (defmacro id (x) `,x)
15864            (id one)
15865            (id two)
15866        ";
15867        let out = e.expand_program(read(src).unwrap()).unwrap();
15868        assert_eq!(out.len(), 2);
15869        assert_eq!(out[0], parse("one"));
15870        assert_eq!(out[1], parse("two"));
15871        assert_eq!(
15872            e.cache_size(),
15873            0,
15874            "zero cache ceiling grew the cache — the ceiling must be respected on every insert",
15875        );
15876    }
15877
15878    #[test]
15879    fn expand_cached_hits_survive_past_the_ceiling() {
15880        // Once a `(name, args)` pair sits IN the cache under the
15881        // ceiling, subsequent calls hit it — the ceiling only gates
15882        // NEW-KEY inserts, never lookups. This test seeds the cache
15883        // with two entries under a ceiling of `2`, then re-issues the
15884        // SAME calls to prove the cached hits still fire (cache_size
15885        // stays at `2` because no new keys are inserted, and the
15886        // expansion output is stable). LOAD-BEARING true-arm catch
15887        // that the ceiling gates the INSERT path alone — a regression
15888        // that ever refused cache LOOKUPS at the ceiling would
15889        // silently bifurcate correctness on the second call.
15890        let mut e = Expander::new();
15891        e.set_max_cache_entries(2);
15892        let src = "
15893            (defmacro id (x) `,x)
15894            (id one)
15895            (id two)
15896            (id one)
15897            (id two)
15898        ";
15899        let out = e.expand_program(read(src).unwrap()).unwrap();
15900        assert_eq!(out.len(), 4);
15901        assert_eq!(out[0], parse("one"));
15902        assert_eq!(out[1], parse("two"));
15903        assert_eq!(out[2], parse("one"));
15904        assert_eq!(out[3], parse("two"));
15905        assert_eq!(
15906            e.cache_size(),
15907            2,
15908            "cache grew past the max_cache_entries ceiling on repeated (name, args) pairs",
15909        );
15910    }
15911
15912    #[test]
15913    fn clear_cache_reopens_the_insert_path_after_the_ceiling_was_hit() {
15914        // After the cache reaches its ceiling and subsequent inserts
15915        // are skipped, `clear_cache` empties the memo and RE-OPENS the
15916        // insert path — the next expansion populates the freshly-
15917        // empty cache without operator intervention on the ceiling
15918        // itself. The pin binds the operator-facing recovery path
15919        // documented on [`DEFAULT_MAX_CACHE_ENTRIES`]: "skip cache
15920        // insertion until the operator clears the cache via
15921        // [`Expander::clear_cache`]".
15922        let mut e = Expander::new();
15923        e.set_max_cache_entries(1);
15924        let src_fill = "
15925            (defmacro id (x) `,x)
15926            (id one)
15927            (id two)
15928        ";
15929        let _ = e.expand_program(read(src_fill).unwrap()).unwrap();
15930        assert_eq!(e.cache_size(), 1, "cache did not stop at the ceiling");
15931        e.clear_cache();
15932        assert_eq!(e.cache_size(), 0, "clear_cache did not empty the memo");
15933        let out = e
15934            .expand_program(read("(defmacro id (x) `,x) (id three)").unwrap())
15935            .unwrap();
15936        assert_eq!(out.len(), 1);
15937        assert_eq!(out[0], parse("three"));
15938        assert_eq!(
15939            e.cache_size(),
15940            1,
15941            "clear_cache did not re-open the insert path — the cache should have accepted the fresh (id, three) pair",
15942        );
15943    }
15944
15945    // ── max_expansion_size: bounded-output ceiling ─────────────────────
15946
15947    #[test]
15948    fn expander_default_max_expansion_size_is_the_module_constant() {
15949        // Both constructors seed `max_expansion_size` from the module
15950        // constant — a regression that drifts one constructor's
15951        // default from the constant (e.g. a hardcoded `65_536` at
15952        // ONE site with the other left uninitialized after a
15953        // `Default` derive addition) fails this pin. Peer to the
15954        // `max_expansion_depth` + `max_cache_entries` default-
15955        // coherence pins one RESOURCE-DIMENSION axis over.
15956        assert_eq!(
15957            Expander::new().max_expansion_size(),
15958            DEFAULT_MAX_EXPANSION_SIZE
15959        );
15960        assert_eq!(
15961            Expander::new_substitute_only().max_expansion_size(),
15962            DEFAULT_MAX_EXPANSION_SIZE
15963        );
15964        assert_eq!(DEFAULT_MAX_EXPANSION_SIZE, 65_536);
15965    }
15966
15967    #[test]
15968    fn set_max_expansion_size_takes_effect_on_subsequent_expand() {
15969        // Setter mirrors accessor — a regression that ever forgets to
15970        // write through to the field, or writes to a shadow field the
15971        // accessor does not read, fails this pin. Peer to the
15972        // setter/accessor coherence pins on the depth + cache axes.
15973        let mut e = Expander::new();
15974        e.set_max_expansion_size(64);
15975        assert_eq!(e.max_expansion_size(), 64);
15976    }
15977
15978    #[test]
15979    fn expand_expansion_bomb_rejects_at_size_limit_bytecode_path() {
15980        // `(defmacro bomb (x) `(list ,x ,x ,x ,x))` applied to
15981        // `(bomb hey)` produces `(list hey hey hey hey)` — 6 nodes
15982        // (outer List + 5 children). With a ceiling of `4` this
15983        // crosses the OUTPUT-SIZE threshold at the first apply and
15984        // the expander returns a typed
15985        // `LispError::ExpansionSizeExceeded` with `macro_name`,
15986        // `size`, and `limit` all populated. Pre-lift the runaway
15987        // just accumulated in a growing tree until the process
15988        // heap ran out; post-lift the rejection sits at the same
15989        // call boundary as every other macro-expansion failure.
15990        let mut e = Expander::new();
15991        e.set_max_expansion_size(4);
15992        let forms = read("(defmacro bomb (x) `(list ,x ,x ,x ,x)) (bomb hey)").unwrap();
15993        let err = e.expand_program(forms).unwrap_err();
15994        match err {
15995            LispError::ExpansionSizeExceeded {
15996                macro_name,
15997                size,
15998                limit,
15999            } => {
16000                assert_eq!(macro_name, "bomb");
16001                assert_eq!(size, 6);
16002                assert_eq!(limit, 4);
16003            }
16004            other => panic!("expected ExpansionSizeExceeded, got: {other:?}"),
16005        }
16006    }
16007
16008    #[test]
16009    fn expand_expansion_bomb_rejects_at_size_limit_substitute_path() {
16010        // The substitute strategy shares the same `expand_with_depth`
16011        // outer walker with the bytecode strategy, so the ceiling
16012        // fires at the SAME gate regardless of which apply-layer is
16013        // active. The pin makes that shared-outer-walker property
16014        // structural: a regression that ever branches the size guard
16015        // on strategy (e.g. moves it into `apply_compiled` alone)
16016        // fails this test on the substitute expander. Peer to
16017        // `expand_recursive_macro_rejects_at_depth_limit_substitute_path`
16018        // one RESOURCE axis over.
16019        let mut e = Expander::new_substitute_only();
16020        e.set_max_expansion_size(4);
16021        let forms = read("(defmacro bomb (x) `(list ,x ,x ,x ,x)) (bomb hey)").unwrap();
16022        let err = e.expand_program(forms).unwrap_err();
16023        match err {
16024            LispError::ExpansionSizeExceeded {
16025                macro_name,
16026                size,
16027                limit,
16028            } => {
16029                assert_eq!(macro_name, "bomb");
16030                assert_eq!(size, 6);
16031                assert_eq!(limit, 4);
16032            }
16033            other => panic!("expected ExpansionSizeExceeded, got: {other:?}"),
16034        }
16035    }
16036
16037    #[test]
16038    fn expand_lawful_output_within_size_ceiling_succeeds() {
16039        // `(defmacro twice (x) `(list ,x ,x))` applied to `(twice hey)`
16040        // produces `(list hey hey)` — 4 nodes. With a ceiling of `4`
16041        // the output sits AT the ceiling (`<=` admits equality; only
16042        // `>` rejects), so the expansion succeeds. Fail-before/pass-
16043        // after negative control for the bomb rejection tests: a
16044        // regression that ever rejected at-ceiling output would fail
16045        // this pin. Peer to `expand_lawful_nested_macros_within_ceiling_succeed`
16046        // one RESOURCE axis over.
16047        let mut e = Expander::new();
16048        e.set_max_expansion_size(4);
16049        let forms = read("(defmacro twice (x) `(list ,x ,x)) (twice hey)").unwrap();
16050        let out = e.expand_program(forms).unwrap();
16051        assert_eq!(out[0], parse("(list hey hey)"));
16052    }
16053
16054    #[test]
16055    fn expand_size_ceiling_ignores_lawful_tree_nesting_size() {
16056        // A macro-free lawful large tree is NOT gated by the size
16057        // ceiling — the ceiling gates macro-`apply` outputs alone,
16058        // not tree-child descents through non-macro forms. A `usize::MAX`
16059        // ceiling would trivially admit anything; this pin uses a
16060        // deliberately small ceiling of `4` on a tree with 6 nodes
16061        // to catch a regression that ever bumps the ceiling on tree
16062        // descent. A tree-only walk should NEVER fire the ceiling.
16063        // Peer to `expand_depth_ceiling_ignores_lawful_tree_nesting_depth`
16064        // one RESOURCE axis over.
16065        let mut e = Expander::new();
16066        e.set_max_expansion_size(4);
16067        // `(a (b (c d)))` — 6 nodes, no macros. Passes cleanly.
16068        let forms = read("(a (b (c d)))").unwrap();
16069        let out = e.expand_program(forms).unwrap();
16070        assert_eq!(out[0], parse("(a (b (c d)))"));
16071    }
16072
16073    #[test]
16074    fn expansion_size_exceeded_position_is_none() {
16075        // The variant joins the non-positional cohort — the offending
16076        // macro's byte offset is not what an expansion-bomb diagnostic
16077        // anchors to; the (offending macro name, observed size,
16078        // configured ceiling) triple is. This pin keeps the variant
16079        // in the `position() -> None` cohort so a future span-carrying
16080        // edit lands the field in ONE place across every non-positional
16081        // variant simultaneously. Peer to
16082        // `expansion_depth_exceeded_position_is_none` one RESOURCE
16083        // axis over.
16084        let err = LispError::ExpansionSizeExceeded {
16085            macro_name: "bomb".to_string(),
16086            size: 512,
16087            limit: 256,
16088        };
16089        assert_eq!(err.position(), None);
16090    }
16091
16092    #[test]
16093    fn expansion_size_exceeded_display_matches_typed_variant_shape() {
16094        // Display projection carries `macro_name`, `size`, AND
16095        // `limit`, so authoring surfaces that substring-grep the
16096        // rendered diagnostic (`tatara-check`, REPL, LSP) see ALL
16097        // THREE halves at the variant boundary and never need to
16098        // substring-parse free-form text to recover the identity of
16099        // the offending macro, the observed size, or the ceiling it
16100        // breached. Peer to `expansion_depth_exceeded_display_matches_typed_variant_shape`
16101        // one RESOURCE axis over.
16102        let err = LispError::ExpansionSizeExceeded {
16103            macro_name: "bomb".to_string(),
16104            size: 512,
16105            limit: 256,
16106        };
16107        let rendered = err.to_string();
16108        assert!(rendered.contains("bomb"), "rendered: {rendered}");
16109        assert!(rendered.contains("512"), "rendered: {rendered}");
16110        assert!(rendered.contains("256"), "rendered: {rendered}");
16111        assert!(
16112            rendered.contains("macro expansion output size exceeded"),
16113            "rendered: {rendered}"
16114        );
16115    }
16116
16117    #[test]
16118    fn expand_size_ceiling_names_the_offending_macro_in_a_nested_expansion() {
16119        // A lawful outer macro that expands to a call of a bomb inner
16120        // macro rejects at the INNER call — `macro_name` at the
16121        // variant boundary is the name of the macro whose `apply`
16122        // output crossed the ceiling, not the outermost caller.
16123        // Load-bearing name-provenance pin: an operator whose bomb
16124        // sits N levels deep in a compose chain needs to see the
16125        // INNER macro's name in the diagnostic, not the wrapping
16126        // shape. A regression that ever populates `macro_name` from
16127        // the outermost caller's head fails this pin.
16128        let mut e = Expander::new();
16129        e.set_max_expansion_size(4);
16130        // `wrapper` re-expands into `bomb`; `bomb` produces a size-6
16131        // output that crosses the ceiling. Expected: the diagnostic
16132        // names `bomb`, not `wrapper`.
16133        let src = "
16134            (defmacro bomb (x) `(list ,x ,x ,x ,x))
16135            (defmacro wrapper (x) `(bomb ,x))
16136            (wrapper hey)
16137        ";
16138        let err = e.expand_program(read(src).unwrap()).unwrap_err();
16139        match err {
16140            LispError::ExpansionSizeExceeded {
16141                macro_name,
16142                size,
16143                limit,
16144            } => {
16145                assert_eq!(macro_name, "bomb");
16146                assert_eq!(size, 6);
16147                assert_eq!(limit, 4);
16148            }
16149            other => panic!("expected ExpansionSizeExceeded, got: {other:?}"),
16150        }
16151    }
16152
16153    // ── max_macro_body_size: REGISTRATION-time body-size ceiling ──────
16154    // Peer to depth (recursion length) + cache (memoization width) +
16155    // expansion size (single-`apply` OUTPUT size) one PIPELINE-STAGE
16156    // axis over — the three prior guards fire at EXPAND time; this
16157    // one fires at REGISTER time. Closes the expander's RESOURCE
16158    // surface at a fourth typed dimension across two pipeline stages.
16159
16160    #[test]
16161    fn expander_default_max_macro_body_size_is_the_module_constant() {
16162        // Both constructors seed `max_macro_body_size` from the
16163        // module constant — a regression that drifts one
16164        // constructor's default from the constant (e.g. a hardcoded
16165        // `16_384` at ONE site with the other left uninitialized
16166        // after a `Default` derive addition) fails this pin. Peer
16167        // to the `expander_default_max_expansion_{depth,size}_is_the_module_constant`
16168        // pins one PIPELINE-STAGE axis over.
16169        assert_eq!(
16170            Expander::new().max_macro_body_size(),
16171            DEFAULT_MAX_MACRO_BODY_SIZE
16172        );
16173        assert_eq!(
16174            Expander::new_substitute_only().max_macro_body_size(),
16175            DEFAULT_MAX_MACRO_BODY_SIZE
16176        );
16177        assert_eq!(DEFAULT_MAX_MACRO_BODY_SIZE, 16_384);
16178    }
16179
16180    #[test]
16181    fn set_max_macro_body_size_takes_effect_on_subsequent_register() {
16182        // Setter mirrors accessor — a regression that ever forgets to
16183        // write through to the field, or writes to a shadow field the
16184        // accessor does not read, fails this pin. Peer to the
16185        // setter/accessor coherence pins on the depth + cache +
16186        // expansion-size axes.
16187        let mut e = Expander::new();
16188        e.set_max_macro_body_size(64);
16189        assert_eq!(e.max_macro_body_size(), 64);
16190    }
16191
16192    #[test]
16193    fn register_macro_body_bomb_rejects_at_body_size_limit_bytecode_path() {
16194        // `(defmacro huge (x) `(list a b c d e))` has body
16195        // `` `(list a b c d e) `` — a `Sexp::Quasiquote` wrapping a
16196        // list of 6 nodes (outer List + 5 children); the outer
16197        // quasi-quote wrapper adds one → 8 nodes total for
16198        // `def.body.node_count()`. With a ceiling of `4` this
16199        // crosses the REGISTRATION-time BODY-SIZE threshold and the
16200        // expander returns a typed `LispError::MacroBodySizeExceeded`
16201        // with `macro_name`, `size`, and `limit` all populated. Peer
16202        // to `expand_expansion_bomb_rejects_at_size_limit_bytecode_path`
16203        // one PIPELINE-STAGE axis over.
16204        let mut e = Expander::new();
16205        e.set_max_macro_body_size(4);
16206        let forms = read("(defmacro huge (x) `(list a b c d e))").unwrap();
16207        let err = e.expand_program(forms).unwrap_err();
16208        match err {
16209            LispError::MacroBodySizeExceeded {
16210                macro_name,
16211                size,
16212                limit,
16213            } => {
16214                assert_eq!(macro_name, "huge");
16215                assert_eq!(size, 8);
16216                assert_eq!(limit, 4);
16217            }
16218            other => panic!("expected MacroBodySizeExceeded, got: {other:?}"),
16219        }
16220    }
16221
16222    #[test]
16223    fn register_macro_body_bomb_rejects_at_body_size_limit_substitute_path() {
16224        // The substitute strategy shares the same
16225        // `register_macro_def` outer registration entry with the
16226        // bytecode strategy, so the REGISTRATION-time BODY-SIZE
16227        // ceiling fires at the SAME gate regardless of which
16228        // apply-layer is active. The pin makes that shared-registration
16229        // property structural: a regression that ever branches the
16230        // body-size guard on strategy (e.g. moves it into the
16231        // `compile_templates` arm alone) fails this test on the
16232        // substitute expander. Peer to
16233        // `expand_expansion_bomb_rejects_at_size_limit_substitute_path`
16234        // one PIPELINE-STAGE axis over.
16235        let mut e = Expander::new_substitute_only();
16236        e.set_max_macro_body_size(4);
16237        let forms = read("(defmacro huge (x) `(list a b c d e))").unwrap();
16238        let err = e.expand_program(forms).unwrap_err();
16239        match err {
16240            LispError::MacroBodySizeExceeded {
16241                macro_name,
16242                size,
16243                limit,
16244            } => {
16245                assert_eq!(macro_name, "huge");
16246                assert_eq!(size, 8);
16247                assert_eq!(limit, 4);
16248            }
16249            other => panic!("expected MacroBodySizeExceeded, got: {other:?}"),
16250        }
16251    }
16252
16253    #[test]
16254    fn register_macro_body_at_ceiling_admits() {
16255        // `(defmacro twice (x) `(list ,x))` has body
16256        // `` `(list ,x) `` — `Sexp::Quasiquote` wrapping a 3-node
16257        // list (List + `list` atom + `,x` unquote which is
16258        // 1 + inner atom = 2 nodes) = 1 + (1 + 1 + 2) = 5 nodes.
16259        // With a ceiling of `5` the body sits AT the ceiling
16260        // (`<=` admits equality; only `>` rejects), so the
16261        // registration succeeds and the subsequent expansion
16262        // proceeds normally. Fail-before/pass-after negative
16263        // control for the body-bomb rejection tests: a regression
16264        // that ever rejected at-ceiling bodies would fail this pin.
16265        let mut e = Expander::new();
16266        e.set_max_macro_body_size(5);
16267        let forms = read("(defmacro twice (x) `(list ,x)) (twice hey)").unwrap();
16268        let out = e.expand_program(forms).unwrap();
16269        assert_eq!(out[0], parse("(list hey)"));
16270    }
16271
16272    #[test]
16273    fn register_failure_leaves_both_tables_pristine() {
16274        // A rejected registration MUST leave BOTH `self.macros`
16275        // AND `self.templates` exactly as they were — no
16276        // partial-write window in which one table carries the
16277        // over-ceiling entry while the other does not, or in
16278        // which the template pre-compile already ran. This pin
16279        // catches a regression that ever moves the body-size
16280        // gate below `self.templates.insert` or below
16281        // `self.macros.insert`.
16282        let mut e = Expander::new();
16283        e.set_max_macro_body_size(4);
16284        let forms = read("(defmacro huge (x) `(list a b c d e))").unwrap();
16285        assert!(e.expand_program(forms).is_err());
16286        assert!(
16287            !e.has("huge"),
16288            "macros table must stay pristine after rejection"
16289        );
16290        assert_eq!(e.len(), 0);
16291    }
16292
16293    #[test]
16294    fn macro_body_size_exceeded_position_is_none() {
16295        // The variant joins the non-positional cohort — the
16296        // offending macro's byte offset is not what a body-bomb
16297        // diagnostic anchors to; the (offending macro name,
16298        // observed body size, configured ceiling) triple is.
16299        // This pin keeps the variant in the `position() -> None`
16300        // cohort so a future span-carrying edit lands the field
16301        // in ONE place across every non-positional variant
16302        // simultaneously. Peer to
16303        // `expansion_{depth,size}_exceeded_position_is_none` one
16304        // PIPELINE-STAGE axis over.
16305        let err = LispError::MacroBodySizeExceeded {
16306            macro_name: "huge".to_string(),
16307            size: 512,
16308            limit: 256,
16309        };
16310        assert_eq!(err.position(), None);
16311    }
16312
16313    #[test]
16314    fn macro_body_size_exceeded_display_matches_typed_variant_shape() {
16315        // Display projection carries `macro_name`, `size`, AND
16316        // `limit`, so authoring surfaces that substring-grep the
16317        // rendered diagnostic (`tatara-check`, REPL, LSP) see
16318        // ALL THREE halves at the variant boundary and never need
16319        // to substring-parse free-form text to recover the
16320        // identity of the offending macro, the observed body
16321        // size, or the ceiling it breached. Peer to
16322        // `expansion_size_exceeded_display_matches_typed_variant_shape`
16323        // one PIPELINE-STAGE axis over.
16324        let err = LispError::MacroBodySizeExceeded {
16325            macro_name: "huge".to_string(),
16326            size: 512,
16327            limit: 256,
16328        };
16329        let rendered = err.to_string();
16330        assert!(rendered.contains("huge"), "rendered: {rendered}");
16331        assert!(rendered.contains("512"), "rendered: {rendered}");
16332        assert!(rendered.contains("256"), "rendered: {rendered}");
16333        assert!(
16334            rendered.contains("macro body size exceeded"),
16335            "rendered: {rendered}"
16336        );
16337    }
16338
16339    #[test]
16340    fn lawful_macro_body_within_ceiling_registers_and_expands() {
16341        // A lawful hand-authored macro body sits well below any
16342        // realistic ceiling — the default `DEFAULT_MAX_MACRO_BODY_SIZE`
16343        // is deliberately generous so operators never brush it on
16344        // real authoring workloads. This pin locks the default
16345        // ceiling's non-interference posture: a regression that
16346        // ever lowered the default enough to reject the reference
16347        // `(defmacro when (cond x) `(if ,cond ,x))` shape would
16348        // fail here.
16349        let mut e = Expander::new();
16350        let forms = read(
16351            "(defmacro when (cond x) `(if ,cond ,x))
16352             (when #t hey)",
16353        )
16354        .unwrap();
16355        let out = e.expand_program(forms).unwrap();
16356        assert_eq!(out[0], parse("(if #t hey)"));
16357    }
16358
16359    #[test]
16360    fn register_macro_def_direct_call_respects_body_size_ceiling() {
16361        // `register_macro_def` is the substrate primitive both
16362        // `expand_program`'s `defmacro` recognition AND
16363        // `with_macros`'s bulk load route through. The body-size
16364        // ceiling fires on the primitive itself, not on any one
16365        // consumer — a regression that ever moved the gate into
16366        // `expand_program`'s arm alone would fail this pin.
16367        let mut e = Expander::new();
16368        e.set_max_macro_body_size(4);
16369        let def = MacroDef {
16370            name: "huge".to_string(),
16371            params: MacroParams {
16372                required: vec!["x".to_string()],
16373                optional: Vec::new(),
16374                rest: None,
16375            },
16376            body: Sexp::List(vec![
16377                Sexp::Atom(crate::ast::Atom::Symbol("a".to_string())),
16378                Sexp::Atom(crate::ast::Atom::Symbol("b".to_string())),
16379                Sexp::Atom(crate::ast::Atom::Symbol("c".to_string())),
16380                Sexp::Atom(crate::ast::Atom::Symbol("d".to_string())),
16381                Sexp::Atom(crate::ast::Atom::Symbol("e".to_string())),
16382            ]),
16383        };
16384        // Body node_count: List(5 atoms) = 1 + 5 = 6 nodes.
16385        let err = e.register_macro_def(def).unwrap_err();
16386        match err {
16387            LispError::MacroBodySizeExceeded {
16388                macro_name,
16389                size,
16390                limit,
16391            } => {
16392                assert_eq!(macro_name, "huge");
16393                assert_eq!(size, 6);
16394                assert_eq!(limit, 4);
16395            }
16396            other => panic!("expected MacroBodySizeExceeded, got: {other:?}"),
16397        }
16398        assert!(!e.has("huge"));
16399    }
16400
16401    // ── max_registered_macros: REGISTRATION-time table-count ceiling ──
16402    // Peer to body-size (REGISTER-time SIZE) one RESOURCE-DIMENSION
16403    // axis over — where body-size bounds a per-registration SIZE,
16404    // this ceiling bounds cumulative registration COUNT. Peer to
16405    // cache-entries (EXPAND-time COUNT) one PIPELINE-STAGE axis over
16406    // — the two entry-count guards close the two pipeline stages
16407    // symmetrically. Closes the expander's RESOURCE surface at a
16408    // FIFTH typed dimension across two pipeline stages: the
16409    // REGISTER-time face is now a two-dimensional (size × count)
16410    // closure symmetric with the EXPAND-time (depth + count + size)
16411    // triple.
16412
16413    #[test]
16414    fn expander_default_max_registered_macros_is_the_module_constant() {
16415        // Both constructors seed `max_registered_macros` from the
16416        // module constant — a regression that drifts one
16417        // constructor's default from the constant (e.g. a hardcoded
16418        // `4096` at ONE site with the other left uninitialized
16419        // after a `Default` derive addition) fails this pin. Peer
16420        // to the four prior default-coherence pins on depth,
16421        // cache-entries, expansion-size, and body-size.
16422        assert_eq!(
16423            Expander::new().max_registered_macros(),
16424            DEFAULT_MAX_REGISTERED_MACROS
16425        );
16426        assert_eq!(
16427            Expander::new_substitute_only().max_registered_macros(),
16428            DEFAULT_MAX_REGISTERED_MACROS
16429        );
16430        assert_eq!(DEFAULT_MAX_REGISTERED_MACROS, 4096);
16431    }
16432
16433    #[test]
16434    fn set_max_registered_macros_takes_effect_on_subsequent_register() {
16435        // Setter mirrors accessor — a regression that ever forgets
16436        // to write through to the field, or writes to a shadow
16437        // field the accessor does not read, fails this pin. Peer
16438        // to the four prior setter/accessor coherence pins.
16439        let mut e = Expander::new();
16440        e.set_max_registered_macros(64);
16441        assert_eq!(e.max_registered_macros(), 64);
16442    }
16443
16444    #[test]
16445    fn register_fresh_macro_past_table_ceiling_rejects_bytecode_path() {
16446        // With a ceiling of `2`, the first two fresh registrations
16447        // land in the table and the third fresh key rejects with
16448        // `LispError::RegisteredMacrosExceeded { macro_name: "c",
16449        // count: 2, limit: 2 }`. `count` is the pre-insert table
16450        // length (which equals `limit` at rejection in the common
16451        // case); `macro_name` is the OFFENDING call's name (the
16452        // fresh key we could not admit), not the outermost caller.
16453        let mut e = Expander::new();
16454        e.set_max_registered_macros(2);
16455        let forms = read(
16456            "(defmacro a (x) `(list ,x))
16457             (defmacro b (x) `(list ,x))
16458             (defmacro c (x) `(list ,x))",
16459        )
16460        .unwrap();
16461        let err = e.expand_program(forms).unwrap_err();
16462        match err {
16463            LispError::RegisteredMacrosExceeded {
16464                macro_name,
16465                count,
16466                limit,
16467            } => {
16468                assert_eq!(macro_name, "c");
16469                assert_eq!(count, 2);
16470                assert_eq!(limit, 2);
16471            }
16472            other => panic!("expected RegisteredMacrosExceeded, got: {other:?}"),
16473        }
16474        // First two admitted; third rejected leaves the table at
16475        // its pre-third-call size.
16476        assert!(e.has("a"));
16477        assert!(e.has("b"));
16478        assert!(!e.has("c"));
16479        assert_eq!(e.len(), 2);
16480    }
16481
16482    #[test]
16483    fn register_fresh_macro_past_table_ceiling_rejects_substitute_path() {
16484        // The substitute strategy shares the same
16485        // `register_macro_def` outer registration entry with the
16486        // bytecode strategy, so the REGISTRATION-time TABLE-COUNT
16487        // ceiling fires at the SAME gate regardless of which
16488        // apply-layer is active. The pin makes that shared-
16489        // registration property structural: a regression that ever
16490        // branches the table-count guard on strategy (e.g. moves
16491        // it into the `compile_templates` arm alone) fails this
16492        // test on the substitute expander. Peer to the four prior
16493        // shared-registration pins.
16494        let mut e = Expander::new_substitute_only();
16495        e.set_max_registered_macros(2);
16496        let forms = read(
16497            "(defmacro a (x) `(list ,x))
16498             (defmacro b (x) `(list ,x))
16499             (defmacro c (x) `(list ,x))",
16500        )
16501        .unwrap();
16502        let err = e.expand_program(forms).unwrap_err();
16503        match err {
16504            LispError::RegisteredMacrosExceeded {
16505                macro_name,
16506                count,
16507                limit,
16508            } => {
16509                assert_eq!(macro_name, "c");
16510                assert_eq!(count, 2);
16511                assert_eq!(limit, 2);
16512            }
16513            other => panic!("expected RegisteredMacrosExceeded, got: {other:?}"),
16514        }
16515        assert!(e.has("a"));
16516        assert!(e.has("b"));
16517        assert!(!e.has("c"));
16518        assert_eq!(e.len(), 2);
16519    }
16520
16521    #[test]
16522    fn register_overwrite_of_existing_key_at_table_ceiling_admits() {
16523        // OVERWRITES (a re-registration of an already-registered
16524        // key) do not grow the table and MUST be admitted at any
16525        // ceiling — operators redefining a macro at capacity is a
16526        // legitimate authoring pattern. This pin catches a
16527        // regression that ever treats overwrites the same as fresh
16528        // keys (a `contains_key` check that inverts, or a
16529        // `set_max_registered_macros(0)` that would reject even
16530        // pre-registered overwrites).
16531        let mut e = Expander::new();
16532        // Land two macros with a permissive ceiling.
16533        e.expand_program(
16534            read(
16535                "(defmacro a (x) `(list ,x))
16536                 (defmacro b (x) `(list ,x))",
16537            )
16538            .unwrap(),
16539        )
16540        .unwrap();
16541        assert_eq!(e.len(), 2);
16542        // Drop the ceiling to exactly the current size — a fresh
16543        // third key would now reject, but an overwrite of `a` MUST
16544        // admit.
16545        e.set_max_registered_macros(2);
16546        let forms = read("(defmacro a (x y) `(pair ,x ,y))").unwrap();
16547        e.expand_program(forms).unwrap();
16548        assert_eq!(e.len(), 2, "overwrite must not grow the table");
16549        // Re-expand a call to `a` — the NEW definition (two
16550        // required params) is active, and the OLD definition (one
16551        // required param) is gone. Under the new definition,
16552        // `(a 1 2)` expands to `(pair 1 2)`.
16553        let expanded = e.expand_program(read("(a foo bar)").unwrap()).unwrap();
16554        assert_eq!(expanded[0], parse("(pair foo bar)"));
16555    }
16556
16557    #[test]
16558    fn register_at_table_ceiling_admits_up_to_but_not_past() {
16559        // Fail-before/pass-after negative control on the exact
16560        // boundary: with a ceiling of `3`, exactly three fresh keys
16561        // admit and the fourth rejects. A regression that ever
16562        // shifted the boundary by one (e.g. keyed on `>` instead of
16563        // `>=`, or checked `len() > cap` before insert instead of
16564        // `len() >= cap`) would fail one of the two arms — either
16565        // the third admission or the fourth rejection.
16566        let mut e = Expander::new();
16567        e.set_max_registered_macros(3);
16568        // Three fresh keys admit.
16569        e.expand_program(
16570            read(
16571                "(defmacro a (x) `(list ,x))
16572                 (defmacro b (x) `(list ,x))
16573                 (defmacro c (x) `(list ,x))",
16574            )
16575            .unwrap(),
16576        )
16577        .unwrap();
16578        assert_eq!(e.len(), 3);
16579        // Fourth fresh key rejects.
16580        let err = e
16581            .expand_program(read("(defmacro d (x) `(list ,x))").unwrap())
16582            .unwrap_err();
16583        match err {
16584            LispError::RegisteredMacrosExceeded {
16585                macro_name,
16586                count,
16587                limit,
16588            } => {
16589                assert_eq!(macro_name, "d");
16590                assert_eq!(count, 3);
16591                assert_eq!(limit, 3);
16592            }
16593            other => panic!("expected RegisteredMacrosExceeded, got: {other:?}"),
16594        }
16595        // Table stays at pre-rejection size — no partial write.
16596        assert_eq!(e.len(), 3);
16597        assert!(!e.has("d"));
16598    }
16599
16600    #[test]
16601    fn register_table_ceiling_leaves_both_tables_pristine_on_rejection() {
16602        // A rejected registration MUST leave BOTH `self.macros` AND
16603        // `self.templates` exactly as they were — no partial-write
16604        // window in which one table carries the over-ceiling entry
16605        // while the other does not, or in which the template
16606        // pre-compile already ran. Table-count gate fires BEFORE
16607        // body-size gate walks the body AND BEFORE any insert lands,
16608        // so a regression that ever moved the table-count gate
16609        // below `self.templates.insert` or below
16610        // `self.macros.insert` would fail this pin. Peer to
16611        // `register_failure_leaves_both_tables_pristine` on the
16612        // body-size axis.
16613        let mut e = Expander::new();
16614        e.set_max_registered_macros(1);
16615        // Land one macro at ceiling.
16616        e.expand_program(read("(defmacro a (x) `(list ,x))").unwrap())
16617            .unwrap();
16618        assert!(e.has("a"));
16619        // Reject a fresh second key.
16620        let err = e
16621            .expand_program(read("(defmacro b (x) `(list ,x))").unwrap())
16622            .unwrap_err();
16623        assert!(matches!(err, LispError::RegisteredMacrosExceeded { .. }));
16624        // Table stays at pre-rejection state — `b` is NOT present
16625        // in either self.macros OR self.templates.
16626        assert_eq!(e.len(), 1);
16627        assert!(e.has("a"));
16628        assert!(!e.has("b"));
16629        // Expanding a call to `a` still works (templates table for
16630        // `a` untouched by the failed `b` registration).
16631        let expanded = e.expand_program(read("(a hey)").unwrap()).unwrap();
16632        assert_eq!(expanded[0], parse("(list hey)"));
16633    }
16634
16635    #[test]
16636    fn set_max_registered_macros_zero_rejects_every_fresh_registration() {
16637        // Zero ceiling is the "contract-test that this expander
16638        // accepts no new macros" posture — every fresh key rejects
16639        // at the first `defmacro` because `0 >= 0`. Peer to the
16640        // depth-ceiling zero-admits-and-rejects-every-macro-call
16641        // pin one RESOURCE axis over.
16642        let mut e = Expander::new();
16643        e.set_max_registered_macros(0);
16644        let err = e
16645            .expand_program(read("(defmacro a (x) `(list ,x))").unwrap())
16646            .unwrap_err();
16647        match err {
16648            LispError::RegisteredMacrosExceeded {
16649                macro_name,
16650                count,
16651                limit,
16652            } => {
16653                assert_eq!(macro_name, "a");
16654                assert_eq!(count, 0);
16655                assert_eq!(limit, 0);
16656            }
16657            other => panic!("expected RegisteredMacrosExceeded, got: {other:?}"),
16658        }
16659        assert!(e.is_empty());
16660    }
16661
16662    #[test]
16663    fn registered_macros_exceeded_position_is_none() {
16664        // The variant joins the non-positional cohort — the
16665        // offending macro's byte offset is not what a table-count
16666        // diagnostic anchors to; the (offending fresh macro name,
16667        // observed table count, configured ceiling) triple is.
16668        // This pin keeps the variant in the `position() -> None`
16669        // cohort so a future span-carrying edit lands the field
16670        // in ONE place across every non-positional variant
16671        // simultaneously. Peer to the four prior
16672        // `*_exceeded_position_is_none` pins on the resource-
16673        // ceiling cohort.
16674        let err = LispError::RegisteredMacrosExceeded {
16675            macro_name: "fresh-1000".to_string(),
16676            count: 4096,
16677            limit: 4096,
16678        };
16679        assert_eq!(err.position(), None);
16680    }
16681
16682    #[test]
16683    fn registered_macros_exceeded_display_matches_typed_variant_shape() {
16684        // Display projection carries `macro_name`, `count`, AND
16685        // `limit`, so authoring surfaces that substring-grep the
16686        // rendered diagnostic (`tatara-check`, REPL, LSP) see ALL
16687        // THREE halves at the variant boundary and never need to
16688        // substring-parse free-form text to recover the identity
16689        // of the offending fresh key, the observed table count, or
16690        // the ceiling it breached. Peer to the four prior
16691        // `*_exceeded_display_matches_typed_variant_shape` pins.
16692        let err = LispError::RegisteredMacrosExceeded {
16693            macro_name: "fresh-1000".to_string(),
16694            count: 4096,
16695            limit: 4096,
16696        };
16697        let rendered = err.to_string();
16698        assert!(rendered.contains("fresh-1000"), "rendered: {rendered}");
16699        assert!(rendered.contains("4096"), "rendered: {rendered}");
16700        assert!(
16701            rendered.contains("registered macros count exceeded"),
16702            "rendered: {rendered}"
16703        );
16704    }
16705
16706    #[test]
16707    fn lawful_typescape_within_ceiling_registers_and_expands() {
16708        // A lawful hand-authored typescape (a handful of macros)
16709        // sits well below any realistic ceiling — the default
16710        // `DEFAULT_MAX_REGISTERED_MACROS` is deliberately generous
16711        // so operators never brush it on real authoring workloads.
16712        // This pin locks the default ceiling's non-interference
16713        // posture: a regression that ever lowered the default
16714        // enough to reject a small typescape would fail here. Peer
16715        // to `lawful_macro_body_within_ceiling_registers_and_expands`.
16716        let mut e = Expander::new();
16717        let forms = read(
16718            "(defmacro when (cond x) `(if ,cond ,x))
16719             (defmacro twice (x) `(list ,x ,x))
16720             (defmacro pair (x y) `(list ,x ,y))
16721             (when #t (twice hey))",
16722        )
16723        .unwrap();
16724        let out = e.expand_program(forms).unwrap();
16725        assert_eq!(out[0], parse("(if #t (list hey hey))"));
16726        assert_eq!(e.len(), 3);
16727    }
16728
16729    #[test]
16730    fn register_macro_def_direct_call_respects_table_ceiling() {
16731        // `register_macro_def` is the substrate primitive both
16732        // `expand_program`'s `defmacro` recognition AND
16733        // `with_macros`'s bulk load route through. The table-count
16734        // ceiling fires on the primitive itself, not on any one
16735        // consumer — a regression that ever moved the gate into
16736        // `expand_program`'s arm alone would fail this pin. Peer
16737        // to `register_macro_def_direct_call_respects_body_size_ceiling`
16738        // one RESOURCE-DIMENSION axis over.
16739        let mut e = Expander::new();
16740        e.set_max_registered_macros(1);
16741        let def_a = MacroDef {
16742            name: "a".to_string(),
16743            params: MacroParams {
16744                required: vec!["x".to_string()],
16745                optional: Vec::new(),
16746                rest: None,
16747            },
16748            body: Sexp::Atom(crate::ast::Atom::Symbol("y".to_string())),
16749        };
16750        let def_b = MacroDef {
16751            name: "b".to_string(),
16752            params: MacroParams {
16753                required: vec!["x".to_string()],
16754                optional: Vec::new(),
16755                rest: None,
16756            },
16757            body: Sexp::Atom(crate::ast::Atom::Symbol("y".to_string())),
16758        };
16759        // First admits — table sits at ceiling.
16760        e.register_macro_def(def_a).unwrap();
16761        assert!(e.has("a"));
16762        assert_eq!(e.len(), 1);
16763        // Second rejects — fresh key past ceiling.
16764        let err = e.register_macro_def(def_b).unwrap_err();
16765        match err {
16766            LispError::RegisteredMacrosExceeded {
16767                macro_name,
16768                count,
16769                limit,
16770            } => {
16771                assert_eq!(macro_name, "b");
16772                assert_eq!(count, 1);
16773                assert_eq!(limit, 1);
16774            }
16775            other => panic!("expected RegisteredMacrosExceeded, got: {other:?}"),
16776        }
16777        assert!(!e.has("b"));
16778        assert_eq!(e.len(), 1);
16779    }
16780
16781    #[test]
16782    fn register_macro_def_direct_call_admits_overwrite_at_table_ceiling() {
16783        // Direct `register_macro_def` calls must ALSO admit
16784        // overwrites at ceiling — the overwrite discipline lives
16785        // on the primitive, not on `expand_program`'s arm alone.
16786        // Sibling of the source-level overwrite admission test.
16787        let mut e = Expander::new();
16788        e.set_max_registered_macros(1);
16789        let def_a_v1 = MacroDef {
16790            name: "a".to_string(),
16791            params: MacroParams {
16792                required: vec!["x".to_string()],
16793                optional: Vec::new(),
16794                rest: None,
16795            },
16796            body: Sexp::Atom(crate::ast::Atom::Symbol("y".to_string())),
16797        };
16798        let def_a_v2 = MacroDef {
16799            name: "a".to_string(),
16800            params: MacroParams {
16801                required: vec!["x".to_string(), "y".to_string()],
16802                optional: Vec::new(),
16803                rest: None,
16804            },
16805            body: Sexp::Atom(crate::ast::Atom::Symbol("z".to_string())),
16806        };
16807        e.register_macro_def(def_a_v1).unwrap();
16808        assert_eq!(e.len(), 1);
16809        // Overwrite of `a` at ceiling admits — no growth.
16810        e.register_macro_def(def_a_v2).unwrap();
16811        assert_eq!(e.len(), 1);
16812    }
16813
16814    // ── `MacroParams::total_arity` — the total-slots projection ──
16815    //
16816    // `total_arity()` names `required.len() + optional.len() +
16817    // rest.is_some() as usize` on the typed `MacroParams` algebra —
16818    // the `Vec`-free peer of `names().len()` AND the axis both
16819    // `MacroParams::bind`'s `Vec::with_capacity` hint AND the
16820    // REGISTER-time arity-ceiling gate consult. These pins cover:
16821    // structural identity vs. `names().len()`, the
16822    // `fixed_arity + rest.is_some() as usize` decomposition, and the
16823    // three canonical shapes (empty, all-required, mixed with rest).
16824    #[test]
16825    fn macro_params_total_arity_matches_names_len() {
16826        // The structural identity `total_arity() == names().len()`
16827        // — the `Vec`-free primitive's projection equals the flat-
16828        // index list's length. A regression that ever miscounts
16829        // one slot (drops rest, double-counts optional) fails this
16830        // pin. Sibling to `fixed_arity`'s peer-arithmetic pins.
16831        let params = MacroParams {
16832            required: vec!["a".into(), "b".into()],
16833            optional: vec![OptionalParam::bare("c"), OptionalParam::bare("d")],
16834            rest: Some("e".into()),
16835        };
16836        assert_eq!(params.total_arity(), params.names().len());
16837        assert_eq!(params.total_arity(), 5);
16838    }
16839
16840    #[test]
16841    fn macro_params_total_arity_equals_fixed_arity_plus_rest_bit() {
16842        // The `fixed_arity + rest.is_some() as usize` decomposition
16843        // — pins the composition on the [`MacroParams`] algebra so
16844        // a future consumer that wants "total slots" via
16845        // `fixed_arity + rest?` binds to the same arithmetic
16846        // `total_arity` names.
16847        let rest_none = MacroParams {
16848            required: vec!["a".into()],
16849            optional: vec![OptionalParam::bare("b")],
16850            rest: None,
16851        };
16852        assert_eq!(rest_none.total_arity(), rest_none.fixed_arity());
16853        assert_eq!(rest_none.total_arity(), 2);
16854        let rest_some = MacroParams {
16855            required: vec!["a".into()],
16856            optional: vec![OptionalParam::bare("b")],
16857            rest: Some("r".into()),
16858        };
16859        assert_eq!(rest_some.total_arity(), rest_some.fixed_arity() + 1);
16860        assert_eq!(rest_some.total_arity(), 3);
16861    }
16862
16863    #[test]
16864    fn macro_params_total_arity_is_zero_for_the_empty_param_list() {
16865        // Nullary macro edge: `(defmacro nullary () BODY)` has
16866        // `total_arity() == 0`. Under a `set_max_macro_arity(0)`
16867        // ceiling the arity gate keys on `>` (equality admits), so
16868        // this nullary registration succeeds while any non-nullary
16869        // one rejects — the exact contract the arity-gate
16870        // zero-admits-nullary test below relies on.
16871        let params = MacroParams::default();
16872        assert_eq!(params.total_arity(), 0);
16873    }
16874
16875    // ── max_macro_arity: REGISTRATION-time param-count ceiling ──
16876    // Peer to body-size (REGISTER-time BODY SIZE) one RESOURCE-
16877    // DIMENSION axis over — where body-size bounds a per-registration
16878    // BODY node count, this ceiling bounds a per-registration PARAM
16879    // slot count. Peer to registered-macros (REGISTER-time TABLE
16880    // COUNT) one RESOURCE-DIMENSION axis over — where
16881    // registered-macros bounds cumulative registration COUNT, this
16882    // ceiling bounds a per-registration PARAM-LIST WIDTH. Together
16883    // the three REGISTER-time ceilings close the (per-body SIZE,
16884    // per-body ARITY, per-table COUNT) three-corner surface.
16885
16886    #[test]
16887    fn expander_default_max_macro_arity_is_the_module_constant() {
16888        // Both constructors seed `max_macro_arity` from the module
16889        // constant — a regression that drifts one constructor's
16890        // default from the constant (e.g. a hardcoded `128` at ONE
16891        // site with the other left uninitialized after a `Default`
16892        // derive addition) fails this pin. Peer to the five prior
16893        // default-coherence pins on depth, cache-entries,
16894        // expansion-size, body-size, and registered-macros.
16895        assert_eq!(Expander::new().max_macro_arity(), DEFAULT_MAX_MACRO_ARITY);
16896        assert_eq!(
16897            Expander::new_substitute_only().max_macro_arity(),
16898            DEFAULT_MAX_MACRO_ARITY
16899        );
16900        assert_eq!(DEFAULT_MAX_MACRO_ARITY, 128);
16901    }
16902
16903    #[test]
16904    fn set_max_macro_arity_takes_effect_on_subsequent_register() {
16905        // Setter mirrors accessor — a regression that ever forgets to
16906        // write through to the field, or writes to a shadow field the
16907        // accessor does not read, fails this pin. Peer to the five
16908        // prior setter/accessor coherence pins.
16909        let mut e = Expander::new();
16910        e.set_max_macro_arity(16);
16911        assert_eq!(e.max_macro_arity(), 16);
16912    }
16913
16914    #[test]
16915    fn register_arity_bomb_rejects_at_arity_limit_bytecode_path() {
16916        // `(defmacro huge (a b c) `,a)` declares 3 required params.
16917        // Under `set_max_macro_arity(2)` the arity gate fires:
16918        // 3 > 2 → `LispError::MacroArityExceeded { macro_name: "huge",
16919        // arity: 3, limit: 2 }`. `arity` is the observed
16920        // `def.params.total_arity()`, NOT `required.len()` alone —
16921        // even an optional-only or rest-only overrun trips the gate
16922        // (pinned separately). Peer to the register-time body-size
16923        // rejection tests one RESOURCE-DIMENSION axis over.
16924        let mut e = Expander::new();
16925        e.set_max_macro_arity(2);
16926        let forms = read("(defmacro huge (a b c) `,a)").unwrap();
16927        let err = e.expand_program(forms).unwrap_err();
16928        match err {
16929            LispError::MacroArityExceeded {
16930                macro_name,
16931                arity,
16932                limit,
16933            } => {
16934                assert_eq!(macro_name, "huge");
16935                assert_eq!(arity, 3);
16936                assert_eq!(limit, 2);
16937            }
16938            other => panic!("expected MacroArityExceeded, got: {other:?}"),
16939        }
16940        assert!(!e.has("huge"));
16941    }
16942
16943    #[test]
16944    fn register_arity_bomb_rejects_at_arity_limit_substitute_path() {
16945        // The substitute strategy shares the same
16946        // `register_macro_def` outer registration entry with the
16947        // bytecode strategy, so the REGISTRATION-time ARITY ceiling
16948        // fires at the SAME gate regardless of which apply-layer is
16949        // active. Peer to
16950        // `register_arity_bomb_rejects_at_arity_limit_bytecode_path`.
16951        let mut e = Expander::new_substitute_only();
16952        e.set_max_macro_arity(2);
16953        let forms = read("(defmacro huge (a b c) `,a)").unwrap();
16954        let err = e.expand_program(forms).unwrap_err();
16955        match err {
16956            LispError::MacroArityExceeded {
16957                macro_name,
16958                arity,
16959                limit,
16960            } => {
16961                assert_eq!(macro_name, "huge");
16962                assert_eq!(arity, 3);
16963                assert_eq!(limit, 2);
16964            }
16965            other => panic!("expected MacroArityExceeded, got: {other:?}"),
16966        }
16967        assert!(!e.has("huge"));
16968    }
16969
16970    #[test]
16971    fn register_arity_gate_counts_optional_slots() {
16972        // `(defmacro foo (a &optional b c) `,a)` has 1 required + 2
16973        // optional + 0 rest = arity 3 via `total_arity`. Under a
16974        // ceiling of `2` the gate rejects, matching the required-only
16975        // arity-bomb — the arity axis is `total_arity()`, not
16976        // `required.len()`. A regression that drops the optional slots
16977        // from the arity computation would ADMIT this shape and fail
16978        // this pin.
16979        let mut e = Expander::new();
16980        e.set_max_macro_arity(2);
16981        let forms = read("(defmacro foo (a &optional b c) `,a)").unwrap();
16982        let err = e.expand_program(forms).unwrap_err();
16983        match err {
16984            LispError::MacroArityExceeded {
16985                macro_name,
16986                arity,
16987                limit,
16988            } => {
16989                assert_eq!(macro_name, "foo");
16990                assert_eq!(arity, 3);
16991                assert_eq!(limit, 2);
16992            }
16993            other => panic!("expected MacroArityExceeded, got: {other:?}"),
16994        }
16995    }
16996
16997    #[test]
16998    fn register_arity_gate_counts_rest_slot() {
16999        // `(defmacro spread (a b &rest r) `,a)` has 2 required + 0
17000        // optional + 1 rest = arity 3 via `total_arity`. Under a
17001        // ceiling of `2` the gate rejects, matching the required-only
17002        // arity-bomb — the arity axis includes the rest slot as ONE
17003        // named param, not zero. A regression that drops the rest slot
17004        // from the arity computation would ADMIT this shape and fail
17005        // this pin.
17006        let mut e = Expander::new();
17007        e.set_max_macro_arity(2);
17008        let forms = read("(defmacro spread (a b &rest r) `,a)").unwrap();
17009        let err = e.expand_program(forms).unwrap_err();
17010        match err {
17011            LispError::MacroArityExceeded {
17012                macro_name,
17013                arity,
17014                limit,
17015            } => {
17016                assert_eq!(macro_name, "spread");
17017                assert_eq!(arity, 3);
17018                assert_eq!(limit, 2);
17019            }
17020            other => panic!("expected MacroArityExceeded, got: {other:?}"),
17021        }
17022    }
17023
17024    #[test]
17025    fn register_arity_at_ceiling_admits() {
17026        // `(defmacro pair (x y) `(list ,x ,y))` has 2 required = arity
17027        // 2 via `total_arity`. Under a ceiling of `2` the arity gate
17028        // keys on `>` (equality admits; only strict overrun rejects),
17029        // so the registration succeeds and the expansion proceeds
17030        // normally. Fail-before/pass-after negative control on the
17031        // exact boundary: a regression that ever shifted the boundary
17032        // by one (e.g. keyed on `>=` instead of `>`) would fail this
17033        // pin. Peer to `register_macro_body_at_ceiling_admits` on the
17034        // body-size axis.
17035        let mut e = Expander::new();
17036        e.set_max_macro_arity(2);
17037        let forms = read("(defmacro pair (x y) `(list ,x ,y)) (pair a b)").unwrap();
17038        let out = e.expand_program(forms).unwrap();
17039        assert_eq!(out[0], parse("(list a b)"));
17040        assert_eq!(e.len(), 1);
17041    }
17042
17043    #[test]
17044    fn register_arity_ceiling_leaves_both_tables_pristine_on_rejection() {
17045        // A rejected arity-gated registration MUST leave BOTH
17046        // `self.macros` AND `self.templates` exactly as they were —
17047        // no partial-write window in which one table carries the
17048        // over-ceiling entry while the other does not, or in which
17049        // the template pre-compile already ran. Arity gate fires
17050        // BEFORE the body-size gate walks the body AND BEFORE any
17051        // insert lands, so a regression that ever moved the arity
17052        // gate below `self.templates.insert` or below
17053        // `self.macros.insert` would fail this pin. Peer to
17054        // `register_failure_leaves_both_tables_pristine` (body-size)
17055        // and `register_table_ceiling_leaves_both_tables_pristine_on_rejection`
17056        // (table-count).
17057        let mut e = Expander::new();
17058        e.set_max_macro_arity(1);
17059        let forms = read("(defmacro huge (a b c) `,a)").unwrap();
17060        assert!(e.expand_program(forms).is_err());
17061        assert!(
17062            !e.has("huge"),
17063            "macros table must stay pristine after rejection"
17064        );
17065        assert_eq!(e.len(), 0);
17066        // Expanding a NEW lawful macro afterwards still works — the
17067        // failed arity-gate rejection has not corrupted expander
17068        // state.
17069        let out = e
17070            .expand_program(read("(defmacro id (x) `,x) (id hey)").unwrap())
17071            .unwrap();
17072        assert_eq!(out[0], parse("hey"));
17073    }
17074
17075    #[test]
17076    fn set_max_macro_arity_zero_rejects_every_non_nullary_registration() {
17077        // Zero ceiling is the "contract-test that this expander
17078        // accepts only nullary macros" posture — every fresh
17079        // non-nullary key rejects at the arity gate because
17080        // `total_arity() > 0`, while nullary `(defmacro nullary ()
17081        // BODY)` still admits (its arity is exactly 0, and only
17082        // strict overrun rejects). Peer to the body-size zero-rejects-
17083        // every-non-empty-body pin one RESOURCE-DIMENSION axis over
17084        // (though body-size zero rejects even nullary-body shapes,
17085        // since `Sexp::Nil::node_count() == 1 > 0`; arity zero
17086        // preserves the nullary registration surface).
17087        let mut e = Expander::new();
17088        e.set_max_macro_arity(0);
17089        // Nullary admits — arity 0 <= 0.
17090        e.expand_program(read("(defmacro nullary () `unit)").unwrap())
17091            .unwrap();
17092        assert!(e.has("nullary"));
17093        // Non-nullary rejects — arity 1 > 0.
17094        let err = e
17095            .expand_program(read("(defmacro id (x) `,x)").unwrap())
17096            .unwrap_err();
17097        match err {
17098            LispError::MacroArityExceeded {
17099                macro_name,
17100                arity,
17101                limit,
17102            } => {
17103                assert_eq!(macro_name, "id");
17104                assert_eq!(arity, 1);
17105                assert_eq!(limit, 0);
17106            }
17107            other => panic!("expected MacroArityExceeded, got: {other:?}"),
17108        }
17109        assert!(!e.has("id"));
17110    }
17111
17112    #[test]
17113    fn macro_arity_exceeded_position_is_none() {
17114        // The variant joins the non-positional cohort — the offending
17115        // macro's byte offset is not what an arity-bomb diagnostic
17116        // anchors to; the (macro name, observed arity, configured
17117        // ceiling) triple is. This pin keeps the variant in the
17118        // `position() -> None` cohort so a future span-carrying edit
17119        // lands the field in ONE place across every non-positional
17120        // variant simultaneously. Peer to the five prior
17121        // `*_exceeded_position_is_none` pins on the resource-ceiling
17122        // cohort.
17123        let err = LispError::MacroArityExceeded {
17124            macro_name: "huge".to_string(),
17125            arity: 512,
17126            limit: 128,
17127        };
17128        assert_eq!(err.position(), None);
17129    }
17130
17131    #[test]
17132    fn macro_arity_exceeded_display_matches_typed_variant_shape() {
17133        // Display projection carries `macro_name`, `arity`, AND
17134        // `limit`, so authoring surfaces that substring-grep the
17135        // rendered diagnostic (`tatara-check`, REPL, LSP) see ALL
17136        // THREE halves at the variant boundary and never need to
17137        // substring-parse free-form text to recover the identity of
17138        // the offending macro, the observed arity, or the ceiling it
17139        // breached. Peer to the five prior
17140        // `*_exceeded_display_matches_typed_variant_shape` pins.
17141        let err = LispError::MacroArityExceeded {
17142            macro_name: "huge".to_string(),
17143            arity: 512,
17144            limit: 128,
17145        };
17146        let rendered = err.to_string();
17147        assert!(rendered.contains("huge"), "rendered: {rendered}");
17148        assert!(rendered.contains("512"), "rendered: {rendered}");
17149        assert!(rendered.contains("128"), "rendered: {rendered}");
17150        assert!(
17151            rendered.contains("macro arity exceeded"),
17152            "rendered: {rendered}"
17153        );
17154    }
17155
17156    #[test]
17157    fn lawful_macro_arity_within_ceiling_registers_and_expands() {
17158        // A lawful hand-authored macro arity sits well below any
17159        // realistic ceiling — the default `DEFAULT_MAX_MACRO_ARITY`
17160        // is deliberately generous so operators never brush it on
17161        // real authoring workloads. This pin locks the default
17162        // ceiling's non-interference posture: a regression that
17163        // ever lowered the default enough to reject the reference
17164        // multi-slot `(defmacro when (cond x) …)` shape would fail
17165        // here. Peer to `lawful_macro_body_within_ceiling_registers_and_expands`
17166        // and `lawful_typescape_within_ceiling_registers_and_expands`.
17167        let mut e = Expander::new();
17168        let forms = read(
17169            "(defmacro when (cond x) `(if ,cond ,x))
17170             (defmacro triple (x y z) `(list ,x ,y ,z))
17171             (when #t (triple a b c))",
17172        )
17173        .unwrap();
17174        let out = e.expand_program(forms).unwrap();
17175        assert_eq!(out[0], parse("(if #t (list a b c))"));
17176    }
17177
17178    #[test]
17179    fn register_macro_def_direct_call_respects_arity_ceiling() {
17180        // `register_macro_def` is the substrate primitive both
17181        // `expand_program`'s `defmacro` recognition AND `with_macros`'s
17182        // bulk load route through. The arity ceiling fires on the
17183        // primitive itself, not on any one consumer — a regression
17184        // that ever moved the gate into `expand_program`'s arm alone
17185        // would fail this pin. Peer to
17186        // `register_macro_def_direct_call_respects_body_size_ceiling`
17187        // AND `register_macro_def_direct_call_respects_table_ceiling`
17188        // one RESOURCE-DIMENSION axis over.
17189        let mut e = Expander::new();
17190        e.set_max_macro_arity(2);
17191        let def = MacroDef {
17192            name: "huge".to_string(),
17193            params: MacroParams {
17194                required: vec!["a".to_string(), "b".to_string(), "c".to_string()],
17195                optional: Vec::new(),
17196                rest: None,
17197            },
17198            body: Sexp::Atom(crate::ast::Atom::Symbol("a".to_string())),
17199        };
17200        // Arity 3 > ceiling 2 — rejected before body-size gate.
17201        let err = e.register_macro_def(def).unwrap_err();
17202        match err {
17203            LispError::MacroArityExceeded {
17204                macro_name,
17205                arity,
17206                limit,
17207            } => {
17208                assert_eq!(macro_name, "huge");
17209                assert_eq!(arity, 3);
17210                assert_eq!(limit, 2);
17211            }
17212            other => panic!("expected MacroArityExceeded, got: {other:?}"),
17213        }
17214        assert!(!e.has("huge"));
17215    }
17216
17217    #[test]
17218    fn arity_gate_fires_before_body_size_gate() {
17219        // ORDERING PIN — the arity gate fires BEFORE the body-size
17220        // gate. With both `set_max_macro_arity(1)` AND
17221        // `set_max_macro_body_size(2)` set below a shape that violates
17222        // BOTH ceilings — `(defmacro dual (a b) `(list ,a ,b))` has
17223        // arity 2 AND body node count > 2 — the arity gate's
17224        // rejection wins, so the returned variant is
17225        // `MacroArityExceeded`, not `MacroBodySizeExceeded`. A
17226        // regression that ever swapped the two gates' order would fail
17227        // this pin. The ordering isn't cosmetic: arity is O(1)
17228        // (three-arm addition on `def.params` fields), body-size is
17229        // O(body_nodes) (`def.body.node_count()` walks the AST), so
17230        // the O(1)-first order minimises the cost of rejecting the
17231        // worst case (a large-body large-arity macro).
17232        let mut e = Expander::new();
17233        e.set_max_macro_arity(1);
17234        e.set_max_macro_body_size(2);
17235        let forms = read("(defmacro dual (a b) `(list ,a ,b))").unwrap();
17236        let err = e.expand_program(forms).unwrap_err();
17237        assert!(
17238            matches!(err, LispError::MacroArityExceeded { .. }),
17239            "arity gate must fire before body-size gate; got: {err:?}"
17240        );
17241    }
17242
17243    #[test]
17244    fn arity_gate_fires_after_table_count_gate() {
17245        // ORDERING PIN — the table-count gate fires BEFORE the arity
17246        // gate. Under `set_max_registered_macros(1)` at capacity AND
17247        // `set_max_macro_arity(1)`, a fresh over-arity registration
17248        // trips the table-count gate FIRST (its `HashMap::len`
17249        // comparison is O(1) cache-friendly and gates cumulative
17250        // registration cost); the arity gate is O(1) but on `def`,
17251        // not on shared state, so it sits AFTER the table-count gate
17252        // on the O(1)-first ordering of the REGISTER-time chain. A
17253        // regression that ever swapped the two would fail this pin.
17254        let mut e = Expander::new();
17255        e.set_max_registered_macros(1);
17256        e.set_max_macro_arity(1);
17257        // Land one macro at table ceiling.
17258        e.expand_program(read("(defmacro anchor (x) `,x)").unwrap())
17259            .unwrap();
17260        assert_eq!(e.len(), 1);
17261        // Fresh registration with BOTH violations — table-count wins.
17262        let forms = read("(defmacro huge (a b c) `,a)").unwrap();
17263        let err = e.expand_program(forms).unwrap_err();
17264        assert!(
17265            matches!(err, LispError::RegisteredMacrosExceeded { .. }),
17266            "table-count gate must fire before arity gate; got: {err:?}"
17267        );
17268    }
17269
17270    // ── ResourceLimits: bundled resource-ceiling snapshot ────────────
17271
17272    #[test]
17273    fn default_resource_limits_binds_each_field_to_matching_module_constant() {
17274        // Pin the (constant × aggregation) corner: the const-form
17275        // aggregate MUST bind every field to its individually-defined
17276        // module constant. A re-tuning of ONE module constant
17277        // (e.g. `DEFAULT_MAX_EXPANSION_DEPTH` raised from 256 to 512)
17278        // has to propagate through the aggregate at ONE site; this
17279        // test fires the divergence loudly if a future edit sets a
17280        // module constant to a fresh value but forgets to update the
17281        // matching aggregate slot. Field-by-field equality carries
17282        // the message better than one deep-equality assertion.
17283        assert_eq!(
17284            DEFAULT_RESOURCE_LIMITS.max_expansion_depth,
17285            DEFAULT_MAX_EXPANSION_DEPTH
17286        );
17287        assert_eq!(
17288            DEFAULT_RESOURCE_LIMITS.max_cache_entries,
17289            DEFAULT_MAX_CACHE_ENTRIES
17290        );
17291        assert_eq!(
17292            DEFAULT_RESOURCE_LIMITS.max_expansion_size,
17293            DEFAULT_MAX_EXPANSION_SIZE
17294        );
17295        assert_eq!(
17296            DEFAULT_RESOURCE_LIMITS.max_macro_body_size,
17297            DEFAULT_MAX_MACRO_BODY_SIZE
17298        );
17299        assert_eq!(
17300            DEFAULT_RESOURCE_LIMITS.max_registered_macros,
17301            DEFAULT_MAX_REGISTERED_MACROS
17302        );
17303        assert_eq!(
17304            DEFAULT_RESOURCE_LIMITS.max_macro_arity,
17305            DEFAULT_MAX_MACRO_ARITY
17306        );
17307    }
17308
17309    #[test]
17310    fn resource_limits_default_impl_matches_const_form() {
17311        // The two projections of the shipped posture — the `const`
17312        // form [`DEFAULT_RESOURCE_LIMITS`] and the runtime
17313        // `Default::default()` form — must reach the SAME value. Pins
17314        // that a future edit which flips one to a new default cannot
17315        // silently drift the other.
17316        assert_eq!(ResourceLimits::default(), DEFAULT_RESOURCE_LIMITS);
17317    }
17318
17319    #[test]
17320    fn expander_new_resource_limits_matches_shipped_defaults() {
17321        // Fresh-expander posture on the bundled-getter surface — the
17322        // six ceilings [`Expander::new`] seeds MUST snapshot to
17323        // [`DEFAULT_RESOURCE_LIMITS`] verbatim. Peer of the six
17324        // individual `expander_default_max_*_is_the_module_constant`
17325        // pins one AGGREGATION axis over — where each individual
17326        // pin catches ONE field drift, this pin catches ANY of the
17327        // six drifting AND the snapshot projection itself losing a
17328        // field.
17329        assert_eq!(Expander::new().resource_limits(), DEFAULT_RESOURCE_LIMITS);
17330    }
17331
17332    #[test]
17333    fn expander_new_substitute_only_resource_limits_matches_shipped_defaults() {
17334        // Substitute-only expander seeds the SAME six ceilings from
17335        // the SAME module constants as [`Expander::new`] — the two
17336        // constructors share the resource-ceiling posture even
17337        // though they diverge on `compile_templates` +
17338        // `cache_enabled`. Pins that a future constructor which
17339        // forgets to seed one of the six ceilings from its module
17340        // constant fails loudly through the bundled getter.
17341        assert_eq!(
17342            Expander::new_substitute_only().resource_limits(),
17343            DEFAULT_RESOURCE_LIMITS
17344        );
17345    }
17346
17347    #[test]
17348    fn resource_limits_snapshot_reflects_each_individual_setter() {
17349        // Coherence pin between the six individual setters and the
17350        // bundled getter — flipping ONE ceiling via its individual
17351        // setter must project through [`Expander::resource_limits`]
17352        // to the SAME value the individual getter returns. A
17353        // regression that leaves ONE field out of the snapshot
17354        // projection (a copy-paste omission in the struct literal
17355        // constructor) fires here. Distinct low-non-default values
17356        // so a stray field carrying the default fails loudly.
17357        let mut e = Expander::new();
17358        e.set_max_expansion_depth(3);
17359        e.set_max_cache_entries(5);
17360        e.set_max_expansion_size(7);
17361        e.set_max_macro_body_size(11);
17362        e.set_max_registered_macros(13);
17363        e.set_max_macro_arity(17);
17364        let snap = e.resource_limits();
17365        assert_eq!(snap.max_expansion_depth, 3);
17366        assert_eq!(snap.max_cache_entries, 5);
17367        assert_eq!(snap.max_expansion_size, 7);
17368        assert_eq!(snap.max_macro_body_size, 11);
17369        assert_eq!(snap.max_registered_macros, 13);
17370        assert_eq!(snap.max_macro_arity, 17);
17371        // And the individual getters see the same values — the
17372        // snapshot is a projection, not a divergent copy.
17373        assert_eq!(e.max_expansion_depth(), 3);
17374        assert_eq!(e.max_cache_entries(), 5);
17375        assert_eq!(e.max_expansion_size(), 7);
17376        assert_eq!(e.max_macro_body_size(), 11);
17377        assert_eq!(e.max_registered_macros(), 13);
17378        assert_eq!(e.max_macro_arity(), 17);
17379    }
17380
17381    #[test]
17382    fn set_resource_limits_bulk_propagates_every_field_to_individual_getters() {
17383        // Sibling pin to `resource_limits_snapshot_reflects_...` on the
17384        // WRITE axis — bulk-setting via [`Expander::set_resource_limits`]
17385        // must reach every individual `max_*` getter. A regression
17386        // that drops one field from the destructuring assignment in
17387        // the bulk setter fires here. Distinct low-non-default values
17388        // so a stray field carrying the previous value fails loudly.
17389        let mut e = Expander::new();
17390        e.set_resource_limits(ResourceLimits {
17391            max_expansion_depth: 2,
17392            max_cache_entries: 4,
17393            max_expansion_size: 8,
17394            max_macro_body_size: 16,
17395            max_registered_macros: 32,
17396            max_macro_arity: 64,
17397        });
17398        assert_eq!(e.max_expansion_depth(), 2);
17399        assert_eq!(e.max_cache_entries(), 4);
17400        assert_eq!(e.max_expansion_size(), 8);
17401        assert_eq!(e.max_macro_body_size(), 16);
17402        assert_eq!(e.max_registered_macros(), 32);
17403        assert_eq!(e.max_macro_arity(), 64);
17404    }
17405
17406    #[test]
17407    fn resource_limits_round_trip_through_bundled_getter_and_setter_is_identity() {
17408        // Round-trip theorem — for any starting expander, the
17409        // composition `set_resource_limits(resource_limits())` is
17410        // the identity on the resource-ceiling surface. This is the
17411        // ONE typed statement that pins the (getter, setter) pair as
17412        // strict projections of the same six-field surface, no
17413        // hidden lossy field, no ordering asymmetry between the
17414        // struct literal and the destructuring pattern.
17415        let mut e = Expander::new();
17416        e.set_max_expansion_depth(9);
17417        e.set_max_cache_entries(19);
17418        e.set_max_expansion_size(29);
17419        e.set_max_macro_body_size(39);
17420        e.set_max_registered_macros(49);
17421        e.set_max_macro_arity(59);
17422        let snap = e.resource_limits();
17423        // Reset to defaults so any leftover state would show up in
17424        // the post-round-trip snapshot as the default rather than
17425        // the intended value.
17426        e.set_resource_limits(ResourceLimits::default());
17427        assert_eq!(e.resource_limits(), DEFAULT_RESOURCE_LIMITS);
17428        // Apply the captured snapshot — the expander must land at
17429        // the same six values `snap` recorded.
17430        e.set_resource_limits(snap);
17431        assert_eq!(e.resource_limits(), snap);
17432    }
17433
17434    #[test]
17435    fn resource_limits_struct_update_syntax_overrides_one_ceiling() {
17436        // The `Copy` posture on [`ResourceLimits`] gives callers the
17437        // cheap ergonomic of a struct-update literal — take the
17438        // shipped posture and override ONE ceiling — that a chain of
17439        // six independent setters cannot express in ONE typed expression.
17440        // Pins the intended use-shape so a future refactor which
17441        // removes `Copy` from the struct fails to compile at THIS
17442        // site rather than at every downstream consumer.
17443        let mut e = Expander::new();
17444        e.set_resource_limits(ResourceLimits {
17445            max_macro_arity: 4,
17446            ..DEFAULT_RESOURCE_LIMITS
17447        });
17448        // Overridden field carries the fresh value.
17449        assert_eq!(e.max_macro_arity(), 4);
17450        // Every other field carries the shipped default — the
17451        // struct-update literal is a full projection, not a
17452        // selective mutation.
17453        assert_eq!(e.max_expansion_depth(), DEFAULT_MAX_EXPANSION_DEPTH);
17454        assert_eq!(e.max_cache_entries(), DEFAULT_MAX_CACHE_ENTRIES);
17455        assert_eq!(e.max_expansion_size(), DEFAULT_MAX_EXPANSION_SIZE);
17456        assert_eq!(e.max_macro_body_size(), DEFAULT_MAX_MACRO_BODY_SIZE);
17457        assert_eq!(e.max_registered_macros(), DEFAULT_MAX_REGISTERED_MACROS);
17458    }
17459
17460    #[test]
17461    fn expander_default_derives_resource_limits_from_bundled_field_default() {
17462        // Post the [`Expander::limits`]-field consolidation the derived
17463        // `Default` on [`Expander`] projects through [`ResourceLimits`]'s
17464        // own `Default` — which returns [`DEFAULT_RESOURCE_LIMITS`] — so
17465        // `Expander::default().resource_limits()` lands at the shipped
17466        // ceiling posture verbatim. Pre-consolidation the six independent
17467        // `max_*: usize` fields defaulted to the bare `usize` zero, so
17468        // `Expander::default()` yielded a below-floor posture with every
17469        // ceiling clamped to `0` — a shape no consumer wanted and every
17470        // test path avoided by routing through [`Expander::new`] instead.
17471        // The consolidation is what turned that latent below-floor
17472        // derived-`Default` shape into a lawful shipped posture on the
17473        // structural axis. Peer of
17474        // `expander_new_resource_limits_matches_shipped_defaults` one
17475        // CONSTRUCTOR axis over — where that test pins the explicit
17476        // [`Expander::new`] constructor, this test pins the derived-
17477        // [`Default`] constructor route to the SAME six values through
17478        // the SAME bundled projection. Sibling of
17479        // `resource_limits_default_impl_matches_const_form` one
17480        // TYPE-LEVEL axis over — that test pins [`ResourceLimits::default`]
17481        // to [`DEFAULT_RESOURCE_LIMITS`]; this test pins [`Expander`]'s
17482        // derived `Default` to project through it.
17483        assert_eq!(
17484            Expander::default().resource_limits(),
17485            DEFAULT_RESOURCE_LIMITS
17486        );
17487    }
17488
17489    #[test]
17490    fn resource_limits_bulk_setter_keeps_the_arity_gate_in_effect() {
17491        // Behavioral pin — the bundled setter is not a decorative
17492        // getter/setter pair; the six ceilings it propagates must
17493        // reach the SAME check sites the individual setters do. Set
17494        // `max_macro_arity` to 1 via the bulk setter and confirm the
17495        // arity gate fires on a two-arg macro registration. A
17496        // regression that wired the bulk setter to a shadow field
17497        // rather than the actual `Expander` state fails here.
17498        let mut e = Expander::new();
17499        e.set_resource_limits(ResourceLimits {
17500            max_macro_arity: 1,
17501            ..DEFAULT_RESOURCE_LIMITS
17502        });
17503        let forms = read("(defmacro two (a b) `,a)").unwrap();
17504        let err = e.expand_program(forms).unwrap_err();
17505        assert!(
17506            matches!(
17507                err,
17508                LispError::MacroArityExceeded {
17509                    arity: 2,
17510                    limit: 1,
17511                    ..
17512                }
17513            ),
17514            "bulk-set arity ceiling must reach the register-time gate; got: {err:?}"
17515        );
17516    }
17517
17518    // ── Expander::with_limits — at-construction resource-posture ───────
17519
17520    #[test]
17521    fn expander_with_limits_seeds_the_provided_resource_posture() {
17522        // Round-trip pin at CONSTRUCTION time — the bundle the caller
17523        // passes into [`Expander::with_limits`] MUST project back
17524        // through [`Expander::resource_limits`] verbatim. Peer of the
17525        // `set_resource_limits(l); resource_limits() == l` round-trip
17526        // one CONSTRUCTOR-STAGE axis over — that pin covers the
17527        // POST-construction assignment; this pin covers the
17528        // AT-construction assignment. A regression that dropped the
17529        // `limits` argument on the floor (a copy-paste that left
17530        // `limits: DEFAULT_RESOURCE_LIMITS` in the struct literal
17531        // instead of `limits`) fires here on every non-default field.
17532        // Distinct low-non-default values so a stray field defaulting
17533        // fails loudly.
17534        let want = ResourceLimits {
17535            max_expansion_depth: 3,
17536            max_cache_entries: 5,
17537            max_expansion_size: 7,
17538            max_macro_body_size: 11,
17539            max_registered_macros: 13,
17540            max_macro_arity: 17,
17541        };
17542        assert_eq!(Expander::with_limits(want).resource_limits(), want);
17543    }
17544
17545    #[test]
17546    fn expander_with_default_limits_agrees_with_new_on_resource_posture() {
17547        // Delegation identity — [`Expander::new`] delegates to
17548        // [`Expander::with_limits`] with [`DEFAULT_RESOURCE_LIMITS`] so
17549        // the two constructors MUST agree on the resource-ceiling
17550        // projection. Pins the "no drift between the two paths to a
17551        // fresh default-posture expander" theorem: a future refactor
17552        // that breaks the delegation (an inline field literal reappearing
17553        // in [`Expander::new`], an accidental hardcoded const at ONE
17554        // site with the other unchanged) fires here. Peer of
17555        // `resource_limits_default_impl_matches_const_form` one
17556        // TYPE-LEVEL axis over — that test pins the two projections of
17557        // the shipped posture on [`ResourceLimits`] itself; this test
17558        // pins the two constructor paths on [`Expander`] to reach the
17559        // SAME six values through the SAME bundle.
17560        assert_eq!(
17561            Expander::with_limits(DEFAULT_RESOURCE_LIMITS).resource_limits(),
17562            Expander::new().resource_limits()
17563        );
17564    }
17565
17566    #[test]
17567    fn expander_with_limits_composes_with_struct_update_override() {
17568        // The `Copy` posture on [`ResourceLimits`] lets a caller compose
17569        // "the shipped posture with ONE ceiling overridden" as ONE
17570        // struct-update literal AND thread that composition through
17571        // [`Expander::with_limits`] as ONE typed call — the ergonomic
17572        // shape that six independent setter invocations cannot express
17573        // in ONE expression. Overridden ceiling carries the fresh value,
17574        // every other ceiling carries its shipped default. A future
17575        // refactor that removes `Copy` from [`ResourceLimits`] fails to
17576        // compile at THIS site rather than at every downstream consumer.
17577        let e = Expander::with_limits(ResourceLimits {
17578            max_macro_arity: 4,
17579            ..DEFAULT_RESOURCE_LIMITS
17580        });
17581        assert_eq!(e.max_macro_arity(), 4);
17582        assert_eq!(e.max_expansion_depth(), DEFAULT_MAX_EXPANSION_DEPTH);
17583        assert_eq!(e.max_cache_entries(), DEFAULT_MAX_CACHE_ENTRIES);
17584        assert_eq!(e.max_expansion_size(), DEFAULT_MAX_EXPANSION_SIZE);
17585        assert_eq!(e.max_macro_body_size(), DEFAULT_MAX_MACRO_BODY_SIZE);
17586        assert_eq!(e.max_registered_macros(), DEFAULT_MAX_REGISTERED_MACROS);
17587    }
17588
17589    #[test]
17590    fn expander_with_limits_carries_the_new_execution_strategy() {
17591        // The (compile_templates, cache_enabled) execution-strategy pair
17592        // MUST carry the [`Expander::new`] default posture — bytecode +
17593        // cache both on — regardless of which resource posture the
17594        // caller passes in. Cache-count observation is the least-
17595        // invasive check available on the public surface: a fresh
17596        // `with_limits` expander that expands a repeat-call site must
17597        // populate at least one cache entry (bytecode + cache both on),
17598        // and a substitute-only sibling would leave `cache_size()` at
17599        // zero even after every call site expanded. A regression that
17600        // wired the wrong execution strategy into `with_limits`
17601        // (compile_templates: false, cache_enabled: false) fires here.
17602        let mut e = Expander::with_limits(DEFAULT_RESOURCE_LIMITS);
17603        let forms = read(
17604            "(defmacro id (x) `,x)
17605             (id one)
17606             (id one)",
17607        )
17608        .unwrap();
17609        e.expand_program(forms).unwrap();
17610        assert!(
17611            e.cache_size() >= 1,
17612            "with_limits carries the bytecode+cache strategy; cache_size must be >= 1 after repeat calls, got {}",
17613            e.cache_size()
17614        );
17615    }
17616
17617    #[test]
17618    fn expander_with_limits_fires_the_arity_gate_from_construction() {
17619        // Behavioral pin — the at-construction bundle assignment MUST
17620        // reach the SAME check sites the individual + bulk POST-
17621        // construction setters do. Construct an expander with
17622        // `max_macro_arity: 1` directly and confirm the arity gate
17623        // fires on a two-arg macro registration. Sibling of
17624        // `resource_limits_bulk_setter_keeps_the_arity_gate_in_effect`
17625        // one CONSTRUCTOR-STAGE axis over — that pin covers the POST-
17626        // construction setter reaching the gate; this pin covers the
17627        // AT-construction assignment reaching the SAME gate through
17628        // the SAME `self.limits.max_macro_arity` load. A regression
17629        // that wired `with_limits` to a shadow field rather than
17630        // `self.limits` fails here.
17631        let mut e = Expander::with_limits(ResourceLimits {
17632            max_macro_arity: 1,
17633            ..DEFAULT_RESOURCE_LIMITS
17634        });
17635        let forms = read("(defmacro two (a b) `,a)").unwrap();
17636        let err = e.expand_program(forms).unwrap_err();
17637        assert!(
17638            matches!(
17639                err,
17640                LispError::MacroArityExceeded {
17641                    arity: 2,
17642                    limit: 1,
17643                    ..
17644                }
17645            ),
17646            "at-construction arity ceiling must reach the register-time gate; got: {err:?}"
17647        );
17648    }
17649
17650    // ── UNBOUNDED_RESOURCE_LIMITS — ceiling-lifted preset posture ──────
17651
17652    #[test]
17653    fn unbounded_resource_limits_binds_every_ceiling_to_usize_max() {
17654        // Field-level pin — the ceiling-lifted preset carries
17655        // [`usize::MAX`] on every axis of the six-field surface. Pinned
17656        // as six independent field asserts (rather than one struct
17657        // equality) so a drift on ONE field carries a distinct-name
17658        // failure that names the drifting axis. A future extension
17659        // that adds a SEVENTH ceiling requires an additional field
17660        // assert here in lockstep with the const literal — rustc's
17661        // field-exhaustiveness on the const forces the new field to
17662        // appear, and this pin exercises it.
17663        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_expansion_depth, usize::MAX);
17664        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_cache_entries, usize::MAX);
17665        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_expansion_size, usize::MAX);
17666        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_macro_body_size, usize::MAX);
17667        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_registered_macros, usize::MAX);
17668        assert_eq!(UNBOUNDED_RESOURCE_LIMITS.max_macro_arity, usize::MAX);
17669    }
17670
17671    #[test]
17672    fn unbounded_resource_limits_disagrees_with_default_on_every_ceiling() {
17673        // Structural-distinctness pin — the two constants
17674        // [`DEFAULT_RESOURCE_LIMITS`] and [`UNBOUNDED_RESOURCE_LIMITS`]
17675        // sit at DISTINCT points on the posture axis of the (posture ×
17676        // aggregation) grid, and the disagreement is EXHAUSTIVE on the
17677        // six-field surface — every shipped `DEFAULT_MAX_*` module
17678        // constant is a concrete positive value strictly less than
17679        // [`usize::MAX`]. Pins that a future re-tuning which cranked one
17680        // shipped default up to [`usize::MAX`] (collapsing the two
17681        // presets on that field) fires here loudly rather than
17682        // silently letting the two presets become indistinguishable
17683        // on that axis. A regression which flipped ONE unbounded-const
17684        // field back to its default value also fires here — either
17685        // half of the disagreement breaks the theorem.
17686        assert_ne!(
17687            UNBOUNDED_RESOURCE_LIMITS.max_expansion_depth,
17688            DEFAULT_RESOURCE_LIMITS.max_expansion_depth
17689        );
17690        assert_ne!(
17691            UNBOUNDED_RESOURCE_LIMITS.max_cache_entries,
17692            DEFAULT_RESOURCE_LIMITS.max_cache_entries
17693        );
17694        assert_ne!(
17695            UNBOUNDED_RESOURCE_LIMITS.max_expansion_size,
17696            DEFAULT_RESOURCE_LIMITS.max_expansion_size
17697        );
17698        assert_ne!(
17699            UNBOUNDED_RESOURCE_LIMITS.max_macro_body_size,
17700            DEFAULT_RESOURCE_LIMITS.max_macro_body_size
17701        );
17702        assert_ne!(
17703            UNBOUNDED_RESOURCE_LIMITS.max_registered_macros,
17704            DEFAULT_RESOURCE_LIMITS.max_registered_macros
17705        );
17706        assert_ne!(
17707            UNBOUNDED_RESOURCE_LIMITS.max_macro_arity,
17708            DEFAULT_RESOURCE_LIMITS.max_macro_arity
17709        );
17710    }
17711
17712    #[test]
17713    fn expander_with_unbounded_limits_projects_through_resource_limits_getter() {
17714        // Round-trip pin at CONSTRUCTION time — an expander built at
17715        // the ceiling-lifted preset MUST project back through
17716        // [`Expander::resource_limits`] to the SAME constant verbatim.
17717        // Peer of `expander_with_limits_seeds_the_provided_resource_posture`
17718        // one PRESET-POSTURE axis over — that pin exercises the
17719        // constructor's carry via a distinct-low-value bundle; this
17720        // pin exercises it via the ceiling-lifted preset constant.
17721        // Cross-pin the six-field snapshot equals the ceiling-lifted
17722        // constant so a regression that broke either the constant OR
17723        // the constructor's carry fires here.
17724        assert_eq!(
17725            Expander::with_limits(UNBOUNDED_RESOURCE_LIMITS).resource_limits(),
17726            UNBOUNDED_RESOURCE_LIMITS
17727        );
17728    }
17729
17730    #[test]
17731    fn expander_with_unbounded_limits_admits_a_body_over_the_default_size_ceiling() {
17732        // Behavioral pin — the ceiling-lifted preset MUST reach the
17733        // register-time body-size gate as an actually-lifted ceiling.
17734        // Register a macro whose body node count strictly exceeds
17735        // [`DEFAULT_MAX_MACRO_BODY_SIZE`] (a body which under the
17736        // shipped default would fail [`register_macro_def`] with a
17737        // [`LispError::MacroBodySizeExceeded`]) and confirm the
17738        // registration succeeds under the ceiling-lifted preset. Only
17739        // the body-size ceiling being lifted lets a body of that
17740        // magnitude land; the arity ceiling is left at 1 param via
17741        // struct-update override to prove the preset's other five
17742        // ceilings STILL project cleanly at [`usize::MAX`] alongside
17743        // the ceiling under test. A regression that failed to lift
17744        // ONE ceiling across the preset (a stale `DEFAULT_MAX_*` field
17745        // in the const literal) fires here on the axis it dropped.
17746        let mut e = Expander::with_limits(UNBOUNDED_RESOURCE_LIMITS);
17747        // Build a huge quasi-quoted list body: `(k k k k k … k) whose
17748        // node_count() eclipses DEFAULT_MAX_MACRO_BODY_SIZE = 16384.
17749        let mut body = String::from("(defmacro huge (k) `(");
17750        for _ in 0..(DEFAULT_MAX_MACRO_BODY_SIZE + 1) {
17751            body.push_str(",k ");
17752        }
17753        body.push_str("))");
17754        let forms = read(&body).unwrap();
17755        e.expand_program(forms)
17756            .expect("unbounded body-size ceiling must admit a body over the default limit");
17757        assert!(e.has("huge"));
17758    }
17759
17760    #[test]
17761    fn expander_with_unbounded_limits_admits_arity_over_the_default_arity_ceiling() {
17762        // Behavioral pin sibling of the body-size pin above one
17763        // RESOURCE-DIMENSION axis over on the REGISTER-time surface —
17764        // the ceiling-lifted preset MUST also reach the arity gate as
17765        // an actually-lifted ceiling. Register a macro whose param
17766        // list strictly exceeds [`DEFAULT_MAX_MACRO_ARITY`] (a
17767        // 129-slot lambda list which under the shipped default fails
17768        // with [`LispError::MacroArityExceeded`]) and confirm the
17769        // registration succeeds under the ceiling-lifted preset.
17770        use std::fmt::Write as _;
17771        let mut e = Expander::with_limits(UNBOUNDED_RESOURCE_LIMITS);
17772        let mut src = String::from("(defmacro many-arity (");
17773        for i in 0..(DEFAULT_MAX_MACRO_ARITY + 1) {
17774            write!(src, "a-{i} ").unwrap();
17775        }
17776        src.push_str(") `,a-0)");
17777        let forms = read(&src).unwrap();
17778        e.expand_program(forms)
17779            .expect("unbounded arity ceiling must admit a param list over the default limit");
17780        assert!(e.has("many-arity"));
17781    }
17782
17783    #[test]
17784    fn unbounded_resource_limits_composes_via_struct_update_to_isolate_one_ceiling() {
17785        // Ergonomic pin — the `Copy` posture on [`ResourceLimits`] lets
17786        // a caller build "every ceiling lifted EXCEPT this ONE" as ONE
17787        // struct-update literal on the ceiling-lifted preset AND thread
17788        // that composition through [`Expander::with_limits`] as ONE
17789        // typed call. This is the "only THIS ceiling gates" fixture
17790        // shape a chain of six independent setters cannot express in
17791        // ONE expression, and the shape the last commit named as a
17792        // future benefit of the (getter, setter, at-construction)
17793        // three-corner face closing at ONE bundle.
17794        //
17795        // Set only `max_macro_arity: 1`; confirm the arity gate fires
17796        // on a two-arg registration while the five other lifted
17797        // ceilings project through the getter as [`usize::MAX`]. A
17798        // regression that broke the ceiling-lifted preset would EITHER
17799        // fail to project [`usize::MAX`] on one of the five other
17800        // fields OR fail to gate the isolated arity ceiling at 1.
17801        let mut e = Expander::with_limits(ResourceLimits {
17802            max_macro_arity: 1,
17803            ..UNBOUNDED_RESOURCE_LIMITS
17804        });
17805        assert_eq!(e.max_macro_arity(), 1);
17806        assert_eq!(e.max_expansion_depth(), usize::MAX);
17807        assert_eq!(e.max_cache_entries(), usize::MAX);
17808        assert_eq!(e.max_expansion_size(), usize::MAX);
17809        assert_eq!(e.max_macro_body_size(), usize::MAX);
17810        assert_eq!(e.max_registered_macros(), usize::MAX);
17811        let forms = read("(defmacro two (a b) `,a)").unwrap();
17812        let err = e.expand_program(forms).unwrap_err();
17813        assert!(
17814            matches!(
17815                err,
17816                LispError::MacroArityExceeded {
17817                    arity: 2,
17818                    limit: 1,
17819                    ..
17820                }
17821            ),
17822            "struct-update override on the ceiling-lifted preset must \
17823             leave the isolated arity ceiling gating; got: {err:?}"
17824        );
17825    }
17826
17827    #[test]
17828    fn unbounded_resource_limits_carries_through_the_bulk_setter() {
17829        // Coherence pin between the AT-construction constructor and
17830        // the POST-construction bulk setter — both paths that reach the
17831        // ceiling-lifted preset must land at the SAME six values on
17832        // the expander state. Pre-lift a caller wanting the unbounded
17833        // posture composed six individual setter invocations at the
17834        // call site; post-lift both `Expander::with_limits(UNBOUNDED_
17835        // RESOURCE_LIMITS)` and `e.set_resource_limits(UNBOUNDED_
17836        // RESOURCE_LIMITS)` reach the SAME snapshot through the SAME
17837        // constant. A regression that wired the bulk setter to a
17838        // shadow field, or the ceiling-lifted preset to a stale field
17839        // literal, fires here on the constructor-vs-setter identity.
17840        let a = Expander::with_limits(UNBOUNDED_RESOURCE_LIMITS);
17841        let mut b = Expander::new();
17842        b.set_resource_limits(UNBOUNDED_RESOURCE_LIMITS);
17843        assert_eq!(a.resource_limits(), b.resource_limits());
17844        assert_eq!(a.resource_limits(), UNBOUNDED_RESOURCE_LIMITS);
17845    }
17846
17847    // ── ResourceLimits::strictest / ::most_permissive — meet/join ──────
17848
17849    /// A hand-authored asymmetric posture used across the lattice-law
17850    /// tests. Every field is a distinct positive value strictly less
17851    /// than [`usize::MAX`] and distinct from every corresponding
17852    /// [`DEFAULT_MAX_*`] module constant, so composition witnesses
17853    /// (meet, join, absorption, distributivity) discriminate across
17854    /// all three postures unambiguously.
17855    const HAND_AUTHORED_MID_POSTURE: ResourceLimits = ResourceLimits {
17856        max_expansion_depth: 7,
17857        max_cache_entries: 11,
17858        max_expansion_size: 13,
17859        max_macro_body_size: 17,
17860        max_registered_macros: 19,
17861        max_macro_arity: 23,
17862    };
17863
17864    /// A second asymmetric posture whose per-axis values are on both
17865    /// sides of `HAND_AUTHORED_MID_POSTURE`'s values — three axes
17866    /// smaller (so the meet picks THIS posture on those axes and the
17867    /// join picks `MID` on those axes) and three axes larger (mirror).
17868    /// Together the two postures exercise every ordering combination
17869    /// on the six-axis pointwise `min`/`max` cascade.
17870    const HAND_AUTHORED_OTHER_POSTURE: ResourceLimits = ResourceLimits {
17871        max_expansion_depth: 3,   // smaller than MID's 7
17872        max_cache_entries: 29,    // larger  than MID's 11
17873        max_expansion_size: 5,    // smaller than MID's 13
17874        max_macro_body_size: 31,  // larger  than MID's 17
17875        max_registered_macros: 2, // smaller than MID's 19
17876        max_macro_arity: 41,      // larger  than MID's 23
17877    };
17878
17879    #[test]
17880    fn resource_limits_strictest_of_default_and_unbounded_projects_the_default() {
17881        // Concrete-preset pin — the meet of the (shipped default,
17882        // ceiling-lifted unbounded) preset pair is the DEFAULT preset
17883        // structurally: every `DEFAULT_MAX_*` module constant is a
17884        // concrete positive value strictly less than [`usize::MAX`],
17885        // so on every axis the pointwise-`min` picks the DEFAULT side.
17886        // Peer of `most_permissive_of_default_and_unbounded_projects_the_unbounded`
17887        // one COMBINATOR axis over on the same shipped-preset-pair
17888        // surface.
17889        assert_eq!(
17890            DEFAULT_RESOURCE_LIMITS.strictest(UNBOUNDED_RESOURCE_LIMITS),
17891            DEFAULT_RESOURCE_LIMITS,
17892        );
17893        assert_eq!(
17894            UNBOUNDED_RESOURCE_LIMITS.strictest(DEFAULT_RESOURCE_LIMITS),
17895            DEFAULT_RESOURCE_LIMITS,
17896        );
17897    }
17898
17899    #[test]
17900    fn resource_limits_most_permissive_of_default_and_unbounded_projects_the_unbounded() {
17901        // Concrete-preset pin — the join of the (shipped default,
17902        // ceiling-lifted unbounded) preset pair is the UNBOUNDED
17903        // preset structurally: every axis's pointwise-`max` picks the
17904        // [`usize::MAX`] side over any concrete default. Peer of
17905        // `strictest_of_default_and_unbounded_projects_the_default`.
17906        assert_eq!(
17907            DEFAULT_RESOURCE_LIMITS.most_permissive(UNBOUNDED_RESOURCE_LIMITS),
17908            UNBOUNDED_RESOURCE_LIMITS,
17909        );
17910        assert_eq!(
17911            UNBOUNDED_RESOURCE_LIMITS.most_permissive(DEFAULT_RESOURCE_LIMITS),
17912            UNBOUNDED_RESOURCE_LIMITS,
17913        );
17914    }
17915
17916    #[test]
17917    fn resource_limits_strictest_takes_pointwise_min_on_every_axis() {
17918        // Field-level pin — the meet operation projects the pointwise
17919        // `min` across the six-field surface. Pinned as six
17920        // independent field asserts (rather than one struct equality)
17921        // so a drift on ONE axis carries a distinct-name failure that
17922        // names the drifting axis; a future SEVENTH ceiling extension
17923        // requires an additional field assert here in lockstep.
17924        let m = HAND_AUTHORED_MID_POSTURE.strictest(HAND_AUTHORED_OTHER_POSTURE);
17925        assert_eq!(m.max_expansion_depth, 3);
17926        assert_eq!(m.max_cache_entries, 11);
17927        assert_eq!(m.max_expansion_size, 5);
17928        assert_eq!(m.max_macro_body_size, 17);
17929        assert_eq!(m.max_registered_macros, 2);
17930        assert_eq!(m.max_macro_arity, 23);
17931    }
17932
17933    #[test]
17934    fn resource_limits_most_permissive_takes_pointwise_max_on_every_axis() {
17935        // Field-level pin — the join operation projects the pointwise
17936        // `max` across the six-field surface. Pinned as six
17937        // independent field asserts so a drift on ONE axis carries a
17938        // distinct-name failure that names the drifting axis.
17939        let j = HAND_AUTHORED_MID_POSTURE.most_permissive(HAND_AUTHORED_OTHER_POSTURE);
17940        assert_eq!(j.max_expansion_depth, 7);
17941        assert_eq!(j.max_cache_entries, 29);
17942        assert_eq!(j.max_expansion_size, 13);
17943        assert_eq!(j.max_macro_body_size, 31);
17944        assert_eq!(j.max_registered_macros, 19);
17945        assert_eq!(j.max_macro_arity, 41);
17946    }
17947
17948    #[test]
17949    fn resource_limits_strictest_is_idempotent() {
17950        // Lattice law — `a ∧ a = a`. Every posture is a fixed point of
17951        // its own meet with itself; a regression that changed the
17952        // per-axis primitive to something other than pointwise `min`
17953        // (a `min - 1` off-by-one, or an arbitrary tie-breaker on
17954        // equal values) fires here.
17955        assert_eq!(
17956            DEFAULT_RESOURCE_LIMITS.strictest(DEFAULT_RESOURCE_LIMITS),
17957            DEFAULT_RESOURCE_LIMITS,
17958        );
17959        assert_eq!(
17960            UNBOUNDED_RESOURCE_LIMITS.strictest(UNBOUNDED_RESOURCE_LIMITS),
17961            UNBOUNDED_RESOURCE_LIMITS,
17962        );
17963        assert_eq!(
17964            HAND_AUTHORED_MID_POSTURE.strictest(HAND_AUTHORED_MID_POSTURE),
17965            HAND_AUTHORED_MID_POSTURE,
17966        );
17967    }
17968
17969    #[test]
17970    fn resource_limits_most_permissive_is_idempotent() {
17971        // Lattice law — `a ∨ a = a`. Sibling of the strictest
17972        // idempotence pin one COMBINATOR axis over.
17973        assert_eq!(
17974            DEFAULT_RESOURCE_LIMITS.most_permissive(DEFAULT_RESOURCE_LIMITS),
17975            DEFAULT_RESOURCE_LIMITS,
17976        );
17977        assert_eq!(
17978            UNBOUNDED_RESOURCE_LIMITS.most_permissive(UNBOUNDED_RESOURCE_LIMITS),
17979            UNBOUNDED_RESOURCE_LIMITS,
17980        );
17981        assert_eq!(
17982            HAND_AUTHORED_MID_POSTURE.most_permissive(HAND_AUTHORED_MID_POSTURE),
17983            HAND_AUTHORED_MID_POSTURE,
17984        );
17985    }
17986
17987    #[test]
17988    fn resource_limits_strictest_is_commutative() {
17989        // Lattice law — `a ∧ b = b ∧ a`. Pointwise `min` is symmetric
17990        // on every axis, so the composition inherits commutativity
17991        // structurally. A regression that broke ordering (e.g. a
17992        // per-axis "prefer left on tie") fires here.
17993        assert_eq!(
17994            HAND_AUTHORED_MID_POSTURE.strictest(HAND_AUTHORED_OTHER_POSTURE),
17995            HAND_AUTHORED_OTHER_POSTURE.strictest(HAND_AUTHORED_MID_POSTURE),
17996        );
17997        assert_eq!(
17998            DEFAULT_RESOURCE_LIMITS.strictest(HAND_AUTHORED_MID_POSTURE),
17999            HAND_AUTHORED_MID_POSTURE.strictest(DEFAULT_RESOURCE_LIMITS),
18000        );
18001    }
18002
18003    #[test]
18004    fn resource_limits_most_permissive_is_commutative() {
18005        // Lattice law — `a ∨ b = b ∨ a`. Sibling of the strictest
18006        // commutativity pin one COMBINATOR axis over.
18007        assert_eq!(
18008            HAND_AUTHORED_MID_POSTURE.most_permissive(HAND_AUTHORED_OTHER_POSTURE),
18009            HAND_AUTHORED_OTHER_POSTURE.most_permissive(HAND_AUTHORED_MID_POSTURE),
18010        );
18011        assert_eq!(
18012            DEFAULT_RESOURCE_LIMITS.most_permissive(HAND_AUTHORED_MID_POSTURE),
18013            HAND_AUTHORED_MID_POSTURE.most_permissive(DEFAULT_RESOURCE_LIMITS),
18014        );
18015    }
18016
18017    #[test]
18018    fn resource_limits_strictest_is_associative() {
18019        // Lattice law — `(a ∧ b) ∧ c = a ∧ (b ∧ c)`. Pointwise `min`
18020        // is associative on every axis, so the composition inherits
18021        // associativity structurally. A regression that broke
18022        // grouping (e.g. an accidental accumulator that skewed the
18023        // order of `min` invocations) fires here.
18024        let a = DEFAULT_RESOURCE_LIMITS;
18025        let b = HAND_AUTHORED_MID_POSTURE;
18026        let c = HAND_AUTHORED_OTHER_POSTURE;
18027        assert_eq!(a.strictest(b).strictest(c), a.strictest(b.strictest(c)));
18028    }
18029
18030    #[test]
18031    fn resource_limits_most_permissive_is_associative() {
18032        // Lattice law — `(a ∨ b) ∨ c = a ∨ (b ∨ c)`. Sibling of the
18033        // strictest associativity pin one COMBINATOR axis over.
18034        let a = DEFAULT_RESOURCE_LIMITS;
18035        let b = HAND_AUTHORED_MID_POSTURE;
18036        let c = HAND_AUTHORED_OTHER_POSTURE;
18037        assert_eq!(
18038            a.most_permissive(b).most_permissive(c),
18039            a.most_permissive(b.most_permissive(c)),
18040        );
18041    }
18042
18043    #[test]
18044    fn resource_limits_meet_and_join_satisfy_absorption() {
18045        // Lattice absorption identities — `a ∧ (a ∨ b) = a` AND
18046        // `a ∨ (a ∧ b) = a`. Together with commutativity, idempotence,
18047        // and associativity they define the lattice axioms; the two
18048        // absorption laws are the operator PAIR's interlock (each
18049        // undoes the other's contribution on the shared operand).
18050        // Any per-axis primitive pair for which `min(a, max(a, b)) = a`
18051        // AND `max(a, min(a, b)) = a` inherits this at the pointwise
18052        // composition; pointwise-`min`/`max` on [`usize`] satisfy both.
18053        let a = HAND_AUTHORED_MID_POSTURE;
18054        let b = HAND_AUTHORED_OTHER_POSTURE;
18055        assert_eq!(a.strictest(a.most_permissive(b)), a);
18056        assert_eq!(a.most_permissive(a.strictest(b)), a);
18057    }
18058
18059    #[test]
18060    fn resource_limits_meet_distributes_over_join() {
18061        // Distributive-lattice pin — `a ∧ (b ∨ c) = (a ∧ b) ∨ (a ∧ c)`.
18062        // Pointwise-`min` distributes over pointwise-`max` on [`usize`]
18063        // on every axis, so the composition inherits distributivity
18064        // structurally. A regression that broke the pointwise
18065        // structure (e.g. a per-axis operation that used SUBTRACTION
18066        // rather than MIN/MAX) fires here — subtraction does NOT
18067        // distribute over addition and would break the law on the
18068        // asymmetric-posture witness pair.
18069        let a = DEFAULT_RESOURCE_LIMITS;
18070        let b = HAND_AUTHORED_MID_POSTURE;
18071        let c = HAND_AUTHORED_OTHER_POSTURE;
18072        assert_eq!(
18073            a.strictest(b.most_permissive(c)),
18074            a.strictest(b).most_permissive(a.strictest(c)),
18075        );
18076    }
18077
18078    #[test]
18079    fn resource_limits_join_distributes_over_meet() {
18080        // Distributive-lattice pin, sibling of the meet-over-join
18081        // distributivity pin one COMBINATOR axis over — `a ∨ (b ∧ c) =
18082        // (a ∨ b) ∧ (a ∨ c)`. In a distributive lattice BOTH
18083        // distributivity identities hold; pin both so a regression
18084        // that broke ONE without the other cannot slip through.
18085        let a = DEFAULT_RESOURCE_LIMITS;
18086        let b = HAND_AUTHORED_MID_POSTURE;
18087        let c = HAND_AUTHORED_OTHER_POSTURE;
18088        assert_eq!(
18089            a.most_permissive(b.strictest(c)),
18090            a.most_permissive(b).strictest(a.most_permissive(c)),
18091        );
18092    }
18093
18094    #[test]
18095    fn resource_limits_strictest_is_dominated_by_both_operands_pointwise() {
18096        // Meet-lower-bound pin — the meet lies at or below BOTH
18097        // operands on every axis. This is the structural GLB property
18098        // of the meet in the pointwise partial order (tighter-first):
18099        // any admissible input under the meet is admissible under
18100        // both operands, so the meet's ceiling must not exceed either
18101        // operand's ceiling on any axis.
18102        let a = HAND_AUTHORED_MID_POSTURE;
18103        let b = HAND_AUTHORED_OTHER_POSTURE;
18104        let m = a.strictest(b);
18105        assert!(m.max_expansion_depth <= a.max_expansion_depth);
18106        assert!(m.max_expansion_depth <= b.max_expansion_depth);
18107        assert!(m.max_cache_entries <= a.max_cache_entries);
18108        assert!(m.max_cache_entries <= b.max_cache_entries);
18109        assert!(m.max_expansion_size <= a.max_expansion_size);
18110        assert!(m.max_expansion_size <= b.max_expansion_size);
18111        assert!(m.max_macro_body_size <= a.max_macro_body_size);
18112        assert!(m.max_macro_body_size <= b.max_macro_body_size);
18113        assert!(m.max_registered_macros <= a.max_registered_macros);
18114        assert!(m.max_registered_macros <= b.max_registered_macros);
18115        assert!(m.max_macro_arity <= a.max_macro_arity);
18116        assert!(m.max_macro_arity <= b.max_macro_arity);
18117    }
18118
18119    #[test]
18120    fn resource_limits_most_permissive_dominates_both_operands_pointwise() {
18121        // Join-upper-bound pin — the join lies at or above BOTH
18122        // operands on every axis. Sibling of the strictest-lower-
18123        // bound pin one COMBINATOR axis over; the structural LUB
18124        // property of the join in the pointwise partial order.
18125        let a = HAND_AUTHORED_MID_POSTURE;
18126        let b = HAND_AUTHORED_OTHER_POSTURE;
18127        let j = a.most_permissive(b);
18128        assert!(j.max_expansion_depth >= a.max_expansion_depth);
18129        assert!(j.max_expansion_depth >= b.max_expansion_depth);
18130        assert!(j.max_cache_entries >= a.max_cache_entries);
18131        assert!(j.max_cache_entries >= b.max_cache_entries);
18132        assert!(j.max_expansion_size >= a.max_expansion_size);
18133        assert!(j.max_expansion_size >= b.max_expansion_size);
18134        assert!(j.max_macro_body_size >= a.max_macro_body_size);
18135        assert!(j.max_macro_body_size >= b.max_macro_body_size);
18136        assert!(j.max_registered_macros >= a.max_registered_macros);
18137        assert!(j.max_registered_macros >= b.max_registered_macros);
18138        assert!(j.max_macro_arity >= a.max_macro_arity);
18139        assert!(j.max_macro_arity >= b.max_macro_arity);
18140    }
18141
18142    #[test]
18143    fn resource_limits_strictest_composes_at_compile_time_via_const_fn() {
18144        // Const-fn pin — the meet is evaluable in const context, so a
18145        // caller can stitch a new `pub const` preset from two shipped
18146        // presets without deferring the composition to runtime. Pinned
18147        // as a `const` binding at the item level so the compiler
18148        // exercises the const-fn path structurally; the runtime
18149        // assertion is redundant for a `const` value that already
18150        // evaluated at compile time, but pins the identity as a typed
18151        // theorem alongside every OTHER `strictest` pin above.
18152        const DEFAULT_TIGHTENED_BY_MID: ResourceLimits =
18153            DEFAULT_RESOURCE_LIMITS.strictest(HAND_AUTHORED_MID_POSTURE);
18154        assert_eq!(
18155            DEFAULT_TIGHTENED_BY_MID.max_expansion_depth,
18156            min_usize(
18157                DEFAULT_RESOURCE_LIMITS.max_expansion_depth,
18158                HAND_AUTHORED_MID_POSTURE.max_expansion_depth,
18159            ),
18160        );
18161        // Extra check: the composition MUST project the MID posture's
18162        // small value on every axis where MID is smaller than DEFAULT
18163        // — MID's per-axis values (7, 11, 13, 17, 19, 23) are all
18164        // strictly less than every `DEFAULT_MAX_*` constant, so the
18165        // meet projects MID verbatim.
18166        assert_eq!(DEFAULT_TIGHTENED_BY_MID, HAND_AUTHORED_MID_POSTURE);
18167    }
18168
18169    #[test]
18170    fn resource_limits_most_permissive_composes_at_compile_time_via_const_fn() {
18171        // Const-fn pin sibling of the strictest const-composition pin
18172        // one COMBINATOR axis over — the join is also `const fn`, so
18173        // a caller can stitch a new `pub const` preset via the LUB
18174        // combinator at compile time.
18175        const DEFAULT_LOOSENED_BY_UNBOUNDED: ResourceLimits =
18176            DEFAULT_RESOURCE_LIMITS.most_permissive(UNBOUNDED_RESOURCE_LIMITS);
18177        assert_eq!(DEFAULT_LOOSENED_BY_UNBOUNDED, UNBOUNDED_RESOURCE_LIMITS);
18178    }
18179
18180    // ── ResourceLimits::leq — pointwise partial order ─────────────────
18181
18182    #[test]
18183    fn resource_limits_leq_is_pointwise_field_conjunction() {
18184        // Field-level pin — the leq relation projects the pointwise
18185        // `<=` conjunction across the six-field surface. `LOOSER` is
18186        // `MID + 1` on every axis, so `MID.leq(LOOSER)` holds; a
18187        // per-axis tighten on ANY field alone flips the relation to
18188        // false. Pinned as six independent tighten-per-axis asserts so
18189        // a drift on ONE axis carries a distinct-name failure that
18190        // names the drifting axis; a future SEVENTH ceiling extension
18191        // requires an additional per-axis tighten assert here in
18192        // lockstep.
18193        const LOOSER: ResourceLimits = ResourceLimits {
18194            max_expansion_depth: HAND_AUTHORED_MID_POSTURE.max_expansion_depth + 1,
18195            max_cache_entries: HAND_AUTHORED_MID_POSTURE.max_cache_entries + 1,
18196            max_expansion_size: HAND_AUTHORED_MID_POSTURE.max_expansion_size + 1,
18197            max_macro_body_size: HAND_AUTHORED_MID_POSTURE.max_macro_body_size + 1,
18198            max_registered_macros: HAND_AUTHORED_MID_POSTURE.max_registered_macros + 1,
18199            max_macro_arity: HAND_AUTHORED_MID_POSTURE.max_macro_arity + 1,
18200        };
18201        assert!(HAND_AUTHORED_MID_POSTURE.leq(LOOSER));
18202        assert!(!LOOSER.leq(HAND_AUTHORED_MID_POSTURE));
18203
18204        // Per-axis exceed pins: raise MID's field ONE axis above LOOSER
18205        // on that axis; the leq must flip to false. Every other axis
18206        // stays at MID, so LOOSER still dominates on those axes —
18207        // isolating the single-axis violation.
18208        let exceed_depth = ResourceLimits {
18209            max_expansion_depth: LOOSER.max_expansion_depth + 1,
18210            ..HAND_AUTHORED_MID_POSTURE
18211        };
18212        assert!(!exceed_depth.leq(LOOSER));
18213        let exceed_cache = ResourceLimits {
18214            max_cache_entries: LOOSER.max_cache_entries + 1,
18215            ..HAND_AUTHORED_MID_POSTURE
18216        };
18217        assert!(!exceed_cache.leq(LOOSER));
18218        let exceed_expansion = ResourceLimits {
18219            max_expansion_size: LOOSER.max_expansion_size + 1,
18220            ..HAND_AUTHORED_MID_POSTURE
18221        };
18222        assert!(!exceed_expansion.leq(LOOSER));
18223        let exceed_body = ResourceLimits {
18224            max_macro_body_size: LOOSER.max_macro_body_size + 1,
18225            ..HAND_AUTHORED_MID_POSTURE
18226        };
18227        assert!(!exceed_body.leq(LOOSER));
18228        let exceed_registered = ResourceLimits {
18229            max_registered_macros: LOOSER.max_registered_macros + 1,
18230            ..HAND_AUTHORED_MID_POSTURE
18231        };
18232        assert!(!exceed_registered.leq(LOOSER));
18233        let exceed_arity = ResourceLimits {
18234            max_macro_arity: LOOSER.max_macro_arity + 1,
18235            ..HAND_AUTHORED_MID_POSTURE
18236        };
18237        assert!(!exceed_arity.leq(LOOSER));
18238    }
18239
18240    #[test]
18241    fn resource_limits_leq_is_reflexive() {
18242        // Partial-order law — `a ≤ a` for every posture. Reflexivity
18243        // is the identity axiom on any lattice's underlying partial
18244        // order; a regression that changed the per-axis primitive to
18245        // strict `<` (or that added an accidental "prefer other on
18246        // tie" branch) fires here on every shipped posture.
18247        assert!(DEFAULT_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18248        assert!(UNBOUNDED_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18249        assert!(HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_MID_POSTURE));
18250        assert!(HAND_AUTHORED_OTHER_POSTURE.leq(HAND_AUTHORED_OTHER_POSTURE));
18251    }
18252
18253    #[test]
18254    fn resource_limits_leq_is_antisymmetric() {
18255        // Partial-order law — `a ≤ b ∧ b ≤ a ⇒ a = b`. The mutual-
18256        // dominance witness on a partial order pins the two operands
18257        // to structural equality. On the pointwise `<=` composition
18258        // this holds because `a.max_X <= b.max_X && b.max_X <= a.max_X
18259        // ⇒ a.max_X == b.max_X` on every axis, and struct equality
18260        // is field-conjunction. Sibling to reflexivity one AXIOM axis
18261        // over on the partial-order axiom surface.
18262        //
18263        // Two distinct constructions of the SAME six-field posture
18264        // (one via struct literal, one via `..DEFAULT_RESOURCE_LIMITS`
18265        // spread of the shipped preset) — mutual leq holds; struct
18266        // equality holds; antisymmetry pin closes.
18267        let a = DEFAULT_RESOURCE_LIMITS;
18268        let b = ResourceLimits {
18269            max_expansion_depth: DEFAULT_RESOURCE_LIMITS.max_expansion_depth,
18270            max_cache_entries: DEFAULT_RESOURCE_LIMITS.max_cache_entries,
18271            max_expansion_size: DEFAULT_RESOURCE_LIMITS.max_expansion_size,
18272            max_macro_body_size: DEFAULT_RESOURCE_LIMITS.max_macro_body_size,
18273            max_registered_macros: DEFAULT_RESOURCE_LIMITS.max_registered_macros,
18274            max_macro_arity: DEFAULT_RESOURCE_LIMITS.max_macro_arity,
18275        };
18276        assert!(a.leq(b));
18277        assert!(b.leq(a));
18278        assert_eq!(a, b);
18279    }
18280
18281    #[test]
18282    fn resource_limits_leq_is_transitive() {
18283        // Partial-order law — `a ≤ b ∧ b ≤ c ⇒ a ≤ c`. Transitivity
18284        // is the composition axiom on any lattice's partial order.
18285        // Pointwise `<=` on [`usize`] is transitive per axis, so the
18286        // six-field conjunction inherits it structurally.
18287        //
18288        // Chain witness: MID's every-axis `+1` shift is LOOSER, and
18289        // LOOSER's every-axis `+1` shift is LOOSEST; the chain MID
18290        // ≤ LOOSER ≤ LOOSEST implies MID ≤ LOOSEST.
18291        const LOOSER: ResourceLimits = ResourceLimits {
18292            max_expansion_depth: HAND_AUTHORED_MID_POSTURE.max_expansion_depth + 1,
18293            max_cache_entries: HAND_AUTHORED_MID_POSTURE.max_cache_entries + 1,
18294            max_expansion_size: HAND_AUTHORED_MID_POSTURE.max_expansion_size + 1,
18295            max_macro_body_size: HAND_AUTHORED_MID_POSTURE.max_macro_body_size + 1,
18296            max_registered_macros: HAND_AUTHORED_MID_POSTURE.max_registered_macros + 1,
18297            max_macro_arity: HAND_AUTHORED_MID_POSTURE.max_macro_arity + 1,
18298        };
18299        const LOOSEST: ResourceLimits = ResourceLimits {
18300            max_expansion_depth: LOOSER.max_expansion_depth + 1,
18301            max_cache_entries: LOOSER.max_cache_entries + 1,
18302            max_expansion_size: LOOSER.max_expansion_size + 1,
18303            max_macro_body_size: LOOSER.max_macro_body_size + 1,
18304            max_registered_macros: LOOSER.max_registered_macros + 1,
18305            max_macro_arity: LOOSER.max_macro_arity + 1,
18306        };
18307        assert!(HAND_AUTHORED_MID_POSTURE.leq(LOOSER));
18308        assert!(LOOSER.leq(LOOSEST));
18309        assert!(HAND_AUTHORED_MID_POSTURE.leq(LOOSEST));
18310    }
18311
18312    #[test]
18313    fn resource_limits_leq_of_default_and_unbounded_is_a_strict_order() {
18314        // Concrete-preset pin — the leq relation on the shipped
18315        // preset pair is STRICT: DEFAULT ≤ UNBOUNDED holds (every
18316        // `DEFAULT_MAX_*` module constant is a concrete positive
18317        // value at most [`usize::MAX`]) AND UNBOUNDED ≤ DEFAULT
18318        // fails (every `DEFAULT_MAX_*` is strictly less than
18319        // [`usize::MAX`], so on every axis [`usize::MAX`] exceeds
18320        // the matching default). Peer of
18321        // `strictest_of_default_and_unbounded_projects_the_default`
18322        // one PRIMITIVE-KIND axis over on the same shipped-preset-
18323        // pair surface: where the meet composition projects DEFAULT,
18324        // this relation asserts DEFAULT is the tighter of the two.
18325        assert!(DEFAULT_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18326        assert!(!UNBOUNDED_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18327    }
18328
18329    #[test]
18330    fn resource_limits_leq_is_not_total_on_asymmetric_postures() {
18331        // Partial-order NON-total pin — the two hand-authored asymmetric
18332        // postures have MID smaller on three axes (depth, expansion,
18333        // registered) and OTHER smaller on the other three (cache,
18334        // body, arity); neither dominates the other, so BOTH
18335        // directions of the leq relation fail. The pin discriminates
18336        // a regression that promoted `leq` from partial to total
18337        // (e.g. an accidental `||` swap in the conjunction, which
18338        // would make every pair comparable).
18339        assert!(!HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_OTHER_POSTURE));
18340        assert!(!HAND_AUTHORED_OTHER_POSTURE.leq(HAND_AUTHORED_MID_POSTURE));
18341    }
18342
18343    #[test]
18344    fn resource_limits_leq_agrees_with_meet() {
18345        // Lattice cross-check axiom — `a ≤ b ⇔ a ⊓ b = a`. Every
18346        // classification lattice one crate over
18347        // (`tatara-lattice/src/lib.rs`'s preamble) declares this
18348        // agreement axiom binding the partial order to the meet
18349        // combinator, and the default-impl body on
18350        // `tatara_lattice::Lattice::leq` derives `leq` FROM `meet`
18351        // through it. This pin discharges the same axiom on the
18352        // ResourceLimits algebra as a two-direction structural
18353        // equality across the strict-order preset pair AND the
18354        // reflexivity witness.
18355        //
18356        // Forward (a ≤ b ⇒ a ⊓ b = a):
18357        assert!(DEFAULT_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18358        assert_eq!(
18359            DEFAULT_RESOURCE_LIMITS.strictest(UNBOUNDED_RESOURCE_LIMITS),
18360            DEFAULT_RESOURCE_LIMITS,
18361        );
18362        // Reverse (a ⊓ b = a ⇒ a ≤ b), reflexivity witness:
18363        assert_eq!(
18364            DEFAULT_RESOURCE_LIMITS.strictest(DEFAULT_RESOURCE_LIMITS),
18365            DEFAULT_RESOURCE_LIMITS,
18366        );
18367        assert!(DEFAULT_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18368
18369        // Non-agreement witness — the incomparable asymmetric postures
18370        // have meet distinct from EITHER operand, and leq false in
18371        // both directions; agreement holds on the negative side too.
18372        assert!(!HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_OTHER_POSTURE));
18373        assert_ne!(
18374            HAND_AUTHORED_MID_POSTURE.strictest(HAND_AUTHORED_OTHER_POSTURE),
18375            HAND_AUTHORED_MID_POSTURE,
18376        );
18377    }
18378
18379    #[test]
18380    fn resource_limits_leq_agrees_with_join() {
18381        // Lattice cross-check axiom, sibling of the meet-agreement
18382        // pin one COMBINATOR axis over — `a ≤ b ⇔ a ⊔ b = b`.
18383        //
18384        // Forward (a ≤ b ⇒ a ⊔ b = b):
18385        assert!(DEFAULT_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18386        assert_eq!(
18387            DEFAULT_RESOURCE_LIMITS.most_permissive(UNBOUNDED_RESOURCE_LIMITS),
18388            UNBOUNDED_RESOURCE_LIMITS,
18389        );
18390        // Reverse (a ⊔ b = b ⇒ a ≤ b), reflexivity witness:
18391        assert_eq!(
18392            DEFAULT_RESOURCE_LIMITS.most_permissive(DEFAULT_RESOURCE_LIMITS),
18393            DEFAULT_RESOURCE_LIMITS,
18394        );
18395        assert!(DEFAULT_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18396
18397        // Non-agreement witness — the incomparable asymmetric postures
18398        // have join distinct from EITHER operand.
18399        assert!(!HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_OTHER_POSTURE));
18400        assert_ne!(
18401            HAND_AUTHORED_MID_POSTURE.most_permissive(HAND_AUTHORED_OTHER_POSTURE),
18402            HAND_AUTHORED_OTHER_POSTURE,
18403        );
18404    }
18405
18406    #[test]
18407    fn resource_limits_strictest_is_leq_both_operands() {
18408        // Meet-lower-bound pin, typed lattice-relation companion to
18409        // `resource_limits_strictest_is_dominated_by_both_operands_pointwise`
18410        // one PRIMITIVE-KIND axis over — where the per-axis pin
18411        // discharged the six independent `<=` asserts inline, this pin
18412        // routes through the typed `leq` primitive, so the
18413        // greatest-lower-bound property is stated in the LATTICE
18414        // vocabulary (meet-is-leq-both-operands) rather than the
18415        // six-fold field-conjunction vocabulary.
18416        let a = HAND_AUTHORED_MID_POSTURE;
18417        let b = HAND_AUTHORED_OTHER_POSTURE;
18418        let m = a.strictest(b);
18419        assert!(m.leq(a));
18420        assert!(m.leq(b));
18421    }
18422
18423    #[test]
18424    fn resource_limits_most_permissive_is_geq_both_operands() {
18425        // Join-upper-bound pin, typed lattice-relation companion to
18426        // `resource_limits_most_permissive_dominates_both_operands_pointwise`
18427        // one PRIMITIVE-KIND axis over. Stated in the LATTICE
18428        // vocabulary as `a ≤ a ⊔ b ∧ b ≤ a ⊔ b` — sibling of the
18429        // meet-lower-bound pin one COMBINATOR axis over.
18430        let a = HAND_AUTHORED_MID_POSTURE;
18431        let b = HAND_AUTHORED_OTHER_POSTURE;
18432        let j = a.most_permissive(b);
18433        assert!(a.leq(j));
18434        assert!(b.leq(j));
18435    }
18436
18437    #[test]
18438    fn resource_limits_leq_evaluates_at_compile_time_via_const_fn() {
18439        // Const-fn pin — the leq relation is evaluable in const
18440        // context, so a caller can pin a preset-relation identity at
18441        // compile time. Sibling of the const-fn composition pins on
18442        // `strictest` / `most_permissive` one PRIMITIVE-KIND axis
18443        // over: those two operations compose two presets into a
18444        // const-time third, and this operation decides a const-time
18445        // yes/no on two presets. `const _: ()` at item scope is a
18446        // compile-time proof — the compiler rejects the module if
18447        // either identity does not hold; a runtime `assert!` over the
18448        // same expression would fire at test time on the same value
18449        // clippy's `assertions_on_constants` correctly names as a
18450        // redundant late check on a compile-time constant.
18451        //
18452        // Strict-order preset-pair pin — DEFAULT ≤ UNBOUNDED (every
18453        // `DEFAULT_MAX_*` module constant is at most [`usize::MAX`]):
18454        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18455        // Reverse direction fails — [`usize::MAX`] exceeds every
18456        // concrete `DEFAULT_MAX_*` constant, so the partial order is
18457        // STRICT on the shipped preset pair:
18458        const _: () = assert!(!UNBOUNDED_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18459    }
18460
18461    // ── EMPTY_RESOURCE_LIMITS — bounded-lattice bottom preset ─────────
18462
18463    #[test]
18464    fn empty_resource_limits_binds_every_ceiling_to_zero() {
18465        // Field-level pin — the zero preset carries `0` on every axis
18466        // of the six-field surface. Pinned as six independent field
18467        // asserts (rather than one struct equality) so a drift on ONE
18468        // field carries a distinct-name failure that names the drifting
18469        // axis. A future extension that adds a SEVENTH ceiling requires
18470        // an additional field assert here in lockstep with the const
18471        // literal — rustc's field-exhaustiveness on the const forces
18472        // the new field to appear, and this pin exercises the seed
18473        // value. Structural peer of
18474        // `unbounded_resource_limits_binds_every_ceiling_to_usize_max`
18475        // one LATTICE-POLE axis over on the (bottom, top)
18476        // bounded-lattice preset-pair surface.
18477        assert_eq!(EMPTY_RESOURCE_LIMITS.max_expansion_depth, 0);
18478        assert_eq!(EMPTY_RESOURCE_LIMITS.max_cache_entries, 0);
18479        assert_eq!(EMPTY_RESOURCE_LIMITS.max_expansion_size, 0);
18480        assert_eq!(EMPTY_RESOURCE_LIMITS.max_macro_body_size, 0);
18481        assert_eq!(EMPTY_RESOURCE_LIMITS.max_registered_macros, 0);
18482        assert_eq!(EMPTY_RESOURCE_LIMITS.max_macro_arity, 0);
18483    }
18484
18485    #[test]
18486    fn empty_resource_limits_disagrees_with_default_on_every_ceiling() {
18487        // Structural-distinctness pin — the two constants
18488        // [`DEFAULT_RESOURCE_LIMITS`] and [`EMPTY_RESOURCE_LIMITS`] sit
18489        // at DISTINCT points on the preset-posture axis, and the
18490        // disagreement is EXHAUSTIVE on the six-field surface — every
18491        // shipped `DEFAULT_MAX_*` module constant is a concrete
18492        // strictly-positive value, so every axis distinguishes DEFAULT
18493        // from the all-zero bottom. Peer of
18494        // `unbounded_resource_limits_disagrees_with_default_on_every_ceiling`
18495        // one LATTICE-POLE axis over.
18496        assert_ne!(
18497            EMPTY_RESOURCE_LIMITS.max_expansion_depth,
18498            DEFAULT_RESOURCE_LIMITS.max_expansion_depth
18499        );
18500        assert_ne!(
18501            EMPTY_RESOURCE_LIMITS.max_cache_entries,
18502            DEFAULT_RESOURCE_LIMITS.max_cache_entries
18503        );
18504        assert_ne!(
18505            EMPTY_RESOURCE_LIMITS.max_expansion_size,
18506            DEFAULT_RESOURCE_LIMITS.max_expansion_size
18507        );
18508        assert_ne!(
18509            EMPTY_RESOURCE_LIMITS.max_macro_body_size,
18510            DEFAULT_RESOURCE_LIMITS.max_macro_body_size
18511        );
18512        assert_ne!(
18513            EMPTY_RESOURCE_LIMITS.max_registered_macros,
18514            DEFAULT_RESOURCE_LIMITS.max_registered_macros
18515        );
18516        assert_ne!(
18517            EMPTY_RESOURCE_LIMITS.max_macro_arity,
18518            DEFAULT_RESOURCE_LIMITS.max_macro_arity
18519        );
18520    }
18521
18522    #[test]
18523    fn empty_resource_limits_disagrees_with_unbounded_on_every_ceiling() {
18524        // Diagonal-distinctness pin — the (BOTTOM, TOP) preset pair
18525        // disagrees on every axis (`0 != usize::MAX` on every field).
18526        // Structural companion to the DEFAULT-vs-EMPTY and
18527        // UNBOUNDED-vs-DEFAULT disagreement pins one PRESET-PAIR axis
18528        // over — together the three pin the six-field surface at the
18529        // full diagonal of the (EMPTY, DEFAULT, UNBOUNDED) triple.
18530        assert_ne!(
18531            EMPTY_RESOURCE_LIMITS.max_expansion_depth,
18532            UNBOUNDED_RESOURCE_LIMITS.max_expansion_depth
18533        );
18534        assert_ne!(
18535            EMPTY_RESOURCE_LIMITS.max_cache_entries,
18536            UNBOUNDED_RESOURCE_LIMITS.max_cache_entries
18537        );
18538        assert_ne!(
18539            EMPTY_RESOURCE_LIMITS.max_expansion_size,
18540            UNBOUNDED_RESOURCE_LIMITS.max_expansion_size
18541        );
18542        assert_ne!(
18543            EMPTY_RESOURCE_LIMITS.max_macro_body_size,
18544            UNBOUNDED_RESOURCE_LIMITS.max_macro_body_size
18545        );
18546        assert_ne!(
18547            EMPTY_RESOURCE_LIMITS.max_registered_macros,
18548            UNBOUNDED_RESOURCE_LIMITS.max_registered_macros
18549        );
18550        assert_ne!(
18551            EMPTY_RESOURCE_LIMITS.max_macro_arity,
18552            UNBOUNDED_RESOURCE_LIMITS.max_macro_arity
18553        );
18554    }
18555
18556    #[test]
18557    fn empty_resource_limits_is_the_join_identity() {
18558        // Lattice-identity law — `a ⊔ ⊥ = a`. Every posture's
18559        // pointwise `max` against 0 returns the posture verbatim, so
18560        // [`EMPTY_RESOURCE_LIMITS`] acts as the identity element of
18561        // the join monoid. Peer of
18562        // `empty_resource_limits_is_the_meet_annihilator` one
18563        // COMBINATOR axis over on the (meet, join) surface.
18564        //
18565        // Cross-preset exhaustion — verify the identity against every
18566        // named preset (DEFAULT, UNBOUNDED, both hand-authored
18567        // asymmetric postures) so a regression that broke ONE axis of
18568        // the algebra fires here on the operand whose field crosses
18569        // that axis's boundary.
18570        assert_eq!(
18571            DEFAULT_RESOURCE_LIMITS.most_permissive(EMPTY_RESOURCE_LIMITS),
18572            DEFAULT_RESOURCE_LIMITS,
18573        );
18574        assert_eq!(
18575            EMPTY_RESOURCE_LIMITS.most_permissive(DEFAULT_RESOURCE_LIMITS),
18576            DEFAULT_RESOURCE_LIMITS,
18577        );
18578        assert_eq!(
18579            UNBOUNDED_RESOURCE_LIMITS.most_permissive(EMPTY_RESOURCE_LIMITS),
18580            UNBOUNDED_RESOURCE_LIMITS,
18581        );
18582        assert_eq!(
18583            EMPTY_RESOURCE_LIMITS.most_permissive(UNBOUNDED_RESOURCE_LIMITS),
18584            UNBOUNDED_RESOURCE_LIMITS,
18585        );
18586        assert_eq!(
18587            HAND_AUTHORED_MID_POSTURE.most_permissive(EMPTY_RESOURCE_LIMITS),
18588            HAND_AUTHORED_MID_POSTURE,
18589        );
18590        assert_eq!(
18591            HAND_AUTHORED_OTHER_POSTURE.most_permissive(EMPTY_RESOURCE_LIMITS),
18592            HAND_AUTHORED_OTHER_POSTURE,
18593        );
18594        // Idempotence at the identity itself — sibling witness of the
18595        // `most_permissive_is_idempotent` general pin one PRESET axis
18596        // over.
18597        assert_eq!(
18598            EMPTY_RESOURCE_LIMITS.most_permissive(EMPTY_RESOURCE_LIMITS),
18599            EMPTY_RESOURCE_LIMITS,
18600        );
18601    }
18602
18603    #[test]
18604    fn empty_resource_limits_is_the_meet_annihilator() {
18605        // Lattice-annihilator law — `a ⊓ ⊥ = ⊥`. Every posture's
18606        // pointwise `min` against 0 returns 0 on every axis, so
18607        // [`EMPTY_RESOURCE_LIMITS`] acts as the annihilator (absorbing
18608        // element) of the meet operation. Peer of
18609        // `empty_resource_limits_is_the_join_identity` one COMBINATOR
18610        // axis over — the identity on one side of the bounded lattice
18611        // is the annihilator on the other side.
18612        assert_eq!(
18613            DEFAULT_RESOURCE_LIMITS.strictest(EMPTY_RESOURCE_LIMITS),
18614            EMPTY_RESOURCE_LIMITS,
18615        );
18616        assert_eq!(
18617            EMPTY_RESOURCE_LIMITS.strictest(DEFAULT_RESOURCE_LIMITS),
18618            EMPTY_RESOURCE_LIMITS,
18619        );
18620        assert_eq!(
18621            UNBOUNDED_RESOURCE_LIMITS.strictest(EMPTY_RESOURCE_LIMITS),
18622            EMPTY_RESOURCE_LIMITS,
18623        );
18624        assert_eq!(
18625            EMPTY_RESOURCE_LIMITS.strictest(UNBOUNDED_RESOURCE_LIMITS),
18626            EMPTY_RESOURCE_LIMITS,
18627        );
18628        assert_eq!(
18629            HAND_AUTHORED_MID_POSTURE.strictest(EMPTY_RESOURCE_LIMITS),
18630            EMPTY_RESOURCE_LIMITS,
18631        );
18632        assert_eq!(
18633            HAND_AUTHORED_OTHER_POSTURE.strictest(EMPTY_RESOURCE_LIMITS),
18634            EMPTY_RESOURCE_LIMITS,
18635        );
18636    }
18637
18638    #[test]
18639    fn empty_resource_limits_is_the_lattice_minimum() {
18640        // Partial-order minimum law — `⊥ ≤ a` for every `a`. Every
18641        // posture's field is a `usize` and `0 <= x` holds for every
18642        // `usize x`, so [`EMPTY_RESOURCE_LIMITS`] sits at or below
18643        // every posture on every axis of the pointwise `<=` conjunction.
18644        // Peer of the UNBOUNDED-is-maximum property on the
18645        // partial-order face one LATTICE-POLE axis over — those two
18646        // pin the (min, max) bounds of the bounded lattice.
18647        assert!(EMPTY_RESOURCE_LIMITS.leq(EMPTY_RESOURCE_LIMITS));
18648        assert!(EMPTY_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18649        assert!(EMPTY_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18650        assert!(EMPTY_RESOURCE_LIMITS.leq(HAND_AUTHORED_MID_POSTURE));
18651        assert!(EMPTY_RESOURCE_LIMITS.leq(HAND_AUTHORED_OTHER_POSTURE));
18652        // Reverse direction FAILS on every non-EMPTY preset (each has
18653        // at least one field strictly greater than 0). The partial
18654        // order is STRICT between EMPTY and every other named preset,
18655        // structurally exhausting the diagonal of the
18656        // (EMPTY, DEFAULT, UNBOUNDED, MID, OTHER) 5-preset ordering
18657        // face at the BOTTOM row.
18658        assert!(!DEFAULT_RESOURCE_LIMITS.leq(EMPTY_RESOURCE_LIMITS));
18659        assert!(!UNBOUNDED_RESOURCE_LIMITS.leq(EMPTY_RESOURCE_LIMITS));
18660        assert!(!HAND_AUTHORED_MID_POSTURE.leq(EMPTY_RESOURCE_LIMITS));
18661        assert!(!HAND_AUTHORED_OTHER_POSTURE.leq(EMPTY_RESOURCE_LIMITS));
18662    }
18663
18664    #[test]
18665    fn empty_resource_limits_composes_at_compile_time_via_const_fn() {
18666        // Const-fn pin — the bounded-lattice identity + annihilator +
18667        // minimum laws hold at COMPILE time. Sibling of
18668        // `resource_limits_leq_evaluates_at_compile_time_via_const_fn`
18669        // one LATTICE-STRUCTURE axis over — that pin fixed the strict
18670        // order of the (DEFAULT, UNBOUNDED) preset pair at compile
18671        // time; this pin fixes the bounded-lattice pole role of the
18672        // (EMPTY, UNBOUNDED) preset pair. `const _: ()` at item scope
18673        // is a compile-time proof — a regression that broke either
18674        // identity element or the bottom-is-minimum invariant fires at
18675        // rustc time, not test-run time.
18676        //
18677        // Bottom-is-minimum: EMPTY ≤ DEFAULT ≤ UNBOUNDED (the
18678        // three-preset ordering chain on the shipped preset diagonal).
18679        const _: () = assert!(EMPTY_RESOURCE_LIMITS.leq(DEFAULT_RESOURCE_LIMITS));
18680        const _: () = assert!(EMPTY_RESOURCE_LIMITS.leq(UNBOUNDED_RESOURCE_LIMITS));
18681        // Reverse strictness — neither DEFAULT nor UNBOUNDED sits
18682        // below EMPTY (each has a strictly-positive field).
18683        const _: () = assert!(!DEFAULT_RESOURCE_LIMITS.leq(EMPTY_RESOURCE_LIMITS));
18684        const _: () = assert!(!UNBOUNDED_RESOURCE_LIMITS.leq(EMPTY_RESOURCE_LIMITS));
18685        // Join-identity + meet-annihilator laws cross-pinned through
18686        // the `leq` primitive at const time — the round-trip identity
18687        // (`a ⊔ ⊥ = a ⇔ ⊥ ≤ a`, `a ⊓ ⊥ = ⊥ ⇔ ⊥ ≤ a`) means the two
18688        // leq asserts above discharge both algebraic laws by the
18689        // `leq_agrees_with_meet` + `leq_agrees_with_join` lattice
18690        // cross-check axiom already pinned in this cohort.
18691    }
18692
18693    #[test]
18694    fn empty_resource_limits_seeds_most_permissive_fold_over_slice() {
18695        // Fold-identity behavioral pin — a slice of postures folded
18696        // through [`ResourceLimits::most_permissive`] with EMPTY as
18697        // the seed produces the pointwise least upper bound across
18698        // the slice on every axis. The join-identity property
18699        // (`a.most_permissive(EMPTY) == a`) guarantees the seed
18700        // contributes nothing to the fold's result — every field's
18701        // pointwise `max` against the seed's `0` returns the field's
18702        // value verbatim, so the fold reduces to the joined-across-
18703        // -operands upper bound. This is the destination usage the
18704        // constant's docstring names as its fold-identity role.
18705        //
18706        // A regression that seeded from ANY non-empty posture (e.g.
18707        // `DEFAULT_RESOURCE_LIMITS`) would inflate the fold's result
18708        // on every axis where the seed's field exceeds the pointwise
18709        // max of the slice — fires as a distinct-axis inequality here.
18710        let postures: [ResourceLimits; 3] = [
18711            HAND_AUTHORED_MID_POSTURE,
18712            HAND_AUTHORED_OTHER_POSTURE,
18713            DEFAULT_RESOURCE_LIMITS,
18714        ];
18715        let joined = postures
18716            .iter()
18717            .copied()
18718            .fold(EMPTY_RESOURCE_LIMITS, ResourceLimits::most_permissive);
18719        // Every operand is `leq` the joined result — the least
18720        // upper bound dominates each contributor on every axis.
18721        assert!(HAND_AUTHORED_MID_POSTURE.leq(joined));
18722        assert!(HAND_AUTHORED_OTHER_POSTURE.leq(joined));
18723        assert!(DEFAULT_RESOURCE_LIMITS.leq(joined));
18724        // Field-level pin — each axis is the pointwise `max` of the
18725        // three sources' values. Six independent field asserts so a
18726        // drift on ONE axis carries a distinct-name failure.
18727        assert_eq!(
18728            joined.max_expansion_depth,
18729            HAND_AUTHORED_MID_POSTURE
18730                .max_expansion_depth
18731                .max(HAND_AUTHORED_OTHER_POSTURE.max_expansion_depth)
18732                .max(DEFAULT_RESOURCE_LIMITS.max_expansion_depth),
18733        );
18734        assert_eq!(
18735            joined.max_cache_entries,
18736            HAND_AUTHORED_MID_POSTURE
18737                .max_cache_entries
18738                .max(HAND_AUTHORED_OTHER_POSTURE.max_cache_entries)
18739                .max(DEFAULT_RESOURCE_LIMITS.max_cache_entries),
18740        );
18741        assert_eq!(
18742            joined.max_expansion_size,
18743            HAND_AUTHORED_MID_POSTURE
18744                .max_expansion_size
18745                .max(HAND_AUTHORED_OTHER_POSTURE.max_expansion_size)
18746                .max(DEFAULT_RESOURCE_LIMITS.max_expansion_size),
18747        );
18748        assert_eq!(
18749            joined.max_macro_body_size,
18750            HAND_AUTHORED_MID_POSTURE
18751                .max_macro_body_size
18752                .max(HAND_AUTHORED_OTHER_POSTURE.max_macro_body_size)
18753                .max(DEFAULT_RESOURCE_LIMITS.max_macro_body_size),
18754        );
18755        assert_eq!(
18756            joined.max_registered_macros,
18757            HAND_AUTHORED_MID_POSTURE
18758                .max_registered_macros
18759                .max(HAND_AUTHORED_OTHER_POSTURE.max_registered_macros)
18760                .max(DEFAULT_RESOURCE_LIMITS.max_registered_macros),
18761        );
18762        assert_eq!(
18763            joined.max_macro_arity,
18764            HAND_AUTHORED_MID_POSTURE
18765                .max_macro_arity
18766                .max(HAND_AUTHORED_OTHER_POSTURE.max_macro_arity)
18767                .max(DEFAULT_RESOURCE_LIMITS.max_macro_arity),
18768        );
18769        // Empty-slice edge case — folding over an EMPTY slice returns
18770        // the seed verbatim (the identity element as the null aggregate).
18771        // A wrapper-type approach (`Option<ResourceLimits>` for the
18772        // aggregate) would need a distinct absent branch here; the
18773        // typed identity-element approach resolves it structurally.
18774        let empty_slice: [ResourceLimits; 0] = [];
18775        assert_eq!(
18776            empty_slice
18777                .iter()
18778                .copied()
18779                .fold(EMPTY_RESOURCE_LIMITS, ResourceLimits::most_permissive),
18780            EMPTY_RESOURCE_LIMITS,
18781        );
18782    }
18783
18784    #[test]
18785    fn unbounded_resource_limits_seeds_strictest_fold_over_slice() {
18786        // Dual fold-identity pin — a slice of postures folded through
18787        // [`ResourceLimits::strictest`] with UNBOUNDED as the seed
18788        // produces the pointwise greatest lower bound across the slice
18789        // on every axis. Peer of the EMPTY-seeds-most_permissive-fold
18790        // pin one COMBINATOR axis over — the identity element for
18791        // meet is the join's ANNIHILATOR (`UNBOUNDED`), and the
18792        // identity element for join is the meet's annihilator
18793        // (`EMPTY`); the two together bind the bounded-lattice's
18794        // aggregation surface at BOTH poles.
18795        let postures: [ResourceLimits; 3] = [
18796            HAND_AUTHORED_MID_POSTURE,
18797            HAND_AUTHORED_OTHER_POSTURE,
18798            DEFAULT_RESOURCE_LIMITS,
18799        ];
18800        let met = postures
18801            .iter()
18802            .copied()
18803            .fold(UNBOUNDED_RESOURCE_LIMITS, ResourceLimits::strictest);
18804        // The greatest lower bound sits `leq` every operand.
18805        assert!(met.leq(HAND_AUTHORED_MID_POSTURE));
18806        assert!(met.leq(HAND_AUTHORED_OTHER_POSTURE));
18807        assert!(met.leq(DEFAULT_RESOURCE_LIMITS));
18808        // Field-level pin — each axis is the pointwise `min` of the
18809        // three sources' values (the UNBOUNDED seed's `usize::MAX`
18810        // never wins the min against a concrete positive value).
18811        assert_eq!(
18812            met.max_expansion_depth,
18813            HAND_AUTHORED_MID_POSTURE
18814                .max_expansion_depth
18815                .min(HAND_AUTHORED_OTHER_POSTURE.max_expansion_depth)
18816                .min(DEFAULT_RESOURCE_LIMITS.max_expansion_depth),
18817        );
18818        assert_eq!(
18819            met.max_cache_entries,
18820            HAND_AUTHORED_MID_POSTURE
18821                .max_cache_entries
18822                .min(HAND_AUTHORED_OTHER_POSTURE.max_cache_entries)
18823                .min(DEFAULT_RESOURCE_LIMITS.max_cache_entries),
18824        );
18825        assert_eq!(
18826            met.max_expansion_size,
18827            HAND_AUTHORED_MID_POSTURE
18828                .max_expansion_size
18829                .min(HAND_AUTHORED_OTHER_POSTURE.max_expansion_size)
18830                .min(DEFAULT_RESOURCE_LIMITS.max_expansion_size),
18831        );
18832        assert_eq!(
18833            met.max_macro_body_size,
18834            HAND_AUTHORED_MID_POSTURE
18835                .max_macro_body_size
18836                .min(HAND_AUTHORED_OTHER_POSTURE.max_macro_body_size)
18837                .min(DEFAULT_RESOURCE_LIMITS.max_macro_body_size),
18838        );
18839        assert_eq!(
18840            met.max_registered_macros,
18841            HAND_AUTHORED_MID_POSTURE
18842                .max_registered_macros
18843                .min(HAND_AUTHORED_OTHER_POSTURE.max_registered_macros)
18844                .min(DEFAULT_RESOURCE_LIMITS.max_registered_macros),
18845        );
18846        assert_eq!(
18847            met.max_macro_arity,
18848            HAND_AUTHORED_MID_POSTURE
18849                .max_macro_arity
18850                .min(HAND_AUTHORED_OTHER_POSTURE.max_macro_arity)
18851                .min(DEFAULT_RESOURCE_LIMITS.max_macro_arity),
18852        );
18853        // Empty-slice edge — folding over an empty slice returns the
18854        // UNBOUNDED seed verbatim (the meet-identity as the null
18855        // aggregate). Symmetric to the EMPTY-seed empty-slice pin.
18856        let empty_slice: [ResourceLimits; 0] = [];
18857        assert_eq!(
18858            empty_slice
18859                .iter()
18860                .copied()
18861                .fold(UNBOUNDED_RESOURCE_LIMITS, ResourceLimits::strictest),
18862            UNBOUNDED_RESOURCE_LIMITS,
18863        );
18864    }
18865
18866    #[test]
18867    fn resource_limits_strictest_of_empty_slice_returns_the_meet_identity() {
18868        // Null-aggregate pin — the N-ary meet over an empty slice is
18869        // the meet-identity element [`UNBOUNDED_RESOURCE_LIMITS`]. The
18870        // typed identity-element approach resolves the empty-input case
18871        // at the algebra layer; a wrapper-type approach
18872        // (`Option<ResourceLimits>` for the aggregate) would need a
18873        // distinct absent branch here, and a panic-on-empty seed-from-
18874        // -first-element approach would abort the caller.
18875        assert_eq!(ResourceLimits::strictest_of(&[]), UNBOUNDED_RESOURCE_LIMITS,);
18876    }
18877
18878    #[test]
18879    fn resource_limits_most_permissive_of_empty_slice_returns_the_join_identity() {
18880        // Null-aggregate dual pin — the N-ary join over an empty slice
18881        // is the join-identity element [`EMPTY_RESOURCE_LIMITS`]. Peer
18882        // to `strictest_of_empty_slice` one COMBINATOR axis over on the
18883        // (N-ary meet, N-ary join) surface — the identity element for
18884        // join is the meet's ANNIHILATOR and vice versa; the two
18885        // together bind both bounded-lattice poles as null aggregates.
18886        assert_eq!(
18887            ResourceLimits::most_permissive_of(&[]),
18888            EMPTY_RESOURCE_LIMITS,
18889        );
18890    }
18891
18892    #[test]
18893    fn resource_limits_strictest_of_single_element_returns_the_element_verbatim() {
18894        // Meet-identity absorption pin at N=1 — the N-ary meet of ONE
18895        // input is the input verbatim, since the meet-identity seed
18896        // (`UNBOUNDED_RESOURCE_LIMITS`) satisfies
18897        // `UNBOUNDED.strictest(a) == a` by the pointwise `min` behavior
18898        // against [`usize::MAX`]. This is the identity-law-at-N=1
18899        // pin: the fold's seed does not distort the 1-input case.
18900        assert_eq!(
18901            ResourceLimits::strictest_of(&[HAND_AUTHORED_MID_POSTURE]),
18902            HAND_AUTHORED_MID_POSTURE,
18903        );
18904        assert_eq!(
18905            ResourceLimits::strictest_of(&[HAND_AUTHORED_OTHER_POSTURE]),
18906            HAND_AUTHORED_OTHER_POSTURE,
18907        );
18908        assert_eq!(
18909            ResourceLimits::strictest_of(&[DEFAULT_RESOURCE_LIMITS]),
18910            DEFAULT_RESOURCE_LIMITS,
18911        );
18912    }
18913
18914    #[test]
18915    fn resource_limits_most_permissive_of_single_element_returns_the_element_verbatim() {
18916        // Join-identity absorption pin at N=1 — dual of
18917        // `strictest_of_single_element`, using the join-identity seed
18918        // (`EMPTY_RESOURCE_LIMITS`) whose per-axis `0` satisfies
18919        // `EMPTY.most_permissive(a) == a` against any non-zero value.
18920        assert_eq!(
18921            ResourceLimits::most_permissive_of(&[HAND_AUTHORED_MID_POSTURE]),
18922            HAND_AUTHORED_MID_POSTURE,
18923        );
18924        assert_eq!(
18925            ResourceLimits::most_permissive_of(&[HAND_AUTHORED_OTHER_POSTURE]),
18926            HAND_AUTHORED_OTHER_POSTURE,
18927        );
18928        assert_eq!(
18929            ResourceLimits::most_permissive_of(&[DEFAULT_RESOURCE_LIMITS]),
18930            DEFAULT_RESOURCE_LIMITS,
18931        );
18932    }
18933
18934    #[test]
18935    fn resource_limits_strictest_of_two_elements_reduces_to_pairwise_strictest() {
18936        // N=2 reduction pin — the N-ary meet on a 2-element slice
18937        // reduces to the pairwise combinator, since
18938        // `UNBOUNDED.strictest(a).strictest(b) == a.strictest(b)`. The
18939        // strictest_of(&[a, b]) call MUST agree with a.strictest(b) on
18940        // every input pair the algebra distinguishes.
18941        assert_eq!(
18942            ResourceLimits::strictest_of(
18943                &[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE,]
18944            ),
18945            HAND_AUTHORED_MID_POSTURE.strictest(HAND_AUTHORED_OTHER_POSTURE),
18946        );
18947        assert_eq!(
18948            ResourceLimits::strictest_of(&[DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS,]),
18949            DEFAULT_RESOURCE_LIMITS.strictest(UNBOUNDED_RESOURCE_LIMITS),
18950        );
18951    }
18952
18953    #[test]
18954    fn resource_limits_most_permissive_of_two_elements_reduces_to_pairwise_most_permissive() {
18955        // N=2 reduction dual pin — the N-ary join on a 2-element slice
18956        // reduces to the pairwise combinator.
18957        assert_eq!(
18958            ResourceLimits::most_permissive_of(&[
18959                HAND_AUTHORED_MID_POSTURE,
18960                HAND_AUTHORED_OTHER_POSTURE,
18961            ]),
18962            HAND_AUTHORED_MID_POSTURE.most_permissive(HAND_AUTHORED_OTHER_POSTURE),
18963        );
18964        assert_eq!(
18965            ResourceLimits::most_permissive_of(&[DEFAULT_RESOURCE_LIMITS, EMPTY_RESOURCE_LIMITS,]),
18966            DEFAULT_RESOURCE_LIMITS.most_permissive(EMPTY_RESOURCE_LIMITS),
18967        );
18968    }
18969
18970    #[test]
18971    fn resource_limits_strictest_of_agrees_with_direct_fold_over_slice() {
18972        // Method-agrees-with-fold pin — the sole behavioral claim the
18973        // N-ary aggregator makes is that it IS the inline
18974        // `.iter().copied().fold(UNBOUNDED, strictest)` cascade the
18975        // pre-lift caller composed at every consumption site. A drift
18976        // between the method's implementation and the inline fold
18977        // would break this equality on every non-empty slice.
18978        let postures: [ResourceLimits; 3] = [
18979            HAND_AUTHORED_MID_POSTURE,
18980            HAND_AUTHORED_OTHER_POSTURE,
18981            DEFAULT_RESOURCE_LIMITS,
18982        ];
18983        assert_eq!(
18984            ResourceLimits::strictest_of(&postures),
18985            postures
18986                .iter()
18987                .copied()
18988                .fold(UNBOUNDED_RESOURCE_LIMITS, ResourceLimits::strictest),
18989        );
18990    }
18991
18992    #[test]
18993    fn resource_limits_most_permissive_of_agrees_with_direct_fold_over_slice() {
18994        // Method-agrees-with-fold dual pin.
18995        let postures: [ResourceLimits; 3] = [
18996            HAND_AUTHORED_MID_POSTURE,
18997            HAND_AUTHORED_OTHER_POSTURE,
18998            DEFAULT_RESOURCE_LIMITS,
18999        ];
19000        assert_eq!(
19001            ResourceLimits::most_permissive_of(&postures),
19002            postures
19003                .iter()
19004                .copied()
19005                .fold(EMPTY_RESOURCE_LIMITS, ResourceLimits::most_permissive),
19006        );
19007    }
19008
19009    #[test]
19010    fn resource_limits_strictest_of_is_order_independent() {
19011        // Order-independence pin — the fold inherits commutativity AND
19012        // associativity of the pairwise `strictest`, so any permutation
19013        // of the input slice yields the same aggregate. Reversal is
19014        // the maximal permutation for a 3-element input (indices
19015        // 0,1,2 → 2,1,0); an aggregator that leaked fold order would
19016        // discriminate here.
19017        let forward = ResourceLimits::strictest_of(&[
19018            HAND_AUTHORED_MID_POSTURE,
19019            HAND_AUTHORED_OTHER_POSTURE,
19020            DEFAULT_RESOURCE_LIMITS,
19021        ]);
19022        let reversed = ResourceLimits::strictest_of(&[
19023            DEFAULT_RESOURCE_LIMITS,
19024            HAND_AUTHORED_OTHER_POSTURE,
19025            HAND_AUTHORED_MID_POSTURE,
19026        ]);
19027        assert_eq!(forward, reversed);
19028    }
19029
19030    #[test]
19031    fn resource_limits_most_permissive_of_is_order_independent() {
19032        let forward = ResourceLimits::most_permissive_of(&[
19033            HAND_AUTHORED_MID_POSTURE,
19034            HAND_AUTHORED_OTHER_POSTURE,
19035            DEFAULT_RESOURCE_LIMITS,
19036        ]);
19037        let reversed = ResourceLimits::most_permissive_of(&[
19038            DEFAULT_RESOURCE_LIMITS,
19039            HAND_AUTHORED_OTHER_POSTURE,
19040            HAND_AUTHORED_MID_POSTURE,
19041        ]);
19042        assert_eq!(forward, reversed);
19043    }
19044
19045    #[test]
19046    fn resource_limits_strictest_of_composes_at_compile_time_via_const_fn() {
19047        // Compile-time-composition pin — `strictest_of` is `const fn`,
19048        // so a caller binds an N-ary aggregate to a `pub const` and the
19049        // fold evaluates at compile time. Every `DEFAULT_MAX_*` is
19050        // strictly less than `usize::MAX`, so the meet of `[DEFAULT,
19051        // UNBOUNDED]` picks DEFAULT on every axis — a preset-pair
19052        // identity already pinned pairwise, verified here through the
19053        // N-ary method's const-context evaluation. The N-ary const-fn
19054        // peer of the pairwise const-fn pins on
19055        // [`ResourceLimits::strictest`]; a regression that turned
19056        // `strictest_of` into a runtime `fn` would break the `const`
19057        // binding below at compile time.
19058        const AGGREGATED: ResourceLimits =
19059            ResourceLimits::strictest_of(&[DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS]);
19060        assert_eq!(AGGREGATED, DEFAULT_RESOURCE_LIMITS);
19061    }
19062
19063    #[test]
19064    fn resource_limits_most_permissive_of_composes_at_compile_time_via_const_fn() {
19065        // Compile-time-composition dual pin — every `DEFAULT_MAX_*` is
19066        // strictly greater than `0`, so the join of `[EMPTY, DEFAULT]`
19067        // picks DEFAULT on every axis.
19068        const AGGREGATED: ResourceLimits =
19069            ResourceLimits::most_permissive_of(&[EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS]);
19070        assert_eq!(AGGREGATED, DEFAULT_RESOURCE_LIMITS);
19071    }
19072
19073    #[test]
19074    fn resource_limits_strictest_of_result_is_leq_every_operand() {
19075        // Lattice-relation pin — the N-ary meet sits `leq` every
19076        // operand. This is the N-ary extension of the pairwise
19077        // `strictest_is_leq_both_operands` pin; the meet aggregate
19078        // dominates from below (the greatest lower bound of the slice
19079        // sits at-or-below each input on every axis).
19080        let postures: [ResourceLimits; 3] = [
19081            HAND_AUTHORED_MID_POSTURE,
19082            HAND_AUTHORED_OTHER_POSTURE,
19083            DEFAULT_RESOURCE_LIMITS,
19084        ];
19085        let met = ResourceLimits::strictest_of(&postures);
19086        for p in postures {
19087            assert!(met.leq(p));
19088        }
19089    }
19090
19091    #[test]
19092    fn resource_limits_most_permissive_of_result_is_geq_every_operand() {
19093        // Lattice-relation dual pin — every operand sits `leq` the
19094        // N-ary join. The join aggregate is the least upper bound of
19095        // the slice.
19096        let postures: [ResourceLimits; 3] = [
19097            HAND_AUTHORED_MID_POSTURE,
19098            HAND_AUTHORED_OTHER_POSTURE,
19099            DEFAULT_RESOURCE_LIMITS,
19100        ];
19101        let joined = ResourceLimits::most_permissive_of(&postures);
19102        for p in postures {
19103            assert!(p.leq(joined));
19104        }
19105    }
19106
19107    #[test]
19108    fn expander_built_at_strictest_of_slice_gates_at_tightest_ceiling_across_the_slice() {
19109        // Behavioral pin — an expander constructed from the N-ary meet
19110        // of a slice of postures MUST gate at the tightest ceiling
19111        // across the slice on every axis. Peer of
19112        // `expander_built_at_strictest_of_two_presets_gates_at_the_tighter_ceiling`
19113        // one AGGREGATION-ARITY axis over: where the pairwise pin
19114        // composes exactly two postures, this pin composes THREE
19115        // through the N-ary aggregator, confirming the arity extension
19116        // preserves the "meet gates at tightest" behavioral contract.
19117        //
19118        // The slice tightens macro_arity to 1 (the tightest across the
19119        // three postures) via the middle operand; every other axis
19120        // stays at usize::MAX (the outer two operands supply
19121        // usize::MAX; the middle operand's struct-update literal
19122        // preserves usize::MAX everywhere except macro_arity).
19123        let tightened = ResourceLimits::strictest_of(&[
19124            UNBOUNDED_RESOURCE_LIMITS,
19125            ResourceLimits {
19126                max_macro_arity: 1,
19127                ..UNBOUNDED_RESOURCE_LIMITS
19128            },
19129            UNBOUNDED_RESOURCE_LIMITS,
19130        ]);
19131        assert_eq!(tightened.max_macro_arity, 1);
19132        assert_eq!(tightened.max_expansion_depth, usize::MAX);
19133        assert_eq!(tightened.max_cache_entries, usize::MAX);
19134        assert_eq!(tightened.max_expansion_size, usize::MAX);
19135        assert_eq!(tightened.max_macro_body_size, usize::MAX);
19136        assert_eq!(tightened.max_registered_macros, usize::MAX);
19137        let mut e = Expander::with_limits(tightened);
19138        let forms = read("(defmacro two (a b) `,a)").unwrap();
19139        let err = e.expand_program(forms).unwrap_err();
19140        assert!(
19141            matches!(
19142                err,
19143                LispError::MacroArityExceeded {
19144                    arity: 2,
19145                    limit: 1,
19146                    ..
19147                }
19148            ),
19149            "expected MacroArityExceeded from N-ary meet's tightened arity gate, got {err:?}",
19150        );
19151    }
19152
19153    #[test]
19154    fn expander_built_at_strictest_of_two_presets_gates_at_the_tighter_ceiling() {
19155        // Behavioral pin — an expander constructed from the meet of
19156        // two postures MUST gate at the tighter of the two ceilings
19157        // on every axis. Build the meet of the ceiling-lifted preset
19158        // and a hand-authored posture whose `max_macro_arity` sits at
19159        // 1; the meet inherits the 1-slot arity ceiling (tighter than
19160        // [`usize::MAX`]) AND the [`usize::MAX`] ceiling on every
19161        // other axis (tighter than the hand-authored posture's small
19162        // values — no, wait: the OTHER posture's small values ARE
19163        // tighter. So the meet inherits 1 on arity and the OTHER
19164        // small values elsewhere.) Confirm the constructed expander
19165        // rejects a 2-arg macro registration with the arity gate.
19166        let tightened = UNBOUNDED_RESOURCE_LIMITS.strictest(ResourceLimits {
19167            max_macro_arity: 1,
19168            ..UNBOUNDED_RESOURCE_LIMITS
19169        });
19170        assert_eq!(tightened.max_macro_arity, 1);
19171        // Other five axes remain at `usize::MAX` (both operands carry
19172        // `usize::MAX` on those, so the pointwise `min` is `usize::MAX`).
19173        assert_eq!(tightened.max_expansion_depth, usize::MAX);
19174        assert_eq!(tightened.max_cache_entries, usize::MAX);
19175        assert_eq!(tightened.max_expansion_size, usize::MAX);
19176        assert_eq!(tightened.max_macro_body_size, usize::MAX);
19177        assert_eq!(tightened.max_registered_macros, usize::MAX);
19178        let mut e = Expander::with_limits(tightened);
19179        let forms = read("(defmacro two (a b) `,a)").unwrap();
19180        let err = e.expand_program(forms).unwrap_err();
19181        assert!(
19182            matches!(
19183                err,
19184                LispError::MacroArityExceeded {
19185                    arity: 2,
19186                    limit: 1,
19187                    ..
19188                }
19189            ),
19190            "the tighter arity ceiling from the meet MUST reach the register-time gate; got: {err:?}"
19191        );
19192    }
19193
19194    // ── ResourceLimits::clamp — bounded-lattice bracket combinator ────
19195
19196    /// A tighter-than-MID lower-bracket witness — every axis strictly
19197    /// smaller than `HAND_AUTHORED_MID_POSTURE`'s matching axis, so the
19198    /// pointwise `max(mid, floor) == mid` on every axis (`floor.leq(mid)`
19199    /// holds structurally). Peer of `HAND_AUTHORED_CLAMP_CEILING` one
19200    /// BRACKET-BOUND axis over on the (floor, ceiling) bracket-witness
19201    /// pair.
19202    const HAND_AUTHORED_CLAMP_FLOOR: ResourceLimits = ResourceLimits {
19203        max_expansion_depth: 2,
19204        max_cache_entries: 3,
19205        max_expansion_size: 5,
19206        max_macro_body_size: 7,
19207        max_registered_macros: 11,
19208        max_macro_arity: 13,
19209    };
19210
19211    /// A looser-than-MID upper-bracket witness — every axis strictly larger
19212    /// than `HAND_AUTHORED_MID_POSTURE`'s matching axis, so the pointwise
19213    /// `min(mid, ceiling) == mid` on every axis (`mid.leq(ceiling)` holds
19214    /// structurally). Peer of `HAND_AUTHORED_CLAMP_FLOOR` one BRACKET-
19215    /// BOUND axis over. Together with `HAND_AUTHORED_CLAMP_FLOOR` the two
19216    /// bracket `HAND_AUTHORED_MID_POSTURE` strictly on every axis:
19217    /// `FLOOR.leq(MID) && MID.leq(CEILING) && FLOOR != MID && MID !=
19218    /// CEILING`.
19219    const HAND_AUTHORED_CLAMP_CEILING: ResourceLimits = ResourceLimits {
19220        max_expansion_depth: 47,
19221        max_cache_entries: 53,
19222        max_expansion_size: 59,
19223        max_macro_body_size: 61,
19224        max_registered_macros: 67,
19225        max_macro_arity: 71,
19226    };
19227
19228    #[test]
19229    fn resource_limits_clamp_of_posture_already_in_range_returns_the_posture() {
19230        // In-range identity — when the input already sits within the
19231        // `[lower, upper]` bracket (`lower.leq(a) && a.leq(upper)` per-
19232        // axis), the clamp is the identity function: the pointwise
19233        // `max(a, lower) == a` (since `lower ≤ a` per-axis picks `a`),
19234        // then the pointwise `min(a, upper) == a` (since `a ≤ upper`
19235        // per-axis picks `a`). The primary invariant on the "input
19236        // already satisfies the bracket" arm of the three-arm bracket-
19237        // membership surface.
19238        //
19239        // Prerequisite pins — the two hand-authored bounds bracket
19240        // `MID` strictly on every axis (structural check that discharges
19241        // the "already in range" premise). A regression that inflated
19242        // `FLOOR` or deflated `CEILING` past `MID` would fail these
19243        // asserts before the clamp identity was tested, making the
19244        // premise's failure name itself rather than showing up as a
19245        // downstream misleading clamp mismatch.
19246        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_MID_POSTURE));
19247        assert!(HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_CLAMP_CEILING));
19248        let clamped =
19249            HAND_AUTHORED_MID_POSTURE.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19250        assert_eq!(clamped, HAND_AUTHORED_MID_POSTURE);
19251    }
19252
19253    #[test]
19254    fn resource_limits_clamp_of_posture_below_lower_returns_lower() {
19255        // Below-lower floor — when the input is tighter than the
19256        // bracket floor on every axis (`a.leq(lower)` per-axis), the
19257        // clamp lifts the input up to `lower`: the pointwise `max(a,
19258        // lower) == lower` (since `a ≤ lower` per-axis picks `lower`),
19259        // then the pointwise `min(lower, upper) == lower` (since
19260        // `lower ≤ upper` per-axis picks `lower`). Peer of
19261        // `resource_limits_clamp_of_posture_already_in_range_returns_the_posture`
19262        // one BRACKET-POSITION axis over on the (in-range, below-lower,
19263        // above-upper) three-arm bracket-membership surface.
19264        //
19265        // `EMPTY_RESOURCE_LIMITS` sits `leq` every posture (bounded-
19266        // lattice bottom), so `EMPTY.leq(FLOOR)` holds structurally
19267        // for any FLOOR; a clamp of `EMPTY` into `[FLOOR, CEILING]`
19268        // MUST return `FLOOR`.
19269        assert!(EMPTY_RESOURCE_LIMITS.leq(HAND_AUTHORED_CLAMP_FLOOR));
19270        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_CLAMP_CEILING));
19271        let clamped =
19272            EMPTY_RESOURCE_LIMITS.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19273        assert_eq!(clamped, HAND_AUTHORED_CLAMP_FLOOR);
19274    }
19275
19276    #[test]
19277    fn resource_limits_clamp_of_posture_above_upper_returns_upper() {
19278        // Above-upper ceiling — when the input is looser than the
19279        // bracket ceiling on every axis (`upper.leq(a)` per-axis), the
19280        // clamp lowers the input down to `upper`: the pointwise
19281        // `max(a, lower) == a` (since `lower ≤ upper ≤ a` per-axis picks
19282        // `a`), then the pointwise `min(a, upper) == upper` (since
19283        // `upper ≤ a` per-axis picks `upper`). Peer of the
19284        // below-lower arm one BRACKET-POSITION axis over on the three-
19285        // arm bracket-membership surface.
19286        //
19287        // `UNBOUNDED_RESOURCE_LIMITS` sits `geq` every posture (bounded-
19288        // lattice top), so `CEILING.leq(UNBOUNDED)` holds structurally
19289        // for any CEILING; a clamp of `UNBOUNDED` into `[FLOOR, CEILING]`
19290        // MUST return `CEILING`.
19291        assert!(HAND_AUTHORED_CLAMP_CEILING.leq(UNBOUNDED_RESOURCE_LIMITS));
19292        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_CLAMP_CEILING));
19293        let clamped =
19294            UNBOUNDED_RESOURCE_LIMITS.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19295        assert_eq!(clamped, HAND_AUTHORED_CLAMP_CEILING);
19296    }
19297
19298    #[test]
19299    fn resource_limits_clamp_result_sits_within_the_bracket() {
19300        // Bracket-membership contract — the DEFINING invariant on the
19301        // clamp primitive: for every well-formed bracket
19302        // (`lower.leq(upper)`) and every input `a`, the result sits
19303        // within the bracket `lower.leq(a.clamp(lower, upper)) &&
19304        // a.clamp(lower, upper).leq(upper)`. Exercised on ALL FOUR arm
19305        // positions of the bracket-membership surface via the four
19306        // known-preset inputs (below-lower → in-range → above-upper
19307        // → asymmetric-crossing) so a regression that inflated the
19308        // ceiling arm or deflated the floor arm exhausts all four
19309        // input positions and names the failing arm.
19310        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_CLAMP_CEILING));
19311        let inputs = [
19312            EMPTY_RESOURCE_LIMITS,       // below-lower
19313            HAND_AUTHORED_MID_POSTURE,   // in-range
19314            UNBOUNDED_RESOURCE_LIMITS,   // above-upper
19315            HAND_AUTHORED_OTHER_POSTURE, // asymmetric (crosses the bracket)
19316        ];
19317        for a in inputs {
19318            let clamped = a.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19319            assert!(
19320                HAND_AUTHORED_CLAMP_FLOOR.leq(clamped),
19321                "clamp result must sit at or above FLOOR; input={a:?} clamped={clamped:?}",
19322            );
19323            assert!(
19324                clamped.leq(HAND_AUTHORED_CLAMP_CEILING),
19325                "clamp result must sit at or below CEILING; input={a:?} clamped={clamped:?}",
19326            );
19327        }
19328    }
19329
19330    #[test]
19331    fn resource_limits_clamp_with_lattice_extrema_returns_the_input() {
19332        // Extrema-bracket identity — clamping against the bounded-
19333        // lattice extrema (`EMPTY_RESOURCE_LIMITS` as the leq-minimum,
19334        // `UNBOUNDED_RESOURCE_LIMITS` as the leq-maximum) is the
19335        // identity function on every posture: every posture already
19336        // sits within the widest possible bracket. Cross-checks the
19337        // clamp primitive against the bounded-lattice identity elements
19338        // previously lifted; a regression that swapped the two extrema
19339        // in the impl would collapse every input to
19340        // `UNBOUNDED.strictest(EMPTY) == EMPTY` and fail this pin on
19341        // every non-EMPTY input.
19342        for a in [
19343            DEFAULT_RESOURCE_LIMITS,
19344            HAND_AUTHORED_MID_POSTURE,
19345            HAND_AUTHORED_OTHER_POSTURE,
19346            EMPTY_RESOURCE_LIMITS,
19347            UNBOUNDED_RESOURCE_LIMITS,
19348        ] {
19349            assert_eq!(
19350                a.clamp(EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19351                a,
19352                "clamp against [EMPTY, UNBOUNDED] must be the identity on {a:?}",
19353            );
19354        }
19355    }
19356
19357    #[test]
19358    fn resource_limits_clamp_with_equal_bounds_returns_the_bound() {
19359        // Degenerate-bracket collapse — a zero-width bracket
19360        // (`lower == upper`) collapses every input to the single point.
19361        // The pointwise `max(a, x)` lifts every axis to at least `x`,
19362        // then the pointwise `min(., x)` clamps every axis to exactly
19363        // `x` regardless of `a`'s per-axis position (since `x ≤ max(a,
19364        // x) ≤ max(x, x) == x` when `a.leq(x)` and `min(max(a, x), x)
19365        // == x` when `x.leq(a)`).
19366        for x in [
19367            HAND_AUTHORED_MID_POSTURE,
19368            HAND_AUTHORED_OTHER_POSTURE,
19369            DEFAULT_RESOURCE_LIMITS,
19370        ] {
19371            for a in [
19372                EMPTY_RESOURCE_LIMITS,
19373                HAND_AUTHORED_MID_POSTURE,
19374                HAND_AUTHORED_OTHER_POSTURE,
19375                UNBOUNDED_RESOURCE_LIMITS,
19376            ] {
19377                assert_eq!(
19378                    a.clamp(x, x),
19379                    x,
19380                    "zero-width bracket [x, x] must collapse input {a:?} to x={x:?}",
19381                );
19382            }
19383        }
19384    }
19385
19386    #[test]
19387    fn resource_limits_clamp_is_idempotent() {
19388        // Idempotence — re-applying the same bracket to an already-
19389        // clamped posture returns the same result. The clamp primitive
19390        // is idempotent because its output already sits within the
19391        // `[lower, upper]` bracket (pinned by
19392        // `resource_limits_clamp_result_sits_within_the_bracket`), so
19393        // the second application hits the in-range identity arm. Peer
19394        // of the idempotence pins on `strictest` and `most_permissive`
19395        // one ARITY axis over (2-input primitive → 3-input bracket) on
19396        // the lattice-algebra idempotence-law surface.
19397        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_CLAMP_CEILING));
19398        for a in [
19399            EMPTY_RESOURCE_LIMITS,
19400            HAND_AUTHORED_MID_POSTURE,
19401            HAND_AUTHORED_OTHER_POSTURE,
19402            UNBOUNDED_RESOURCE_LIMITS,
19403        ] {
19404            let once = a.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19405            let twice = once.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19406            assert_eq!(
19407                once, twice,
19408                "clamp must be idempotent; input={a:?} once={once:?} twice={twice:?}",
19409            );
19410        }
19411    }
19412
19413    #[test]
19414    fn resource_limits_clamp_composes_at_compile_time_via_const_fn() {
19415        // Const-fn pin — the bracket combinator is evaluable in const
19416        // context, so a caller can pre-clamp a shipped preset against
19417        // an operator-supplied policy bracket at compile time. Sibling
19418        // of the const-fn composition pins on `strictest` /
19419        // `most_permissive` / `strictest_of` / `most_permissive_of`
19420        // one PRIMITIVE-KIND axis over: those four combinators compose
19421        // presets at const time, and this bracket combinator projects
19422        // a preset into a const-time bracket.
19423        //
19424        // A regression to a runtime `fn` here would fail the `pub
19425        // const` binding below at compile time — a stronger guarantee
19426        // than a runtime `assert!` because the const binding is
19427        // evaluated once at compile time, not per test invocation.
19428        const CLAMPED_DEFAULT: ResourceLimits =
19429            DEFAULT_RESOURCE_LIMITS.clamp(EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS);
19430        assert_eq!(CLAMPED_DEFAULT, DEFAULT_RESOURCE_LIMITS);
19431
19432        // Compile-time bracket-membership pin — const `assert!` on the
19433        // lattice-relation contract discharges the "clamp result sits
19434        // within the bracket" invariant at compile time on the
19435        // (DEFAULT, EMPTY, UNBOUNDED) preset triple.
19436        const _: () = assert!(EMPTY_RESOURCE_LIMITS.leq(CLAMPED_DEFAULT));
19437        const _: () = assert!(CLAMPED_DEFAULT.leq(UNBOUNDED_RESOURCE_LIMITS));
19438    }
19439
19440    #[test]
19441    fn resource_limits_clamp_agrees_with_direct_two_step_cascade() {
19442        // Structural-equivalence pin — the sole behavioral claim the
19443        // clamp primitive makes is that it IS the
19444        // `self.most_permissive(lower).strictest(upper)` two-step
19445        // cascade the pre-lift caller composed. A drift between the
19446        // method and the inline cascade would break this equality on
19447        // every input; the pin exhausts the four bracket-position arms
19448        // of the input surface.
19449        for a in [
19450            EMPTY_RESOURCE_LIMITS,
19451            HAND_AUTHORED_MID_POSTURE,
19452            HAND_AUTHORED_OTHER_POSTURE,
19453            UNBOUNDED_RESOURCE_LIMITS,
19454        ] {
19455            let via_method = a.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19456            let via_cascade = a
19457                .most_permissive(HAND_AUTHORED_CLAMP_FLOOR)
19458                .strictest(HAND_AUTHORED_CLAMP_CEILING);
19459            assert_eq!(via_method, via_cascade);
19460        }
19461    }
19462
19463    #[test]
19464    fn expander_built_at_clamped_preset_gates_at_the_bracket_ceiling() {
19465        // Behavioral pin — an expander constructed from a preset
19466        // clamped into `[EMPTY, TIGHT_BRACKET]` MUST gate at the
19467        // bracket ceiling's arity axis. Confirms the clamp primitive
19468        // reaches through to the runtime gates that consume
19469        // `ResourceLimits`, so a future consumer that pre-clamps a
19470        // preset against a policy bracket inherits the bracket's
19471        // ceilings at every guard. Peer of
19472        // `expander_built_at_strictest_of_two_presets_gates_at_the_tighter_ceiling`
19473        // one COMBINATOR-ARITY axis over on the (pairwise-meet,
19474        // N-ary-meet, bracket) combinator-arity face.
19475        //
19476        // The bracket ceiling tightens `max_macro_arity` to 1; every
19477        // other axis stays at `usize::MAX` (the CEILING preset carries
19478        // `usize::MAX` on those, and the input UNBOUNDED preset also
19479        // carries `usize::MAX` on every axis — the clamp's `strictest`
19480        // arm picks `usize::MAX` from either side).
19481        let bracket_ceiling = ResourceLimits {
19482            max_macro_arity: 1,
19483            ..UNBOUNDED_RESOURCE_LIMITS
19484        };
19485        let clamped = UNBOUNDED_RESOURCE_LIMITS.clamp(EMPTY_RESOURCE_LIMITS, bracket_ceiling);
19486        assert_eq!(clamped.max_macro_arity, 1);
19487        assert_eq!(clamped.max_expansion_depth, usize::MAX);
19488        assert_eq!(clamped.max_cache_entries, usize::MAX);
19489        assert_eq!(clamped.max_expansion_size, usize::MAX);
19490        assert_eq!(clamped.max_macro_body_size, usize::MAX);
19491        assert_eq!(clamped.max_registered_macros, usize::MAX);
19492        let mut e = Expander::with_limits(clamped);
19493        let forms = read("(defmacro two (a b) `,a)").unwrap();
19494        let err = e.expand_program(forms).unwrap_err();
19495        assert!(
19496            matches!(
19497                err,
19498                LispError::MacroArityExceeded {
19499                    arity: 2,
19500                    limit: 1,
19501                    ..
19502                }
19503            ),
19504            "the bracket ceiling's arity gate MUST reach the register-time check; got: {err:?}",
19505        );
19506    }
19507
19508    // ── ResourceLimits::within — bracket-membership predicate ─────────
19509
19510    #[test]
19511    fn resource_limits_within_of_posture_in_range_is_true() {
19512        // In-range identity — when the input already sits within the
19513        // `[lower, upper]` bracket on every axis (`lower.leq(a) &&
19514        // a.leq(upper)`), the predicate returns `true`. Peer of
19515        // `resource_limits_clamp_of_posture_already_in_range_returns_the_posture`
19516        // one PRIMITIVE-KIND axis over on the (combinator, predicate)
19517        // face of the bracket-primitive surface.
19518        //
19519        // Prerequisite pins — the two hand-authored bounds bracket
19520        // `MID` strictly on every axis, structurally discharging the
19521        // "already in range" premise.
19522        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_MID_POSTURE));
19523        assert!(HAND_AUTHORED_MID_POSTURE.leq(HAND_AUTHORED_CLAMP_CEILING));
19524        assert!(HAND_AUTHORED_MID_POSTURE
19525            .within(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING),);
19526    }
19527
19528    #[test]
19529    fn resource_limits_within_of_posture_below_lower_is_false() {
19530        // Below-lower rejection — when the input is strictly tighter
19531        // than the bracket floor on some axis (`!lower.leq(a)`), the
19532        // predicate returns `false`. `EMPTY_RESOURCE_LIMITS` has every
19533        // axis at `0`, strictly below `HAND_AUTHORED_CLAMP_FLOOR`'s
19534        // per-axis ceilings; so `FLOOR.leq(EMPTY)` fails on every axis,
19535        // making the conjunction reject regardless of `EMPTY`'s relation
19536        // to `CEILING`. Peer of
19537        // `resource_limits_clamp_of_posture_below_lower_returns_lower`
19538        // one PRIMITIVE-KIND axis over.
19539        assert!(!HAND_AUTHORED_CLAMP_FLOOR.leq(EMPTY_RESOURCE_LIMITS));
19540        assert!(
19541            !EMPTY_RESOURCE_LIMITS.within(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING),
19542        );
19543    }
19544
19545    #[test]
19546    fn resource_limits_within_of_posture_above_upper_is_false() {
19547        // Above-upper rejection — when the input is strictly looser
19548        // than the bracket ceiling on some axis (`!a.leq(upper)`), the
19549        // predicate returns `false`. `UNBOUNDED_RESOURCE_LIMITS` has
19550        // every axis at `usize::MAX`, strictly above
19551        // `HAND_AUTHORED_CLAMP_CEILING`'s per-axis ceilings; so
19552        // `UNBOUNDED.leq(CEILING)` fails on every axis. Peer of
19553        // `resource_limits_clamp_of_posture_above_upper_returns_upper`
19554        // one PRIMITIVE-KIND axis over.
19555        assert!(!UNBOUNDED_RESOURCE_LIMITS.leq(HAND_AUTHORED_CLAMP_CEILING));
19556        assert!(!UNBOUNDED_RESOURCE_LIMITS
19557            .within(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING),);
19558    }
19559
19560    #[test]
19561    fn resource_limits_within_with_lattice_extrema_is_true() {
19562        // Extrema-bracket identity — every posture sits within the
19563        // widest possible bracket `[EMPTY_RESOURCE_LIMITS,
19564        // UNBOUNDED_RESOURCE_LIMITS]`. Cross-checks the predicate
19565        // against the bounded-lattice identity elements (`EMPTY` is
19566        // the leq-minimum, `UNBOUNDED` is the leq-maximum). Peer of
19567        // `resource_limits_clamp_with_lattice_extrema_returns_the_input`
19568        // one PRIMITIVE-KIND axis over — the predicate view of the
19569        // "clamp is the identity" property.
19570        for a in [
19571            DEFAULT_RESOURCE_LIMITS,
19572            HAND_AUTHORED_MID_POSTURE,
19573            HAND_AUTHORED_OTHER_POSTURE,
19574            EMPTY_RESOURCE_LIMITS,
19575            UNBOUNDED_RESOURCE_LIMITS,
19576        ] {
19577            assert!(
19578                a.within(EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19579                "every posture must sit within the extrema bracket [EMPTY, UNBOUNDED]; got: {a:?}",
19580            );
19581        }
19582    }
19583
19584    #[test]
19585    fn resource_limits_within_of_equal_bounds_iff_equal_to_bound() {
19586        // Degenerate-bracket collapse — a zero-width bracket admits
19587        // only the single point at the bounds. `a.within(x, x)` holds
19588        // iff `x.leq(a) && a.leq(x)` iff `a == x` by antisymmetry of
19589        // `leq`. Peer of `resource_limits_clamp_with_equal_bounds_returns_the_bound`
19590        // one PRIMITIVE-KIND axis over: the clamp collapses to `x`
19591        // unconditionally, and the predicate distinguishes the "was
19592        // already equal" case from the "would have been clamped" case.
19593        let bounds = [
19594            HAND_AUTHORED_MID_POSTURE,
19595            HAND_AUTHORED_OTHER_POSTURE,
19596            DEFAULT_RESOURCE_LIMITS,
19597            EMPTY_RESOURCE_LIMITS,
19598            UNBOUNDED_RESOURCE_LIMITS,
19599        ];
19600        for x in bounds {
19601            for a in bounds {
19602                assert_eq!(
19603                    a.within(x, x),
19604                    a == x,
19605                    "a.within(x, x) must hold iff a == x; a={a:?} x={x:?}",
19606                );
19607            }
19608        }
19609    }
19610
19611    #[test]
19612    fn resource_limits_within_of_self_is_reflexive() {
19613        // Reflexive-bracket identity — every posture sits within the
19614        // zero-width bracket at itself, since `a.leq(a)` holds by
19615        // reflexivity of `leq`. The reflexive-bracket peer of the
19616        // reflexive-leq pin (`resource_limits_leq_is_reflexive`) one
19617        // ARITY axis over (pairwise → three-input).
19618        for a in [
19619            EMPTY_RESOURCE_LIMITS,
19620            DEFAULT_RESOURCE_LIMITS,
19621            HAND_AUTHORED_MID_POSTURE,
19622            HAND_AUTHORED_OTHER_POSTURE,
19623            UNBOUNDED_RESOURCE_LIMITS,
19624        ] {
19625            assert!(
19626                a.within(a, a),
19627                "a.within(a, a) must hold by reflexivity of leq; a={a:?}",
19628            );
19629        }
19630    }
19631
19632    #[test]
19633    fn resource_limits_within_agrees_with_clamp_fixed_point() {
19634        // Clamp fixed-point theorem — `a.within(lower, upper) ⇔
19635        // a.clamp(lower, upper) == a`. The CANONICAL cross-check axiom
19636        // binding the `within` predicate to the `clamp` combinator,
19637        // analogous to the meet-agreement (`a.leq(b) ⇔ a.strictest(b)
19638        // == a`, pinned by `resource_limits_leq_agrees_with_meet`) and
19639        // join-agreement (`a.leq(b) ⇔ a.most_permissive(b) == b`,
19640        // pinned by `resource_limits_leq_agrees_with_join`) axioms
19641        // binding the `leq` relation to its combinator peers.
19642        //
19643        // Exercised on all four bracket-position arms of the input
19644        // surface (below-lower / in-range / above-upper / asymmetric-
19645        // crossing) so a regression that broke either direction of the
19646        // fixed-point equivalence exhausts every position and names the
19647        // failing arm.
19648        assert!(HAND_AUTHORED_CLAMP_FLOOR.leq(HAND_AUTHORED_CLAMP_CEILING));
19649        for a in [
19650            EMPTY_RESOURCE_LIMITS,       // below-lower — clamp != a, within false
19651            HAND_AUTHORED_MID_POSTURE,   // in-range   — clamp == a, within true
19652            UNBOUNDED_RESOURCE_LIMITS,   // above-upper — clamp != a, within false
19653            HAND_AUTHORED_OTHER_POSTURE, // asymmetric — clamp != a, within false
19654        ] {
19655            let clamped = a.clamp(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19656            let within_holds = a.within(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19657            let clamp_fixed = clamped == a;
19658            assert_eq!(
19659                within_holds, clamp_fixed,
19660                "within(lower, upper) must agree with clamp fixed-point; \
19661                 a={a:?} within={within_holds} clamp_fixed={clamp_fixed} clamped={clamped:?}",
19662            );
19663        }
19664    }
19665
19666    #[test]
19667    fn resource_limits_within_agrees_with_direct_two_primitive_conjunction() {
19668        // Structural-equivalence pin — the sole behavioral claim the
19669        // predicate makes is that it IS the `lower.leq(a) && a.leq(upper)`
19670        // two-primitive conjunction the pre-lift caller composed. A drift
19671        // between the method and the inline conjunction would break this
19672        // equality on some input; the pin exhausts the four bracket-
19673        // position arms of the input surface. Peer of
19674        // `resource_limits_clamp_agrees_with_direct_two_step_cascade`
19675        // one PRIMITIVE-KIND axis over (combinator ↔ predicate).
19676        for a in [
19677            EMPTY_RESOURCE_LIMITS,
19678            HAND_AUTHORED_MID_POSTURE,
19679            HAND_AUTHORED_OTHER_POSTURE,
19680            UNBOUNDED_RESOURCE_LIMITS,
19681        ] {
19682            let via_method = a.within(HAND_AUTHORED_CLAMP_FLOOR, HAND_AUTHORED_CLAMP_CEILING);
19683            let via_conjunction =
19684                HAND_AUTHORED_CLAMP_FLOOR.leq(a) && a.leq(HAND_AUTHORED_CLAMP_CEILING);
19685            assert_eq!(via_method, via_conjunction, "a={a:?}");
19686        }
19687    }
19688
19689    #[test]
19690    fn resource_limits_within_composes_at_compile_time_via_const_fn() {
19691        // Const-fn pin — the bracket predicate is evaluable in const
19692        // context, so a caller can pin a preset-bracket-membership
19693        // identity at compile time. Sibling of the const-fn evaluability
19694        // pins on `leq` and `clamp` one PRIMITIVE-KIND axis over: the
19695        // relation and combinator both evaluate at const time, and the
19696        // predicate closing the (combinator, predicate) row does too.
19697        //
19698        // A regression to a runtime `fn` here would fail the `const _:
19699        // () = assert!(...)` bindings below at compile time.
19700        const _: () = assert!(
19701            DEFAULT_RESOURCE_LIMITS.within(EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS)
19702        );
19703        const _: () = assert!(
19704            DEFAULT_RESOURCE_LIMITS.within(DEFAULT_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS)
19705        );
19706        const _: () = assert!(
19707            !UNBOUNDED_RESOURCE_LIMITS.within(EMPTY_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS,)
19708        );
19709    }
19710
19711    #[test]
19712    fn expander_built_at_within_gated_preset_reaches_the_runtime_guards() {
19713        // Behavioral pin — a caller that CHECKS bracket-membership
19714        // before constructing an expander can safely reach through to
19715        // the runtime gates: if `within` reports true, `clamp` is a
19716        // no-op (by the fixed-point theorem), so the expander's
19717        // ceilings are the input posture's own. Peer of
19718        // `expander_built_at_clamped_preset_gates_at_the_bracket_ceiling`
19719        // one PRIMITIVE-KIND axis over — the predicate view of the
19720        // "clamp reaches through to the runtime gates" property.
19721        //
19722        // The candidate sits at the bracket ceiling (arity=1, other
19723        // axes at usize::MAX), so it MUST sit within the [EMPTY,
19724        // bracket_ceiling] bracket — the maximal within-membership arm
19725        // of the fixed-point theorem. Every other axis at usize::MAX
19726        // clears the register-time gates, so the arity gate at 1 is
19727        // the one that trips on a 2-parameter macro registration.
19728        let bracket_ceiling = ResourceLimits {
19729            max_macro_arity: 1,
19730            ..UNBOUNDED_RESOURCE_LIMITS
19731        };
19732        let candidate = ResourceLimits {
19733            max_macro_arity: 1,
19734            ..UNBOUNDED_RESOURCE_LIMITS
19735        };
19736        assert!(candidate.within(EMPTY_RESOURCE_LIMITS, bracket_ceiling));
19737        // Since candidate sits within the bracket, clamp is the
19738        // identity; the expander inherits candidate's ceilings verbatim.
19739        let clamped = candidate.clamp(EMPTY_RESOURCE_LIMITS, bracket_ceiling);
19740        assert_eq!(clamped, candidate);
19741        let mut e = Expander::with_limits(clamped);
19742        let forms = read("(defmacro two (a b) `,a)").unwrap();
19743        let err = e.expand_program(forms).unwrap_err();
19744        assert!(
19745            matches!(
19746                err,
19747                LispError::MacroArityExceeded {
19748                    arity: 2,
19749                    limit: 1,
19750                    ..
19751                }
19752            ),
19753            "the within-gated candidate's arity ceiling MUST reach the register-time check; got: {err:?}",
19754        );
19755    }
19756
19757    // ── ResourceLimits::is_lower_bound_of / is_upper_bound_of ─────────
19758    //   N-ary boolean-conjunction peers of the pairwise `leq` relation,
19759    //   closing the (combinator, predicate) row on the N-ary-aggregation
19760    //   face of the lattice-algebra surface. See the docstring on
19761    //   `ResourceLimits::is_lower_bound_of` for the (pairwise, N-ary,
19762    //   bracket) × (combinator, predicate) placement.
19763
19764    #[test]
19765    fn resource_limits_is_lower_bound_of_empty_slice_is_vacuously_true() {
19766        // Empty-conjunction identity — every posture is trivially a
19767        // lower bound of the empty set. Peer of `strictest_of(&[]) ==
19768        // UNBOUNDED_RESOURCE_LIMITS`'s empty-slice identity one
19769        // PRIMITIVE-KIND axis over: the empty slice aggregates to the
19770        // identity element of the underlying operation (`true` for
19771        // boolean conjunction, `UNBOUNDED` for pointwise `min`).
19772        assert!(HAND_AUTHORED_MID_POSTURE.is_lower_bound_of(&[]));
19773        assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&[]));
19774        assert!(UNBOUNDED_RESOURCE_LIMITS.is_lower_bound_of(&[]));
19775        assert!(DEFAULT_RESOURCE_LIMITS.is_lower_bound_of(&[]));
19776    }
19777
19778    #[test]
19779    fn resource_limits_is_upper_bound_of_empty_slice_is_vacuously_true() {
19780        // Empty-conjunction dual — every posture is trivially an upper
19781        // bound of the empty set. Peer of
19782        // `is_lower_bound_of_empty_slice_is_vacuously_true` one
19783        // COMBINATOR-DIRECTION axis over, AND peer of
19784        // `most_permissive_of(&[]) == EMPTY_RESOURCE_LIMITS`'s empty-
19785        // slice identity one PRIMITIVE-KIND axis over.
19786        assert!(HAND_AUTHORED_MID_POSTURE.is_upper_bound_of(&[]));
19787        assert!(EMPTY_RESOURCE_LIMITS.is_upper_bound_of(&[]));
19788        assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&[]));
19789        assert!(DEFAULT_RESOURCE_LIMITS.is_upper_bound_of(&[]));
19790    }
19791
19792    #[test]
19793    fn resource_limits_is_lower_bound_of_single_element_reduces_to_leq() {
19794        // 1-input identity — the singleton predicate reduces to the
19795        // pairwise partial-order relation. The N-ary predicate on a
19796        // 1-element slice is the pairwise `leq` verbatim, mirroring
19797        // `strictest_of(&[a]) == a`'s reduction on the combinator side.
19798        let cases: [(ResourceLimits, ResourceLimits); 6] = [
19799            (EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19800            (UNBOUNDED_RESOURCE_LIMITS, EMPTY_RESOURCE_LIMITS),
19801            (DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19802            (UNBOUNDED_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS),
19803            (HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE),
19804            (HAND_AUTHORED_OTHER_POSTURE, HAND_AUTHORED_MID_POSTURE),
19805        ];
19806        for (a, b) in cases {
19807            assert_eq!(
19808                a.is_lower_bound_of(&[b]),
19809                a.leq(b),
19810                "1-input is_lower_bound_of must agree with pairwise leq for ({a:?}, {b:?})",
19811            );
19812        }
19813    }
19814
19815    #[test]
19816    fn resource_limits_is_upper_bound_of_single_element_reduces_to_leq() {
19817        // 1-input dual identity — the singleton upper-bound predicate
19818        // is the pairwise `leq` with the direction flipped
19819        // (`b.leq(a)`), since `self` sits ABOVE the operand rather than
19820        // BELOW it.
19821        let cases: [(ResourceLimits, ResourceLimits); 6] = [
19822            (EMPTY_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19823            (UNBOUNDED_RESOURCE_LIMITS, EMPTY_RESOURCE_LIMITS),
19824            (DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS),
19825            (UNBOUNDED_RESOURCE_LIMITS, DEFAULT_RESOURCE_LIMITS),
19826            (HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE),
19827            (HAND_AUTHORED_OTHER_POSTURE, HAND_AUTHORED_MID_POSTURE),
19828        ];
19829        for (a, b) in cases {
19830            assert_eq!(
19831                a.is_upper_bound_of(&[b]),
19832                b.leq(a),
19833                "1-input is_upper_bound_of must agree with pairwise leq(other, self) for ({a:?}, {b:?})",
19834            );
19835        }
19836    }
19837
19838    #[test]
19839    fn resource_limits_is_lower_bound_of_holds_for_the_meet_of_the_slice() {
19840        // Definitional bridge — the N-ary meet is always a lower bound
19841        // of the slice it aggregates. Pins the link between the N-ary
19842        // COMBINATOR (`strictest_of`) and the N-ary PREDICATE
19843        // (`is_lower_bound_of`): the meet is an ELEMENT of the lower-
19844        // bound set the predicate CHARACTERIZES.
19845        let postures: [ResourceLimits; 3] = [
19846            HAND_AUTHORED_MID_POSTURE,
19847            HAND_AUTHORED_OTHER_POSTURE,
19848            DEFAULT_RESOURCE_LIMITS,
19849        ];
19850        let met = ResourceLimits::strictest_of(&postures);
19851        assert!(met.is_lower_bound_of(&postures));
19852    }
19853
19854    #[test]
19855    fn resource_limits_is_upper_bound_of_holds_for_the_join_of_the_slice() {
19856        // Dual definitional bridge — the N-ary join is always an upper
19857        // bound of the slice it aggregates.
19858        let postures: [ResourceLimits; 3] = [
19859            HAND_AUTHORED_MID_POSTURE,
19860            HAND_AUTHORED_OTHER_POSTURE,
19861            DEFAULT_RESOURCE_LIMITS,
19862        ];
19863        let joined = ResourceLimits::most_permissive_of(&postures);
19864        assert!(joined.is_upper_bound_of(&postures));
19865    }
19866
19867    #[test]
19868    fn resource_limits_empty_is_universal_lower_bound_of_every_slice() {
19869        // Universal-bottom witness — the lattice bottom is a common
19870        // lower bound of every slice, since `EMPTY.leq(a) == true` for
19871        // every posture by the lattice-bottom axiom (`0 ≤ x` per-axis).
19872        // The bounded-lattice specialization of the general "the meet-
19873        // identity is a lower bound of every set" theorem.
19874        let postures: [ResourceLimits; 4] = [
19875            HAND_AUTHORED_MID_POSTURE,
19876            HAND_AUTHORED_OTHER_POSTURE,
19877            DEFAULT_RESOURCE_LIMITS,
19878            UNBOUNDED_RESOURCE_LIMITS,
19879        ];
19880        assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&postures));
19881        // The three preset-carried postures each individually — the
19882        // universal-bottom witness holds on the singleton slice too.
19883        assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&[DEFAULT_RESOURCE_LIMITS]));
19884        assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&[UNBOUNDED_RESOURCE_LIMITS]));
19885        assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&[HAND_AUTHORED_MID_POSTURE]));
19886    }
19887
19888    #[test]
19889    fn resource_limits_unbounded_is_universal_upper_bound_of_every_slice() {
19890        // Universal-top witness — the lattice top is a common upper
19891        // bound of every slice, since `a.leq(UNBOUNDED) == true` for
19892        // every posture by the lattice-top axiom (`x ≤ usize::MAX`
19893        // per-axis).
19894        let postures: [ResourceLimits; 4] = [
19895            HAND_AUTHORED_MID_POSTURE,
19896            HAND_AUTHORED_OTHER_POSTURE,
19897            DEFAULT_RESOURCE_LIMITS,
19898            EMPTY_RESOURCE_LIMITS,
19899        ];
19900        assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&postures));
19901        assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&[DEFAULT_RESOURCE_LIMITS]));
19902        assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&[EMPTY_RESOURCE_LIMITS]));
19903        assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&[HAND_AUTHORED_MID_POSTURE]));
19904    }
19905
19906    #[test]
19907    fn resource_limits_is_lower_bound_of_rejects_when_any_operand_is_below_self() {
19908        // Any-operand short-circuit — if `self` fails `leq` against ANY
19909        // operand in the slice, the N-ary conjunction rejects. The dual
19910        // of the "meet is a lower bound" witness on the false side:
19911        // moving `self` STRICTLY ABOVE an operand on any axis costs the
19912        // lower-bound property.
19913        //
19914        // MID sits above the join (element-wise) on some axes and below
19915        // on others, so MID is NEITHER a lower bound of {MID, OTHER}
19916        // (fails against OTHER's smaller-axis values) NOR is OTHER a
19917        // lower bound of {MID, OTHER} (fails against MID's smaller-axis
19918        // values).
19919        assert!(!HAND_AUTHORED_MID_POSTURE
19920            .is_lower_bound_of(&[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE,]));
19921        assert!(!HAND_AUTHORED_OTHER_POSTURE
19922            .is_lower_bound_of(&[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE,]));
19923        // A UNIVERSAL rejection — the lattice top is a lower bound
19924        // ONLY of the empty set OR of slices whose every element equals
19925        // UNBOUNDED. Given a strictly-smaller preset in the slice, the
19926        // top's per-axis usize::MAX loses `leq` on every axis whose
19927        // preset value is strictly smaller.
19928        assert!(!UNBOUNDED_RESOURCE_LIMITS.is_lower_bound_of(&[EMPTY_RESOURCE_LIMITS]));
19929        assert!(!UNBOUNDED_RESOURCE_LIMITS.is_lower_bound_of(&[DEFAULT_RESOURCE_LIMITS]));
19930    }
19931
19932    #[test]
19933    fn resource_limits_is_upper_bound_of_rejects_when_any_operand_is_above_self() {
19934        // Dual any-operand short-circuit — if ANY operand fails `leq`
19935        // against `self`, the N-ary conjunction rejects.
19936        assert!(!HAND_AUTHORED_MID_POSTURE
19937            .is_upper_bound_of(&[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE,]));
19938        assert!(!HAND_AUTHORED_OTHER_POSTURE
19939            .is_upper_bound_of(&[HAND_AUTHORED_MID_POSTURE, HAND_AUTHORED_OTHER_POSTURE,]));
19940        // The bottom cannot bound anything from above except the empty
19941        // set OR a slice of EMPTY-only postures.
19942        assert!(!EMPTY_RESOURCE_LIMITS.is_upper_bound_of(&[DEFAULT_RESOURCE_LIMITS]));
19943        assert!(!EMPTY_RESOURCE_LIMITS.is_upper_bound_of(&[UNBOUNDED_RESOURCE_LIMITS]));
19944    }
19945
19946    #[test]
19947    fn resource_limits_is_lower_bound_of_agrees_with_direct_all_leq_conjunction() {
19948        // Method-vs-scaffold cross-check — the lifted N-ary predicate
19949        // agrees with the pre-lift two-primitive
19950        // `postures.iter().all(|p| self.leq(*p))` scaffolding on every
19951        // preset-carried posture across the shipped bounded-lattice
19952        // extrema AND the two hand-authored asymmetric witnesses. Pins
19953        // the lift is a semantic no-op — post-lift the consumer routes
19954        // through ONE name instead of composing the conjunction inline,
19955        // but the two paths compute the same boolean.
19956        let candidates: [ResourceLimits; 5] = [
19957            EMPTY_RESOURCE_LIMITS,
19958            DEFAULT_RESOURCE_LIMITS,
19959            UNBOUNDED_RESOURCE_LIMITS,
19960            HAND_AUTHORED_MID_POSTURE,
19961            HAND_AUTHORED_OTHER_POSTURE,
19962        ];
19963        let slice: [ResourceLimits; 3] = [
19964            HAND_AUTHORED_MID_POSTURE,
19965            HAND_AUTHORED_OTHER_POSTURE,
19966            DEFAULT_RESOURCE_LIMITS,
19967        ];
19968        for c in candidates {
19969            let direct = slice.iter().all(|p| c.leq(*p));
19970            assert_eq!(
19971                c.is_lower_bound_of(&slice),
19972                direct,
19973                "is_lower_bound_of must agree with iter().all(|p| self.leq(*p)) on candidate {c:?}",
19974            );
19975        }
19976    }
19977
19978    #[test]
19979    fn resource_limits_is_upper_bound_of_agrees_with_direct_all_leq_conjunction() {
19980        // Dual method-vs-scaffold cross-check.
19981        let candidates: [ResourceLimits; 5] = [
19982            EMPTY_RESOURCE_LIMITS,
19983            DEFAULT_RESOURCE_LIMITS,
19984            UNBOUNDED_RESOURCE_LIMITS,
19985            HAND_AUTHORED_MID_POSTURE,
19986            HAND_AUTHORED_OTHER_POSTURE,
19987        ];
19988        let slice: [ResourceLimits; 3] = [
19989            HAND_AUTHORED_MID_POSTURE,
19990            HAND_AUTHORED_OTHER_POSTURE,
19991            DEFAULT_RESOURCE_LIMITS,
19992        ];
19993        for c in candidates {
19994            let direct = slice.iter().all(|p| p.leq(c));
19995            assert_eq!(
19996                c.is_upper_bound_of(&slice),
19997                direct,
19998                "is_upper_bound_of must agree with iter().all(|p| p.leq(self)) on candidate {c:?}",
19999            );
20000        }
20001    }
20002
20003    #[test]
20004    fn resource_limits_is_lower_bound_of_composes_at_compile_time_via_const_fn() {
20005        // Const-fn pin — the N-ary lower-bound predicate is evaluable
20006        // in const context, so a caller can pin an N-ary-bound-
20007        // membership identity at compile time. Sibling of the const-fn
20008        // evaluability pins on `leq` and `within` one PRIMITIVE-KIND
20009        // axis over, AND sibling of `strictest_of`'s const-fn pin one
20010        // COMBINATOR-KIND axis over.
20011        //
20012        // A regression to a runtime `fn` here would fail the `const _:
20013        // () = assert!(...)` bindings below at compile time.
20014        const _: () = assert!(EMPTY_RESOURCE_LIMITS
20015            .is_lower_bound_of(&[DEFAULT_RESOURCE_LIMITS, UNBOUNDED_RESOURCE_LIMITS]));
20016        const _: () = assert!(EMPTY_RESOURCE_LIMITS.is_lower_bound_of(&[]));
20017        const _: () =
20018            assert!(!UNBOUNDED_RESOURCE_LIMITS.is_lower_bound_of(&[EMPTY_RESOURCE_LIMITS]));
20019    }
20020
20021    #[test]
20022    fn resource_limits_is_upper_bound_of_composes_at_compile_time_via_const_fn() {
20023        // Const-fn dual pin.
20024        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS
20025            .is_upper_bound_of(&[DEFAULT_RESOURCE_LIMITS, EMPTY_RESOURCE_LIMITS]));
20026        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.is_upper_bound_of(&[]));
20027        const _: () =
20028            assert!(!EMPTY_RESOURCE_LIMITS.is_upper_bound_of(&[UNBOUNDED_RESOURCE_LIMITS]));
20029    }
20030
20031    /// The full canonical preset roster the strict-order pins sweep
20032    /// through — the (bottom, middle, top) bounded-lattice preset
20033    /// triple plus the two hand-authored incomparable asymmetric
20034    /// postures. Every strict-order law below quantifies over this
20035    /// slice, so a regression that breaks strict `<` on any preset
20036    /// pair discriminates at ONE named fixture rather than at a
20037    /// per-test ad-hoc literal.
20038    const STRICT_ORDER_ROSTER: &[ResourceLimits] = &[
20039        EMPTY_RESOURCE_LIMITS,
20040        DEFAULT_RESOURCE_LIMITS,
20041        UNBOUNDED_RESOURCE_LIMITS,
20042        HAND_AUTHORED_MID_POSTURE,
20043        HAND_AUTHORED_OTHER_POSTURE,
20044    ];
20045
20046    #[test]
20047    fn resource_limits_lt_is_irreflexive() {
20048        // Strict partial-order law — `a < a == false` for every
20049        // posture. Irreflexivity is the strict-relation axiom peer of
20050        // reflexivity on `leq` one STRICTNESS axis over on the
20051        // partial-order axiom surface; a regression that dropped the
20052        // antisymmetric leg (`!other.leq(self)`) — leaving the body at
20053        // `self.leq(other)` — would fire here on every preset since
20054        // `leq` is reflexive.
20055        for &a in STRICT_ORDER_ROSTER {
20056            assert!(!a.lt(a), "irreflexivity failed on {a:?}");
20057        }
20058    }
20059
20060    #[test]
20061    fn resource_limits_lt_is_asymmetric() {
20062        // Strict partial-order law — `a < b ⇒ ¬(b < a)` for every
20063        // pair. The two directions of the strict relation cannot both
20064        // hold, since `a.lt(b)` implies `!b.leq(a)` and `b.lt(a)`
20065        // requires `b.leq(a)`. Sweeps every ordered pair in the
20066        // canonical preset roster; discriminates a regression that
20067        // dropped the antisymmetric leg in either direction.
20068        for &a in STRICT_ORDER_ROSTER {
20069            for &b in STRICT_ORDER_ROSTER {
20070                if a.lt(b) {
20071                    assert!(!b.lt(a), "asymmetry failed on ({a:?}, {b:?})");
20072                }
20073            }
20074        }
20075    }
20076
20077    #[test]
20078    fn resource_limits_lt_is_transitive() {
20079        // Strict partial-order law — `a < b ∧ b < c ⇒ a < c`. Sweeps
20080        // every ordered triple in the canonical preset roster; each
20081        // consequence is checked whenever both antecedents hold. On
20082        // the (bottom, middle, top) diagonal the canonical witness
20083        // `EMPTY < DEFAULT < UNBOUNDED` composes to
20084        // `EMPTY < UNBOUNDED`, exercising the transitivity chain on
20085        // the bounded-lattice diagonal explicitly; the sweep pins the
20086        // same law on every other transitively-related triple in the
20087        // roster.
20088        for &a in STRICT_ORDER_ROSTER {
20089            for &b in STRICT_ORDER_ROSTER {
20090                for &c in STRICT_ORDER_ROSTER {
20091                    if a.lt(b) && b.lt(c) {
20092                        assert!(a.lt(c), "transitivity failed on ({a:?}, {b:?}, {c:?})");
20093                    }
20094                }
20095            }
20096        }
20097    }
20098
20099    #[test]
20100    fn resource_limits_lt_refines_leq() {
20101        // Refinement axiom — `a < b ⇒ a ≤ b`. The strict relation is
20102        // a refinement of the non-strict one; a witness of strict `<`
20103        // is also a witness of `≤`. Sweeps every ordered pair in the
20104        // canonical preset roster.
20105        for &a in STRICT_ORDER_ROSTER {
20106            for &b in STRICT_ORDER_ROSTER {
20107                if a.lt(b) {
20108                    assert!(a.leq(b), "refinement failed on ({a:?}, {b:?})");
20109                }
20110            }
20111        }
20112    }
20113
20114    #[test]
20115    fn resource_limits_lt_agrees_with_leq_minus_equality() {
20116        // Antisymmetry consequence — on ANY antisymmetric partial
20117        // order (which the pointwise `≤` on `ResourceLimits` is),
20118        // `a < b ⇔ a ≤ b ∧ a ≠ b`. Cross-checks the shipped
20119        // antisymmetric-leq encoding against the leq-and-not-equal
20120        // encoding across the full 5×5 preset matrix; a regression to
20121        // a non-antisymmetric preorder encoding would fire here.
20122        for &a in STRICT_ORDER_ROSTER {
20123            for &b in STRICT_ORDER_ROSTER {
20124                let shipped = a.lt(b);
20125                let alt = a.leq(b) && a != b;
20126                assert_eq!(
20127                    shipped, alt,
20128                    "encoding cross-check failed on ({a:?}, {b:?})",
20129                );
20130            }
20131        }
20132    }
20133
20134    #[test]
20135    fn resource_limits_lt_of_bottom_diagonal_pinned() {
20136        // Concrete-preset pin — the strict chain across the (bottom,
20137        // middle, top) preset triple on the bounded-lattice diagonal.
20138        // Every `DEFAULT_MAX_*` module constant is a concrete positive
20139        // value strictly less than [`usize::MAX`] and strictly greater
20140        // than `0`, so on every axis the three endpoints are strictly
20141        // ordered; the six-axis pointwise conjunction lifts the
20142        // per-axis strict chain to the posture-level strict chain.
20143        //
20144        // Peer of `resource_limits_leq_of_default_and_unbounded_is_a_
20145        // strict_order` one STRICTNESS axis over — where the non-
20146        // strict pin asserted "leq holds one way and not the other"
20147        // via two `leq` calls, this pin asserts the strict-chain
20148        // conclusion via one `lt` call per link and pinpoints the
20149        // three-preset chain on the bounded-lattice diagonal.
20150        assert!(EMPTY_RESOURCE_LIMITS.lt(DEFAULT_RESOURCE_LIMITS));
20151        assert!(DEFAULT_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS));
20152        assert!(EMPTY_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS));
20153    }
20154
20155    #[test]
20156    fn resource_limits_lt_rejects_incomparable_postures() {
20157        // Partial-order non-total pin — the two hand-authored
20158        // asymmetric postures have MID smaller on three axes and OTHER
20159        // smaller on the other three, so neither `leq` direction
20160        // holds; strict `<` therefore fails in BOTH directions. The
20161        // pin discriminates a regression that promoted strict `<`
20162        // from partial to total (e.g. treating incomparable pairs as
20163        // strictly `<` in some canonical direction), and pairs with
20164        // `resource_limits_leq_is_not_total_on_asymmetric_postures`
20165        // one STRICTNESS axis over.
20166        assert!(!HAND_AUTHORED_MID_POSTURE.lt(HAND_AUTHORED_OTHER_POSTURE));
20167        assert!(!HAND_AUTHORED_OTHER_POSTURE.lt(HAND_AUTHORED_MID_POSTURE));
20168    }
20169
20170    #[test]
20171    fn resource_limits_lt_agrees_with_direct_antisymmetric_encoding() {
20172        // Direct-encoding cross-check — `a.lt(b) == (a.leq(b) &&
20173        // !b.leq(a))` at every pair by definition. Sibling of the
20174        // `leq_minus_equality` pin one ENCODING axis over: that pin
20175        // routes through the equality-based encoding (an
20176        // antisymmetry consequence), this pin routes through the
20177        // definitional antisymmetric-leq encoding. A regression that
20178        // shipped a different (e.g. axis-wise strict) encoding of
20179        // `lt` diverges from this cross-check on the incomparable
20180        // asymmetric-preset pair.
20181        for &a in STRICT_ORDER_ROSTER {
20182            for &b in STRICT_ORDER_ROSTER {
20183                let shipped = a.lt(b);
20184                let direct = a.leq(b) && !b.leq(a);
20185                assert_eq!(
20186                    shipped, direct,
20187                    "direct-encoding cross-check failed on ({a:?}, {b:?})",
20188                );
20189            }
20190        }
20191    }
20192
20193    #[test]
20194    fn resource_limits_gt_is_dual_of_lt() {
20195        // Direction-flip pin — `a.gt(b) == b.lt(a)` at every pair. The
20196        // shipped `gt` body delegates to `lt` verbatim so this
20197        // agreement holds definitionally, but the pin discriminates a
20198        // regression that re-implemented `gt` independently and
20199        // dropped the direction flip. Sweeps the full 5×5 preset
20200        // matrix so incomparable + strictly-ordered + equal pairs
20201        // are all exercised.
20202        for &a in STRICT_ORDER_ROSTER {
20203            for &b in STRICT_ORDER_ROSTER {
20204                assert_eq!(a.gt(b), b.lt(a), "gt/lt duality failed on ({a:?}, {b:?})");
20205            }
20206        }
20207    }
20208
20209    #[test]
20210    fn resource_limits_gt_of_top_diagonal_pinned() {
20211        // Concrete-preset pin — the strict chain across the (top,
20212        // middle, bottom) preset triple on the bounded-lattice
20213        // diagonal, walked in the "above" direction. Peer of
20214        // `resource_limits_lt_of_bottom_diagonal_pinned` one
20215        // DIRECTION axis over.
20216        assert!(UNBOUNDED_RESOURCE_LIMITS.gt(DEFAULT_RESOURCE_LIMITS));
20217        assert!(DEFAULT_RESOURCE_LIMITS.gt(EMPTY_RESOURCE_LIMITS));
20218        assert!(UNBOUNDED_RESOURCE_LIMITS.gt(EMPTY_RESOURCE_LIMITS));
20219    }
20220
20221    #[test]
20222    fn resource_limits_gt_is_irreflexive() {
20223        // Strict partial-order law dual — `a > a == false` for every
20224        // posture. Inherits from the irreflexivity of `lt` via the
20225        // shipped `gt` body's delegation, but pinned explicitly so a
20226        // future non-delegating re-implementation of `gt` still has
20227        // its irreflexivity gated at the type-level test cohort.
20228        for &a in STRICT_ORDER_ROSTER {
20229            assert!(!a.gt(a), "irreflexivity failed on {a:?}");
20230        }
20231    }
20232
20233    #[test]
20234    fn resource_limits_lt_evaluates_at_compile_time_via_const_fn() {
20235        // Const-fn pin — the strict `<` relation is evaluable in
20236        // const context, so a caller can pin a preset-strict-order
20237        // identity at compile time. Sibling of the const-fn
20238        // evaluability pin on `leq` one STRICTNESS axis over.
20239        //
20240        // A regression to a runtime `fn` here would fail the `const
20241        // _: () = assert!(...)` bindings below at compile time.
20242        const _: () = assert!(EMPTY_RESOURCE_LIMITS.lt(DEFAULT_RESOURCE_LIMITS));
20243        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS));
20244        const _: () = assert!(EMPTY_RESOURCE_LIMITS.lt(UNBOUNDED_RESOURCE_LIMITS));
20245        const _: () = assert!(!DEFAULT_RESOURCE_LIMITS.lt(DEFAULT_RESOURCE_LIMITS));
20246        const _: () = assert!(!UNBOUNDED_RESOURCE_LIMITS.lt(EMPTY_RESOURCE_LIMITS));
20247    }
20248
20249    #[test]
20250    fn resource_limits_gt_evaluates_at_compile_time_via_const_fn() {
20251        // Const-fn dual pin — sibling of `lt`'s const-fn pin one
20252        // DIRECTION axis over.
20253        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.gt(DEFAULT_RESOURCE_LIMITS));
20254        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.gt(EMPTY_RESOURCE_LIMITS));
20255        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.gt(EMPTY_RESOURCE_LIMITS));
20256        const _: () = assert!(!DEFAULT_RESOURCE_LIMITS.gt(DEFAULT_RESOURCE_LIMITS));
20257        const _: () = assert!(!EMPTY_RESOURCE_LIMITS.gt(UNBOUNDED_RESOURCE_LIMITS));
20258    }
20259
20260    #[test]
20261    fn resource_limits_geq_is_dual_of_leq() {
20262        // Direction-flip pin — `a.geq(b) == b.leq(a)` at every pair.
20263        // The shipped `geq` body delegates to `leq` verbatim so this
20264        // agreement holds definitionally, but the pin discriminates a
20265        // regression that re-implemented `geq` independently and
20266        // dropped the direction flip. Sweeps the full 5×5 preset
20267        // matrix so incomparable + strictly-ordered + equal pairs are
20268        // all exercised. Peer of `resource_limits_gt_is_dual_of_lt`
20269        // one STRICTNESS axis over on the (below, above) × (non-strict,
20270        // strict) 2×2 pairwise partial-order face.
20271        for &a in STRICT_ORDER_ROSTER {
20272            for &b in STRICT_ORDER_ROSTER {
20273                assert_eq!(
20274                    a.geq(b),
20275                    b.leq(a),
20276                    "geq/leq duality failed on ({a:?}, {b:?})",
20277                );
20278            }
20279        }
20280    }
20281
20282    #[test]
20283    fn resource_limits_geq_is_reflexive() {
20284        // Non-strict partial-order law — `a ≥ a == true` for every
20285        // posture. Reflexivity is the non-strict-relation axiom peer of
20286        // irreflexivity on `gt` one STRICTNESS axis over on the
20287        // partial-order axiom surface; a regression that hardened the
20288        // delegation into a strict `!other.leq(self)` would fire here
20289        // on every preset since the reflexive pair collapses.
20290        for &a in STRICT_ORDER_ROSTER {
20291            assert!(a.geq(a), "reflexivity failed on {a:?}");
20292        }
20293    }
20294
20295    #[test]
20296    fn resource_limits_geq_is_antisymmetric() {
20297        // Non-strict partial-order law — `a ≥ b ∧ b ≥ a ⇒ a == b`.
20298        // Antisymmetry is inherited from `leq` via the shipped `geq`
20299        // body's `other.leq(self)` delegation (which combined with
20300        // `b.geq(a)` — i.e. `a.leq(b)` — gives `leq` antisymmetry on
20301        // the pair). Sweeps the full 5×5 preset matrix; the pin fires
20302        // on any regression that let two DISTINCT postures satisfy
20303        // `geq` in both directions.
20304        for &a in STRICT_ORDER_ROSTER {
20305            for &b in STRICT_ORDER_ROSTER {
20306                if a.geq(b) && b.geq(a) {
20307                    assert_eq!(a, b, "antisymmetry failed on ({a:?}, {b:?})");
20308                }
20309            }
20310        }
20311    }
20312
20313    #[test]
20314    fn resource_limits_geq_is_transitive() {
20315        // Non-strict partial-order law — `a ≥ b ∧ b ≥ c ⇒ a ≥ c`.
20316        // Inherits from the transitivity of `leq` via the shipped
20317        // delegation; sweeps every ordered triple in the canonical
20318        // preset roster. Peer of `resource_limits_lt_is_transitive`
20319        // one STRICTNESS axis over and `resource_limits_leq_is_
20320        // transitive` one DIRECTION axis over.
20321        for &a in STRICT_ORDER_ROSTER {
20322            for &b in STRICT_ORDER_ROSTER {
20323                for &c in STRICT_ORDER_ROSTER {
20324                    if a.geq(b) && b.geq(c) {
20325                        assert!(a.geq(c), "transitivity failed on ({a:?}, {b:?}, {c:?})");
20326                    }
20327                }
20328            }
20329        }
20330    }
20331
20332    #[test]
20333    fn resource_limits_geq_refines_gt() {
20334        // Refinement axiom peer — `a > b ⇒ a ≥ b`. The strict
20335        // relation is a refinement of the non-strict one in the
20336        // "above" direction, exactly as `lt` refines `leq` in the
20337        // "below" direction (pinned by
20338        // `resource_limits_lt_refines_leq` one DIRECTION axis over).
20339        // A witness of strict `>` is also a witness of `≥`.
20340        for &a in STRICT_ORDER_ROSTER {
20341            for &b in STRICT_ORDER_ROSTER {
20342                if a.gt(b) {
20343                    assert!(a.geq(b), "refinement failed on ({a:?}, {b:?})");
20344                }
20345            }
20346        }
20347    }
20348
20349    #[test]
20350    fn resource_limits_geq_agrees_with_gt_plus_equality() {
20351        // Antisymmetry consequence peer — on ANY antisymmetric partial
20352        // order, `a ≥ b ⇔ a > b ∨ a == b`. Cross-checks the shipped
20353        // delegation against the strict-or-equal encoding across the
20354        // full 5×5 preset matrix; a regression that shipped a non-
20355        // antisymmetric preorder encoding for `geq` fires here. Peer
20356        // of `resource_limits_lt_agrees_with_leq_minus_equality` one
20357        // DIRECTION-AND-STRICTNESS axis over.
20358        for &a in STRICT_ORDER_ROSTER {
20359            for &b in STRICT_ORDER_ROSTER {
20360                let shipped = a.geq(b);
20361                let alt = a.gt(b) || a == b;
20362                assert_eq!(
20363                    shipped, alt,
20364                    "encoding cross-check failed on ({a:?}, {b:?})",
20365                );
20366            }
20367        }
20368    }
20369
20370    #[test]
20371    fn resource_limits_geq_of_top_diagonal_pinned() {
20372        // Concrete-preset pin — the non-strict chain across the (top,
20373        // middle, bottom) preset triple on the bounded-lattice
20374        // diagonal, walked in the "above" direction. Peer of
20375        // `resource_limits_gt_of_top_diagonal_pinned` one STRICTNESS
20376        // axis over: the non-strict pin also holds at the reflexive
20377        // diagonal, so every posture sits above ITSELF via `geq` in
20378        // addition to sitting above the strictly-tighter presets.
20379        assert!(UNBOUNDED_RESOURCE_LIMITS.geq(DEFAULT_RESOURCE_LIMITS));
20380        assert!(DEFAULT_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS));
20381        assert!(UNBOUNDED_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS));
20382        assert!(EMPTY_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS));
20383        assert!(DEFAULT_RESOURCE_LIMITS.geq(DEFAULT_RESOURCE_LIMITS));
20384        assert!(UNBOUNDED_RESOURCE_LIMITS.geq(UNBOUNDED_RESOURCE_LIMITS));
20385    }
20386
20387    #[test]
20388    fn resource_limits_geq_rejects_incomparable_postures() {
20389        // Partial-order non-total pin — the two hand-authored
20390        // asymmetric postures have MID smaller on three axes and OTHER
20391        // smaller on the other three, so neither `leq` direction holds
20392        // and therefore neither `geq` direction holds. The pin
20393        // discriminates a regression that promoted `geq` from partial
20394        // to total, and pairs with
20395        // `resource_limits_lt_rejects_incomparable_postures` one
20396        // STRICTNESS-AND-DIRECTION axis over.
20397        assert!(!HAND_AUTHORED_MID_POSTURE.geq(HAND_AUTHORED_OTHER_POSTURE));
20398        assert!(!HAND_AUTHORED_OTHER_POSTURE.geq(HAND_AUTHORED_MID_POSTURE));
20399    }
20400
20401    #[test]
20402    fn resource_limits_geq_agrees_with_is_upper_bound_of_singleton() {
20403        // Arity-reduction pin — `a.geq(b) == a.is_upper_bound_of(&[b])`
20404        // at every pair. The pairwise "above" relation is exactly the
20405        // 1-input specialization of the N-ary upper-bound predicate;
20406        // the pin cross-checks that the two named entries agree on
20407        // the single-element case, which discriminates a regression
20408        // that drifted one from the other. Peer of the single-element
20409        // identity docstring on `is_upper_bound_of` one ARITY axis
20410        // over.
20411        for &a in STRICT_ORDER_ROSTER {
20412            for &b in STRICT_ORDER_ROSTER {
20413                assert_eq!(
20414                    a.geq(b),
20415                    a.is_upper_bound_of(&[b]),
20416                    "arity-reduction failed on ({a:?}, {b:?})",
20417                );
20418            }
20419        }
20420    }
20421
20422    #[test]
20423    fn resource_limits_geq_evaluates_at_compile_time_via_const_fn() {
20424        // Const-fn pin — the non-strict `≥` relation is evaluable in
20425        // const context, so a caller can pin a preset-non-strict-order
20426        // identity at compile time. Sibling of the const-fn evaluability
20427        // pins on `leq` one DIRECTION axis over AND on `gt` one
20428        // STRICTNESS axis over.
20429        //
20430        // A regression to a runtime `fn` here would fail the `const _:
20431        // () = assert!(...)` bindings below at compile time.
20432        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.geq(DEFAULT_RESOURCE_LIMITS));
20433        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS));
20434        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.geq(EMPTY_RESOURCE_LIMITS));
20435        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.geq(DEFAULT_RESOURCE_LIMITS));
20436        const _: () = assert!(!EMPTY_RESOURCE_LIMITS.geq(UNBOUNDED_RESOURCE_LIMITS));
20437    }
20438
20439    #[test]
20440    fn resource_limits_is_incomparable_is_irreflexive() {
20441        // Antichain-corner reflexivity pin — `a.is_incomparable(a) ==
20442        // false` on every posture. Every posture is COMPARABLE to
20443        // itself via `leq`'s reflexivity (`a.leq(a) == true`), so the
20444        // conjunction's `!self.leq(other)` leg forces the verdict to
20445        // `false` at every diagonal input. Discriminates a regression
20446        // that dropped the `!` on `self.leq(other)` (leaving the body
20447        // at `self.leq(other) && !self.geq(other)`) — that would fold
20448        // the diagonal to `false` on the reflexive `leq` arm but
20449        // silently flip the antichain corner on non-diagonal cells.
20450        for &a in STRICT_ORDER_ROSTER {
20451            assert!(!a.is_incomparable(a), "irreflexivity failed on {a:?}");
20452        }
20453    }
20454
20455    #[test]
20456    fn resource_limits_is_incomparable_is_symmetric() {
20457        // Antichain-corner symmetry pin — `a.is_incomparable(b) ==
20458        // b.is_incomparable(a)` on every pair. The composition
20459        // `!self.leq(other) && !self.geq(other)` folds through the
20460        // `self.leq(other) == other.geq(self)` dual identity on `geq`
20461        // and the conjunction's commutativity into an order-
20462        // independent projection on its two inputs. Sweeps every
20463        // ordered pair in the canonical preset roster;
20464        // discriminates a regression that broke either leg's dual
20465        // identity — one direction would fold to `false` on cells the
20466        // other still folded to `true`.
20467        for &a in STRICT_ORDER_ROSTER {
20468            for &b in STRICT_ORDER_ROSTER {
20469                assert_eq!(
20470                    a.is_incomparable(b),
20471                    b.is_incomparable(a),
20472                    "symmetry failed on ({a:?}, {b:?})",
20473                );
20474            }
20475        }
20476    }
20477
20478    #[test]
20479    fn resource_limits_is_incomparable_is_de_morgan_dual_of_comparable() {
20480        // De Morgan dual pin — `a.is_incomparable(b) == !(a.leq(b) ||
20481        // a.geq(b))` on every pair. The antichain corner is the SET-
20482        // COMPLEMENT of the union of the two pointwise-domination
20483        // directions on the (comparable, incomparable) two-cell
20484        // partition of the ordered pair × verdict surface. Sweeps
20485        // every ordered pair in the canonical preset roster;
20486        // discriminates a regression that drifted the antichain
20487        // characterization off the substrate's `!leq && !geq`
20488        // composition (e.g., a shortcut through `!self.leq(other) ||
20489        // !self.geq(other)` — the OR-form falsely reports
20490        // incomparability on non-equal comparable pairs).
20491        for &a in STRICT_ORDER_ROSTER {
20492            for &b in STRICT_ORDER_ROSTER {
20493                let via_named = a.is_incomparable(b);
20494                let via_composition = !(a.leq(b) || a.geq(b));
20495                assert_eq!(
20496                    via_named, via_composition,
20497                    "De Morgan dual failed on ({a:?}, {b:?})",
20498                );
20499            }
20500        }
20501    }
20502
20503    #[test]
20504    fn resource_limits_is_incomparable_partitions_ordered_pair_surface_with_comparable() {
20505        // Two-cell partition pin — on every ordered pair EITHER
20506        // (a.leq(b) || a.geq(b)) OR a.is_incomparable(b), and NEVER
20507        // both. The (comparable, incomparable) partition of the
20508        // ordered pair × verdict surface is exhaustive-and-disjoint —
20509        // a substrate-level EXCLUSIVITY THEOREM the projection pins
20510        // once. Sweeps every ordered pair in the canonical preset
20511        // roster; discriminates a regression that let the two cells
20512        // overlap (e.g., an implementation that reported the antichain
20513        // corner as `true` on the comparable diagonal).
20514        for &a in STRICT_ORDER_ROSTER {
20515            for &b in STRICT_ORDER_ROSTER {
20516                let comparable = a.leq(b) || a.geq(b);
20517                let incomparable = a.is_incomparable(b);
20518                assert!(
20519                    comparable != incomparable,
20520                    "partition failed on ({a:?}, {b:?}): comparable={comparable}, incomparable={incomparable}",
20521                );
20522            }
20523        }
20524    }
20525
20526    #[test]
20527    fn resource_limits_is_incomparable_folds_false_at_the_bottom_pole() {
20528        // Bottom-pole absorption pin — `a.is_incomparable(
20529        // EMPTY_RESOURCE_LIMITS) == false` on every posture `a`.
20530        // `EMPTY_RESOURCE_LIMITS` is the bounded-lattice bottom, so
20531        // `EMPTY.leq(a)` holds on every axis (usize zero is the
20532        // minimum), which gives `a.geq(EMPTY)` through the `geq`
20533        // dual identity, which falsifies the `!self.geq(other)` leg.
20534        // Sweeps the canonical preset roster; pairs with the top-
20535        // pole absorption sibling one POLE axis over.
20536        for &a in STRICT_ORDER_ROSTER {
20537            assert!(
20538                !a.is_incomparable(EMPTY_RESOURCE_LIMITS),
20539                "bottom-pole absorption failed on {a:?}",
20540            );
20541            assert!(
20542                !EMPTY_RESOURCE_LIMITS.is_incomparable(a),
20543                "bottom-pole absorption failed on flipped ({a:?}, EMPTY)",
20544            );
20545        }
20546    }
20547
20548    #[test]
20549    fn resource_limits_is_incomparable_folds_false_at_the_top_pole() {
20550        // Top-pole absorption pin — `a.is_incomparable(
20551        // UNBOUNDED_RESOURCE_LIMITS) == false` on every posture `a`.
20552        // `UNBOUNDED_RESOURCE_LIMITS` is the bounded-lattice top, so
20553        // `a.leq(UNBOUNDED)` holds on every axis (`a.max_* <=
20554        // usize::MAX` unconditionally), which falsifies the
20555        // `!self.leq(other)` leg. Sweeps the canonical preset
20556        // roster; pairs with the bottom-pole absorption sibling one
20557        // POLE axis over.
20558        for &a in STRICT_ORDER_ROSTER {
20559            assert!(
20560                !a.is_incomparable(UNBOUNDED_RESOURCE_LIMITS),
20561                "top-pole absorption failed on {a:?}",
20562            );
20563            assert!(
20564                !UNBOUNDED_RESOURCE_LIMITS.is_incomparable(a),
20565                "top-pole absorption failed on flipped ({a:?}, UNBOUNDED)",
20566            );
20567        }
20568    }
20569
20570    #[test]
20571    fn resource_limits_is_incomparable_holds_on_the_hand_authored_antichain_pair() {
20572        // Antichain load-bearing arm — the two hand-authored
20573        // asymmetric postures sit on distinct branches (each is
20574        // smaller on three axes and larger on the other three), so
20575        // neither pointwise-domination direction closes and the
20576        // antichain verdict HOLDS in both orderings. Pairs with
20577        // `resource_limits_geq_rejects_incomparable_postures` +
20578        // `resource_limits_lt_rejects_incomparable_postures` one COVER
20579        // axis over: those pin the two comparable-direction predicates
20580        // FALSIFY on this pair; THIS pin confirms the antichain
20581        // verdict HOLDS on the SAME pair — the two are the negation-
20582        // paired sides of the (comparable, incomparable) two-cell
20583        // partition.
20584        assert!(
20585            HAND_AUTHORED_MID_POSTURE.is_incomparable(HAND_AUTHORED_OTHER_POSTURE),
20586            "antichain arm failed on (MID, OTHER)",
20587        );
20588        assert!(
20589            HAND_AUTHORED_OTHER_POSTURE.is_incomparable(HAND_AUTHORED_MID_POSTURE),
20590            "antichain arm failed on flipped (OTHER, MID)",
20591        );
20592    }
20593
20594    #[test]
20595    fn resource_limits_is_incomparable_folds_false_on_the_shipped_preset_triangle() {
20596        // Shipped preset comparability pin — every ordered pair drawn
20597        // from the (EMPTY, DEFAULT, UNBOUNDED) shipped preset triangle
20598        // is COMPARABLE (in both directions the pairwise partial-order
20599        // relation gives a verdict). The three shipped presets form a
20600        // TOTALLY ORDERED chain on the pointwise partial-order (EMPTY
20601        // <= DEFAULT <= UNBOUNDED on every field), so the antichain
20602        // corner MUST fold to `false` on every ordered pair drawn from
20603        // them. Discriminates a regression that promoted a shipped
20604        // preset off the total-order chain by drifting ONE field
20605        // silently.
20606        const TRIANGLE: &[ResourceLimits] = &[
20607            EMPTY_RESOURCE_LIMITS,
20608            DEFAULT_RESOURCE_LIMITS,
20609            UNBOUNDED_RESOURCE_LIMITS,
20610        ];
20611        for &a in TRIANGLE {
20612            for &b in TRIANGLE {
20613                assert!(
20614                    !a.is_incomparable(b),
20615                    "shipped preset triangle comparability failed on ({a:?}, {b:?})",
20616                );
20617            }
20618        }
20619    }
20620
20621    #[test]
20622    fn resource_limits_is_incomparable_evaluates_at_compile_time_via_const_fn() {
20623        // Const-fn pin — the antichain characterization is evaluable
20624        // in const context, so a caller can pin an antichain-
20625        // disagreement identity at compile time. Sibling of the
20626        // const-fn evaluability pins on `leq` / `geq` / `lt` / `gt`
20627        // one COVER axis over on the pairwise-relation surface.
20628        //
20629        // A regression to a runtime `fn` here would fail the `const _:
20630        // () = assert!(...)` bindings below at compile time.
20631        const _: () = assert!(!EMPTY_RESOURCE_LIMITS.is_incomparable(EMPTY_RESOURCE_LIMITS));
20632        const _: () = assert!(!DEFAULT_RESOURCE_LIMITS.is_incomparable(DEFAULT_RESOURCE_LIMITS));
20633        const _: () =
20634            assert!(!UNBOUNDED_RESOURCE_LIMITS.is_incomparable(UNBOUNDED_RESOURCE_LIMITS));
20635        const _: () = assert!(!EMPTY_RESOURCE_LIMITS.is_incomparable(UNBOUNDED_RESOURCE_LIMITS));
20636        const _: () = assert!(!UNBOUNDED_RESOURCE_LIMITS.is_incomparable(EMPTY_RESOURCE_LIMITS));
20637        const _: () = assert!(!DEFAULT_RESOURCE_LIMITS.is_incomparable(EMPTY_RESOURCE_LIMITS));
20638        const _: () = assert!(!UNBOUNDED_RESOURCE_LIMITS.is_incomparable(DEFAULT_RESOURCE_LIMITS));
20639    }
20640
20641    // ── ResourceLimits::is_comparable ─────────────────────────────────
20642    //
20643    // The COMPARABLE CORNER on the pairwise-relation surface — the
20644    // DIRECT POSITIVE DUAL of `is_incomparable` one COVER-COMPLEMENT
20645    // axis over on the (comparable, incomparable) two-cell partition
20646    // of the ordered pair × verdict surface. See `ResourceLimits::is_comparable`
20647    // for the algebra; the pins below fix each documented property
20648    // exactly once so a regression on any leg fires on a NAMED test
20649    // rather than an ad-hoc downstream break.
20650
20651    #[test]
20652    fn resource_limits_is_comparable_is_reflexive() {
20653        // Comparable-corner reflexivity pin — `a.is_comparable(a) ==
20654        // true` on every posture. Every posture is COMPARABLE to
20655        // itself via `leq`'s reflexivity (`a.leq(a) == true`), so
20656        // the disjunction's `self.leq(other)` leg holds at every
20657        // diagonal input. Sibling of `is_incomparable`'s
20658        // irreflexivity one CELL axis over on the (comparable,
20659        // incomparable) partition — the diagonal MUST fold to `true`
20660        // here iff it folds to `false` there.
20661        for &a in STRICT_ORDER_ROSTER {
20662            assert!(a.is_comparable(a), "reflexivity failed on {a:?}");
20663        }
20664    }
20665
20666    #[test]
20667    fn resource_limits_is_comparable_is_symmetric() {
20668        // Comparable-corner symmetry pin — `a.is_comparable(b) ==
20669        // b.is_comparable(a)` on every pair. The composition
20670        // `self.leq(other) || self.geq(other)` folds through the
20671        // `self.leq(other) == other.geq(self)` dual identity on
20672        // `geq` and the disjunction's commutativity into an order-
20673        // independent projection on its two inputs. Both cells of an
20674        // EXHAUSTIVE-AND-DISJOINT partition of a symmetric surface
20675        // must themselves be symmetric — the two verdicts flip in
20676        // lockstep on argument swap.
20677        for &a in STRICT_ORDER_ROSTER {
20678            for &b in STRICT_ORDER_ROSTER {
20679                assert_eq!(
20680                    a.is_comparable(b),
20681                    b.is_comparable(a),
20682                    "symmetry failed on ({a:?}, {b:?})",
20683                );
20684            }
20685        }
20686    }
20687
20688    #[test]
20689    fn resource_limits_is_comparable_is_de_morgan_dual_of_incomparable() {
20690        // De Morgan dual pin — `a.is_comparable(b) ==
20691        // !a.is_incomparable(b)` on every pair. The comparable corner
20692        // is the SET-COMPLEMENT of the antichain corner on the
20693        // (comparable, incomparable) two-cell partition of the
20694        // ordered pair × verdict surface. The DIRECT dual of
20695        // `resource_limits_is_incomparable_is_de_morgan_dual_of_comparable`
20696        // one CELL axis over: that test pins the antichain corner as
20697        // the negation of the `leq || geq` disjunction; THIS test
20698        // pins the comparability corner as the negation of the
20699        // antichain projection. Together the two pin BOTH SIDES of
20700        // the partition, closing it as a substrate-level
20701        // EXCLUSIVITY-AND-EXHAUSTIVENESS THEOREM.
20702        for &a in STRICT_ORDER_ROSTER {
20703            for &b in STRICT_ORDER_ROSTER {
20704                let via_named = a.is_comparable(b);
20705                let via_negated_dual = !a.is_incomparable(b);
20706                assert_eq!(
20707                    via_named, via_negated_dual,
20708                    "De Morgan dual failed on ({a:?}, {b:?})",
20709                );
20710            }
20711        }
20712    }
20713
20714    #[test]
20715    fn resource_limits_is_comparable_agrees_with_leq_or_geq_composition() {
20716        // Direct composition pin — `a.is_comparable(b) == a.leq(b) ||
20717        // a.geq(b)` on every pair. Discriminates a regression that
20718        // drifted the comparability body off the substrate's `leq ||
20719        // geq` composition (e.g., a shortcut through `a.leq(b) &&
20720        // a.geq(b)` — the AND-form falsely reports comparability
20721        // only on the equality diagonal, dropping the strict-order
20722        // cells). Anchors the two-primitive delegation as a
20723        // substrate-level identity.
20724        for &a in STRICT_ORDER_ROSTER {
20725            for &b in STRICT_ORDER_ROSTER {
20726                let via_named = a.is_comparable(b);
20727                let via_composition = a.leq(b) || a.geq(b);
20728                assert_eq!(
20729                    via_named, via_composition,
20730                    "leq||geq composition failed on ({a:?}, {b:?})",
20731                );
20732            }
20733        }
20734    }
20735
20736    #[test]
20737    fn resource_limits_is_comparable_folds_true_at_the_bottom_pole() {
20738        // Bottom-pole absorption pin — `a.is_comparable(
20739        // EMPTY_RESOURCE_LIMITS) == true` on every posture `a`.
20740        // `EMPTY_RESOURCE_LIMITS` is the bounded-lattice bottom, so
20741        // `EMPTY.leq(a)` holds on every axis (`usize` zero is the
20742        // minimum), which gives `a.geq(EMPTY)` through the `geq`
20743        // dual identity, which satisfies the `self.geq(other)` leg.
20744        // The DIRECT COMPLEMENT of
20745        // `resource_limits_is_incomparable_folds_false_at_the_bottom_pole`
20746        // one CELL axis over on the SAME preset roster — where the
20747        // antichain verdict FOLDS FALSE at the bottom pole, the
20748        // comparability verdict FOLDS TRUE.
20749        for &a in STRICT_ORDER_ROSTER {
20750            assert!(
20751                a.is_comparable(EMPTY_RESOURCE_LIMITS),
20752                "bottom-pole absorption failed on {a:?}",
20753            );
20754            assert!(
20755                EMPTY_RESOURCE_LIMITS.is_comparable(a),
20756                "bottom-pole absorption failed on flipped ({a:?}, EMPTY)",
20757            );
20758        }
20759    }
20760
20761    #[test]
20762    fn resource_limits_is_comparable_folds_true_at_the_top_pole() {
20763        // Top-pole absorption pin — `a.is_comparable(
20764        // UNBOUNDED_RESOURCE_LIMITS) == true` on every posture `a`.
20765        // `UNBOUNDED_RESOURCE_LIMITS` is the bounded-lattice top, so
20766        // `a.leq(UNBOUNDED)` holds on every axis (`a.max_* <=
20767        // usize::MAX` unconditionally), which satisfies the
20768        // `self.leq(other)` leg. The DIRECT COMPLEMENT of
20769        // `resource_limits_is_incomparable_folds_false_at_the_top_pole`
20770        // one CELL axis over on the SAME preset roster.
20771        for &a in STRICT_ORDER_ROSTER {
20772            assert!(
20773                a.is_comparable(UNBOUNDED_RESOURCE_LIMITS),
20774                "top-pole absorption failed on {a:?}",
20775            );
20776            assert!(
20777                UNBOUNDED_RESOURCE_LIMITS.is_comparable(a),
20778                "top-pole absorption failed on flipped ({a:?}, UNBOUNDED)",
20779            );
20780        }
20781    }
20782
20783    #[test]
20784    fn resource_limits_is_comparable_falsifies_on_the_hand_authored_antichain_pair() {
20785        // Antichain load-bearing arm — the two hand-authored
20786        // asymmetric postures sit on distinct branches (each is
20787        // smaller on three axes and larger on the other three), so
20788        // NEITHER pointwise-domination direction closes and the
20789        // comparability verdict FALSIFIES in both orderings. The
20790        // DIRECT NEGATION of
20791        // `resource_limits_is_incomparable_holds_on_the_hand_authored_antichain_pair`
20792        // one CELL axis over on the SAME hand-authored pair — the
20793        // two are the negation-paired sides of the (comparable,
20794        // incomparable) two-cell partition, and pinning BOTH cells'
20795        // verdicts on the SAME load-bearing antichain pair closes
20796        // the partition as a substrate-level EXCLUSIVITY THEOREM.
20797        assert!(
20798            !HAND_AUTHORED_MID_POSTURE.is_comparable(HAND_AUTHORED_OTHER_POSTURE),
20799            "antichain arm failed on (MID, OTHER)",
20800        );
20801        assert!(
20802            !HAND_AUTHORED_OTHER_POSTURE.is_comparable(HAND_AUTHORED_MID_POSTURE),
20803            "antichain arm failed on flipped (OTHER, MID)",
20804        );
20805    }
20806
20807    #[test]
20808    fn resource_limits_is_comparable_holds_on_the_shipped_preset_triangle() {
20809        // Shipped preset comparability pin — every ordered pair
20810        // drawn from the (EMPTY, DEFAULT, UNBOUNDED) shipped preset
20811        // triangle is COMPARABLE (in at least one direction the
20812        // pairwise partial-order relation gives a verdict). The
20813        // three shipped presets form a TOTALLY ORDERED chain on the
20814        // pointwise partial-order (EMPTY <= DEFAULT <= UNBOUNDED on
20815        // every field), so the comparability corner MUST fold to
20816        // `true` on every ordered pair drawn from them. The DIRECT
20817        // COMPLEMENT of
20818        // `resource_limits_is_incomparable_folds_false_on_the_shipped_preset_triangle`
20819        // one CELL axis over on the SAME triangle. Discriminates a
20820        // regression that promoted a shipped preset off the total-
20821        // order chain by drifting ONE field silently.
20822        const TRIANGLE: &[ResourceLimits] = &[
20823            EMPTY_RESOURCE_LIMITS,
20824            DEFAULT_RESOURCE_LIMITS,
20825            UNBOUNDED_RESOURCE_LIMITS,
20826        ];
20827        for &a in TRIANGLE {
20828            for &b in TRIANGLE {
20829                assert!(
20830                    a.is_comparable(b),
20831                    "shipped preset triangle comparability failed on ({a:?}, {b:?})",
20832                );
20833            }
20834        }
20835    }
20836
20837    #[test]
20838    fn resource_limits_is_comparable_partitions_ordered_pair_surface_exhaustively() {
20839        // Exhaustive-and-disjoint partition pin — on every ordered
20840        // pair EXACTLY ONE of `a.is_comparable(b)` OR
20841        // `a.is_incomparable(b)` holds. The (comparable,
20842        // incomparable) partition of the ordered pair × verdict
20843        // surface is exhaustive-and-disjoint — a substrate-level
20844        // EXCLUSIVITY-AND-EXHAUSTIVENESS THEOREM the two projections
20845        // together pin. Complements
20846        // `resource_limits_is_incomparable_partitions_ordered_pair_surface_with_comparable`
20847        // one CELL axis over: that test pins the partition against
20848        // the raw `a.leq(b) || a.geq(b)` composition; THIS test pins
20849        // the partition against the NAMED comparability projection —
20850        // a regression that drifted `is_comparable` off the
20851        // composition without breaking the composition-based test
20852        // would still fire here.
20853        for &a in STRICT_ORDER_ROSTER {
20854            for &b in STRICT_ORDER_ROSTER {
20855                let comparable = a.is_comparable(b);
20856                let incomparable = a.is_incomparable(b);
20857                assert!(
20858                    comparable != incomparable,
20859                    "partition failed on ({a:?}, {b:?}): comparable={comparable}, incomparable={incomparable}",
20860                );
20861            }
20862        }
20863    }
20864
20865    #[test]
20866    fn resource_limits_is_comparable_evaluates_at_compile_time_via_const_fn() {
20867        // Const-fn pin — the comparability characterization is
20868        // evaluable in const context, so a caller can pin a
20869        // comparability-agreement identity at compile time. Sibling
20870        // of the const-fn evaluability pins on `leq` / `geq` / `lt`
20871        // / `gt` / `is_incomparable` one COVER axis over on the
20872        // pairwise-relation surface.
20873        //
20874        // A regression to a runtime `fn` here would fail the `const _:
20875        // () = assert!(...)` bindings below at compile time.
20876        const _: () = assert!(EMPTY_RESOURCE_LIMITS.is_comparable(EMPTY_RESOURCE_LIMITS));
20877        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.is_comparable(DEFAULT_RESOURCE_LIMITS));
20878        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.is_comparable(UNBOUNDED_RESOURCE_LIMITS));
20879        const _: () = assert!(EMPTY_RESOURCE_LIMITS.is_comparable(UNBOUNDED_RESOURCE_LIMITS));
20880        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.is_comparable(EMPTY_RESOURCE_LIMITS));
20881        const _: () = assert!(DEFAULT_RESOURCE_LIMITS.is_comparable(EMPTY_RESOURCE_LIMITS));
20882        const _: () = assert!(UNBOUNDED_RESOURCE_LIMITS.is_comparable(DEFAULT_RESOURCE_LIMITS));
20883    }
20884
20885    // ── ResourceLimits::partial_cmp ───────────────────────────────────
20886    //
20887    // The `Option<Ordering>` FULL-VERDICT corner on the pairwise-
20888    // relation surface — the JOINT LIFT of the (comparable, incomparable)
20889    // two-cell partition together with the ordering-direction refinement
20890    // on the comparable arm. See `ResourceLimits::partial_cmp` for the
20891    // algebra; the pins below fix each documented property exactly once
20892    // so a regression on any leg fires on a NAMED test rather than an
20893    // ad-hoc downstream break.
20894
20895    #[test]
20896    fn resource_limits_partial_cmp_is_equal_on_the_diagonal() {
20897        // Diagonal-equality pin — `a.partial_cmp(a) ==
20898        // Some(Ordering::Equal)` on every posture. The `leq`
20899        // reflexivity closes both arms of the equality-guarding
20900        // conjunction, so the diagonal cell of the (Less, Equal,
20901        // Greater, None) 4-tile partition pins to the Equal tile.
20902        // Sibling of `is_comparable_is_reflexive` one PROJECTION
20903        // axis over — where the projection folds TRUE at the
20904        // diagonal, THIS method refines the verdict to `Equal`.
20905        for &a in STRICT_ORDER_ROSTER {
20906            assert_eq!(
20907                a.partial_cmp(a),
20908                Some(Ordering::Equal),
20909                "diagonal-equality failed on {a:?}",
20910            );
20911        }
20912    }
20913
20914    #[test]
20915    fn resource_limits_partial_cmp_reverses_under_argument_swap() {
20916        // Antisymmetric-swap pin — `a.partial_cmp(b) ==
20917        // b.partial_cmp(a).map(Ordering::reverse)` on every pair.
20918        // Swapping the argument pair FLIPS the ordering-direction
20919        // verdict (`Less <-> Greater`), fixes the diagonal-equality
20920        // arm, and fixes the antichain arm. Discriminates
20921        // `is_incomparable` / `is_comparable` (both SYMMETRIC on
20922        // argument swap) one POSTURE axis over — the FULL verdict
20923        // carries directional content that inverts under swap where
20924        // the projections do not.
20925        for &a in STRICT_ORDER_ROSTER {
20926            for &b in STRICT_ORDER_ROSTER {
20927                let forward = a.partial_cmp(b);
20928                let swapped_reversed = b.partial_cmp(a).map(Ordering::reverse);
20929                assert_eq!(
20930                    forward, swapped_reversed,
20931                    "swap-reverse failed on ({a:?}, {b:?})",
20932                );
20933            }
20934        }
20935    }
20936
20937    #[test]
20938    fn resource_limits_partial_cmp_none_iff_incomparable() {
20939        // Antichain-arm identity pin — `a.partial_cmp(b).is_none()
20940        // == a.is_incomparable(b)` on every pair. The `None` arm of
20941        // the return coincides EXACTLY with the antichain-corner
20942        // projection. Anchors the joint-lift claim: THIS method
20943        // subsumes `is_incomparable` at its `is_none()` projection
20944        // without drifting the antichain characterization.
20945        for &a in STRICT_ORDER_ROSTER {
20946            for &b in STRICT_ORDER_ROSTER {
20947                let via_partial_cmp = a.partial_cmp(b).is_none();
20948                let via_is_incomparable = a.is_incomparable(b);
20949                assert_eq!(
20950                    via_partial_cmp, via_is_incomparable,
20951                    "none-iff-incomparable failed on ({a:?}, {b:?})",
20952                );
20953            }
20954        }
20955    }
20956
20957    #[test]
20958    fn resource_limits_partial_cmp_some_iff_comparable() {
20959        // Comparability-arm identity pin — `a.partial_cmp(b).is_some()
20960        // == a.is_comparable(b)` on every pair. The `Some(_)` arm of
20961        // the return coincides EXACTLY with the comparability-corner
20962        // projection. The DUAL of the antichain-arm pin one CELL axis
20963        // over on the (comparable, incomparable) partition — together
20964        // the two pins close the JOINT LIFT of both cells at THIS
20965        // method.
20966        for &a in STRICT_ORDER_ROSTER {
20967            for &b in STRICT_ORDER_ROSTER {
20968                let via_partial_cmp = a.partial_cmp(b).is_some();
20969                let via_is_comparable = a.is_comparable(b);
20970                assert_eq!(
20971                    via_partial_cmp, via_is_comparable,
20972                    "some-iff-comparable failed on ({a:?}, {b:?})",
20973                );
20974            }
20975        }
20976    }
20977
20978    #[test]
20979    fn resource_limits_partial_cmp_less_iff_lt() {
20980        // Strict-less agreement pin — `a.partial_cmp(b) ==
20981        // Some(Ordering::Less)` iff `a.lt(b)`. The strict-less arm of
20982        // the (Less, Equal, Greater, None) 4-tile partition binds to
20983        // the named `lt` companion primitive at exactly one dispatch
20984        // site. A regression that drifted the arm off `lt` (e.g., to
20985        // `leq` — including the equality diagonal) would fire here.
20986        for &a in STRICT_ORDER_ROSTER {
20987            for &b in STRICT_ORDER_ROSTER {
20988                let via_partial_cmp = a.partial_cmp(b) == Some(Ordering::Less);
20989                let via_lt = a.lt(b);
20990                assert_eq!(
20991                    via_partial_cmp, via_lt,
20992                    "less-iff-lt failed on ({a:?}, {b:?})",
20993                );
20994            }
20995        }
20996    }
20997
20998    #[test]
20999    fn resource_limits_partial_cmp_greater_iff_gt() {
21000        // Strict-greater agreement pin — `a.partial_cmp(b) ==
21001        // Some(Ordering::Greater)` iff `a.gt(b)`. Dual of the strict-
21002        // less agreement pin one DIRECTION axis over on the (Less,
21003        // Greater) strict-order pair — a regression that dropped the
21004        // asymmetry between the two arms (e.g., collapsed both to
21005        // `Less`) would fire here.
21006        for &a in STRICT_ORDER_ROSTER {
21007            for &b in STRICT_ORDER_ROSTER {
21008                let via_partial_cmp = a.partial_cmp(b) == Some(Ordering::Greater);
21009                let via_gt = a.gt(b);
21010                assert_eq!(
21011                    via_partial_cmp, via_gt,
21012                    "greater-iff-gt failed on ({a:?}, {b:?})",
21013                );
21014            }
21015        }
21016    }
21017
21018    #[test]
21019    fn resource_limits_partial_cmp_equal_iff_eq() {
21020        // Equality agreement pin — `a.partial_cmp(b) ==
21021        // Some(Ordering::Equal)` iff `a == b`. The equality arm of the
21022        // 4-tile partition binds through the pointwise antisymmetric-
21023        // equality characterization (`a.leq(b) && b.leq(a) → a == b`,
21024        // pinned by `resource_limits_leq_is_antisymmetric`) to the
21025        // `PartialEq::eq` companion. Discriminates a regression that
21026        // drifted the Equal arm off the antisymmetric conjunction.
21027        for &a in STRICT_ORDER_ROSTER {
21028            for &b in STRICT_ORDER_ROSTER {
21029                let via_partial_cmp = a.partial_cmp(b) == Some(Ordering::Equal);
21030                let via_eq = a == b;
21031                assert_eq!(
21032                    via_partial_cmp, via_eq,
21033                    "equal-iff-eq failed on ({a:?}, {b:?})",
21034                );
21035            }
21036        }
21037    }
21038
21039    #[test]
21040    fn resource_limits_partial_cmp_folds_less_ascending_shipped_preset_chain() {
21041        // Bottom-to-top-pole ordering pin — every strictly-ascending
21042        // pair on the shipped preset chain (EMPTY <= DEFAULT <=
21043        // UNBOUNDED) folds to `Some(Ordering::Less)`. The three
21044        // shipped presets form a TOTALLY ORDERED chain on the
21045        // pointwise partial-order (every DEFAULT_MAX_* is a concrete
21046        // positive value strictly less than usize::MAX), so the
21047        // strict-less arm fires on every ordered pair drawn in
21048        // ascending direction.
21049        assert_eq!(
21050            EMPTY_RESOURCE_LIMITS.partial_cmp(DEFAULT_RESOURCE_LIMITS),
21051            Some(Ordering::Less),
21052        );
21053        assert_eq!(
21054            DEFAULT_RESOURCE_LIMITS.partial_cmp(UNBOUNDED_RESOURCE_LIMITS),
21055            Some(Ordering::Less),
21056        );
21057        assert_eq!(
21058            EMPTY_RESOURCE_LIMITS.partial_cmp(UNBOUNDED_RESOURCE_LIMITS),
21059            Some(Ordering::Less),
21060        );
21061    }
21062
21063    #[test]
21064    fn resource_limits_partial_cmp_folds_greater_descending_shipped_preset_chain() {
21065        // Top-to-bottom-pole ordering pin — every strictly-descending
21066        // pair on the shipped preset chain folds to
21067        // `Some(Ordering::Greater)`. The DUAL of the ascending pin
21068        // one DIRECTION axis over — together the two pins close the
21069        // (ascending, descending) direction pair on the shipped
21070        // preset chain at the ordering-direction refinement.
21071        assert_eq!(
21072            DEFAULT_RESOURCE_LIMITS.partial_cmp(EMPTY_RESOURCE_LIMITS),
21073            Some(Ordering::Greater),
21074        );
21075        assert_eq!(
21076            UNBOUNDED_RESOURCE_LIMITS.partial_cmp(DEFAULT_RESOURCE_LIMITS),
21077            Some(Ordering::Greater),
21078        );
21079        assert_eq!(
21080            UNBOUNDED_RESOURCE_LIMITS.partial_cmp(EMPTY_RESOURCE_LIMITS),
21081            Some(Ordering::Greater),
21082        );
21083    }
21084
21085    #[test]
21086    fn resource_limits_partial_cmp_folds_none_on_the_hand_authored_antichain_pair() {
21087        // Antichain load-bearing arm — the two hand-authored
21088        // asymmetric postures sit on distinct branches (each is
21089        // smaller on three axes and larger on the other three), so
21090        // NEITHER pointwise-domination direction closes and the
21091        // fall-through `None` arm fires in both orderings. The DIRECT
21092        // LIFT of `is_incomparable_holds_on_the_hand_authored_antichain_pair`
21093        // one PROJECTION axis over on the SAME hand-authored pair —
21094        // where the projection folds TRUE, THIS method folds to
21095        // `None`. The two are the negation-paired sides of the
21096        // (Some(_), None) two-cell partition of `Option<Ordering>`.
21097        assert_eq!(
21098            HAND_AUTHORED_MID_POSTURE.partial_cmp(HAND_AUTHORED_OTHER_POSTURE),
21099            None,
21100        );
21101        assert_eq!(
21102            HAND_AUTHORED_OTHER_POSTURE.partial_cmp(HAND_AUTHORED_MID_POSTURE),
21103            None,
21104        );
21105    }
21106
21107    #[test]
21108    fn resource_limits_partial_cmp_evaluates_at_compile_time_via_const_fn() {
21109        // Const-fn pin — the full-verdict projection is evaluable in
21110        // const context, so a caller can pin an ordering-direction
21111        // identity at compile time. Sibling of the const-fn
21112        // evaluability pins on `leq` / `geq` / `lt` / `gt` /
21113        // `is_incomparable` / `is_comparable` one COVER axis over on
21114        // the pairwise-relation surface.
21115        //
21116        // `matches!` in const context requires the pattern's variants
21117        // to be const-constructible — `Ordering::Less` / `Equal` /
21118        // `Greater` are plain enum constructors, so the assertions
21119        // below stay inside stable const-fn scope. A regression to a
21120        // runtime `fn` here would fail the `const _: () = assert!(
21121        // matches!(...))` bindings below at compile time.
21122        const _: () = assert!(matches!(
21123            EMPTY_RESOURCE_LIMITS.partial_cmp(EMPTY_RESOURCE_LIMITS),
21124            Some(Ordering::Equal),
21125        ));
21126        const _: () = assert!(matches!(
21127            EMPTY_RESOURCE_LIMITS.partial_cmp(UNBOUNDED_RESOURCE_LIMITS),
21128            Some(Ordering::Less),
21129        ));
21130        const _: () = assert!(matches!(
21131            UNBOUNDED_RESOURCE_LIMITS.partial_cmp(EMPTY_RESOURCE_LIMITS),
21132            Some(Ordering::Greater),
21133        ));
21134        const _: () = assert!(matches!(
21135            DEFAULT_RESOURCE_LIMITS.partial_cmp(UNBOUNDED_RESOURCE_LIMITS),
21136            Some(Ordering::Less),
21137        ));
21138        const _: () = assert!(matches!(
21139            EMPTY_RESOURCE_LIMITS.partial_cmp(DEFAULT_RESOURCE_LIMITS),
21140            Some(Ordering::Less),
21141        ));
21142    }
21143
21144    #[test]
21145    fn resource_limits_is_chain_empty_slice_is_vacuously_true() {
21146        // Empty-slice vacuous truth — the empty conjunction is vacuously
21147        // true; the empty slice contains no distinct pairs to reject.
21148        // Peer of `is_lower_bound_of(&[]) == true`'s empty-slice identity
21149        // one PRIMITIVE-KIND axis over on the (leq, comparable) ×
21150        // (pairwise, N-ary) primitive surface.
21151        assert!(ResourceLimits::is_chain(&[]));
21152    }
21153
21154    #[test]
21155    fn resource_limits_is_antichain_empty_slice_is_vacuously_true() {
21156        // Empty-slice vacuous truth — the empty conjunction is vacuously
21157        // true; the empty slice contains no distinct pairs to reject.
21158        // Peer of `is_chain(&[]) == true`'s empty-slice identity one
21159        // COVER-COMPLEMENT axis over: both cells of the (chain, antichain)
21160        // set-level pair AGREE at the empty slice on the vacuous-truth
21161        // verdict — the two cells coincide at the empty face of the
21162        // set-level verdict surface.
21163        assert!(ResourceLimits::is_antichain(&[]));
21164    }
21165
21166    #[test]
21167    fn resource_limits_is_chain_singleton_is_vacuously_true() {
21168        // Singleton vacuous truth — a one-element slice contains no
21169        // distinct pairs, so the outer-and-inner-index conjunction never
21170        // enters the inner loop and returns `true` vacuously. Pinned on
21171        // every canonical preset AND on both hand-authored asymmetric
21172        // postures so the vacuous-truth verdict holds regardless of the
21173        // singleton element's lattice position.
21174        assert!(ResourceLimits::is_chain(&[EMPTY_RESOURCE_LIMITS]));
21175        assert!(ResourceLimits::is_chain(&[DEFAULT_RESOURCE_LIMITS]));
21176        assert!(ResourceLimits::is_chain(&[UNBOUNDED_RESOURCE_LIMITS]));
21177        assert!(ResourceLimits::is_chain(&[HAND_AUTHORED_MID_POSTURE]));
21178        assert!(ResourceLimits::is_chain(&[HAND_AUTHORED_OTHER_POSTURE]));
21179    }
21180
21181    #[test]
21182    fn resource_limits_is_antichain_singleton_is_vacuously_true() {
21183        // Singleton vacuous truth — a one-element slice contains no
21184        // distinct pairs, so the outer-and-inner-index conjunction never
21185        // enters the inner loop and returns `true` vacuously. Peer of
21186        // `is_chain`'s singleton-vacuous-truth identity: both cells AGREE
21187        // at singleton slices — the (chain, antichain) verdict surface
21188        // coincides at every slice with strictly fewer than two distinct
21189        // pairs.
21190        assert!(ResourceLimits::is_antichain(&[EMPTY_RESOURCE_LIMITS]));
21191        assert!(ResourceLimits::is_antichain(&[DEFAULT_RESOURCE_LIMITS]));
21192        assert!(ResourceLimits::is_antichain(&[UNBOUNDED_RESOURCE_LIMITS]));
21193        assert!(ResourceLimits::is_antichain(&[HAND_AUTHORED_MID_POSTURE]));
21194        assert!(ResourceLimits::is_antichain(&[HAND_AUTHORED_OTHER_POSTURE]));
21195    }
21196
21197    #[test]
21198    fn resource_limits_is_chain_of_diagonal_duplicate_is_true() {
21199        // Diagonal-duplicate identity — `is_comparable` is REFLEXIVE
21200        // (`a.is_comparable(a) == true`), so the single distinct index
21201        // pair `(0, 1)` binds `a.is_comparable(a) == true` and the
21202        // conjunction holds at every diagonal-duplicate slice.
21203        // Distinguishes it from `is_antichain` one COVER-COMPLEMENT axis
21204        // over, whose diagonal-duplicate verdict is `false` via
21205        // `is_incomparable`'s IRREFLEXIVITY.
21206        assert!(ResourceLimits::is_chain(&[
21207            DEFAULT_RESOURCE_LIMITS,
21208            DEFAULT_RESOURCE_LIMITS,
21209        ]));
21210        assert!(ResourceLimits::is_chain(&[
21211            HAND_AUTHORED_MID_POSTURE,
21212            HAND_AUTHORED_MID_POSTURE,
21213        ]));
21214    }
21215
21216    #[test]
21217    fn resource_limits_is_antichain_of_diagonal_duplicate_is_false() {
21218        // Diagonal-duplicate rejection — `is_incomparable` is
21219        // IRREFLEXIVE (`a.is_incomparable(a) == false`), so the single
21220        // distinct index pair `(0, 1)` binds `a.is_incomparable(a) ==
21221        // false` and the conjunction rejects at every diagonal-duplicate
21222        // slice. The reflexivity/irreflexivity divergence between the
21223        // two pair-level projections PROPAGATES to the set-level
21224        // projections at any slice with a duplicated element — the two
21225        // set-level cells DIVERGE at every diagonal-duplicate slice, the
21226        // mirror of their AGREEMENT at every empty-or-singleton slice
21227        // one CARDINALITY axis over.
21228        assert!(!ResourceLimits::is_antichain(&[
21229            DEFAULT_RESOURCE_LIMITS,
21230            DEFAULT_RESOURCE_LIMITS,
21231        ]));
21232        assert!(!ResourceLimits::is_antichain(&[
21233            HAND_AUTHORED_MID_POSTURE,
21234            HAND_AUTHORED_MID_POSTURE,
21235        ]));
21236    }
21237
21238    #[test]
21239    fn resource_limits_is_chain_holds_on_the_shipped_preset_triple() {
21240        // Ordered-chain closure — the shipped-preset triple on the
21241        // bounded-lattice diagonal is an ascending chain (`EMPTY <=
21242        // DEFAULT <= UNBOUNDED` pointwise), so every distinct pair among
21243        // the three is comparable. Pinned in every permutation of the
21244        // triple so the doubly-indexed all-pairs walk closes on every
21245        // enumeration of the same chain.
21246        assert!(ResourceLimits::is_chain(&[
21247            EMPTY_RESOURCE_LIMITS,
21248            DEFAULT_RESOURCE_LIMITS,
21249            UNBOUNDED_RESOURCE_LIMITS,
21250        ]));
21251        assert!(ResourceLimits::is_chain(&[
21252            UNBOUNDED_RESOURCE_LIMITS,
21253            DEFAULT_RESOURCE_LIMITS,
21254            EMPTY_RESOURCE_LIMITS,
21255        ]));
21256        assert!(ResourceLimits::is_chain(&[
21257            DEFAULT_RESOURCE_LIMITS,
21258            EMPTY_RESOURCE_LIMITS,
21259            UNBOUNDED_RESOURCE_LIMITS,
21260        ]));
21261    }
21262
21263    #[test]
21264    fn resource_limits_is_antichain_rejects_the_shipped_preset_pair() {
21265        // Chain rejection — the shipped-preset pair on the bounded-
21266        // lattice diagonal is a strict chain (`EMPTY.lt(DEFAULT)`), so
21267        // the single distinct pair is COMPARABLE and the antichain
21268        // conjunction rejects. The DIRECT FALSIFICATION of
21269        // `is_chain_holds_on_the_shipped_preset_triple` one CELL axis
21270        // over on the SAME lattice diagonal — the two set-level cells
21271        // carry the negation-paired verdict at every strictly-chained
21272        // slice, the mirror of their AGREEMENT at every empty-or-
21273        // singleton slice one CARDINALITY axis over.
21274        assert!(!ResourceLimits::is_antichain(&[
21275            EMPTY_RESOURCE_LIMITS,
21276            DEFAULT_RESOURCE_LIMITS,
21277        ]));
21278        assert!(!ResourceLimits::is_antichain(&[
21279            DEFAULT_RESOURCE_LIMITS,
21280            UNBOUNDED_RESOURCE_LIMITS,
21281        ]));
21282        assert!(!ResourceLimits::is_antichain(&[
21283            EMPTY_RESOURCE_LIMITS,
21284            DEFAULT_RESOURCE_LIMITS,
21285            UNBOUNDED_RESOURCE_LIMITS,
21286        ]));
21287    }
21288
21289    #[test]
21290    fn resource_limits_is_chain_rejects_the_hand_authored_antichain_pair() {
21291        // Antichain rejection — the two hand-authored asymmetric
21292        // postures sit on distinct branches (each is smaller on three
21293        // axes and larger on the other three), so neither pointwise-
21294        // domination direction closes and the pair is INCOMPARABLE. The
21295        // chain conjunction rejects at the first incomparable pair.
21296        // Pinned in both orderings so the doubly-indexed pair walk
21297        // rejects regardless of index order (the diagonal-adjacent
21298        // pair-level rejection is symmetric on argument swap).
21299        assert!(!ResourceLimits::is_chain(&[
21300            HAND_AUTHORED_MID_POSTURE,
21301            HAND_AUTHORED_OTHER_POSTURE,
21302        ]));
21303        assert!(!ResourceLimits::is_chain(&[
21304            HAND_AUTHORED_OTHER_POSTURE,
21305            HAND_AUTHORED_MID_POSTURE,
21306        ]));
21307    }
21308
21309    #[test]
21310    fn resource_limits_is_antichain_holds_on_the_hand_authored_antichain_pair() {
21311        // Hand-authored antichain closure — the two hand-authored
21312        // asymmetric postures sit on distinct branches (each is smaller
21313        // on three axes and larger on the other three), so neither
21314        // pointwise-domination direction closes and the pair is
21315        // INCOMPARABLE. The antichain conjunction accepts every distinct
21316        // pair of the slice. DIRECT LIFT of `is_chain`'s antichain
21317        // rejection one CELL axis over on the SAME hand-authored pair —
21318        // pinning BOTH cells' verdicts on the SAME load-bearing
21319        // antichain pair closes the (chain, antichain) two-cell face on
21320        // arity-2 slices as a substrate-level EXCLUSIVITY THEOREM.
21321        assert!(ResourceLimits::is_antichain(&[
21322            HAND_AUTHORED_MID_POSTURE,
21323            HAND_AUTHORED_OTHER_POSTURE,
21324        ]));
21325        assert!(ResourceLimits::is_antichain(&[
21326            HAND_AUTHORED_OTHER_POSTURE,
21327            HAND_AUTHORED_MID_POSTURE,
21328        ]));
21329    }
21330
21331    #[test]
21332    fn resource_limits_is_chain_rejects_mixed_slice_with_one_antichain_pair() {
21333        // Mixed-set rejection — both `(DEFAULT, MID)` and `(DEFAULT,
21334        // OTHER)` are comparable pairs (DEFAULT's shipped `DEFAULT_MAX_*`
21335        // per-axis values are all much larger than either hand-authored
21336        // posture's, so DEFAULT dominates both pointwise), but the
21337        // `(MID, OTHER)` pair is the hand-authored INCOMPARABLE pair.
21338        // The set-level chain verdict is stricter than the union of its
21339        // pair-level verdicts — one antichain pair anywhere in the
21340        // slice falsifies the whole projection.
21341        assert!(!ResourceLimits::is_chain(&[
21342            DEFAULT_RESOURCE_LIMITS,
21343            HAND_AUTHORED_MID_POSTURE,
21344            HAND_AUTHORED_OTHER_POSTURE,
21345        ]));
21346    }
21347
21348    #[test]
21349    fn resource_limits_is_antichain_rejects_mixed_slice_with_one_comparable_pair() {
21350        // Mixed-set rejection — even though the second-and-third pair
21351        // is incomparable (the hand-authored antichain pair), the first
21352        // pair (`DEFAULT`-and-`MID`) is comparable, so the whole-set
21353        // conjunction rejects. Pins the three-cell (chain, antichain,
21354        // mixed) set-level face at the mixed-set cell: the same slice
21355        // both `is_chain` AND `is_antichain` reject at, and the third
21356        // cell of the three-cell face the two set-level projections
21357        // carry the ends of.
21358        assert!(!ResourceLimits::is_antichain(&[
21359            DEFAULT_RESOURCE_LIMITS,
21360            HAND_AUTHORED_MID_POSTURE,
21361            HAND_AUTHORED_OTHER_POSTURE,
21362        ]));
21363    }
21364
21365    #[test]
21366    fn resource_limits_is_antichain_and_is_chain_are_mutually_exclusive_on_distinct_pairs() {
21367        // De Morgan mirror at arity 2 — on any two-element slice `[a,
21368        // b]` with `a != b`, the set-level (chain, antichain) two-cell
21369        // face reduces to the pair-level (comparable, incomparable)
21370        // two-cell partition and the two cells become MUTUALLY
21371        // EXCLUSIVE (the mixed cell requires ≥3 elements to open). The
21372        // set-level De Morgan mirror of the pair-level identity
21373        // `a.is_comparable(b) == !a.is_incomparable(b)` `is_comparable`
21374        // pins one CARDINALITY axis over.
21375        let presets: [ResourceLimits; 5] = [
21376            EMPTY_RESOURCE_LIMITS,
21377            DEFAULT_RESOURCE_LIMITS,
21378            UNBOUNDED_RESOURCE_LIMITS,
21379            HAND_AUTHORED_MID_POSTURE,
21380            HAND_AUTHORED_OTHER_POSTURE,
21381        ];
21382        for a in presets {
21383            for b in presets {
21384                if a == b {
21385                    continue;
21386                }
21387                let pair = [a, b];
21388                let chain = ResourceLimits::is_chain(&pair);
21389                let antichain = ResourceLimits::is_antichain(&pair);
21390                assert_ne!(
21391                    chain, antichain,
21392                    "distinct-pair mutual exclusivity failed on pair {a:?} / {b:?}",
21393                );
21394            }
21395        }
21396    }
21397
21398    #[test]
21399    fn resource_limits_is_chain_and_is_antichain_agree_at_empty_and_singleton_slices() {
21400        // Empty-and-singleton agreement — the two set-level projections
21401        // carry the SAME vacuous-truth verdict at every slice with
21402        // strictly fewer than two distinct pairs (empty AND singleton).
21403        // Pinned as a substrate-level identity so a future rewrite of
21404        // either projection cannot silently drift from the shared
21405        // vacuous-truth verdict.
21406        assert_eq!(
21407            ResourceLimits::is_chain(&[]),
21408            ResourceLimits::is_antichain(&[]),
21409        );
21410        let presets: [ResourceLimits; 5] = [
21411            EMPTY_RESOURCE_LIMITS,
21412            DEFAULT_RESOURCE_LIMITS,
21413            UNBOUNDED_RESOURCE_LIMITS,
21414            HAND_AUTHORED_MID_POSTURE,
21415            HAND_AUTHORED_OTHER_POSTURE,
21416        ];
21417        for a in presets {
21418            let single = [a];
21419            assert_eq!(
21420                ResourceLimits::is_chain(&single),
21421                ResourceLimits::is_antichain(&single),
21422                "singleton agreement failed on {a:?}",
21423            );
21424        }
21425    }
21426
21427    #[test]
21428    fn resource_limits_is_chain_evaluates_at_compile_time_via_const_fn() {
21429        // Const-fn pin — the set-level chain projection is evaluable in
21430        // const context, so a caller can pin a chain-membership
21431        // identity at compile time. Sibling of the const-fn
21432        // evaluability pins on `is_comparable` one CARDINALITY axis
21433        // over and on `is_lower_bound_of` / `is_upper_bound_of` one
21434        // PRIMITIVE-KIND axis over on the N-ary aggregation surface.
21435        const _: () = assert!(ResourceLimits::is_chain(&[]));
21436        const _: () = assert!(ResourceLimits::is_chain(&[EMPTY_RESOURCE_LIMITS]));
21437        const _: () = assert!(ResourceLimits::is_chain(&[
21438            EMPTY_RESOURCE_LIMITS,
21439            DEFAULT_RESOURCE_LIMITS,
21440            UNBOUNDED_RESOURCE_LIMITS,
21441        ]));
21442    }
21443
21444    #[test]
21445    fn resource_limits_is_antichain_evaluates_at_compile_time_via_const_fn() {
21446        // Const-fn pin — the set-level antichain projection is
21447        // evaluable in const context, so a caller can pin an antichain-
21448        // membership identity at compile time. Sibling of the const-fn
21449        // evaluability pins on `is_incomparable` one CARDINALITY axis
21450        // over and on `is_chain` one COVER-COMPLEMENT axis over.
21451        const _: () = assert!(ResourceLimits::is_antichain(&[]));
21452        const _: () = assert!(ResourceLimits::is_antichain(&[EMPTY_RESOURCE_LIMITS]));
21453        const _: () = assert!(ResourceLimits::is_antichain(&[
21454            HAND_AUTHORED_MID_POSTURE,
21455            HAND_AUTHORED_OTHER_POSTURE,
21456        ]));
21457    }
21458
21459    #[test]
21460    fn resource_limits_is_mixed_empty_slice_is_false() {
21461        // Empty-slice contract — is_chain and is_antichain BOTH return
21462        // `true` vacuously, so `!true && !true == false`. The empty
21463        // slice is NOT mixed. Sibling posture to the vacuous-truth
21464        // verdicts of is_chain + is_antichain one CELL axis over on the
21465        // (chain, antichain, mixed) three-cell face.
21466        assert!(!ResourceLimits::is_mixed(&[]));
21467    }
21468
21469    #[test]
21470    fn resource_limits_is_mixed_singleton_is_false() {
21471        // Singleton contract — a one-element slice contains no distinct
21472        // pairs; is_chain + is_antichain BOTH return `true` vacuously.
21473        // Pinned on every canonical preset AND on both hand-authored
21474        // asymmetric postures so the false-at-singleton verdict holds
21475        // regardless of the singleton element's lattice position.
21476        assert!(!ResourceLimits::is_mixed(&[EMPTY_RESOURCE_LIMITS]));
21477        assert!(!ResourceLimits::is_mixed(&[DEFAULT_RESOURCE_LIMITS]));
21478        assert!(!ResourceLimits::is_mixed(&[UNBOUNDED_RESOURCE_LIMITS]));
21479        assert!(!ResourceLimits::is_mixed(&[HAND_AUTHORED_MID_POSTURE]));
21480        assert!(!ResourceLimits::is_mixed(&[HAND_AUTHORED_OTHER_POSTURE]));
21481    }
21482
21483    #[test]
21484    fn resource_limits_is_mixed_of_diagonal_duplicate_is_false() {
21485        // Diagonal-duplicate contract — is_chain returns `true` via
21486        // is_comparable's REFLEXIVITY; is_antichain returns `false` via
21487        // is_incomparable's IRREFLEXIVITY; the conjunction
21488        // `!true && !false == false`. The diagonal-duplicate slice is a
21489        // CHAIN (per the reflexivity arm), not mixed.
21490        assert!(!ResourceLimits::is_mixed(&[
21491            DEFAULT_RESOURCE_LIMITS,
21492            DEFAULT_RESOURCE_LIMITS,
21493        ]));
21494        assert!(!ResourceLimits::is_mixed(&[
21495            HAND_AUTHORED_MID_POSTURE,
21496            HAND_AUTHORED_MID_POSTURE,
21497        ]));
21498    }
21499
21500    #[test]
21501    fn resource_limits_is_mixed_rejects_the_shipped_preset_triple() {
21502        // Chain rejection — the shipped-preset triple on the bounded-
21503        // lattice diagonal is an ascending chain, is_chain returns
21504        // `true`, and `!true && …` short-circuits to `false`. A PURE
21505        // chain is not mixed. Pinned in every permutation of the triple
21506        // so the projection factors through the ordering-agnostic
21507        // sibling is_chain verdict on the same chain.
21508        assert!(!ResourceLimits::is_mixed(&[
21509            EMPTY_RESOURCE_LIMITS,
21510            DEFAULT_RESOURCE_LIMITS,
21511            UNBOUNDED_RESOURCE_LIMITS,
21512        ]));
21513        assert!(!ResourceLimits::is_mixed(&[
21514            UNBOUNDED_RESOURCE_LIMITS,
21515            DEFAULT_RESOURCE_LIMITS,
21516            EMPTY_RESOURCE_LIMITS,
21517        ]));
21518    }
21519
21520    #[test]
21521    fn resource_limits_is_mixed_rejects_the_hand_authored_antichain_pair() {
21522        // Antichain rejection — the hand-authored antichain pair binds
21523        // is_antichain to `true`, and `!… && !true == false`. A PURE
21524        // antichain is not mixed. Pinned in both orderings so the
21525        // projection factors through the argument-swap-symmetric
21526        // sibling is_antichain verdict on the same antichain.
21527        assert!(!ResourceLimits::is_mixed(&[
21528            HAND_AUTHORED_MID_POSTURE,
21529            HAND_AUTHORED_OTHER_POSTURE,
21530        ]));
21531        assert!(!ResourceLimits::is_mixed(&[
21532            HAND_AUTHORED_OTHER_POSTURE,
21533            HAND_AUTHORED_MID_POSTURE,
21534        ]));
21535    }
21536
21537    #[test]
21538    fn resource_limits_is_mixed_holds_on_the_mixed_slice_with_one_comparable_and_one_antichain_pair(
21539    ) {
21540        // Mixed-set closure — LOAD-BEARING `true`-arm catch. The
21541        // (DEFAULT, MID) pair is comparable (falsifying is_antichain)
21542        // while the (MID, OTHER) pair is incomparable (falsifying
21543        // is_chain); the conjunction `!false && !false == true`. This
21544        // is the SAME fixture the sibling is_chain + is_antichain BOTH
21545        // reject at — the DISCRIMINATING positive-arm catch for the
21546        // (mixed) cell of the three-cell face the two set-level
21547        // projections carry the ends of.
21548        assert!(ResourceLimits::is_mixed(&[
21549            DEFAULT_RESOURCE_LIMITS,
21550            HAND_AUTHORED_MID_POSTURE,
21551            HAND_AUTHORED_OTHER_POSTURE,
21552        ]));
21553    }
21554
21555    #[test]
21556    fn resource_limits_is_chain_is_antichain_and_is_mixed_partition_the_verdict_surface_on_distinct_slices(
21557    ) {
21558        // Trichotomy exhaustiveness at arity ≥2 with pairwise-distinct
21559        // elements — EXACTLY ONE of is_chain, is_antichain, is_mixed
21560        // returns `true` on every pair OR triple drawn from the shipped
21561        // presets whose elements are pairwise distinct. The three cells
21562        // partition the set-level verdict surface at every non-
21563        // degenerate slice. Substrate-level identity: pinning it here
21564        // catches a future rewrite of any of the three projections that
21565        // silently drifts from the partition contract.
21566        let presets: [ResourceLimits; 5] = [
21567            EMPTY_RESOURCE_LIMITS,
21568            DEFAULT_RESOURCE_LIMITS,
21569            UNBOUNDED_RESOURCE_LIMITS,
21570            HAND_AUTHORED_MID_POSTURE,
21571            HAND_AUTHORED_OTHER_POSTURE,
21572        ];
21573        // Arity-2 sweep — the mixed cell CANNOT open at arity 2 (only
21574        // one distinct pair), so is_mixed is always false and the
21575        // partition reduces to the (chain, antichain) two-cell face
21576        // that is_chain + is_antichain already pinned MUTUALLY
21577        // EXCLUSIVE. Cross-check that the three-cell projection
21578        // AGREES with the two-cell projection at arity 2.
21579        for a in presets {
21580            for b in presets {
21581                if a == b {
21582                    continue;
21583                }
21584                let pair = [a, b];
21585                let chain = ResourceLimits::is_chain(&pair);
21586                let antichain = ResourceLimits::is_antichain(&pair);
21587                let mixed = ResourceLimits::is_mixed(&pair);
21588                let true_count = usize::from(chain) + usize::from(antichain) + usize::from(mixed);
21589                assert_eq!(
21590                    true_count, 1,
21591                    "trichotomy partition failed on distinct pair {a:?} / {b:?} — (chain, antichain, mixed) = ({chain}, {antichain}, {mixed})",
21592                );
21593                assert!(
21594                    !mixed,
21595                    "is_mixed unexpectedly true on distinct pair {a:?} / {b:?} — the mixed cell requires ≥3 elements to open",
21596                );
21597            }
21598        }
21599        // Arity-3 sweep on distinct triples — the mixed cell opens
21600        // whenever the triple carries both a comparable and an
21601        // incomparable pair (e.g. DEFAULT + MID + OTHER above).
21602        for a in presets {
21603            for b in presets {
21604                for c in presets {
21605                    if a == b || b == c || a == c {
21606                        continue;
21607                    }
21608                    let triple = [a, b, c];
21609                    let chain = ResourceLimits::is_chain(&triple);
21610                    let antichain = ResourceLimits::is_antichain(&triple);
21611                    let mixed = ResourceLimits::is_mixed(&triple);
21612                    let true_count =
21613                        usize::from(chain) + usize::from(antichain) + usize::from(mixed);
21614                    assert_eq!(
21615                        true_count, 1,
21616                        "trichotomy partition failed on distinct triple {a:?} / {b:?} / {c:?} — (chain, antichain, mixed) = ({chain}, {antichain}, {mixed})",
21617                    );
21618                }
21619            }
21620        }
21621    }
21622
21623    #[test]
21624    fn resource_limits_is_mixed_evaluates_at_compile_time_via_const_fn() {
21625        // Const-fn pin — the set-level mixed projection is evaluable
21626        // in const context, so a caller can pin a mixed-set identity
21627        // at compile time. Sibling of the const-fn evaluability pins
21628        // on is_chain + is_antichain one CELL axis over.
21629        const _: () = assert!(!ResourceLimits::is_mixed(&[]));
21630        const _: () = assert!(!ResourceLimits::is_mixed(&[EMPTY_RESOURCE_LIMITS]));
21631        const _: () = assert!(ResourceLimits::is_mixed(&[
21632            DEFAULT_RESOURCE_LIMITS,
21633            HAND_AUTHORED_MID_POSTURE,
21634            HAND_AUTHORED_OTHER_POSTURE,
21635        ]));
21636    }
21637
21638    // ── ResourceLimits::is_ascending / ::is_descending — sequence-level ──
21639
21640    #[test]
21641    fn resource_limits_is_ascending_empty_slice_is_vacuously_true() {
21642        // Empty-slice vacuous truth — the empty conjunction is vacuously
21643        // true; the empty slice contains no consecutive pairs to reject.
21644        // Peer of `is_chain(&[]) == true`'s empty-slice identity one
21645        // CONSECUTIVE-VS-ALL-PAIRS axis over: both cells of the (set-
21646        // level, sequence-level) projection pair AGREE at the empty slice
21647        // on the vacuous-truth verdict.
21648        assert!(ResourceLimits::is_ascending(&[]));
21649    }
21650
21651    #[test]
21652    fn resource_limits_is_descending_empty_slice_is_vacuously_true() {
21653        // Peer of is_ascending's empty-slice identity one PAIR-LEVEL-
21654        // PRIMITIVE axis over: both cells of the (ascending, descending)
21655        // sequence-level pair AGREE at the empty slice.
21656        assert!(ResourceLimits::is_descending(&[]));
21657    }
21658
21659    #[test]
21660    fn resource_limits_is_ascending_singleton_is_vacuously_true() {
21661        // Singleton vacuous truth — a one-element slice contains no
21662        // consecutive pairs, so the walk never enters the loop and
21663        // returns `true` vacuously. Pinned on every canonical preset AND
21664        // on both hand-authored asymmetric postures.
21665        assert!(ResourceLimits::is_ascending(&[EMPTY_RESOURCE_LIMITS]));
21666        assert!(ResourceLimits::is_ascending(&[DEFAULT_RESOURCE_LIMITS]));
21667        assert!(ResourceLimits::is_ascending(&[UNBOUNDED_RESOURCE_LIMITS]));
21668        assert!(ResourceLimits::is_ascending(&[HAND_AUTHORED_MID_POSTURE]));
21669        assert!(ResourceLimits::is_ascending(&[HAND_AUTHORED_OTHER_POSTURE]));
21670    }
21671
21672    #[test]
21673    fn resource_limits_is_descending_singleton_is_vacuously_true() {
21674        assert!(ResourceLimits::is_descending(&[EMPTY_RESOURCE_LIMITS]));
21675        assert!(ResourceLimits::is_descending(&[DEFAULT_RESOURCE_LIMITS]));
21676        assert!(ResourceLimits::is_descending(&[UNBOUNDED_RESOURCE_LIMITS]));
21677        assert!(ResourceLimits::is_descending(&[HAND_AUTHORED_MID_POSTURE]));
21678        assert!(ResourceLimits::is_descending(&[
21679            HAND_AUTHORED_OTHER_POSTURE
21680        ]));
21681    }
21682
21683    #[test]
21684    fn resource_limits_is_ascending_of_diagonal_duplicate_is_true() {
21685        // Diagonal-duplicate identity — `leq` is REFLEXIVE
21686        // (`a.leq(a) == true` via each axis's `<=` reflexivity), so the
21687        // single consecutive pair `(0, 1)` binds `a.leq(a) == true` and
21688        // the conjunction holds at every diagonal-duplicate slice.
21689        // AGREES with is_chain's diagonal-duplicate verdict — the two
21690        // projections diverge only where the ORDER matters, and
21691        // duplicated elements carry no ordering information.
21692        assert!(ResourceLimits::is_ascending(&[
21693            DEFAULT_RESOURCE_LIMITS,
21694            DEFAULT_RESOURCE_LIMITS,
21695        ]));
21696        assert!(ResourceLimits::is_ascending(&[
21697            HAND_AUTHORED_MID_POSTURE,
21698            HAND_AUTHORED_MID_POSTURE,
21699        ]));
21700    }
21701
21702    #[test]
21703    fn resource_limits_is_descending_of_diagonal_duplicate_is_true() {
21704        // Diagonal-duplicate identity — `geq` is REFLEXIVE, so the
21705        // single consecutive pair `(0, 1)` binds `a.geq(a) == true`.
21706        // AGREES with is_ascending's diagonal-duplicate verdict — both
21707        // leq and geq carry reflexivity, so the sequence-level (T, T)
21708        // corner opens at every diagonal-duplicate slice, distinct from
21709        // the set-level (T, F) verdict is_chain / is_antichain carry on
21710        // the same slice.
21711        assert!(ResourceLimits::is_descending(&[
21712            DEFAULT_RESOURCE_LIMITS,
21713            DEFAULT_RESOURCE_LIMITS,
21714        ]));
21715        assert!(ResourceLimits::is_descending(&[
21716            HAND_AUTHORED_MID_POSTURE,
21717            HAND_AUTHORED_MID_POSTURE,
21718        ]));
21719    }
21720
21721    #[test]
21722    fn resource_limits_is_ascending_holds_on_the_ascending_shipped_preset_triple() {
21723        // Ordered ascending closure — the shipped-preset triple on the
21724        // bounded-lattice diagonal enumerated leq-ascending has
21725        // `EMPTY.leq(DEFAULT) == true` and `DEFAULT.leq(UNBOUNDED) ==
21726        // true`, so every consecutive pair passes the conjunction.
21727        // Pinned on the arity-2 prefix AND on the full arity-3 chain so
21728        // the walk closes at every non-degenerate ascending enumeration
21729        // length.
21730        assert!(ResourceLimits::is_ascending(&[
21731            EMPTY_RESOURCE_LIMITS,
21732            DEFAULT_RESOURCE_LIMITS,
21733        ]));
21734        assert!(ResourceLimits::is_ascending(&[
21735            DEFAULT_RESOURCE_LIMITS,
21736            UNBOUNDED_RESOURCE_LIMITS,
21737        ]));
21738        assert!(ResourceLimits::is_ascending(&[
21739            EMPTY_RESOURCE_LIMITS,
21740            DEFAULT_RESOURCE_LIMITS,
21741            UNBOUNDED_RESOURCE_LIMITS,
21742        ]));
21743    }
21744
21745    #[test]
21746    fn resource_limits_is_descending_holds_on_the_descending_shipped_preset_triple() {
21747        // Ordered descending closure — the reversed enumeration is a
21748        // geq-monotone descending sequence. The DIRECT MIRROR of
21749        // is_ascending's ascending closure one PERMUTATION axis over.
21750        assert!(ResourceLimits::is_descending(&[
21751            DEFAULT_RESOURCE_LIMITS,
21752            EMPTY_RESOURCE_LIMITS,
21753        ]));
21754        assert!(ResourceLimits::is_descending(&[
21755            UNBOUNDED_RESOURCE_LIMITS,
21756            DEFAULT_RESOURCE_LIMITS,
21757        ]));
21758        assert!(ResourceLimits::is_descending(&[
21759            UNBOUNDED_RESOURCE_LIMITS,
21760            DEFAULT_RESOURCE_LIMITS,
21761            EMPTY_RESOURCE_LIMITS,
21762        ]));
21763    }
21764
21765    #[test]
21766    fn resource_limits_is_ascending_rejects_the_descending_shipped_preset_triple() {
21767        // Order-sensitivity — the reversed enumeration of the same
21768        // shipped-preset chain has `UNBOUNDED.leq(DEFAULT) == false` at
21769        // the first consecutive pair. is_chain accepts BOTH orderings
21770        // (order-INSENSITIVE); this projection accepts only the leq-
21771        // sorted one — the DISCRIMINATING arm between the sequence-level
21772        // and set-level projections.
21773        assert!(!ResourceLimits::is_ascending(&[
21774            UNBOUNDED_RESOURCE_LIMITS,
21775            DEFAULT_RESOURCE_LIMITS,
21776            EMPTY_RESOURCE_LIMITS,
21777        ]));
21778    }
21779
21780    #[test]
21781    fn resource_limits_is_descending_rejects_the_ascending_shipped_preset_triple() {
21782        // The direct falsification of is_ascending's ascending closure
21783        // one CELL axis over on the SAME slice.
21784        assert!(!ResourceLimits::is_descending(&[
21785            EMPTY_RESOURCE_LIMITS,
21786            DEFAULT_RESOURCE_LIMITS,
21787            UNBOUNDED_RESOURCE_LIMITS,
21788        ]));
21789    }
21790
21791    #[test]
21792    fn resource_limits_is_ascending_rejects_the_non_monotone_chain_permutation() {
21793        // Non-monotone chain permutation — [DEFAULT, EMPTY, UNBOUNDED]
21794        // is a permutation of the shipped-preset chain, so it IS a chain
21795        // (is_chain accepts it), but the first consecutive pair binds
21796        // `DEFAULT.leq(EMPTY) == false`, so the sequence-level ascending
21797        // walk rejects. Also is_descending rejects — the third
21798        // consecutive pair binds `EMPTY.geq(UNBOUNDED) == false`. Pins
21799        // the DISCRIMINATING arm between the sequence-level and set-
21800        // level projections at a slice that is a chain but neither
21801        // ascending nor descending.
21802        let slice = [
21803            DEFAULT_RESOURCE_LIMITS,
21804            EMPTY_RESOURCE_LIMITS,
21805            UNBOUNDED_RESOURCE_LIMITS,
21806        ];
21807        assert!(ResourceLimits::is_chain(&slice));
21808        assert!(!ResourceLimits::is_ascending(&slice));
21809        assert!(!ResourceLimits::is_descending(&slice));
21810    }
21811
21812    #[test]
21813    fn resource_limits_is_ascending_rejects_the_hand_authored_antichain_pair() {
21814        // The two hand-authored asymmetric postures are incomparable
21815        // (neither MID.leq(OTHER) nor OTHER.leq(MID)), so the single
21816        // consecutive pair rejects. Pinned in both orderings — the pair-
21817        // level leq verdict rejects on the antichain pair regardless of
21818        // which side is `self`.
21819        assert!(!ResourceLimits::is_ascending(&[
21820            HAND_AUTHORED_MID_POSTURE,
21821            HAND_AUTHORED_OTHER_POSTURE,
21822        ]));
21823        assert!(!ResourceLimits::is_ascending(&[
21824            HAND_AUTHORED_OTHER_POSTURE,
21825            HAND_AUTHORED_MID_POSTURE,
21826        ]));
21827    }
21828
21829    #[test]
21830    fn resource_limits_is_descending_rejects_the_hand_authored_antichain_pair() {
21831        assert!(!ResourceLimits::is_descending(&[
21832            HAND_AUTHORED_MID_POSTURE,
21833            HAND_AUTHORED_OTHER_POSTURE,
21834        ]));
21835        assert!(!ResourceLimits::is_descending(&[
21836            HAND_AUTHORED_OTHER_POSTURE,
21837            HAND_AUTHORED_MID_POSTURE,
21838        ]));
21839    }
21840
21841    #[test]
21842    fn resource_limits_is_ascending_implies_is_chain_on_every_shipped_slice() {
21843        // Transitivity theorem — for every slice `postures`,
21844        // `is_ascending(postures) == true` implies `is_chain(postures)
21845        // == true`. The pointwise partial order is TRANSITIVE, so a
21846        // consecutive-pair leq-chain closes under transitive composition
21847        // into the all-pairs leq-chain. Substrate-level THEOREM: the
21848        // sequence-level ascending verdict is strictly STRONGER than the
21849        // set-level chain verdict. Same identity for is_descending —
21850        // both sequence-level projections imply the set-level chain
21851        // projection.
21852        let presets: [ResourceLimits; 5] = [
21853            EMPTY_RESOURCE_LIMITS,
21854            DEFAULT_RESOURCE_LIMITS,
21855            UNBOUNDED_RESOURCE_LIMITS,
21856            HAND_AUTHORED_MID_POSTURE,
21857            HAND_AUTHORED_OTHER_POSTURE,
21858        ];
21859        // Every arity-2 slice from the 5×5 preset matrix witnesses the
21860        // implication in both directions of the (ascending, descending)
21861        // pair.
21862        for a in presets {
21863            for b in presets {
21864                let slice = [a, b];
21865                if ResourceLimits::is_ascending(&slice) {
21866                    assert!(
21867                        ResourceLimits::is_chain(&slice),
21868                        "is_ascending ⇒ is_chain failed on pair {a:?} / {b:?}",
21869                    );
21870                }
21871                if ResourceLimits::is_descending(&slice) {
21872                    assert!(
21873                        ResourceLimits::is_chain(&slice),
21874                        "is_descending ⇒ is_chain failed on pair {a:?} / {b:?}",
21875                    );
21876                }
21877            }
21878        }
21879        // Load-bearing arity-3 witness — the shipped ascending chain
21880        // triple satisfies is_ascending AND is_chain; the descending
21881        // reversal satisfies is_descending AND is_chain.
21882        let asc = [
21883            EMPTY_RESOURCE_LIMITS,
21884            DEFAULT_RESOURCE_LIMITS,
21885            UNBOUNDED_RESOURCE_LIMITS,
21886        ];
21887        assert!(ResourceLimits::is_ascending(&asc));
21888        assert!(ResourceLimits::is_chain(&asc));
21889        let desc = [
21890            UNBOUNDED_RESOURCE_LIMITS,
21891            DEFAULT_RESOURCE_LIMITS,
21892            EMPTY_RESOURCE_LIMITS,
21893        ];
21894        assert!(ResourceLimits::is_descending(&desc));
21895        assert!(ResourceLimits::is_chain(&desc));
21896    }
21897
21898    #[test]
21899    fn resource_limits_is_ascending_and_is_descending_agree_at_empty_and_singleton_slices() {
21900        // Empty-and-singleton agreement — the two sequence-level
21901        // projections carry the SAME vacuous-truth verdict at every
21902        // slice with fewer than two consecutive pairs. Pinned as a
21903        // substrate-level identity so a future rewrite of either
21904        // projection cannot silently drift from the shared vacuous-
21905        // truth verdict.
21906        assert_eq!(
21907            ResourceLimits::is_ascending(&[]),
21908            ResourceLimits::is_descending(&[]),
21909        );
21910        let presets: [ResourceLimits; 5] = [
21911            EMPTY_RESOURCE_LIMITS,
21912            DEFAULT_RESOURCE_LIMITS,
21913            UNBOUNDED_RESOURCE_LIMITS,
21914            HAND_AUTHORED_MID_POSTURE,
21915            HAND_AUTHORED_OTHER_POSTURE,
21916        ];
21917        for a in presets {
21918            let single = [a];
21919            assert_eq!(
21920                ResourceLimits::is_ascending(&single),
21921                ResourceLimits::is_descending(&single),
21922                "empty-and-singleton agreement failed on singleton {a:?}",
21923            );
21924            let dup = [a, a];
21925            assert_eq!(
21926                ResourceLimits::is_ascending(&dup),
21927                ResourceLimits::is_descending(&dup),
21928                "diagonal-duplicate agreement failed on {a:?}",
21929            );
21930        }
21931    }
21932
21933    #[test]
21934    fn resource_limits_is_ascending_evaluates_at_compile_time_via_const_fn() {
21935        // Const-fn pin — the sequence-level ascending projection is
21936        // evaluable in const context, so a caller can pin an ascending-
21937        // sequence identity at compile time.
21938        const _: () = assert!(ResourceLimits::is_ascending(&[]));
21939        const _: () = assert!(ResourceLimits::is_ascending(&[EMPTY_RESOURCE_LIMITS]));
21940        const _: () = assert!(ResourceLimits::is_ascending(&[
21941            EMPTY_RESOURCE_LIMITS,
21942            DEFAULT_RESOURCE_LIMITS,
21943            UNBOUNDED_RESOURCE_LIMITS,
21944        ]));
21945        const _: () = assert!(!ResourceLimits::is_ascending(&[
21946            UNBOUNDED_RESOURCE_LIMITS,
21947            EMPTY_RESOURCE_LIMITS,
21948        ]));
21949    }
21950
21951    #[test]
21952    fn resource_limits_is_descending_evaluates_at_compile_time_via_const_fn() {
21953        // Const-fn pin — sibling of is_ascending one PAIR-LEVEL-
21954        // PRIMITIVE axis over.
21955        const _: () = assert!(ResourceLimits::is_descending(&[]));
21956        const _: () = assert!(ResourceLimits::is_descending(&[EMPTY_RESOURCE_LIMITS]));
21957        const _: () = assert!(ResourceLimits::is_descending(&[
21958            UNBOUNDED_RESOURCE_LIMITS,
21959            DEFAULT_RESOURCE_LIMITS,
21960            EMPTY_RESOURCE_LIMITS,
21961        ]));
21962        const _: () = assert!(!ResourceLimits::is_descending(&[
21963            EMPTY_RESOURCE_LIMITS,
21964            UNBOUNDED_RESOURCE_LIMITS,
21965        ]));
21966    }
21967}