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, and the complete-set witness.
2//!
3//! Declarations only, with every road that reaches a private field in `type_guard.rs`, this file's own child.
4
5use super::type_contract::slot_in;
6use crate::identity::{GeneratedUnit, Identity, OwnerFact, Profile};
7use core::marker::PhantomData;
8
9#[path = "type_guard.rs"]
10mod guard;
11
12/// What one request produces.
13///
14/// 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.
15/// Nothing seals this trait and nothing registers an implementation of it.
16///
17/// # Examples
18///
19/// ```rust
20/// use macroonz_compiler::{Kind, NoQuestions, SoleRole};
21///
22/// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
23/// struct GreetImpl;
24///
25/// impl Kind for GreetImpl {
26/// const NAME: &'static str = "greet.impl";
27/// type Content = ();
28/// type Role = SoleRole;
29/// type Question = NoQuestions;
30/// }
31///
32/// assert_eq!(GreetImpl::NAME, "greet.impl");
33/// ```
34pub trait Kind: 'static {
35 /// The name this kind is spelled by wherever a name is written down.
36 ///
37 /// Declared rather than read off the Rust spelling, so renaming the marker renames no identity.
38 const NAME: &'static str;
39
40 /// The facts a request of this kind carries beyond its captured tokens.
41 ///
42 /// Its canonical encoding is the content commitment's material, so changing any fact a renderer may read changes the commitment before a plan exists.
43 type Content: CanonicalContent;
44
45 /// The seats this kind's rendering fills.
46 type Role: Role;
47
48 /// The questions this kind owes beyond the universal ones.
49 type Question: Question;
50}
51
52/// Kind-specific facts with one complete canonical encoding.
53///
54/// The encoding is semantic material rather than a rendering for a person.
55/// A kind owns the implementation for its content, and the compiler frames the complete result before deriving the content commitment.
56pub trait CanonicalContent: Clone + Eq + core::fmt::Debug {
57 /// Append every fact this content carries in its declared order.
58 fn encode_content_into(&self, into: &mut Vec<u8>);
59
60 /// The complete canonical bytes of this content.
61 #[must_use]
62 fn canonical_content_bytes(&self) -> Vec<u8> {
63 let mut bytes = Vec::new();
64 self.encode_content_into(&mut bytes);
65 bytes
66 }
67}
68
69/// One seat a kind's rendering fills.
70///
71/// 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.
72pub trait Role: Copy + Eq + core::fmt::Debug + 'static {
73 /// The complete roster, in the order the kind states it.
74 ///
75 /// 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.
76 const ALL: &'static [Self];
77
78 /// This role's declared name.
79 #[must_use]
80 fn name(self) -> &'static str;
81
82 /// Where the unit rendered under this role lands.
83 ///
84 /// A property of the seat, so two plans of one kind cannot disagree about which build compiles their units.
85 #[must_use]
86 fn destination(self) -> Destination;
87
88 /// This role's position in the roster, which a rendered unit's transcript carries.
89 ///
90 /// A role the roster does not carry has no position and reads as the roster's length.
91 #[must_use]
92 fn slot(self) -> u16 {
93 slot_in(Self::ALL, self)
94 }
95}
96
97/// One question a kind owes an answer to, beyond the questions every kind owes.
98pub trait Question: Copy + Eq + core::fmt::Debug + 'static {
99 /// The complete roster, in the order the kind states it.
100 const ALL: &'static [Self];
101
102 /// The typed answer to a question of this roster.
103 type Answer: Answer<Question = Self>;
104
105 /// This question's declared name.
106 #[must_use]
107 fn name(self) -> &'static str;
108
109 /// This question's position in the roster, which an explanation's preimage carries.
110 #[must_use]
111 fn slot(self) -> u16 {
112 slot_in(Self::ALL, self)
113 }
114}
115
116/// One typed answer, and the question it answers.
117pub trait Answer: Clone + Eq + core::fmt::Debug {
118 /// The roster this answer belongs to.
119 type Question: Question;
120
121 /// The question this answer answers.
122 #[must_use]
123 fn question(&self) -> Self::Question;
124
125 /// Append this answer's canonical bytes.
126 fn encode_into(&self, into: &mut Vec<u8>);
127
128 /// This answer rendered for a person.
129 ///
130 /// A projection: no identity, decision, or refusal reads one back.
131 #[must_use]
132 fn human(&self) -> String;
133}
134
135/// The question roster of a kind that owes nothing beyond the universal questions.
136///
137/// 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.
138#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
139pub enum NoQuestions {}
140
141/// The role roster of a kind that renders exactly one unit, at the declaration site.
142///
143/// 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.
144#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
145pub enum SoleRole {
146 /// The kind's one rendered unit.
147 Sole,
148}
149
150crate::roster! {
151 /// Where a rendered unit lands.
152 ///
153 /// Four deliveries, and a role names exactly one of them.
154 pub enum Destination {
155 /// The tokens the consumer's normal build compiles where the declaration stands.
156 DeclarationSite = "declaration-site",
157 /// The deferred cargo the consumer's test target invokes; the normal build compiles none of it.
158 TestCarrier = "test-carrier",
159 /// The deferred cargo the consumer's bench target invokes, on the same terms and through the same shell.
160 BenchCarrier = "bench-carrier",
161 /// A standalone artifact a publication step writes to its own address.
162 PublicationArtifact = "publication-artifact",
163 }
164}
165
166/// What happened to one kind that could have been generated.
167///
168/// Silence is not a variant: where a projection is absent, the absence has a name and cites the fact that caused it.
169/// 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.
170#[must_use = "a disposition is what happened to a kind, and silence is not a variant"]
171#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
172pub enum Disposition {
173 /// It was generated, and this is the unit it produced.
174 Generated {
175 /// The generated unit's semantic key.
176 unit: Identity<GeneratedUnit>,
177 },
178 /// It does not apply here, and this is the fact that makes it inapplicable.
179 NotApplicable {
180 /// The fact the answer rests on.
181 because: OwnerFact,
182 },
183 /// Nobody asked for it, and this is the fact that says so.
184 NotRequested {
185 /// The fact the answer rests on.
186 because: OwnerFact,
187 },
188 /// The profile the request ran under does not offer it.
189 UnavailableUnderProfile {
190 /// The profile that does not offer it.
191 profile: Profile,
192 /// The fact naming what that profile could not furnish.
193 because: OwnerFact,
194 },
195}
196
197/// A consumer-owned record that can surrender its named dispositions in kind declaration order.
198///
199/// Implementations state rows, not completeness.
200/// [`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.
201pub trait DispositionRecord: Clone + Eq + core::fmt::Debug {
202 /// Surrender every stated kind name and disposition, in the set's declaration order.
203 fn into_dispositions(self) -> impl Iterator<Item = (&'static str, Disposition)>;
204}
205
206/// One declared set of kinds and the record from which its complete disposition witness is built.
207///
208/// The trait remains open, but naming a record here does not certify its completeness.
209/// Only [`DispositionSet::complete`] can turn the record into the private-field witness [`Accounted`](crate::Accounted) accepts.
210pub trait KindSet {
211 /// The consumer-owned disposition record for this set.
212 type Dispositions: DispositionRecord;
213
214 /// Every kind's declared name, in the order the set states them.
215 const NAMES: &'static [&'static str];
216}
217
218/// A disposition for every declared kind of one set, in declaration order.
219///
220/// 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.
221#[must_use = "a complete disposition set is the witness an accounted expansion requires"]
222#[derive(Debug, Clone, PartialEq, Eq)]
223pub struct DispositionSet<Set: KindSet> {
224 dispositions: Vec<Disposition>,
225 kind_set: PhantomData<fn() -> Set>,
226}
227
228/// How a disposition record refuses to become a complete set witness.
229#[must_use = "a disposition-set refusal names the count or kind-name disagreement"]
230#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
231pub enum DispositionSetError {
232 /// The record surrendered a different number of rows than the kind set declares.
233 CountMismatch {
234 /// How many kind names the set declares.
235 expected: usize,
236 /// How many disposition rows the record surrendered.
237 observed: usize,
238 },
239 /// One surrendered row names a kind other than the kind declared at that position.
240 KindMismatch {
241 /// The kind name the set declares at this position.
242 expected: &'static str,
243 /// The kind name the record surrendered at this position.
244 observed: &'static str,
245 },
246}