Skip to main content

caixa_theme/
style.rs

1//! The small semantic-style enum every caixa tool agrees on.
2
3use serde::{Deserialize, Serialize};
4
5#[derive(
6    Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, gen_platform::IsVariant,
7)]
8pub enum Semantic {
9    /// Language keywords — `defcaixa`, `defteia`, `let`, `lambda`, etc.
10    Keyword,
11    /// Non-keyword symbols — identifiers, function names, variant names.
12    Symbol,
13    /// `:keyword-positioned` atoms.
14    KeywordArg,
15    /// `"string literals"`.
16    String,
17    /// `42`, `3.14`.
18    Number,
19    /// `#t`, `#f`, `nil`.
20    Literal,
21    /// `; comments`.
22    Comment,
23    /// Primary accent — useful for highlights, carets, focused tokens.
24    Accent,
25    /// Dim text — metadata, line numbers, help text.
26    Muted,
27
28    // Diagnostic severities.
29    Error,
30    Warning,
31    Info,
32    Hint,
33
34    // Diff decorations — used by formatter preview and lint output.
35    Added,
36    Removed,
37    Unchanged,
38}
39
40impl Semantic {
41    /// Every variant of [`Semantic`] in declaration order.
42    ///
43    /// The single canonical arm-list every substrate consumer that has
44    /// to walk the closed 15-arm semantic-style partition (the two
45    /// theme overlays' exhaustive-match resolver functions
46    /// `blackmatter_dark_color` / `blackmatter_light_color` in
47    /// [`crate::blackmatter`], the future LSP-side per-Semantic
48    /// `SemanticTokenType` dispatch at
49    /// `caixa-lsp/src/main.rs`, a future
50    /// `feira lint --list-styles` operator-facing enumeration verb)
51    /// reads for. Peer of the sibling closed-set fieldless typed
52    /// enums' `ALL` slices already carried by
53    /// [`caixa_core::CaixaKind`] /
54    /// [`caixa_core::supervisor::RestartStrategy`] /
55    /// [`caixa_core::supervisor::RestartPolicy`] /
56    /// [`caixa_core::aplicacao::PlacementStrategy`] /
57    /// [`caixa_core::upgrade::UpgradeInstruction`] /
58    /// `caixa_lint::diagnostic::Severity` /
59    /// `caixa_lint::diagnostic::FixSafety` /
60    /// `caixa_arch::InvariantKind` / `caixa_arch::ArchVerdict` /
61    /// `caixa_provedor::FerriteRuntime` closed-set typed-enum
62    /// discriminator axes.
63    pub const ALL: &'static [Self] = &[
64        Self::Keyword,
65        Self::Symbol,
66        Self::KeywordArg,
67        Self::String,
68        Self::Number,
69        Self::Literal,
70        Self::Comment,
71        Self::Accent,
72        Self::Muted,
73        Self::Error,
74        Self::Warning,
75        Self::Info,
76        Self::Hint,
77        Self::Added,
78        Self::Removed,
79        Self::Unchanged,
80    ];
81
82    /// Canonical kebab-case discriminator scalar for this variant — the
83    /// single substrate-primitive `&'static str` projection every
84    /// downstream consumer of the closed 16-arm [`Semantic`] partition
85    /// (a future LSP-side per-Semantic `SemanticTokenType` name-mapping
86    /// dispatch at `caixa-lsp/src/main.rs`, a future
87    /// `feira lint --list-styles` operator-facing enumeration verb, a
88    /// future `caixa.nvim` per-Semantic highlight-group name resolver
89    /// that reaches for a stable kebab identifier per arm, a future
90    /// `blackmatter-shell` per-arm classname the terminal emitter
91    /// composes into a `data-semantic="<kebab>"` attribute) reaches
92    /// through. Kebab-case matches the peer
93    /// [`gen_platform::IsVariant`]-derived kebab discriminant convention
94    /// the sibling closed-set typed enums
95    /// ([`caixa_core::CaixaKind::as_str`],
96    /// [`caixa_core::supervisor::RestartStrategy::as_str`],
97    /// [`caixa_core::supervisor::RestartPolicy::as_str`],
98    /// [`caixa_core::aplicacao::PlacementStrategy::as_str`],
99    /// [`caixa_core::upgrade::UpgradeInstruction::as_str`],
100    /// `caixa_lint::diagnostic::Severity::as_str`,
101    /// `caixa_lint::diagnostic::FixSafety::as_str`,
102    /// `caixa_arch::InvariantKind::as_str`,
103    /// `caixa_arch::ArchVerdict::as_str`,
104    /// `caixa_provedor::FerriteRuntime::variant_slug`) already emit
105    /// on their canonical `&'static str` projection axis.
106    ///
107    /// The 16 arms return the kebab-case forms of their `PascalCase`
108    /// variant names (`Keyword` → `"keyword"`, `KeywordArg` →
109    /// `"keyword-arg"`, `Unchanged` → `"unchanged"`, etc.), matching
110    /// the peer closed-set typed-enum canonical byte-string conventions.
111    ///
112    /// Peer of the [`std::fmt::Display`] and [`AsRef<str>`] impls on
113    /// this enum, which both route through this accessor so
114    /// `format!("{s}")`, `s.as_str()`, and
115    /// `<Semantic as AsRef<str>>::as_ref(&s)` resolve to the same
116    /// per-arm byte-string.
117    #[must_use]
118    pub const fn as_str(self) -> &'static str {
119        match self {
120            Self::Keyword => "keyword",
121            Self::Symbol => "symbol",
122            Self::KeywordArg => "keyword-arg",
123            Self::String => "string",
124            Self::Number => "number",
125            Self::Literal => "literal",
126            Self::Comment => "comment",
127            Self::Accent => "accent",
128            Self::Muted => "muted",
129            Self::Error => "error",
130            Self::Warning => "warning",
131            Self::Info => "info",
132            Self::Hint => "hint",
133            Self::Added => "added",
134            Self::Removed => "removed",
135            Self::Unchanged => "unchanged",
136        }
137    }
138
139    /// Reverse projection on the [`Semantic`] closed 16-arm enum's
140    /// canonical kebab-tag axis — parses a `"keyword"` / `"symbol"` /
141    /// `"keyword-arg"` / `"string"` / `"number"` / `"literal"` /
142    /// `"comment"` / `"accent"` / `"muted"` / `"error"` / `"warning"` /
143    /// `"info"` / `"hint"` / `"added"` / `"removed"` / `"unchanged"` wire
144    /// byte-string back to the typed enum, or returns `None` when `s`
145    /// lies outside the 16-arm accept-set [`Self::as_str`] emits. The
146    /// single `&str → Self` projection every future re-entry point on
147    /// the caixa-theme semantic-style axis dispatches through (a future
148    /// `feira lint --list-styles` operator-facing enumeration verb
149    /// hydrating a per-arm row from a stored kebab identifier back to
150    /// the typed enum before rendering, a future `caixa-lsp`-side
151    /// per-`SemanticTokenType` re-parse binding a prior
152    /// [`Self::as_str`] output back to the typed enum for
153    /// per-Semantic-highlight dispatch, a future `caixa.nvim` per-
154    /// Semantic highlight-group resolver re-loading a stored kebab
155    /// identifier back to the typed enum, a future `blackmatter-shell`
156    /// per-arm classname reverse-lookup binding a
157    /// `data-semantic="<kebab>"` DOM attribute back to the typed enum
158    /// for per-arm style dispatch, a `tracing::field::Value::Str`-arm
159    /// structured-log re-loader binding a prior emission's
160    /// [`Self::as_str`] output back to the typed enum for cross-run
161    /// per-Semantic-paint-histogram diff) would have had to re-inline a
162    /// 16-arm `match s` cascade that expressed no compile-time link back
163    /// to the substrate primitive.
164    ///
165    /// Same closed-set-reverse-projection discipline the sibling
166    /// [`caixa_core::CaixaKind::from_wire`] (2aa6d23),
167    /// [`caixa_core::CaixaDialeto::from_wire`] (d0e65ea),
168    /// [`caixa_core::supervisor::RestartStrategy::from_wire`] (4eec29c),
169    /// [`caixa_core::supervisor::RestartPolicy::from_wire`] (dd32ccf),
170    /// [`caixa_core::aplicacao::PlacementStrategy::from_wire`] (18c7342),
171    /// [`caixa_core::dep::DepList::from_wire`] (45ee563),
172    /// [`caixa_core::render::PathShapeViolation::from_wire`] (aebd9c6),
173    /// `caixa_arch::invariants::InvariantKind::from_wire` (b9e4e61),
174    /// `caixa_arch::report::ArchVerdict::from_wire` (6afe564),
175    /// `caixa_lint::diagnostic::Severity::from_wire` (5afff0e), and
176    /// `caixa_lint::diagnostic::FixSafety::from_wire` (bd505a1) typed
177    /// enums carry on the peer wire-side `str → Self` axes — extends
178    /// the substrate-wide `(as_str, from_wire)` round-trip family onto
179    /// the caixa-theme closed-set fieldless typed-enum axis (the first
180    /// closed-set fieldless typed enum on `caixa-theme` to converge on
181    /// the reverse-projection discipline), matching the same two-way
182    /// `str ↔ Self` round-trip every sibling closed-set enum already
183    /// carries. Method-named `from_wire` (not `from_str`) to match the
184    /// peer shapes verbatim and side-step a
185    /// `clippy::should_implement_trait` lint that a plain `from_str`
186    /// name would otherwise trigger without paired
187    /// [`std::str::FromStr`] impl scaffolding this axis does not carry
188    /// today. Returns `Option<Self>` (rather than `Result<Self, _>`) to
189    /// match the peer shapes: the caller picks the diagnostic form
190    /// appropriate for its use site (a `feira lint --list-styles` CLI
191    /// arg-parse renders its own per-verb error message; a future
192    /// admission-webhook rejection body wraps the `None` outcome with
193    /// the accepted-set enumeration `Semantic::ALL.iter().map(…)` for
194    /// operator diagnostics).
195    ///
196    /// Pinned load-bearing at the substrate-primitive level by
197    /// [`tests::semantic_from_wire_accepts_every_as_str_output`]
198    /// (round-trip witness against the peer [`Self::as_str`] axis) and
199    /// [`tests::semantic_from_wire_rejects_unknown_byte_strings`]
200    /// (rejection witness against silent accept-set widening).
201    #[must_use]
202    pub fn from_wire(s: &str) -> Option<Self> {
203        match s {
204            "keyword" => Some(Self::Keyword),
205            "symbol" => Some(Self::Symbol),
206            "keyword-arg" => Some(Self::KeywordArg),
207            "string" => Some(Self::String),
208            "number" => Some(Self::Number),
209            "literal" => Some(Self::Literal),
210            "comment" => Some(Self::Comment),
211            "accent" => Some(Self::Accent),
212            "muted" => Some(Self::Muted),
213            "error" => Some(Self::Error),
214            "warning" => Some(Self::Warning),
215            "info" => Some(Self::Info),
216            "hint" => Some(Self::Hint),
217            "added" => Some(Self::Added),
218            "removed" => Some(Self::Removed),
219            "unchanged" => Some(Self::Unchanged),
220            _ => None,
221        }
222    }
223}
224
225/// [`std::fmt::Display`] routed through [`Semantic::as_str`], so the
226/// pretty-printed byte-string every consumer that formats the semantic
227/// style as user-facing text lands on (a future `feira lint --list-styles`
228/// operator-facing enumeration verb's per-arm line, a future
229/// `caixa-lsp` diagnostic-source line naming the offending semantic,
230/// a future `caixa.nvim` per-Semantic highlight-group name emitter,
231/// a `tracing::field::display(&sem)` structured-log recorder on the
232/// paint-side emit path) reaches for the same lifted kebab-case
233/// per-arm byte-string [`Semantic::as_str`] returns.
234///
235/// Peer of the sibling [`std::fmt::Display`] impls on the closed-set
236/// typed enums the substrate carries — [`caixa_core::CaixaKind`],
237/// [`caixa_core::supervisor::RestartStrategy`],
238/// [`caixa_core::supervisor::RestartPolicy`],
239/// [`caixa_core::aplicacao::PlacementStrategy`],
240/// [`caixa_core::upgrade::UpgradeInstruction`],
241/// `caixa_lint::diagnostic::Severity`,
242/// `caixa_lint::diagnostic::FixSafety`,
243/// `caixa_arch::InvariantKind`, `caixa_arch::ArchVerdict`,
244/// `caixa_provedor::FerriteRuntime` — extended to the second-to-last
245/// un-lifted `caixa-theme` closed-set fieldless typed enum on the
246/// substrate-wide `(as_str, AsRef<str>, Display)` canonical-projection
247/// triple ratchet the prior lifts converged onto.
248impl std::fmt::Display for Semantic {
249    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
250        f.write_str(self.as_str())
251    }
252}
253
254/// Substrate-canonical [`AsRef<str>`] projection on the caixa-theme
255/// [`Semantic`] closed-set fieldless typed enum — routes through the
256/// same [`Semantic::as_str`] `pub const fn` scalar accessor the paired
257/// [`std::fmt::Display`] impl already reaches for.
258///
259/// Peer of the sibling [`AsRef<str>`] impls the substrate carries on
260/// [`caixa_core::CaixaKind`], [`caixa_core::CaixaVersion`],
261/// [`caixa_core::CaixaDialeto`], [`caixa_core::dep::DepList`],
262/// [`caixa_core::supervisor::RestartStrategy`],
263/// [`caixa_core::supervisor::RestartPolicy`],
264/// [`caixa_core::aplicacao::PlacementStrategy`],
265/// [`caixa_core::aplicacao::RateLimitUnit`],
266/// `caixa_lint::diagnostic::Severity`,
267/// `caixa_arch::InvariantKind`, `caixa_arch::ArchVerdict`,
268/// `caixa_provedor::FerriteRuntime` — extends the substrate-wide
269/// `(as_str, AsRef<str>, Display)` canonical-projection triple onto
270/// the caixa-theme closed-set typed-enum axis, so a future consumer
271/// bound through the trait-idiomatic `.as_ref()` (a
272/// `HashMap::get::<str>(sem.as_ref())` per-Semantic style-lookup, a
273/// future `caixa-lsp` `SemanticTokenType::new(sem.as_ref())`
274/// registration site, any `impl AsRef<str>`-bound generic function)
275/// reaches the same kebab byte-string [`Semantic::as_str`] returns
276/// rather than an open-coded `.as_str()` projection at every
277/// wire-up.
278impl AsRef<str> for Semantic {
279    fn as_ref(&self) -> &str {
280        self.as_str()
281    }
282}
283
284#[cfg(test)]
285mod tests {
286    use super::*;
287
288    #[test]
289    fn semantic_all_enumerates_every_variant_in_declaration_order() {
290        // Fail-before-pass-after pin on the [`Semantic::ALL`] slice:
291        // the slice must list every one of the 15 variants in
292        // declaration order (Keyword → Symbol → KeywordArg → String →
293        // Number → Literal → Comment → Accent → Muted → Error →
294        // Warning → Info → Hint → Added → Removed → Unchanged). Peer
295        // of the sibling ALL slices on the closed-set typed-enum
296        // discriminator axes ([`caixa_core::CaixaKind::ALL`],
297        // [`caixa_core::supervisor::RestartStrategy::ALL`],
298        // [`caixa_core::supervisor::RestartPolicy::ALL`],
299        // [`caixa_core::aplicacao::PlacementStrategy::ALL`],
300        // [`caixa_core::upgrade::UpgradeInstruction::ALL`]). A future
301        // arm addition (a `Namespace` tier between `Symbol` and
302        // `KeywordArg` for the M4 tatara-lisp module system's
303        // qualified-name semantic-token dispatch, a `Deleted` tier
304        // for a hard-delete-mark distinct from `Removed` the future
305        // 3-way diff surface grows) that lands the arm on the enum
306        // but forgets to extend `ALL` must trip this pin rather than
307        // surface as a downstream consumer's silently-partial
308        // iteration.
309        assert_eq!(
310            Semantic::ALL,
311            &[
312                Semantic::Keyword,
313                Semantic::Symbol,
314                Semantic::KeywordArg,
315                Semantic::String,
316                Semantic::Number,
317                Semantic::Literal,
318                Semantic::Comment,
319                Semantic::Accent,
320                Semantic::Muted,
321                Semantic::Error,
322                Semantic::Warning,
323                Semantic::Info,
324                Semantic::Hint,
325                Semantic::Added,
326                Semantic::Removed,
327                Semantic::Unchanged,
328            ],
329        );
330        // Also pin the per-arm `IsVariant`-derived partition: every
331        // arm in `ALL` must satisfy exactly one of the 15 generated
332        // arm-discriminator predicates.
333        for variant in Semantic::ALL {
334            let row = [
335                variant.is_keyword(),
336                variant.is_symbol(),
337                variant.is_keyword_arg(),
338                variant.is_string(),
339                variant.is_number(),
340                variant.is_literal(),
341                variant.is_comment(),
342                variant.is_accent(),
343                variant.is_muted(),
344                variant.is_error(),
345                variant.is_warning(),
346                variant.is_info(),
347                variant.is_hint(),
348                variant.is_added(),
349                variant.is_removed(),
350                variant.is_unchanged(),
351            ];
352            let hits = row.iter().filter(|b| **b).count();
353            assert_eq!(
354                hits, 1,
355                "Semantic::{variant:?} must satisfy exactly one of the \
356                 15 is_* arm-discriminator predicates; got {row:?}",
357            );
358        }
359    }
360
361    #[test]
362    fn semantic_is_variant_predicates_partition_the_arm_set() {
363        // Fail-before-pass-after pin on the [`gen_platform::IsVariant`]
364        // derive: for each of the 15 variants, exactly one of the
365        // generated is_* predicates returns `true` and the other 14
366        // return `false`. Pre-derive the closed 15-arm partition
367        // lived only inside the two theme overlays' 15-arm match
368        // resolvers; a future rebrand (a `#[is_variant(name = "…")]`
369        // drift, a manual hand-rolled `impl` that shadows the
370        // derive-generated method, an arm rename) trips this pin at
371        // caixa-theme build time rather than surfacing far from the
372        // derive declaration. Peer of the sibling
373        // [`caixa_core::CaixaKind`] `IsVariant` partition pin.
374        // A copy-paste flip that reroutes one arm through the wrong
375        // predicate lane trips at the identity-diagonal assertion,
376        // since each variant's row is generated live from `ALL`'s
377        // declaration order rather than transcribed by hand.
378        for (idx, variant) in Semantic::ALL.iter().enumerate() {
379            let observed: [bool; 16] = [
380                variant.is_keyword(),
381                variant.is_symbol(),
382                variant.is_keyword_arg(),
383                variant.is_string(),
384                variant.is_number(),
385                variant.is_literal(),
386                variant.is_comment(),
387                variant.is_accent(),
388                variant.is_muted(),
389                variant.is_error(),
390                variant.is_warning(),
391                variant.is_info(),
392                variant.is_hint(),
393                variant.is_added(),
394                variant.is_removed(),
395                variant.is_unchanged(),
396            ];
397            let mut expected = [false; 16];
398            expected[idx] = true;
399            assert_eq!(
400                observed, expected,
401                "Semantic::{variant:?} at ALL[{idx}] is_* predicates \
402                 must fire only on their own arm lane (identity \
403                 diagonal); got {observed:?}",
404            );
405        }
406    }
407
408    #[test]
409    fn semantic_as_str_returns_canonical_kebab_case_per_arm() {
410        // Fail-before-pass-after per-arm byte-string pin on
411        // [`Semantic::as_str`] — the substrate-canonical `&'static str`
412        // projection every downstream consumer of the closed 16-arm
413        // partition reaches through. A future arm rename (a `Symbol` →
414        // `Identifier` rebrand tracking a hypothetical LSP-side
415        // `SemanticTokenType` reshuffle, an `Accent` → `Highlight`
416        // rebrand tracking a `blackmatter-shell` classname rework) that
417        // touches the enum arm but forgets to update the paired
418        // `as_str` arm — or vice versa — trips this pin at caixa-theme
419        // build time rather than surfacing as a downstream
420        // `feira lint --list-styles` operator-facing enumeration verb's
421        // silently-renamed row far from the two-declaration site.
422        //
423        // Kebab-case matches the peer [`gen_platform::IsVariant`]-
424        // derived kebab discriminant convention the sibling closed-set
425        // typed enums already emit on their canonical byte-string
426        // projection axis.
427        assert_eq!(Semantic::Keyword.as_str(), "keyword");
428        assert_eq!(Semantic::Symbol.as_str(), "symbol");
429        assert_eq!(Semantic::KeywordArg.as_str(), "keyword-arg");
430        assert_eq!(Semantic::String.as_str(), "string");
431        assert_eq!(Semantic::Number.as_str(), "number");
432        assert_eq!(Semantic::Literal.as_str(), "literal");
433        assert_eq!(Semantic::Comment.as_str(), "comment");
434        assert_eq!(Semantic::Accent.as_str(), "accent");
435        assert_eq!(Semantic::Muted.as_str(), "muted");
436        assert_eq!(Semantic::Error.as_str(), "error");
437        assert_eq!(Semantic::Warning.as_str(), "warning");
438        assert_eq!(Semantic::Info.as_str(), "info");
439        assert_eq!(Semantic::Hint.as_str(), "hint");
440        assert_eq!(Semantic::Added.as_str(), "added");
441        assert_eq!(Semantic::Removed.as_str(), "removed");
442        assert_eq!(Semantic::Unchanged.as_str(), "unchanged");
443    }
444
445    #[test]
446    fn semantic_as_str_projections_are_all_distinct_across_arms() {
447        // Fail-before-pass-after pin on the injectivity of
448        // [`Semantic::as_str`]'s projection — no two arms may share
449        // their canonical kebab byte-string, since a future consumer
450        // that keys a per-arm dispatch table off the projection (a
451        // `HashMap::<&str, _>::from_iter(Semantic::ALL.iter().map(|s|
452        // (s.as_str(), …)))` style-lookup, a future `caixa-lsp`
453        // `SemanticTokenType::new(sem.as_ref())` registration table
454        // keyed by kebab identifier, a future `feira lint --list-styles`
455        // one-row-per-arm enumeration table) would silently collapse
456        // the two colliding arms onto one entry, dropping the second
457        // insertion. A future arm addition (a `Namespace` tier between
458        // `Symbol` and `KeywordArg` for the M4 tatara-lisp module
459        // system's qualified-name semantic-token dispatch, a `Deleted`
460        // tier for a hard-delete-mark distinct from `Removed` a future
461        // 3-way diff surface grows) that lands the arm on the enum and
462        // reuses a peer arm's kebab identifier (a copy-paste-derived
463        // `"removed"` on the new `Deleted` arm) trips this pin rather
464        // than surfacing far from the arm addition site.
465        let mut projections: Vec<&'static str> = Semantic::ALL.iter().map(|s| s.as_str()).collect();
466        let before = projections.len();
467        projections.sort_unstable();
468        projections.dedup();
469        assert_eq!(
470            projections.len(),
471            before,
472            "Semantic::as_str must be injective across ALL — collisions: \
473             {projections:?}",
474        );
475    }
476
477    #[test]
478    fn semantic_display_and_as_ref_str_route_through_as_str_accessor() {
479        // Fail-before-pass-after three-path convergence pin on the
480        // substrate-wide `(as_str, AsRef<str>, Display)` canonical-
481        // projection triple for the caixa-theme [`Semantic`] closed-set
482        // fieldless typed enum. For every arm in [`Semantic::ALL`], the
483        // paired [`std::fmt::Display`] impl + [`AsRef<str>`] impl + the
484        // substrate-canonical [`Semantic::as_str`] `pub const fn`
485        // scalar accessor must resolve to the same `&'static str`
486        // per arm. Peer of the sibling three-path-convergence pins the
487        // substrate carries on the closed-set typed enums the prior
488        // lifts converged onto
489        // (`restart_strategy_display_routes_through_as_str_helper` /
490        // `restart_strategy_as_ref_str_routes_through_as_str_accessor`
491        // on [`caixa_core::supervisor::RestartStrategy`],
492        // `ferrite_runtime_display_and_as_ref_str_route_through_variant_slug_accessor`
493        // on `caixa_provedor::FerriteRuntime`, and the analogous pins
494        // on `caixa_lint::Severity` / `caixa_lint::FixSafety` /
495        // `caixa_arch::InvariantKind` / `caixa_arch::ArchVerdict`).
496        //
497        // A future accidental split (a hand-rolled `impl fmt::Display`
498        // that shadows this route through a divergent per-arm match, an
499        // `impl AsRef<str>` that returns the compiler-derived `Debug`
500        // string via `format!("{:?}", self)` — allocating and diverging
501        // on every arm — or a `#[serde(rename_all = "…")]` attribute
502        // drift that quietly forks the projection) trips this pin at
503        // caixa-theme build time rather than surfacing as a downstream
504        // consumer's silently-forked per-Semantic dispatch far from the
505        // trait-impl declaration site.
506        for &sem in Semantic::ALL {
507            let via_as_str: &str = sem.as_str();
508            let via_display: String = format!("{sem}");
509            let via_as_ref: &str = <Semantic as AsRef<str>>::as_ref(&sem);
510            assert_eq!(
511                via_display, via_as_str,
512                "Semantic::{sem:?} — Display routes off `as_str`; got \
513                 Display={via_display:?} vs as_str={via_as_str:?}",
514            );
515            assert_eq!(
516                via_as_ref, via_as_str,
517                "Semantic::{sem:?} — AsRef<str> routes off `as_str`; got \
518                 AsRef={via_as_ref:?} vs as_str={via_as_str:?}",
519            );
520        }
521    }
522
523    #[test]
524    fn semantic_as_str_is_usable_in_const_context() {
525        // The [`Semantic::as_str`] accessor is declared `pub const fn`,
526        // matching the peer closed-set typed enums' canonical
527        // `&'static str` projection accessors
528        // ([`caixa_core::CaixaKind::as_str`],
529        // [`caixa_core::supervisor::RestartStrategy::as_str`],
530        // [`caixa_core::aplicacao::PlacementStrategy::as_str`],
531        // `caixa_provedor::FerriteRuntime::variant_slug`). Pin the
532        // same posture with a `const {}` assertion block so a future
533        // accidental downgrade to non-`const` (an added runtime helper
534        // reachable only from a non-`const` context) trips at
535        // caixa-theme build time rather than surfacing as a downstream
536        // `const`-context regression far from the accessor
537        // declaration.
538        const KEYWORD: &str = Semantic::Keyword.as_str();
539        const ERROR: &str = Semantic::Error.as_str();
540        const UNCHANGED: &str = Semantic::Unchanged.as_str();
541        const { assert!(KEYWORD.as_bytes()[0] == b'k') };
542        const { assert!(ERROR.as_bytes()[0] == b'e') };
543        const { assert!(UNCHANGED.as_bytes()[0] == b'u') };
544    }
545
546    #[test]
547    fn semantic_from_wire_accepts_every_as_str_output() {
548        // Fail-before-pass-after per-arm accept pin on the newly lifted
549        // [`Semantic::from_wire`] reverse projection: every arm in
550        // [`Semantic::ALL`] must parse back through `from_wire` when fed
551        // its own [`Semantic::as_str`] output, landing on
552        // `Some(same_variant)`. A regression that hand-rolled either
553        // side's per-arm match without threading through the shared
554        // 16-string closed set would silently disagree on any future
555        // arm rename (a `Symbol` → `Identifier` rebrand tracking a
556        // hypothetical LSP-side `SemanticTokenType` reshuffle, an
557        // `Accent` → `Highlight` rebrand tracking a `blackmatter-shell`
558        // classname rework) or new arm the theme grows (a `Namespace`
559        // tier between `Symbol` and `KeywordArg` for the M4 tatara-lisp
560        // module system's qualified-name semantic-token dispatch, a
561        // `Deleted` tier for a hard-delete-mark distinct from `Removed`
562        // the future 3-way diff surface grows) and this pin flags it at
563        // caixa-theme build time rather than at a downstream
564        // `feira lint --list-styles` operator-facing enumeration verb's
565        // silent tag misclassification.
566        //
567        // Peer of the sibling
568        // `caixa_lint::diagnostic::tests::severity_from_wire_accepts_every_as_str_output`
569        // (5afff0e) /
570        // `caixa_lint::diagnostic::tests::fix_safety_from_wire_accepts_every_as_str_output`
571        // (bd505a1) /
572        // `caixa_arch::report::tests::arch_verdict_from_wire_accepts_every_as_str_output`
573        // (6afe564) /
574        // `caixa_arch::invariants::tests::invariant_kind_from_wire_accepts_every_as_str_output`
575        // (b9e4e61) round-trip pins on the peer caixa-lint / caixa-arch
576        // closed-set-enum reverse-projection axes, and of the sibling
577        // `caixa_core::kind::tests::caixa_kind_wire_round_trips_through_from_wire`
578        // (2aa6d23) /
579        // `caixa_core::dialeto::tests::caixa_dialeto_from_wire_accepts_every_as_str_output`
580        // (d0e65ea) /
581        // `caixa_core::aplicacao::tests::placement_strategy_from_wire_accepts_every_lifted_constant`
582        // (18c7342) /
583        // `caixa_core::dep::tests::dep_list_round_trips_through_as_str_and_from_wire`
584        // (45ee563) /
585        // `caixa_core::render::tests::path_shape_violation_from_wire_accepts_every_as_str_output`
586        // (aebd9c6) round-trip pins on the sibling caixa-core closed-
587        // set typed-enum reverse-projection axes.
588        for &variant in Semantic::ALL {
589            let wire = variant.as_str();
590            let parsed = Semantic::from_wire(wire).unwrap_or_else(|| {
591                panic!(
592                    "Semantic::from_wire({wire:?}) must accept every \
593                     Semantic::as_str output — got None for the wire \
594                     byte-string of {variant:?}"
595                )
596            });
597            assert_eq!(
598                parsed, variant,
599                "Semantic::from_wire(Semantic::{variant:?}.as_str()) \
600                 must return Semantic::{variant:?} — the (as_str, \
601                 from_wire) pair must form a total round-trip on the \
602                 closed 16-arm Semantic arm-set",
603            );
604        }
605    }
606
607    #[test]
608    fn semantic_from_wire_rejects_unknown_byte_strings() {
609        // Rejection pin on the [`Semantic::from_wire`] parser's accept-
610        // set: any string outside the 16-arm [`Semantic::as_str`] output
611        // set must return `None`. A future accidental widening of the
612        // accept-set (a case-insensitive match that accepts `"KEYWORD"`
613        // / `"Keyword"`, a silent acceptance of the pre-lift PascalCase
614        // Debug-derived shapes `"Keyword"` / `"KeywordArg"` /
615        // `"Unchanged"` on the wire axis, a snake_case drift accepting
616        // `"keyword_arg"` beside the canonical kebab-case
617        // `"keyword-arg"`, a Levenshtein-forgiving arm-lookup that
618        // admits `"kewyord"` typos, a silent absorption of a hypothetical
619        // future `Namespace` / `Deleted` arm before it lands on the enum
620        // and its paired [`Semantic::as_str`] emitter arm) would
621        // silently drift the parser's accept-set from the emitter's — a
622        // downstream style-report re-loader that bound a prior report's
623        // [`Self::as_str`] output back to the typed enum through this
624        // parser would then bind a malformed byte-string to a plausibly-
625        // wrong typed arm the caller does not route through any
626        // fallback, silently misclassifying the reloaded row.
627        //
628        // Peer of the sibling
629        // `caixa_lint::diagnostic::tests::severity_from_wire_rejects_unknown_byte_strings`
630        // (5afff0e) /
631        // `caixa_lint::diagnostic::tests::fix_safety_from_wire_rejects_unknown_byte_strings`
632        // (bd505a1) /
633        // `caixa_arch::report::tests::arch_verdict_from_wire_rejects_unknown_byte_strings`
634        // (6afe564) /
635        // `caixa_arch::invariants::tests::invariant_kind_from_wire_rejects_unknown_byte_strings`
636        // (b9e4e61) rejection pins on the peer caixa-lint / caixa-arch
637        // axes, and of the sibling
638        // `caixa_kind_from_wire_rejects_unknown_byte_strings` (2aa6d23),
639        // `caixa_dialeto_from_wire_rejects_unknown_byte_strings`
640        // (d0e65ea),
641        // `placement_strategy_from_wire_rejects_unknown_byte_strings`
642        // (18c7342),
643        // `dep_list_from_wire_returns_none_on_unknown_wire_scalar`
644        // (45ee563), and
645        // `path_shape_violation_from_wire_rejects_unknown_byte_strings`
646        // (aebd9c6) rejection pins on the sibling caixa-core axes.
647        //
648        // The rejection set also covers overlapping-byte-string tags
649        // from peer axes: caixa-lint `Severity::as_str` outputs
650        // `"error"`/`"warning"`/`"info"`/`"hint"` and caixa-arch
651        // `InvariantKind::as_str` outputs `"safety"`/`"compliance"`
652        // share zero canonical byte-strings with the widened 16-arm
653        // Semantic set here — a widened parser that admitted the peer's
654        // arm on the sibling axis would still not admit an arm foreign
655        // to the caixa-theme semantic-style discriminator's own accept-
656        // set. Yet four peer-axis strings DO overlap with the
657        // caixa-theme set here (`Severity`'s
658        // `"error"`/`"warning"`/`"info"`/`"hint"` map identically onto
659        // the caixa-theme diagnostic-severity sub-region
660        // `Semantic::Error`/`Warning`/`Info`/`Hint`) — a widened parser
661        // that admitted them under a different arm would collapse the
662        // two axes and silently mislabel; the pin excludes those four
663        // from the rejection set precisely because they must accept.
664        for bad in [
665            "",
666            " ",
667            "Keyword",
668            "KEYWORD",
669            "Symbol",
670            "SYMBOL",
671            "KeywordArg",
672            "keyword_arg",
673            "keywordarg",
674            "String",
675            "STRING",
676            "Number",
677            "Literal",
678            "Comment",
679            "Accent",
680            "Muted",
681            "Error",
682            "ERROR",
683            "Warning",
684            "WARNING",
685            "Info",
686            "INFO",
687            "Hint",
688            "HINT",
689            "Added",
690            "ADDED",
691            "Removed",
692            "REMOVED",
693            "Unchanged",
694            "UNCHANGED",
695            "kewyord",
696            "sym",
697            "kw",
698            "str",
699            "num",
700            "lit",
701            "cmt",
702            "safe",
703            "unsafe",
704            "safety",
705            "compliance",
706            "proven",
707            "rejected",
708            "namespace",
709            "deleted",
710            "highlight",
711            "identifier",
712            "keyword ",
713            " keyword",
714            "keyword\n",
715            "keyword\t",
716            "keyword-arg ",
717            " keyword-arg",
718            "added ",
719            " added",
720            "unchanged ",
721            " unchanged",
722        ] {
723            assert!(
724                Semantic::from_wire(bad).is_none(),
725                "Semantic::from_wire({bad:?}) must return None — the \
726                 parser's accept-set is exactly the 16 Semantic::as_str \
727                 outputs; a widening would silently split the parser's \
728                 accept-set from the emitter's arm-set",
729            );
730        }
731    }
732
733    #[test]
734    fn semantic_is_variant_predicates_are_const_fn() {
735        // The [`gen_platform::IsVariant`] derive emits `const fn`
736        // predicates on the peer [`caixa_core::CaixaKind`] /
737        // [`caixa_core::upgrade::UpgradeInstruction`] /
738        // [`caixa_core::supervisor::RestartStrategy`] /
739        // [`caixa_core::supervisor::RestartPolicy`] closed-set typed
740        // enums — pin the same posture on [`Semantic`] so a future
741        // accidental downgrade to non-`const` (an added runtime helper
742        // reachable only from a non-`const` context, a manual hand-
743        // rolled `impl` that shadows the derive-generated method)
744        // trips at caixa-theme build time rather than surfacing as a
745        // downstream `const`-context regression far from the derive
746        // declaration.
747        const { assert!(Semantic::Keyword.is_keyword()) };
748        const { assert!(Semantic::Error.is_error()) };
749        const { assert!(Semantic::Added.is_added()) };
750        const { assert!(Semantic::Unchanged.is_unchanged()) };
751    }
752}