Skip to main content

vize_l2/
op.rs

1//! The L2 op family: one concrete typed enum per position, never a uniform
2//! `Operation` structure (`docs/davinci/architecture.md`, "What we take
3//! from MLIR, and what we refuse").
4//!
5//! # Regions are owned by their op
6//!
7//! A [`Region`] is a child sequence owned by exactly one op. This is what
8//! makes fusion tractable: the shipped pipeline merges `v-else` /
9//! `v-else-if` siblings onto the **parent's** child list mid-traversal
10//! (`crates/vize_atelier_core/src/lane/structural.rs`), and that
11//! enter/exit sibling mutation is precisely the re-visit source a
12//! region-owning [`IfOp`] never needs - its branches are regions from
13//! birth, so no pass ever revisits a child list to stitch them together.
14//!
15//! # The core is fair, dialect ops are named
16//!
17//! The neutral core (`ui.*`) is a fair abstraction, not Vue's AST renamed;
18//! whatever is genuinely Vue-specific is a `vue.*` dialect op. A dialect
19//! op lands with the transform that needs it, never speculatively:
20//! custom directives as [`BindingOp::VueDirective`], SFC style
21//! `v-bind()` as [`BindingOp::VueCssBind`], Vue 2 `.sync` /
22//! `slot-scope` as [`BindingOp::VueSync`] / [`BindingOp::VueSlotScope`],
23//! Vue 2 pipe filters as [`crate::expr::ExprRef::Filter`], and
24//! `v-once` / `v-memo` / `v-show` / `v-html` / `v-text` / `v-cloak` as
25//! [`BindingOp::VueOnce`] / [`BindingOp::VueMemo`] /
26//! [`BindingOp::VueShow`] / [`BindingOp::VueHtml`] /
27//! [`BindingOp::VueText`] / [`BindingOp::VueCloak`].
28//!
29//! # `Drop`-free by construction
30//!
31//! Every type here is arena-resident through `vize_l0::{Box, Vec}`,
32//! whose const assertion rejects `Drop` payloads (it caught two real
33//! violations during P1-10). The `const` assertions below restate the
34//! property directly, and the node-size assertions pin each op's 64-bit
35//! footprint (guarded on pointer width for the same reason as
36//! `crates/vize_relief/src/relief/elements.rs:31-36` - the wasm32 lane
37//! P2-14 makes required is 32-bit).
38
39use vize_l0::{Box, Vec};
40
41pub mod bind;
42pub mod control;
43pub mod element;
44pub mod handler;
45pub mod model;
46pub mod slot;
47pub mod text;
48pub mod vue;
49
50pub use bind::{BindOp, OnOp};
51pub use control::{ForBinding, ForOp, IfBranch, IfOp};
52mod original_for;
53pub use element::{Attribute, ComponentOp, ElementOp, Namespace};
54pub use handler::{HandlerId, OnHandlerRef};
55pub use model::{BindingContract, ModelOp};
56pub use original_for::{OriginalForId, OriginalForOp};
57pub use slot::{DynamicName, SlotContentOp, SlotOp, SlotsObjectOp};
58pub use text::{CommentOp, InterpolationOp, TextOp};
59pub use vue::{
60    VueCloakOp, VueCssBindOp, VueDirectiveOp, VueHtmlOp, VueMemoOp, VueOnceOp, VueShowOp,
61    VueSlotScopeOp, VueSyncOp, VueTextOp,
62};
63
64/// One L2 op standing in a region (a child position).
65///
66/// Attached ops - bindings that belong to one element or component rather
67/// than to a child position - live in [`BindingOp`] instead; the two enums
68/// together are the closed op family of the stage.
69///
70/// Payloads are boxed into the arena so the enum stays two words - the
71/// shape `vize_relief::TemplateChildNode` already uses for child enums.
72#[derive(Debug)]
73pub enum Op<'a> {
74    /// `ui.element` - a native element with attributes, attached bindings,
75    /// and an owned children region.
76    Element(Box<'a, ElementOp<'a>>),
77    /// `ui.component` - a component reference; same surface as an element,
78    /// resolved at lowering.
79    Component(Box<'a, ComponentOp<'a>>),
80    /// `ui.text` - static text.
81    Text(Box<'a, TextOp<'a>>),
82    /// `ui.interpolation` - an expression rendered as text.
83    Interpolation(Box<'a, InterpolationOp<'a>>),
84    /// `ui.comment` - an authored template comment preserved for DOM output.
85    Comment(Box<'a, CommentOp<'a>>),
86    /// `ui.if` - structured conditional; every branch owns its region.
87    If(Box<'a, IfOp<'a>>),
88    /// `ui.for` - structured iteration; the repeated content is one owned
89    /// region.
90    For(Box<'a, ForOp<'a>>),
91    /// `ui.for` - the genuine original File-owned declaration family.
92    OriginalFor(Box<'a, OriginalForOp<'a>>),
93    /// `ui.slot` - a slot outlet owning its fallback region.
94    Slot(Box<'a, SlotOp<'a>>),
95}
96
97impl Op<'_> {
98    /// The op's stage-wide mnemonic - the keyword its folio line starts
99    /// with.
100    #[must_use]
101    pub const fn mnemonic(&self) -> &'static str {
102        match self {
103            Self::Element(_) => "ui.element",
104            Self::Component(_) => "ui.component",
105            Self::Text(_) => "ui.text",
106            Self::Interpolation(_) => "ui.interpolation",
107            Self::Comment(_) => "ui.comment",
108            Self::If(_) => "ui.if",
109            Self::For(_) => "ui.for",
110            Self::OriginalFor(_) => "ui.for",
111            Self::Slot(_) => "ui.slot",
112        }
113    }
114}
115
116/// One L2 op attached to an element or component (a binding position).
117///
118/// A binding belongs to exactly one owner and never stands in a region -
119/// which is why it is a separate enum rather than more [`Op`] variants: the
120/// type system rules out a floating `ui.model` instead of a verifier rule.
121///
122/// The normalized one-way bindings (`ui.bind`, `ui.on`) landed with the
123/// P2-9 element/binding-family installment - the transform that needs
124/// them - exactly as `ui.slot-content` landed with slot normalization;
125/// the exhaustive-match canary made both arrivals loud.
126#[derive(Debug)]
127pub enum BindingOp<'a> {
128    /// `ui.bind` - one one-way binding (`v-bind` / `:` / `.`).
129    Bind(Box<'a, BindOp<'a>>),
130    /// `ui.on` - one event handler binding (`v-on` / `@`).
131    On(Box<'a, OnOp<'a>>),
132    /// `ui.model` - the two-way binding contract (never its realization).
133    Model(Box<'a, ModelOp<'a>>),
134    /// `ui.slot-content` - one authored `v-slot` spelling on its carrier
135    /// (the syntactic surface; grouping is the slot pass's fact).
136    SlotContent(Box<'a, SlotContentOp<'a>>),
137    /// `ui.slots-object` - a runtime object supplying component slots.
138    SlotsObject(Box<'a, SlotsObjectOp<'a>>),
139    /// `vue.directive` - a Vue custom directive carried through as a
140    /// dialect op.
141    VueDirective(Box<'a, VueDirectiveOp<'a>>),
142    /// `vue.css-bind` - one CSS `v-bind()` in an SFC style block (P2-10).
143    VueCssBind(Box<'a, VueCssBindOp<'a>>),
144    /// `vue.sync` - Vue 2 `:foo.sync` two-way bind sugar.
145    VueSync(Box<'a, VueSyncOp<'a>>),
146    /// `vue.slot-scope` - Vue 2 `slot-scope` / `scope` scoped-slot sugar.
147    VueSlotScope(Box<'a, VueSlotScopeOp<'a>>),
148    /// `vue.once` - Vue `v-once` one-shot render flag.
149    VueOnce(Box<'a, VueOnceOp>),
150    /// `vue.memo` - Vue `v-memo` dependency-memoized render.
151    VueMemo(Box<'a, VueMemoOp<'a>>),
152    /// `vue.show` - Vue `v-show` runtime display toggle.
153    VueShow(Box<'a, VueShowOp<'a>>),
154    /// `vue.html` - Vue `v-html` raw-HTML DOM prop.
155    VueHtml(Box<'a, VueHtmlOp<'a>>),
156    /// `vue.text` - Vue `v-text` text-content DOM prop.
157    VueText(Box<'a, VueTextOp<'a>>),
158    /// `vue.cloak` - Vue `v-cloak` DOM cloak marker.
159    VueCloak(Box<'a, VueCloakOp>),
160}
161
162impl BindingOp<'_> {
163    /// The op's stage-wide mnemonic - the keyword its folio line starts
164    /// with.
165    #[must_use]
166    pub const fn mnemonic(&self) -> &'static str {
167        match self {
168            Self::Bind(_) => "ui.bind",
169            Self::On(_) => "ui.on",
170            Self::Model(_) => "ui.model",
171            Self::SlotContent(_) => "ui.slot-content",
172            Self::SlotsObject(_) => "ui.slots-object",
173            Self::VueDirective(_) => "vue.directive",
174            Self::VueCssBind(_) => "vue.css-bind",
175            Self::VueSync(_) => "vue.sync",
176            Self::VueSlotScope(_) => "vue.slot-scope",
177            Self::VueOnce(_) => "vue.once",
178            Self::VueMemo(_) => "vue.memo",
179            Self::VueShow(_) => "vue.show",
180            Self::VueHtml(_) => "vue.html",
181            Self::VueText(_) => "vue.text",
182            Self::VueCloak(_) => "vue.cloak",
183        }
184    }
185}
186
187/// A child sequence owned by exactly one op.
188///
189/// Ownership is the point (see the module docs): a region is reachable
190/// through its op alone, so no transform ever mutates a sibling list it is
191/// currently visiting.
192#[derive(Debug)]
193pub struct Region<'a> {
194    /// The ops of this region, in document order.
195    pub ops: Vec<'a, Op<'a>>,
196}
197
198/// The op family is `Drop`-free by construction: `vize_l0::{Box, Vec}`
199/// already reject `Drop` payloads at their construction sites, and these
200/// assertions restate the property on the enums themselves so a violation
201/// names this file.
202const _: () = assert!(!core::mem::needs_drop::<Op<'static>>());
203const _: () = assert!(!core::mem::needs_drop::<BindingOp<'static>>());
204const _: () = assert!(!core::mem::needs_drop::<Region<'static>>());
205
206/// Node footprints are pinned per op type (64-bit only: the figures are
207/// pointer-dependent and the wasm32-wasip2 lane is 32-bit, the same guard
208/// rationale as `crates/vize_relief/src/relief/elements.rs:31-36`). The
209/// enums stay two words because every payload is boxed. The figures
210/// include the 16-byte [`crate::expr::ExprRef`] per expression position
211/// (P2-5b replaced the zero-sized `ExprSlot`, moving every
212/// expression-carrying payload's footprint - each is pinned in its own
213/// file).
214#[cfg(target_pointer_width = "64")]
215const _: () = {
216    assert!(core::mem::size_of::<Op<'_>>() == 16);
217    assert!(core::mem::size_of::<BindingOp<'_>>() == 16);
218    assert!(core::mem::size_of::<Region<'_>>() == 24);
219};