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}