Skip to main content

macroonz_compiler/kind/
types.rs

1//! The kind home's declarations: the open semantic traits a consumer implements, the rosters the compiler owns, the disposition vocabulary, the complete-set witness, and the two stamps that write declarations down.
2//!
3//! Declarations only, with every road that reaches a private field in `type_guard.rs`, this file's own child.
4
5use crate::identity::{GeneratedUnit, Identity, OwnerFact, Profile};
6use core::marker::PhantomData;
7
8#[path = "type_guard.rs"]
9mod guard;
10
11/// What one request produces.
12///
13/// A kind is a marker type in the crate that declares it, and the compiler is generic over it from the first step of the road to the last.
14/// Nothing seals this trait and nothing registers an implementation of it.
15///
16/// # Examples
17///
18/// ```rust
19/// use macroonz_compiler::{Kind, NoQuestions, SoleRole};
20///
21/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
22/// struct GreetImpl;
23///
24/// impl Kind for GreetImpl {
25///     const NAME: &'static str = "greet.impl";
26///     type Content = ();
27///     type Role = SoleRole;
28///     type Question = NoQuestions;
29/// }
30///
31/// assert_eq!(GreetImpl::NAME, "greet.impl");
32/// ```
33pub trait Kind: 'static {
34    /// The name this kind is spelled by wherever a name is written down.
35    ///
36    /// Declared rather than read off the Rust spelling, so renaming the marker renames no identity.
37    const NAME: &'static str;
38
39    /// The facts a request of this kind carries beyond its captured tokens.
40    ///
41    /// Its canonical encoding is the content commitment's material, so changing any fact a renderer may read changes the commitment before a plan exists.
42    type Content: CanonicalContent;
43
44    /// The seats this kind's rendering fills.
45    type Role: Role;
46
47    /// The questions this kind owes beyond the universal ones.
48    type Question: Question;
49}
50
51/// Kind-specific facts with one complete canonical encoding.
52///
53/// The encoding is semantic material rather than a rendering for a person.
54/// A kind owns the implementation for its content, and the compiler frames the complete result before deriving the content commitment.
55pub trait CanonicalContent: Clone + Eq + core::fmt::Debug {
56    /// Append every fact this content carries in its declared order.
57    fn encode_content_into(&self, into: &mut Vec<u8>);
58
59    /// The complete canonical bytes of this content.
60    #[must_use]
61    fn canonical_content_bytes(&self) -> Vec<u8> {
62        let mut bytes = Vec::new();
63        self.encode_content_into(&mut bytes);
64        bytes
65    }
66}
67
68/// One seat a kind's rendering fills.
69///
70/// A rendered unit is matched to a planned one by role, so a rendering that produced the right number of units in the wrong seats is caught by the seat rather than by a count.
71pub trait Role: Copy + Eq + core::fmt::Debug + 'static {
72    /// The complete roster, in the order the kind states it.
73    ///
74    /// Every walk over a rendering quantifies over this, and membership admission refuses a member whose role is absent from it — so a lawful value the roster omits cannot become a planned member a walk would never look at.
75    const ALL: &'static [Self];
76
77    /// This role's declared name.
78    #[must_use]
79    fn name(self) -> &'static str;
80
81    /// Where the unit rendered under this role lands.
82    ///
83    /// A property of the seat, so two plans of one kind cannot disagree about which build compiles their units.
84    #[must_use]
85    fn destination(self) -> Destination;
86
87    /// This role's position in the roster, which a rendered unit's transcript carries.
88    ///
89    /// A role the roster does not carry has no position and reads as the roster's length.
90    #[must_use]
91    fn slot(self) -> u16 {
92        slot_in(Self::ALL, self)
93    }
94}
95
96/// One question a kind owes an answer to, beyond the questions every kind owes.
97pub trait Question: Copy + Eq + core::fmt::Debug + 'static {
98    /// The complete roster, in the order the kind states it.
99    const ALL: &'static [Self];
100
101    /// The typed answer to a question of this roster.
102    type Answer: Answer<Question = Self>;
103
104    /// This question's declared name.
105    #[must_use]
106    fn name(self) -> &'static str;
107
108    /// This question's position in the roster, which an explanation's preimage carries.
109    #[must_use]
110    fn slot(self) -> u16 {
111        slot_in(Self::ALL, self)
112    }
113}
114
115/// One typed answer, and the question it answers.
116pub trait Answer: Clone + Eq + core::fmt::Debug {
117    /// The roster this answer belongs to.
118    type Question: Question;
119
120    /// The question this answer answers.
121    #[must_use]
122    fn question(&self) -> Self::Question;
123
124    /// Append this answer's canonical bytes.
125    fn encode_into(&self, into: &mut Vec<u8>);
126
127    /// This answer rendered for a person.
128    ///
129    /// A projection: no identity, decision, or refusal reads one back.
130    #[must_use]
131    fn human(&self) -> String;
132}
133
134/// The question roster of a kind that owes nothing beyond the universal questions.
135///
136/// Uninhabited, so it is its own answer as well as its own roster: there is no value here to ask about or to answer for.
137#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
138pub enum NoQuestions {}
139
140/// The role roster of a kind that renders exactly one unit, at the declaration site.
141///
142/// Not a placeholder and not an absence: a kind whose rendering is one unit says so with a roster of one, and a kind whose one unit lands elsewhere declares its own.
143#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
144pub enum SoleRole {
145    /// The kind's one rendered unit.
146    Sole,
147}
148
149crate::roster! {
150    /// Where a rendered unit lands.
151    ///
152    /// Four deliveries, and a role names exactly one of them.
153    pub enum Destination {
154        /// The tokens the consumer's normal build compiles where the declaration stands.
155        DeclarationSite = "declaration-site",
156        /// The deferred cargo the consumer's test target invokes; the normal build compiles none of it.
157        TestCarrier = "test-carrier",
158        /// The deferred cargo the consumer's bench target invokes, on the same terms and through the same shell.
159        BenchCarrier = "bench-carrier",
160        /// A standalone artifact a publication step writes to its own address.
161        PublicationArtifact = "publication-artifact",
162    }
163}
164
165/// What happened to one kind that could have been generated.
166///
167/// Silence is not a variant: where a projection is absent, the absence has a name and cites the fact that caused it.
168/// There is no refused answer either, because a request that fails a step of the road is refused whole and produces a diagnostic rather than a set.
169#[must_use = "a disposition is what happened to a kind, and silence is not a variant"]
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
171pub enum Disposition {
172    /// It was generated, and this is the unit it produced.
173    Generated {
174        /// The generated unit's semantic key.
175        unit: Identity<GeneratedUnit>,
176    },
177    /// It does not apply here, and this is the fact that makes it inapplicable.
178    NotApplicable {
179        /// The fact the answer rests on.
180        because: OwnerFact,
181    },
182    /// Nobody asked for it, and this is the fact that says so.
183    NotRequested {
184        /// The fact the answer rests on.
185        because: OwnerFact,
186    },
187    /// The profile the request ran under does not offer it.
188    UnavailableUnderProfile {
189        /// The profile that does not offer it.
190        profile: Profile,
191        /// The fact naming what that profile could not furnish.
192        because: OwnerFact,
193    },
194}
195
196/// A consumer-owned record that can surrender its named dispositions in kind declaration order.
197///
198/// Implementations state rows, not completeness.
199/// [`DispositionSet::complete`] compares every surrendered name and the whole row count with the owning [`KindSet`] before the record can become the witness an account seats.
200pub trait DispositionRecord: Clone + Eq + core::fmt::Debug {
201    /// Surrender every stated kind name and disposition, in the set's declaration order.
202    fn into_dispositions(self) -> impl Iterator<Item = (&'static str, Disposition)>;
203}
204
205/// One declared set of kinds and the record from which its complete disposition witness is built.
206///
207/// The trait remains open, but naming a record here does not certify its completeness.
208/// Only [`DispositionSet::complete`] can turn the record into the private-field witness [`Accounted`](crate::Accounted) accepts.
209pub trait KindSet {
210    /// The consumer-owned disposition record for this set.
211    type Dispositions: DispositionRecord;
212
213    /// Every kind's declared name, in the order the set states them.
214    const NAMES: &'static [&'static str];
215}
216
217/// A disposition for every declared kind of one set, in declaration order.
218///
219/// The rows are private and the only public constructor checks every name and the complete row count against [`KindSet::NAMES`], so an omitted, doubled, foreign, or reordered seat cannot become this value and cannot be seated beside an expansion.
220#[must_use = "a complete disposition set is the witness an accounted expansion requires"]
221#[derive(Debug, Clone, PartialEq, Eq)]
222pub struct DispositionSet<Set: KindSet> {
223    dispositions: Vec<Disposition>,
224    kind_set: PhantomData<fn() -> Set>,
225}
226
227/// How a disposition record refuses to become a complete set witness.
228#[must_use = "a disposition-set refusal names the count or kind-name disagreement"]
229#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
230pub enum DispositionSetError {
231    /// The record surrendered a different number of rows than the kind set declares.
232    CountMismatch {
233        /// How many kind names the set declares.
234        expected: usize,
235        /// How many disposition rows the record surrendered.
236        observed: usize,
237    },
238    /// One surrendered row names a kind other than the kind declared at that position.
239    KindMismatch {
240        /// The kind name the set declares at this position.
241        expected: &'static str,
242        /// The kind name the record surrendered at this position.
243        observed: &'static str,
244    },
245}
246
247/// One row's position in its roster, or the roster's length where the roster does not carry it.
248fn slot_in<T: Copy + Eq>(roster: &[T], row: T) -> u16 {
249    let position = roster
250        .iter()
251        .position(|other| *other == row)
252        .unwrap_or(roster.len());
253    u16::try_from(position).unwrap_or(u16::MAX)
254}
255
256/// Declares one closed vocabulary: the enum, its complete roster, and one declared name per row.
257///
258/// For a list of names and nothing else: a role is written by hand instead, because a role also names a destination and an implementation says that better than a stamp with an extra column.
259///
260/// # Examples
261///
262/// ```rust
263/// macroonz_compiler::roster! {
264///     /// Which direction a codec covers.
265///     pub enum Direction {
266///         /// Typed value to canonical bytes.
267///         Encode = "encode",
268///         /// Canonical bytes to typed value.
269///         Decode = "decode",
270///     }
271/// }
272///
273/// assert_eq!(Direction::ALL, &[Direction::Encode, Direction::Decode]);
274/// assert_eq!(Direction::Decode.name(), "decode");
275/// ```
276#[macro_export]
277macro_rules! roster {
278    (
279        $(#[$note:meta])*
280        $vis:vis enum $name:ident {
281            $( $(#[$row:meta])* $variant:ident = $declared:literal ),+ $(,)?
282        }
283    ) => {
284        $(#[$note])*
285        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
286        $vis enum $name {
287            $( $(#[$row])* $variant, )+
288        }
289
290        impl $name {
291            /// The complete roster, in declaration order.
292            $vis const ALL: &'static [Self] = &[$( Self::$variant ),+];
293
294            /// This row's declared name.
295            #[must_use]
296            $vis const fn name(self) -> &'static str {
297                match self {
298                    $( Self::$variant => $declared, )+
299                }
300            }
301        }
302    };
303}
304
305/// Declares one set of kinds: a marker type and its [`Kind`] implementation per row, the enumerated set, its [`KindSet`] implementation, and its [`DispositionRecord`].
306///
307/// One declaration, so the marker, the set, and the record cannot drift apart.
308/// A kind added to a declaration grows all three together and stops the compiler at every construction of the record until somebody says what happens to it.
309/// The record then becomes a [`DispositionSet`] only after the compiler independently checks every surrendered name and the whole row count against the set's declaration.
310///
311/// The seat is the field name the record carries a row's answer under, declared beside the kind rather than composed from the marker's spelling, for the same reason the declared name beside it is: a field renamed by every refactor of a Rust identifier is a field nobody can rely on.
312///
313/// # Examples
314///
315/// ```rust
316/// pub type Greeting = &'static str;
317///
318/// macroonz_compiler::kinds! {
319///     set = GreetKinds;
320///     dispositions = GreetDispositions;
321///
322///     /// Projects a declaration into the implementation that greets.
323///     GreetImpl = "greet.impl", greet_impl => Greeting, SoleRole, NoQuestions;
324/// }
325///
326/// use macroonz_compiler::{Disposition, DispositionSet, KindSet, NoQuestions, OwnerFact, SoleRole};
327///
328/// assert_eq!(<GreetKinds as KindSet>::NAMES, &["greet.impl"]);
329/// assert_eq!(GreetKinds::GreetImpl.name(), "greet.impl");
330///
331/// let record = GreetDispositions {
332///     greet_impl: Disposition::NotApplicable {
333///         because: OwnerFact { home: "greet", name: "not-applicable" },
334///     },
335/// };
336/// assert!(DispositionSet::<GreetKinds>::complete(record).is_ok());
337/// ```
338#[macro_export]
339macro_rules! kinds {
340    (
341        set = $set:ident;
342        dispositions = $record:ident;
343        $(
344            $(#[$note:meta])*
345            $kind:ident = $declared:literal, $seat:ident => $content:ty, $role:ty, $question:ty
346        );+ $(;)?
347    ) => {
348        $(
349            $(#[$note])*
350            #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
351            pub struct $kind;
352
353            impl $crate::kind::Kind for $kind {
354                const NAME: &'static str = $declared;
355                type Content = $content;
356                type Role = $role;
357                type Question = $question;
358            }
359        )+
360
361        /// The kinds this set names, one row each.
362        #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
363        pub enum $set {
364            $( $(#[$note])* $kind ),+
365        }
366
367        impl $set {
368            /// The complete set, in declaration order.
369            pub const ALL: &'static [Self] = &[$( Self::$kind ),+];
370
371            /// This row's kind's declared name, read off the kind itself.
372            #[must_use]
373            pub const fn name(self) -> &'static str {
374                match self {
375                    $( Self::$kind => <$kind as $crate::kind::Kind>::NAME ),+
376                }
377            }
378        }
379
380        impl $crate::kind::KindSet for $set {
381            type Dispositions = $record;
382
383            const NAMES: &'static [&'static str] =
384                &[$( <$kind as $crate::kind::Kind>::NAME ),+];
385        }
386
387        /// What happened to every kind of the set: one required seat per row.
388        #[must_use = "a disposition record is what happened to every kind of the set"]
389        #[derive(Debug, Clone, PartialEq, Eq)]
390        pub struct $record {
391            $(
392                #[doc = concat!("What happened to the `", $declared, "` kind.")]
393                pub $seat: $crate::kind::Disposition
394            ),+
395        }
396
397        impl $record {
398            /// What happened to one kind of the set.
399            ///
400            /// Total: every row reads to exactly one seat, and a row admitted later stops the compiler here until somebody says which seat carries it.
401            #[must_use]
402            pub const fn under(&self, row: $set) -> &$crate::kind::Disposition {
403                match row {
404                    $( $set::$kind => &self.$seat ),+
405                }
406            }
407        }
408
409        impl $crate::kind::DispositionRecord for $record {
410            fn into_dispositions(
411                self,
412            ) -> impl Iterator<Item = (&'static str, $crate::kind::Disposition)> {
413                [$( (<$kind as $crate::kind::Kind>::NAME, self.$seat) ),+].into_iter()
414            }
415        }
416    };
417}