Skip to main content

max / quasi

57.5 KB · 1446 lines History Blame Raw
1 //! The staged twin of a declaration.
2 //!
3 //! A shape marked `#[staged]` gets a second function beside it, which is the
4 //! same description evaluated with no request: every value is a sentinel, every
5 //! guard is a bool a [`Plan`](quasi_router::stage::Plan) chooses, and every
6 //! loop runs as many times as the plan says. Handing its result to the ordinary
7 //! renderer produces the screen's markup with sentinels where the data would
8 //! be, and that string is the residual a request later fills.
9 //!
10 //! # Why this is a rewrite and not a second emitter
11 //!
12 //! Nothing here emits Rust. It rewrites the parsed [`Declaration`] into another
13 //! [`Declaration`] and hands that to [`crate::emit`], so the staged function is
14 //! built by the same code that builds the ordinary one.
15 //!
16 //! That is the whole reason to do it this way. The residual's literals have to
17 //! be the renderer's own output, or the staged path is a second renderer
18 //! wearing the first one's name, and a second emitter here would be the same
19 //! mistake one level up: two spellings of every production, drifting apart one
20 //! conversion at a time. A rewrite over a closed grammar cannot drift, because
21 //! a production it does not know about is a production it cannot silently
22 //! mistranslate -- it refuses instead.
23 //!
24 //! # What it refuses, and why refusing is right
25 //!
26 //! `#[staged]` is a request, so a shape that cannot be staged is a compile
27 //! error naming the construct rather than a silently unstaged shape. Three
28 //! things are refused today, each because the value it needs has no sentinel:
29 //!
30 //! - a region whose kind comes from a supplier, which needs a `RegionKind`
31 //! - a dispatch with no `otherwise`, whose arms become integers and would stop
32 //! being exhaustive
33 //! - a value hole in a position that is not a string, which rustc reports
34 //! against the generated call rather than here
35 //!
36 //! The flag is opt-in for exactly this reason: a shape is staged when someone
37 //! has looked at it, and the rest keep the runtime renderer, which is how the
38 //! two paths coexist during the migration without a switch.
39
40 use proc_macro2::Span;
41 use quote::format_ident;
42 use syn::{Ident, Result};
43
44 use crate::ast::{
45 Arg, Declaration, Emission, Guard, Hole, HoleRoot, Interpolated, Item, Param, Predicate,
46 RegionKind, Source, StrPart,
47 };
48
49 /// The flag that asks for a staged twin.
50 pub const FLAG: &str = "staged";
51
52 /// The flag that says a shape answers the same thing every time.
53 ///
54 /// A promise about the body, made where the body is, which is the only place
55 /// anybody can check it. See [`constant_name`] for how a caller in another
56 /// `declare!` (and possibly another crate) is held to it without the two macros
57 /// ever seeing each other.
58 pub const CONSTANT: &str = "constant";
59
60 /// The shim a `#[constant]` shape gets, which is how a caller opts in.
61 ///
62 /// A proc macro sees one invocation and no other, so a caller cannot read the
63 /// callee's flags: two `declare!`s are two separate expansions, often in two
64 /// crates. The flag therefore has to leave something behind that a call site
65 /// can name and that does not exist otherwise.
66 ///
67 /// That is this function. It takes the shape's own parameters and calls the
68 /// shape, so it is the shape under a second name, and it is emitted only under
69 /// the flag. An `include` whose arguments are all literals calls it instead of
70 /// opening a staged scope; against a shape that made no such promise the call
71 /// does not resolve, and rustc names the function it could not find. The word
72 /// in that name is the word to go and write.
73 pub fn constant_name(name: &Ident) -> Ident {
74 format_ident!("{}_constant", name, span = name.span())
75 }
76
77 /// Whether an argument is settled where it is written.
78 ///
79 /// A literal, or a list of them, and nothing else. A hole is a read, and a
80 /// borrow of one is the same read with an ampersand in front, so neither is
81 /// fixed however constant it looks: `&SECTIONS` names a path this macro cannot
82 /// evaluate. An interpolated string counts only when it interpolates nothing.
83 fn fixed(arg: &Arg) -> bool {
84 match arg {
85 Arg::Str(text) => text
86 .parts
87 .iter()
88 .all(|part| matches!(part, StrPart::Lit(_))),
89 Arg::Int(_) | Arg::Bool(_) => true,
90 Arg::List(items) => items.iter().all(fixed),
91 Arg::Hole(_) | Arg::Borrow(_) => false,
92 }
93 }
94
95 /// Whether this declaration promised to answer the same thing every time.
96 pub fn constant(declaration: &Declaration) -> bool {
97 declaration.flags.iter().any(|flag| flag == CONSTANT)
98 }
99
100 /// The shim, or nothing where no promise was made.
101 pub fn promise(declaration: &Declaration) -> proc_macro2::TokenStream {
102 if !constant(declaration) {
103 return proc_macro2::TokenStream::new();
104 }
105 let name = &declaration.name;
106 let shim = constant_name(name);
107 let vis = declaration
108 .vis
109 .clone()
110 .unwrap_or(syn::Visibility::Inherited);
111 let params: Vec<_> = declaration
112 .params
113 .iter()
114 .map(|param| {
115 let name = &param.name;
116 let ty = &param.ty;
117 quote::quote!(#name: #ty)
118 })
119 .collect();
120 let arguments = declaration.params.iter().map(|param| &param.name);
121 let Ok(returns) = crate::emit::returns_type(&declaration.returns) else {
122 // A return type this crate does not know is refused by `emit` with a
123 // message naming it, and that error is the one worth reporting. Emit no
124 // shim rather than a second complaint about the same line.
125 return proc_macro2::TokenStream::new();
126 };
127 let doc = format!(
128 " [`{name}`], and the promise that it answers the same thing every time.\n\n Emitted by `#[constant]`. A staged `include` whose arguments are all\n literals calls this rather than opening a scope, so what this returns is\n built once while a residual is derived instead of once per request. A\n shape that made no such promise has no function here, and the call site\n fails to resolve rather than quietly baking one evaluation in forever."
129 );
130 quote::quote! {
131 #[doc = #doc]
132 #[allow(dead_code)]
133 #vis fn #shim(#(#params),*) -> #returns {
134 #name(#(#arguments),*)
135 }
136 }
137 }
138
139 /// The parameter the staged function takes in place of the request's reads.
140 pub(crate) const PLAN: &str = "plan";
141
142 /// The staged twin of `name`, which is what an `include` retargets to.
143 pub fn staged_name(name: &Ident) -> Ident {
144 format_ident!("{}_staged", name, span = name.span())
145 }
146
147 /// The const holding the shape's counts, beside the staged function.
148 fn counts_name(name: &Ident) -> Ident {
149 format_ident!(
150 "{}_STAGED",
151 name.to_string().to_uppercase(),
152 span = name.span()
153 )
154 }
155
156 /// Whether this declaration asked to be staged.
157 pub fn wanted(declaration: &Declaration) -> bool {
158 declaration.flags.iter().any(|flag| flag == FLAG)
159 }
160
161 /// One instruction of the fill program: what a request writes, and where.
162 ///
163 /// Collected by the same traversal that numbers the holes, so the filler and
164 /// the residual cannot disagree about order. Written as a second traversal they
165 /// would drift the first time a production was added to one and not the other.
166 pub enum Fill {
167 /// One value, at the hole with this number.
168 ///
169 /// Numbered rather than positioned because render order and declaration
170 /// order genuinely differ: a cell's `activate` address is written after the
171 /// cell's value and renders before it, inside the anchor that wraps it.
172 Hole { id: u16, hole: Hole },
173 /// What one guard places, and the predicate that decides it.
174 Branch { guard: Guard, body: Vec<Fill> },
175 /// One loop, over the collection the declaration named.
176 Repeat {
177 dereferenced: bool,
178 binder: Ident,
179 iterable: Hole,
180 body: Vec<Fill>,
181 },
182 /// Two markups at one position, and the predicate that picks between them.
183 ///
184 /// What a guarded SETTLING setting compiles to. `chosen`, `here` and
185 /// `latched` change the markup the member their own statement produces was
186 /// going to write anyway, so there is no run of members for a branch to
187 /// cover; the member is built both ways instead and a request picks one.
188 ///
189 /// Arm 0 is the setting made, matching [`Plan::swap`][swap], which is what
190 /// the twin reads to decide both which arm it is drawing and whether to
191 /// make the setting. No body, for [`Arms`](Self::Arms)'s reason: both arms'
192 /// holes are numbered at the level around them.
193 ///
194 /// [swap]: quasi_router::stage::Plan::swap
195 Swap { guard: Guard },
196 /// One dispatch, and which arm the request takes.
197 ///
198 /// No bodies. Every arm's holes are staged at the level around the
199 /// dispatch, so the closure that fills the stretch these arms sit in is the
200 /// one that fills the arm -- the same arrangement a guard that swaps markup
201 /// uses, and for the same reason: one arm carries holes another does not,
202 /// so an arm cannot number its own.
203 Arms {
204 scrutinee: Hole,
205 /// One pattern per written arm, in the order they were written. The
206 /// index a pattern maps to is its position, and anything unmatched
207 /// takes the last one, which is `otherwise`.
208 patterns: Vec<crate::ast::Pattern>,
209 },
210 /// Another shape's filler, called where its markup was spliced in.
211 ///
212 /// `site` is the include's ordinal in the shape that holds it, which is
213 /// what the staged twin hands `Plan::enter`. The filler passes the same
214 /// number to `Cursor::enter`, so a caller's walk stops at the boundary
215 /// rather than answering the callee's holes with its own.
216 Include {
217 callee: syn::Path,
218 args: Vec<Arg>,
219 site: u16,
220 },
221 }
222
223 /// Numbering, one counter per thing a plan answers.
224 ///
225 /// Shape-local on purpose. A residual is derived per shape and composed by
226 /// reference, so an included shape's holes are never renumbered against its
227 /// caller's, which is what keeps the derivation linear in the tree rather than
228 /// in the tree's expansion.
229 #[derive(Default)]
230 struct Counters {
231 guards: u16,
232 loops: u16,
233 holes: u16,
234 /// One per `include`, which is what scopes the shape it reaches.
235 sites: u16,
236 /// One per dispatch, which is what the plan answers with an arm index.
237 arms: u16,
238 /// The fill program under construction, innermost body last.
239 stack: Vec<Vec<Fill>>,
240 /// The arm site of the member currently being built both ways, if any.
241 ///
242 /// Set while a member carrying a guarded settling setting is staged, and
243 /// read by that setting: its guard becomes a read of this site's arm rather
244 /// than a refusal, and it writes no fill of its own because the wrapper
245 /// already wrote one.
246 swapping: Option<u16>,
247 }
248
249 impl Counters {
250 /// Start a body: a loop's, or what a guard places.
251 fn enter(&mut self) {
252 self.stack.push(Vec::new());
253 }
254
255 /// Finish the innermost body.
256 fn exit(&mut self) -> Vec<Fill> {
257 self.stack.pop().unwrap_or_default()
258 }
259
260 /// Add one instruction to the innermost body.
261 fn wrote(&mut self, fill: Fill) {
262 if let Some(body) = self.stack.last_mut() {
263 body.push(fill);
264 }
265 }
266 }
267
268 /// A hole reading the plan: `plan.<method>(<id>)`.
269 fn plan_read(method: &str, id: u16) -> Hole {
270 Hole {
271 root: HoleRoot::Binding(Ident::new(PLAN, Span::call_site())),
272 steps: vec![crate::ast::Step::Method {
273 name: Ident::new(method, Span::call_site()),
274 args: vec![Arg::Int(i64::from(id))],
275 }],
276 }
277 }
278
279 /// A hole answering with one sentinel: `plan.hole(<id>)`.
280 ///
281 /// Through the plan rather than a free function because a sentinel carries the
282 /// scope it was read at, and the plan is what knows the scope. Two tables on
283 /// one screen each number their holes from zero, so without the scope they
284 /// would both claim hole 0 and the residual would fill one from the other.
285 fn sentinel(id: u16) -> Hole {
286 plan_read("hole", id)
287 }
288
289 /// Which arm a dispatch takes: `plan.arm(<id>, <count>)`.
290 ///
291 /// The count rides along so a traced render records it. See
292 /// `quasi_router::stage::Plan::arm`.
293 fn plan_arm(id: u16, count: usize) -> Hole {
294 Hole {
295 root: HoleRoot::Binding(Ident::new(PLAN, Span::call_site())),
296 steps: vec![crate::ast::Step::Method {
297 name: Ident::new("arm", Span::call_site()),
298 args: vec![Arg::Int(i64::from(id)), Arg::Int(count as i64)],
299 }],
300 }
301 }
302
303 /// A hole answering with one numeric stand-in: `plan.number(<id>)`.
304 ///
305 /// See `quasi_router::stage::number_at`. A number has no `ZQH` to carry, so a
306 /// slot typed `usize` was refused a hole until this existed, and a pager is
307 /// three of them.
308 fn counted(id: u16) -> Hole {
309 plan_read("number", id)
310 }
311
312 /// The staged declaration, and the counts a plan needs to drive it.
313 pub struct Staged {
314 pub declaration: Declaration,
315 /// What a request writes into the residual, in declaration order.
316 pub fill: Vec<Fill>,
317 pub counts: (u16, u16, u16, u16),
318 pub counts_ident: Ident,
319 }
320
321 /// Rewrite one declaration into its staged twin.
322 pub fn stage(declaration: &Declaration) -> Result<Staged> {
323 let mut counters = Counters::default();
324 counters.enter();
325 let items = items(&declaration.items, &mut counters)?;
326 let fill = counters.exit();
327
328 let name = staged_name(&declaration.name);
329 let doc = format!(
330 " The staged twin of [`{}`], evaluated with no request.",
331 declaration.name
332 );
333
334 Ok(Staged {
335 fill,
336 counts: (
337 counters.guards,
338 counters.loops,
339 counters.holes,
340 counters.sites,
341 ),
342 counts_ident: counts_name(&declaration.name),
343 declaration: Declaration {
344 docs: vec![doc],
345 // `must_use` and `inline` carry over; the flag that asked for this
346 // does not, or the twin would ask for a twin of its own.
347 flags: declaration
348 .flags
349 .iter()
350 .filter(|flag| *flag != FLAG)
351 .cloned()
352 .collect(),
353 vis: declaration.vis.clone(),
354 name,
355 params: vec![Param {
356 name: Ident::new(PLAN, Span::call_site()),
357 ty: syn::parse_quote!(&::quasi_router::stage::Plan),
358 }],
359 // A shape answering a bare list answers `Staged<Vec<_>>` here: a
360 // `Vec` has nowhere to keep a mark, so the marks ride beside the
361 // value until whatever splices its members in absorbs them. Free to
362 // do, because nothing but the derivation calls a twin.
363 returns: listed(&declaration.returns),
364 items,
365 },
366 })
367 }
368
369 /// A twin's return type: a bare list gains its marks, everything else is itself.
370 ///
371 /// Read syntactically rather than through `emit`'s reader, because this runs
372 /// before the twin is emitted and the answer is one shape of type. A container
373 /// keeps its own marks inside it and needs nothing here.
374 fn listed(returns: &syn::Type) -> syn::Type {
375 let syn::Type::Path(path) = returns else {
376 return returns.clone();
377 };
378 match path.path.segments.last() {
379 Some(last) if last.ident == "Vec" => {
380 syn::parse_quote!(::quasi_router::stage::Staged<#returns>)
381 }
382 _ => returns.clone(),
383 }
384 }
385
386 fn items(items: &[Item], counters: &mut Counters) -> Result<Vec<Item>> {
387 items
388 .iter()
389 .map(|item| self::item(item, counters))
390 .collect()
391 }
392
393 fn item(item: &Item, counters: &mut Counters) -> Result<Item> {
394 Ok(match item {
395 Item::Bind { name, source } => Item::Bind {
396 name: name.clone(),
397 source: self::source(source, counters)?,
398 },
399 // Nothing an author wrote. A twin is built from a declaration, and a
400 // declaration cannot carry one of these, so meeting one means the
401 // rewrite has been run over its own output.
402 Item::Marked { .. } => {
403 return Err(syn::Error::new(
404 Span::call_site(),
405 "a staged twin cannot be staged again",
406 ));
407 }
408 // A setting is one of two things, and which one decides whether a
409 // guard on it can be a mark at all. See [`PLACING`].
410 Item::Attribute {
411 name,
412 args,
413 guard,
414 body,
415 } => {
416 if let Some(guard) = guard {
417 // The member around this one is being built both ways, and this
418 // setting is what differs between them. Its guard becomes the
419 // arm the plan asks for; the wrapper wrote the fill.
420 if !placing(name)
421 && let Some(site) = counters.swapping
422 {
423 return Ok(Item::Attribute {
424 name: name.clone(),
425 args: staged_args(name, args, counters)?,
426 guard: Some(Guard {
427 negated: false,
428 predicate: Predicate::Truth(plan_read("swap", site)),
429 span: guard.span,
430 }),
431 body: items(body, counters)?,
432 });
433 }
434 if !placing(name) {
435 return Err(syn::Error::new(
436 guard.span,
437 "a staged shape cannot guard a settling setting: it varies \
438 markup INSIDE the member its own statement produces, and \
439 a mark covers a run of members. The answer is two arms \
440 over that member -- built with the setting and without it \
441 -- which is the shape `given` already compiles to",
442 ));
443 }
444 // A placing setting is a member in all but name, so its guard
445 // is an ordinary mark over the run it places. The setting is
446 // made unconditionally and the mark says what a request
447 // decides, exactly as for a guarded member.
448 let site = counters.guards;
449 counters.guards += 1;
450 counters.enter();
451 let args = staged_args(name, args, counters)?;
452 // Inside the branch, because a setting that is not made places
453 // none of what its body would have settled either.
454 let inner = items(body, counters)?;
455 let recorded = counters.exit();
456 counters.wrote(Fill::Branch {
457 guard: (*guard).clone(),
458 body: recorded,
459 });
460 return Ok(Item::Marked {
461 site,
462 varies: crate::ast::Varies::Absent,
463 body: vec![Item::Attribute {
464 name: name.clone(),
465 args,
466 guard: None,
467 body: inner,
468 }],
469 });
470 }
471 // A body may settle the argument under a guard, and that guard
472 // is the settling case: `chosen` is ` selected` inside the
473 // `<option>` this same statement produces, so what varies is the
474 // markup of this member rather than a run of members beside it.
475 // The member becomes two arms of itself.
476 let settling: Vec<&Guard> = body
477 .iter()
478 .filter_map(|item| match item {
479 Item::Attribute {
480 guard: Some(guard),
481 name,
482 ..
483 } if !placing(name) => Some(guard),
484 _ => None,
485 })
486 .collect();
487 if let Some(guard) = settling.first() {
488 if settling.len() > 1 {
489 return Err(syn::Error::new(
490 guard.span,
491 "two settling settings on one member would be four arms of \
492 it, and nothing has wanted that: say one of them another \
493 way, or teach the twin the combinations",
494 ));
495 }
496 let site = counters.arms;
497 counters.arms += 1;
498
499 // No scope of its own, for `Fill::Arms`'s reason: both arms'
500 // holes are numbered at the level around them, so the closure
501 // filling the stretch these arms sit in is the one that fills
502 // the arm. That is what lets one arm carry a hole the other
503 // does not.
504 let args = staged_args(name, args, counters)?;
505 let inner = swapped(body, site, counters)?;
506 counters.wrote(Fill::Swap {
507 guard: (*guard).clone(),
508 });
509
510 return Ok(Item::Marked {
511 site,
512 varies: crate::ast::Varies::Arm {
513 of: 2,
514 taken: plan_arm(site, 2),
515 },
516 body: vec![Item::Attribute {
517 name: name.clone(),
518 args,
519 guard: None,
520 body: inner,
521 }],
522 });
523 }
524
525 // The argument's own holes render where the argument does, and
526 // the body's settings render inside the markup the argument
527 // builds. So the args are staged first and the body follows.
528 Item::Attribute {
529 name: name.clone(),
530 args: staged_args(name, args, counters)?,
531 guard: None,
532 body: items(body, counters)?,
533 }
534 }
535 // The binder is never read: every hole rooted at it was rewritten to a
536 // sentinel above, so the loop runs for its count and nothing else. It
537 // keeps its name with an underscore so a reader can still see which
538 // loop this was.
539 Item::For {
540 binder,
541 body,
542 dereferenced,
543 iterable,
544 } => {
545 let id = counters.loops;
546 counters.loops += 1;
547 counters.enter();
548 let staged = items(body, counters)?;
549 let inner = counters.exit();
550 counters.wrote(Fill::Repeat {
551 dereferenced: *dereferenced,
552 binder: binder.clone(),
553 iterable: iterable.clone(),
554 body: inner,
555 });
556 Item::Marked {
557 site: id,
558 varies: crate::ast::Varies::Repeated,
559 body: vec![Item::For {
560 dereferenced: false,
561 binder: format_ident!("_{}", binder, span = binder.span()),
562 iterable: plan_read("rows", id),
563 body: staged,
564 }],
565 }
566 }
567 // A guarded member is placed unconditionally and marked instead.
568 //
569 // The guard is what named the members it controls, so the twin says so
570 // and the renderer records where they landed. Rendering the screen a
571 // second time with the guard off, to see what went missing, is the
572 // thing this replaces.
573 Item::Emit(Emission::Guarded { guard, inner }) => {
574 let site = counters.guards;
575 counters.guards += 1;
576 counters.enter();
577 let inner = self::emission(inner, counters)?;
578 let body = counters.exit();
579 counters.wrote(Fill::Branch {
580 guard: (*guard).clone(),
581 body,
582 });
583 Item::Marked {
584 site,
585 varies: crate::ast::Varies::Absent,
586 body: vec![Item::Emit(inner)],
587 }
588 }
589 // A dispatch draws the arm the plan asks for, and says how many there
590 // are. The derivation renders it once per arm and reads each arm's own
591 // cover, so an arm may hold loops and guards of its own -- which is
592 // what "the arms are flat" used to be about.
593 Item::Emit(Emission::Given {
594 scrutinee,
595 arms,
596 otherwise,
597 }) => {
598 let Some(otherwise) = otherwise else {
599 return Err(syn::Error::new(
600 scrutinee.span(),
601 "a staged dispatch says what it does when nothing matches: \
602 a residual holds one arm per position and there has to be \
603 one to hold",
604 ));
605 };
606 let site = counters.arms;
607 counters.arms += 1;
608
609 // Every arm's holes are numbered at THIS level rather than inside
610 // the arm, which is what lets one arm carry a hole another does not.
611 // See `Fill::Arms`.
612 let staged = arms
613 .iter()
614 .map(|(pattern, arm)| {
615 Ok((pattern.clone(), Box::new(self::emission(arm, counters)?)))
616 })
617 .collect::<Result<Vec<_>>>()?;
618 let last = Box::new(self::emission(otherwise, counters)?);
619
620 counters.wrote(Fill::Arms {
621 scrutinee: (*scrutinee).clone(),
622 patterns: arms.iter().map(|(pattern, _)| pattern.clone()).collect(),
623 });
624
625 let of = staged.len() + 1;
626 let taken = plan_arm(site, of);
627 Item::Marked {
628 site,
629 varies: crate::ast::Varies::Arm {
630 of,
631 taken: taken.clone(),
632 },
633 body: vec![Item::Emit(Emission::Given {
634 scrutinee: taken,
635 arms: staged
636 .into_iter()
637 .enumerate()
638 .map(|(at, (_, arm))| (crate::ast::Pattern::Int(at as i64), arm))
639 .collect(),
640 otherwise: Some(last),
641 })],
642 }
643 }
644 // A member whose own body settles it under a guard: `latched when x`
645 // inside a chip, which is `chosen`'s case one production over. Built
646 // both ways and marked as two arms of itself, exactly as a settling
647 // setting on an argument is.
648 Item::Emit(emission) if settling_guard(emission).is_some() => {
649 let guard = settling_guard(emission).expect("just asked");
650 let site = counters.arms;
651 counters.arms += 1;
652
653 // No scope of its own, for `Fill::Arms`'s reason: both arms' holes
654 // are numbered at the level around them.
655 let held = counters.swapping.replace(site);
656 let staged = self::emission(emission, counters)?;
657 counters.swapping = held;
658 counters.wrote(Fill::Swap {
659 guard: guard.clone(),
660 });
661
662 Item::Marked {
663 site,
664 varies: crate::ast::Varies::Arm {
665 of: 2,
666 taken: plan_arm(site, 2),
667 },
668 body: vec![Item::Emit(staged)],
669 }
670 }
671 Item::Emit(emission) => Item::Emit(self::emission(emission, counters)?),
672 })
673 }
674
675 /// The one guarded settling setting in a member's own body, if it has one.
676 ///
677 /// A member may settle itself under a guard, which is what `latched when x`
678 /// inside a chip is: what varies is the markup this statement produces rather
679 /// than a run of members beside it, so the member becomes two arms of itself.
680 ///
681 /// Shallow on purpose. It reads the body this emission owns and no deeper: a
682 /// guard further in belongs to whatever member IS further in, and that member
683 /// is staged in its own right when the walk reaches it.
684 fn settling_guard(emission: &Emission) -> Option<&Guard> {
685 body_of(emission)?.iter().find_map(|item| match item {
686 Item::Attribute {
687 guard: Some(guard),
688 name,
689 ..
690 } if !placing(name) => Some(guard),
691 _ => None,
692 })
693 }
694
695 /// The items a member's own body holds, for the emissions that have one.
696 ///
697 /// The three placement wrappers carry no body of their own and pass the
698 /// question to what they place, which is the same reading every other pass over
699 /// this grammar takes of them.
700 fn body_of(emission: &Emission) -> Option<&[Item]> {
701 Some(match emission {
702 Emission::Simple { body, .. }
703 | Emission::Chip { body, .. }
704 | Emission::Screen { body, .. }
705 | Emission::Row { body, .. }
706 | Emission::Form { body, .. }
707 | Emission::Field { body, .. }
708 | Emission::Act { body, .. }
709 | Emission::Offers { body, .. }
710 | Emission::Column { body, .. }
711 | Emission::Cell { body, .. }
712 | Emission::Offering { body, .. }
713 | Emission::Removes { body, .. }
714 | Emission::Repeats { body, .. }
715 | Emission::Region { body, .. }
716 | Emission::Across { body, .. } => body,
717 Emission::List(body) | Emission::Table(body) | Emission::Cells(body) => body,
718 Emission::Framed { inner, .. }
719 | Emission::Beside { inner, .. }
720 | Emission::At { inner, .. } => {
721 return body_of(inner);
722 }
723 Emission::Guarded { .. }
724 | Emission::Given { .. }
725 | Emission::Activate(_)
726 | Emission::Include(_)
727 | Emission::IncludeEach(_)
728 | Emission::Link { .. } => return None,
729 })
730 }
731
732 /// One member's body, with its settling guard reading the plan's arm.
733 ///
734 /// Arm 0 is the setting made, which is [`Plan::swap`][swap]'s own reading and
735 /// the one `Fill::Swap` fills against. Everything else in the body is staged
736 /// the ordinary way.
737 ///
738 /// [swap]: quasi_router::stage::Plan::swap
739 fn swapped(body: &[Item], site: u16, counters: &mut Counters) -> Result<Vec<Item>> {
740 body.iter()
741 .map(|item| match item {
742 Item::Attribute {
743 name,
744 args,
745 guard: Some(_),
746 body,
747 } if !placing(name) => Ok(Item::Attribute {
748 name: name.clone(),
749 args: staged_args(name, args, counters)?,
750 guard: Some(Guard {
751 negated: false,
752 predicate: Predicate::Truth(plan_read("swap", site)),
753 span: Span::call_site(),
754 }),
755 body: items(body, counters)?,
756 }),
757 other => self::item(other, counters),
758 })
759 .collect()
760 }
761
762 /// A guard becomes one read of the plan, and stops being negated.
763 ///
764 /// `unless x` and `when not x` are the same question asked twice, and the plan
765 /// answers the emission rather than the predicate, so the twin keeps neither
766 /// spelling. The predicate's own holes are dropped: R9 says they are evaluated
767 /// whether or not the emission is placed, and a sentinel evaluated for a guard
768 /// nobody reads is a cost with no output.
769 fn guard(guard: &Guard, counters: &mut Counters) -> Guard {
770 let id = counters.guards;
771 counters.guards += 1;
772 Guard {
773 negated: false,
774 predicate: Predicate::Truth(plan_read("guard", id)),
775 span: guard.span,
776 }
777 }
778
779 fn source(source: &Source, counters: &mut Counters) -> Result<Source> {
780 Ok(match source {
781 Source::Str(text) => Source::Str(interpolated(text, counters)),
782 Source::Hole(hole) => Source::Hole(value(hole, counters)),
783 // A dispatch has no residual, and staging one bakes an arm. The
784 // `Emission::Given` arm below carries the whole reason.
785 Source::Choose { scrutinee, .. } => {
786 return Err(syn::Error::new(
787 scrutinee.span(),
788 "a staged shape cannot dispatch: only one arm reaches the \
789 residual, so the others are lost rather than compiled. Say \
790 the arms as guards -- `A when x; B unless x` -- which renders \
791 the same and derives a branch for each",
792 ));
793 }
794 })
795 }
796
797 /// A value a request would have brought, which becomes one sentinel.
798 ///
799 /// A hole rooted at a path is left alone. `REGION` and `PATH` are consts, so
800 /// they are the same string for every request and belong in the literal rather
801 /// than in a hole: staging them would spend a sentinel to reproduce a value the
802 /// renderer already baked in.
803 fn value(hole: &Hole, counters: &mut Counters) -> Hole {
804 stand_in(hole, counters, false)
805 }
806
807 /// The same, for a slot that counts rather than reads. See [`counted`].
808 fn number(hole: &Hole, counters: &mut Counters) -> Hole {
809 stand_in(hole, counters, true)
810 }
811
812 fn stand_in(hole: &Hole, counters: &mut Counters, counting: bool) -> Hole {
813 if matches!(hole.root, HoleRoot::Path(_)) && hole.steps.is_empty() {
814 return Hole {
815 root: match &hole.root {
816 HoleRoot::Path(path) => HoleRoot::Path(path.clone()),
817 _ => unreachable!("checked above"),
818 },
819 steps: Vec::new(),
820 };
821 }
822 let id = counters.holes;
823 counters.holes += 1;
824 counters.wrote(Fill::Hole {
825 id,
826 hole: hole.clone(),
827 });
828 // One `Fill::Hole` either way: what differs is only the alphabet the
829 // stand-in is written in, and by the time the filler sees a residual a hole
830 // is a hole.
831 if counting { counted(id) } else { sentinel(id) }
832 }
833
834 fn interpolated(text: &Interpolated, counters: &mut Counters) -> Interpolated {
835 Interpolated {
836 parts: text
837 .parts
838 .iter()
839 .map(|part| match part {
840 StrPart::Lit(literal) => StrPart::Lit(literal.clone()),
841 StrPart::Hole(hole) => StrPart::Hole(value(hole, counters)),
842 })
843 .collect(),
844 span: text.span,
845 }
846 }
847
848 /// The slots whose value is a structure the description builds, not a string a
849 /// request brings.
850 ///
851 /// A hole in one of these is a constructor call: `token Tag::badge(kind)`,
852 /// `option Choice::new(id, name)`, `stats [Figure::new(count, "Items")]`. What
853 /// the slot takes is a `Tag`, a `Choice`, a `Figure` -- none of which has a
854 /// sentinel, so the call cannot become one, and staging it as a value fails
855 /// with `the trait bound Tag: Fill is not satisfied` against generated code.
856 ///
857 /// The arguments are a different matter. Each of those is an ordinary value a
858 /// request brings, and each does have a sentinel. So the call is kept and
859 /// staged **through**: the derivation builds the tag with a sentinel in it, the
860 /// renderer draws the tag's markup around the sentinel, and the residual holds
861 /// the markup as a literal with the word as a hole. That is the same trade
862 /// [`value`] makes for a `const` path, one level down.
863 ///
864 /// Slot-directed rather than a rule about calls, and that is not a shortcut.
865 /// `text tier_line(profile.priced, prices)` is a supplier answering a string,
866 /// where the whole call is the value and staging through it would hand
867 /// `tier_line` a sentinel where it wants a `&TierPrices`. Only the slot knows
868 /// which of the two it is looking at.
869 const STRUCTURED: &[&str] = &[
870 "token", "meter", "stats", "option", "figure", "more", "back", "forward", "jumping", "chart",
871 "bar",
872 ];
873
874 /// The settings that place a member rather than settling one.
875 ///
876 /// A setting is one of two things, and the vocabulary spelled both the same
877 /// way for a long time.
878 ///
879 /// A **placing** setting adds markup at a position of its own. `more` draws a
880 /// pager after the rows; `figure` accretes onto a strip; `bar` onto a chart;
881 /// `back`, `forward` and `jumping` each draw their own control inside the
882 /// pager. Each is a member in all but name, each counts in its container's
883 /// index space, and a guard on one is an ordinary mark over the run it places.
884 ///
885 /// A **settling** setting changes the markup the member its own statement
886 /// produces was going to write anyway: `chosen` is ` selected` inside an
887 /// `<option>`, `here` and `latched` are classes inside a tag. There is no run
888 /// of members to cover, so a guard on one is two arms over that member instead
889 /// -- quasicoherent `62739b91`.
890 ///
891 /// Everything not here is settling, which is the safe default: a setting
892 /// wrongly called placing marks members its container never drew, and the
893 /// renderer refuses that by name.
894 const PLACING: &[&str] = &[
895 "figure", "bar", "more", "back", "forward", "jumping", "option",
896 ];
897
898 /// Whether a guard on this setting is a mark rather than two arms.
899 fn placing(name: &Ident) -> bool {
900 PLACING.contains(&name.to_string().as_str())
901 }
902
903 /// One setting's arguments, staged the way its slot asks for.
904 ///
905 /// The three answers a slot can give, in the order they are asked: a structure
906 /// is staged through its constructor, a count stands in as a number, and
907 /// anything else is an ordinary value.
908 fn staged_args(name: &Ident, args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
909 if structured(name) {
910 through(args, counters)
911 } else if numbered(name) {
912 counting_args(args, counters)
913 } else {
914 self::args(args, counters)
915 }
916 }
917
918 /// Whether this slot takes a structure rather than a value.
919 fn structured(name: &Ident) -> bool {
920 STRUCTURED.contains(&name.to_string().as_str())
921 }
922
923 /// The slots whose plain arguments are numbers.
924 ///
925 /// Slot-directed for [`STRUCTURED`]'s reason, and it is the same reason: only
926 /// the slot knows whether the value it wants is a word or a count. A pager is
927 /// where this arose -- `Rest::page(from, per)`, `of total`, `Jump::new(page, ..)` --
928 /// and every one of those is a `usize`, which has no sentinel.
929 ///
930 /// Only a PLAIN argument counts: an argument that is itself a call is a
931 /// structure and is staged through, which is what leaves
932 /// `Jump::new(<page>, <action>)` with a number in the first position and an
933 /// `Action` in the second.
934 const NUMBERED: &[&str] = &["of"];
935
936 /// The constructors whose plain arguments are numbers.
937 ///
938 /// Written as `Type::method` rather than as a bare method name, because `new`
939 /// is every constructor's name and only some of them count. A call is matched
940 /// on its last two segments, so a path written out in full matches too.
941 const NUMBERED_CALLS: &[&str] = &[
942 "Rest::page",
943 "Rest::showing",
944 "Rest::more",
945 "Jump::new",
946 // A chart's axis is one number and nothing else, which is what lets the
947 // whole constructor count. A bar is the case that could not: it carries a
948 // place AND a magnitude, so `Bar::at` takes the place and `of` -- already
949 // counted, as a pager's total is -- takes the number.
950 "Chart::new",
951 ];
952
953 /// Whether this slot's plain arguments stand in as numbers.
954 fn numbered(name: &Ident) -> bool {
955 NUMBERED.contains(&name.to_string().as_str())
956 }
957
958 /// Whether this call's plain arguments stand in as numbers.
959 fn numbered_call(path: &syn::Path) -> bool {
960 let mut tail = path
961 .segments
962 .iter()
963 .rev()
964 .take(2)
965 .map(|segment| segment.ident.to_string())
966 .collect::<Vec<_>>();
967 tail.reverse();
968 NUMBERED_CALLS.contains(&tail.join("::").as_str())
969 }
970
971 /// Stage a structured slot's arguments, keeping the calls that build them.
972 ///
973 /// See [`STRUCTURED`]. A call is kept and its own arguments staged; anything
974 /// else is an ordinary value and goes through [`arg`].
975 fn through(args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
976 args.iter().map(|one| one_through(one, counters)).collect()
977 }
978
979 fn one_through(arg: &Arg, counters: &mut Counters) -> Result<Arg> {
980 Ok(match arg {
981 Arg::List(items) => Arg::List(through(items, counters)?),
982 Arg::Borrow(inner) => Arg::Borrow(Box::new(one_through(inner, counters)?)),
983 Arg::Hole(hole) => match &hole.root {
984 HoleRoot::Call { path, args } => Arg::Hole(Hole {
985 root: HoleRoot::Call {
986 path: path.clone(),
987 args: if numbered_call(path) {
988 counting_args(args, counters)?
989 } else {
990 self::args(args, counters)?
991 },
992 },
993 // A builder chain on the constructor -- `Tag::badge(kind).tone(..)`
994 // -- carries values of its own, and they are staged the same way.
995 steps: hole
996 .steps
997 .iter()
998 .map(|step| {
999 Ok(match step {
1000 crate::ast::Step::Field(name) => crate::ast::Step::Field(name.clone()),
1001 crate::ast::Step::Method { name, args } => crate::ast::Step::Method {
1002 name: name.clone(),
1003 args: if numbered(name) {
1004 counting_args(args, counters)?
1005 } else {
1006 self::args(args, counters)?
1007 },
1008 },
1009 })
1010 })
1011 .collect::<Result<Vec<_>>>()?,
1012 }),
1013 // Not a constructor, so there is nothing to stage through and the
1014 // slot is being handed a value from somewhere else. Left as
1015 // written: a path is a const the renderer bakes in, and a binding
1016 // or a method chain is refused by rustc against the slot's own
1017 // type, which names the site.
1018 _ => Arg::Hole(hole.clone()),
1019 },
1020 other => self::arg(other, counters)?,
1021 })
1022 }
1023
1024 fn args(args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
1025 args.iter().map(|arg| self::arg(arg, counters)).collect()
1026 }
1027
1028 /// The same, for a slot [`NUMBERED`] names: a plain argument counts.
1029 fn counting_args(args: &[Arg], counters: &mut Counters) -> Result<Vec<Arg>> {
1030 args.iter()
1031 .map(|arg| match arg {
1032 // A call in a counted slot is still a structure, so it is staged
1033 // through rather than counted. `jumping <page> <action>` is the
1034 // site, and the action is the second half of it.
1035 Arg::Hole(hole) if matches!(hole.root, HoleRoot::Call { .. }) => {
1036 one_through(arg, counters)
1037 }
1038 Arg::Hole(hole) => Ok(Arg::Hole(number(hole, counters))),
1039 other => self::arg(other, counters),
1040 })
1041 .collect()
1042 }
1043
1044 fn arg(arg: &Arg, counters: &mut Counters) -> Result<Arg> {
1045 Ok(match arg {
1046 Arg::Str(text) => Arg::Str(interpolated(text, counters)),
1047 Arg::Hole(hole) => Arg::Hole(value(hole, counters)),
1048 Arg::List(items) => Arg::List(args(items, counters)?),
1049 Arg::Borrow(inner) => Arg::Borrow(Box::new(self::arg(inner, counters)?)),
1050 Arg::Int(value) => Arg::Int(*value),
1051 Arg::Bool(value) => Arg::Bool(*value),
1052 })
1053 }
1054
1055 /// An action's target and its modifiers both carry addresses, and an address is
1056 /// a value a request brings. Rewriting them is what puts every `href` and every
1057 /// `hx-delete` into the residual as a hole rather than baking one request's
1058 /// into the literal.
1059 fn action(action: &crate::ast::Action, counters: &mut Counters) -> Result<crate::ast::Action> {
1060 Ok(crate::ast::Action {
1061 verb: action.verb.clone(),
1062 target: action
1063 .target
1064 .as_ref()
1065 .map(|target| arg(target, counters))
1066 .transpose()?,
1067 modifiers: action
1068 .modifiers
1069 .iter()
1070 .map(|modifier| {
1071 Ok(crate::ast::Modifier {
1072 name: modifier.name.clone(),
1073 args: args(&modifier.args, counters)?,
1074 })
1075 })
1076 .collect::<Result<Vec<_>>>()?,
1077 })
1078 }
1079
1080 fn emission(emission: &Emission, counters: &mut Counters) -> Result<Emission> {
1081 Ok(match emission {
1082 Emission::Simple { member, args, body } => Emission::Simple {
1083 member: member.clone(),
1084 args: if structured(member) {
1085 through(args, counters)?
1086 } else {
1087 self::args(args, counters)?
1088 },
1089 body: items(body, counters)?,
1090 },
1091 Emission::Chip {
1092 value,
1093 action,
1094 removable,
1095 body,
1096 } => Emission::Chip {
1097 value: arg(value, counters)?,
1098 action: self::action(action, counters)?,
1099 removable: *removable,
1100 body: items(body, counters)?,
1101 },
1102 Emission::Screen {
1103 arrangement,
1104 args,
1105 body,
1106 } => Emission::Screen {
1107 arrangement: arrangement.clone(),
1108 args: self::args(args, counters)?,
1109 body: items(body, counters)?,
1110 },
1111 Emission::Row { primary, body } => Emission::Row {
1112 primary: arg(primary, counters)?,
1113 body: items(body, counters)?,
1114 },
1115 Emission::Form { action, body } => Emission::Form {
1116 action: self::action(action, counters)?,
1117 body: items(body, counters)?,
1118 },
1119 Emission::Field {
1120 kind,
1121 name,
1122 label,
1123 body,
1124 } => Emission::Field {
1125 kind: kind.clone(),
1126 name: arg(name, counters)?,
1127 label: arg(label, counters)?,
1128 body: items(body, counters)?,
1129 },
1130 Emission::List(body) => Emission::List(items(body, counters)?),
1131 Emission::Act {
1132 label,
1133 action,
1134 body,
1135 } => Emission::Act {
1136 label: arg(label, counters)?,
1137 action: self::action(action, counters)?,
1138 body: items(body, counters)?,
1139 },
1140 Emission::Offers {
1141 label,
1142 action,
1143 body,
1144 } => Emission::Offers {
1145 label: arg(label, counters)?,
1146 action: self::action(action, counters)?,
1147 body: items(body, counters)?,
1148 },
1149 Emission::Table(body) => Emission::Table(items(body, counters)?),
1150 Emission::Column { name, body } => Emission::Column {
1151 name: arg(name, counters)?,
1152 body: items(body, counters)?,
1153 },
1154 Emission::Cells(body) => Emission::Cells(items(body, counters)?),
1155 Emission::Cell {
1156 column,
1157 value,
1158 body,
1159 } => Emission::Cell {
1160 column: column.as_ref().map(|it| arg(it, counters)).transpose()?,
1161 value: arg(value, counters)?,
1162 body: items(body, counters)?,
1163 },
1164 Emission::Offering {
1165 label,
1166 action,
1167 body,
1168 } => Emission::Offering {
1169 label: arg(label, counters)?,
1170 action: self::action(action, counters)?,
1171 body: items(body, counters)?,
1172 },
1173 Emission::Removes {
1174 label,
1175 action,
1176 body,
1177 } => Emission::Removes {
1178 label: arg(label, counters)?,
1179 action: self::action(action, counters)?,
1180 body: items(body, counters)?,
1181 },
1182 Emission::Repeats {
1183 one,
1184 label,
1185 action,
1186 body,
1187 } => Emission::Repeats {
1188 one: arg(one, counters)?,
1189 label: arg(label, counters)?,
1190 action: self::action(action, counters)?,
1191 body: items(body, counters)?,
1192 },
1193 Emission::Activate(action) => Emission::Activate(self::action(action, counters)?),
1194 Emission::Link { text, action } => Emission::Link {
1195 text: arg(text, counters)?,
1196 action: self::action(action, counters)?,
1197 },
1198 Emission::Include(hole) => Emission::Include(include(hole, counters)?),
1199 // The plural, staged the same way: the callee has a twin of its own and
1200 // a filler of its own, and the caller splices a reference to both.
1201 Emission::IncludeEach(hole) => Emission::IncludeEach(include(hole, counters)?),
1202 Emission::Region { name, kind, body } => Emission::Region {
1203 name: arg(name, counters)?,
1204 kind: match kind {
1205 RegionKind::Variant(variant) => RegionKind::Variant(variant.clone()),
1206 // A `RegionKind` has no sentinel, so a supplied one is refused
1207 // -- unless nothing about it is supplied. `RegionKind::ceded("revenue-chart")`
1208 // names a path and passes a literal, which is the same value on
1209 // every request, so it is evaluated once while the residual is
1210 // derived. That is `#[constant]`'s rule one level down, and it
1211 // is the same test: every argument fixed where it is written.
1212 RegionKind::Supplied(hole) => {
1213 let fixed_kind = match &hole.root {
1214 HoleRoot::Path(_) => hole.steps.is_empty(),
1215 HoleRoot::Call { args, .. } => {
1216 hole.steps.is_empty() && args.iter().all(fixed)
1217 }
1218 HoleRoot::Binding(_) => false,
1219 };
1220 if !fixed_kind {
1221 return Err(syn::Error::new(
1222 hole.span(),
1223 "a staged shape cannot take its region kind from a supplier \
1224 that reads the request: a `RegionKind` has no sentinel to \
1225 stand in for it",
1226 ));
1227 }
1228 RegionKind::Supplied(hole.clone())
1229 }
1230 },
1231 body: items(body, counters)?,
1232 },
1233 Emission::Across { fallback, body } => Emission::Across {
1234 fallback: fallback.clone(),
1235 body: items(body, counters)?,
1236 },
1237 // The name of a frame is written where the member is placed and is not
1238 // a value a request brings, so staging walks through it and leaves it
1239 // alone.
1240 Emission::Framed { label, inner } => Emission::Framed {
1241 label: label.clone(),
1242 inner: Box::new(self::emission(inner, counters)?),
1243 },
1244 Emission::Beside {
1245 priority,
1246 width,
1247 inner,
1248 } => Emission::Beside {
1249 priority: arg(priority, counters)?,
1250 width: width.as_ref().map(|held| arg(held, counters)).transpose()?,
1251 inner: Box::new(self::emission(inner, counters)?),
1252 },
1253 Emission::At { at, inner } => Emission::At {
1254 at: arg(at, counters)?,
1255 inner: Box::new(self::emission(inner, counters)?),
1256 },
1257 Emission::Guarded { guard, inner } => {
1258 let staged = self::guard(guard, counters);
1259 counters.enter();
1260 let inner = Box::new(self::emission(inner, counters)?);
1261 let body = counters.exit();
1262 counters.wrote(Fill::Branch {
1263 guard: (*guard).clone(),
1264 body,
1265 });
1266 Emission::Guarded {
1267 guard: staged,
1268 inner,
1269 }
1270 }
1271 // A dispatch has no residual, and staging one is silently wrong.
1272 //
1273 // The derivation finds a branch by rendering the screen twice with one
1274 // guard changed and reading the difference, and it finds a loop the same
1275 // way with a row count. A dispatch is neither: its arms replace each
1276 // other at one position, so there is no insertion to measure and no
1277 // deletion to put back, and every arm would claim the same bytes.
1278 //
1279 // What happened instead was worse than a refusal. The staged twin reads
1280 // `plan.arm(id)`, which answers zero, so exactly one arm rendered and
1281 // the residual held it as a literal with the others simply gone. MNW's
1282 // forum settings pane compiled to "You haven't joined any forum
1283 // communities yet." and would have served that to every reader.
1284 //
1285 // Refused rather than modelled. `Op::Arms` and a derivation that renders
1286 // once per arm is the general answer and is a feature rather than a fix;
1287 // every dispatch in the tree today is two-way over a bool, which is two
1288 // guards, which the residual already has a shape for.
1289 Emission::Given {
1290 scrutinee,
1291 arms,
1292 otherwise,
1293 } => {
1294 let Some(otherwise) = otherwise else {
1295 return Err(syn::Error::new(
1296 scrutinee.span(),
1297 "a staged dispatch says what it does when nothing matches: \
1298 a residual holds one arm per position and there has to be \
1299 one to hold",
1300 ));
1301 };
1302 let id = counters.arms;
1303 counters.arms += 1;
1304
1305 // Every arm's holes are numbered at THIS level rather than inside
1306 // the arm, which is what lets one arm carry a hole another does not.
1307 // See `Fill::Arms`.
1308 let staged = arms
1309 .iter()
1310 .map(|(pattern, arm)| {
1311 Ok((pattern.clone(), Box::new(self::emission(arm, counters)?)))
1312 })
1313 .collect::<Result<Vec<_>>>()?;
1314 let last = Box::new(self::emission(otherwise, counters)?);
1315
1316 counters.wrote(Fill::Arms {
1317 scrutinee: (*scrutinee).clone(),
1318 patterns: arms.iter().map(|(pattern, _)| pattern.clone()).collect(),
1319 });
1320
1321 let count = staged.len() + 1;
1322 Emission::Given {
1323 // The twin asks the plan which arm to render, and the plan is
1324 // told how many there are so a traced render can say so: the
1325 // derivation renders this site once per arm and nothing else
1326 // counts them.
1327 scrutinee: plan_arm(id, count),
1328 arms: staged
1329 .into_iter()
1330 .enumerate()
1331 .map(|(at, (_, arm))| (crate::ast::Pattern::Int(at as i64), arm))
1332 .collect(),
1333 otherwise: Some(last),
1334 }
1335 }
1336 })
1337 }
1338
1339 /// An `include` retargets to the callee's staged twin, and drops its arguments.
1340 ///
1341 /// This is what makes a residual compose. The callee is staged in its own
1342 /// right, numbered in its own namespace, so a caller splices a reference rather
1343 /// than inlining a body, and a screen's guards do not multiply through its
1344 /// parts.
1345 ///
1346 /// The arguments go because the twin takes the plan and nothing else, scoped by
1347 /// this include's ordinal so the shape it reaches numbers its holes without
1348 /// colliding with its caller's or its siblings'.
1349 ///
1350 /// The old note: That the
1351 /// callee has a twin at all is decided by ordinary name resolution: an
1352 /// `include` of a shape that is not declared, or is declared without
1353 /// `#[staged]`, is a compile error naming the function that does not exist,
1354 /// which is the honest report and the one a reader can act on.
1355 fn include(hole: &Hole, counters: &mut Counters) -> Result<Hole> {
1356 let HoleRoot::Call { path, .. } = &hole.root else {
1357 return Err(syn::Error::new(
1358 hole.span(),
1359 "a staged `include` names a shape to call: a value supplied from a \
1360 binding has no sentinel to stand in for it",
1361 ));
1362 };
1363 if !hole.steps.is_empty() {
1364 return Err(syn::Error::new(
1365 hole.span(),
1366 "a staged `include` calls a shape and reads nothing off it",
1367 ));
1368 }
1369
1370 let HoleRoot::Call { args, .. } = &hole.root else {
1371 unreachable!("checked above");
1372 };
1373
1374 // Every argument a literal, so there is nothing here a request decides and
1375 // no scope worth opening. The call is rewritten to the callee's
1376 // `#[constant]` shim and evaluated where it stands: the markup it produces
1377 // is derived once into the residual rather than built and rendered on every
1378 // request. `/policy` is the site, six times -- each section's prose is
1379 // `own_prose` of a literal, and before this the literal was dropped at the
1380 // boundary and the twin put a sentinel through docengine instead.
1381 //
1382 // A shape that made no promise has no shim, so the rewritten call does not
1383 // resolve and rustc names it. That is the whole enforcement: the macro
1384 // cannot read another `declare!`'s flags, and inlining on the arguments
1385 // alone would bake one evaluation of a shape that reads a clock into the
1386 // residual and serve it forever.
1387 if args.iter().all(fixed) {
1388 let mut path = path.clone();
1389 let last = path
1390 .segments
1391 .last_mut()
1392 .ok_or_else(|| syn::Error::new(hole.span(), "an empty path"))?;
1393 last.ident = constant_name(&last.ident);
1394 return Ok(Hole {
1395 root: HoleRoot::Call {
1396 path,
1397 args: args.clone(),
1398 },
1399 steps: Vec::new(),
1400 });
1401 }
1402
1403 // The filler calls the shape's own name, not the twin's: the twin renders
1404 // markup and the filler fills it, and they are two different functions
1405 // beside one declaration.
1406 let site = counters.sites;
1407 counters.sites += 1;
1408
1409 counters.wrote(Fill::Include {
1410 callee: path.clone(),
1411 args: args.clone(),
1412 site,
1413 });
1414
1415 let mut path = path.clone();
1416 let last = path
1417 .segments
1418 .last_mut()
1419 .ok_or_else(|| syn::Error::new(hole.span(), "an empty path"))?;
1420 last.ident = staged_name(&last.ident);
1421
1422 Ok(Hole {
1423 root: HoleRoot::Call {
1424 path,
1425 args: vec![Arg::Borrow(Box::new(Arg::Hole(Hole {
1426 root: HoleRoot::Binding(Ident::new(PLAN, Span::call_site())),
1427 steps: vec![crate::ast::Step::Method {
1428 name: Ident::new("enter", Span::call_site()),
1429 args: vec![Arg::Int(i64::from(site))],
1430 }],
1431 })))],
1432 },
1433 steps: Vec::new(),
1434 })
1435 }
1436
1437 impl Hole {
1438 /// Where to report a refusal, which is the root the hole was written at.
1439 pub(crate) fn span(&self) -> Span {
1440 match &self.root {
1441 HoleRoot::Binding(name) => name.span(),
1442 HoleRoot::Path(path) | HoleRoot::Call { path, .. } => syn::spanned::Spanned::span(path),
1443 }
1444 }
1445 }
1446