Skip to main content

macroonz_compiler/codec/
type_contract.rs

1//! The tables this home states rather than computes, and the contracts its kind and its refusal stand under.
2//!
3//! Each table is total, so a row admitted later stops the compiler in every one of them until somebody says what that row's answer is.
4
5use super::spell::{
6    CANDIDATE_BINDING, CARRIED_BINDING, CHOSEN_BINDING, COLLECTED_BINDING, ELECTED_BINDING,
7    INTO_BINDING, LENGTH_BINDING, MATERIAL_BINDING, NESTED_BINDING, PRESENT_BINDING,
8    REMAINING_BINDING, WIDTH_BINDING,
9};
10use super::{
11    AssemblyPosture, CodecContent, CodecDirection, CodecError, CodecIssue, CodecMemberShape,
12    CodecPlacement, CodecProjection, CodecTypePath, DECODE_ROAD, DecodeRefusal, ENCODE_ROAD,
13    MemberContract, ROSTER_CONSTANT, SLOT_ROAD,
14};
15use crate::bounded::{Bounded, Capping};
16use crate::diagnostic::{
17    CODEC_DECLARATION_FAMILY, Family, LineBody, Observed, Phase, REPAIR_LIMIT, RefusalClass,
18    Refused, Repair,
19};
20use crate::identity::{OwnerIdentity, encode_bytes, encode_length};
21use crate::kind::{CanonicalContent, Kind, NoQuestions, SoleRole};
22use core::fmt;
23
24impl CanonicalContent for CodecContent {
25    fn encode_content_into(&self, into: &mut Vec<u8>) {
26        encode_path(self.shape.owner(), into);
27        encode_bytes(self.shape.refusal().as_bytes(), into);
28        let assembly = self.shape.assembly();
29        encode_bytes(assembly.road().as_bytes(), into);
30        match assembly.posture() {
31            AssemblyPosture::Total => into.push(0),
32            AssemblyPosture::Checked { refusal } => {
33                into.push(1);
34                encode_path(refusal, into);
35            }
36        }
37        encode_length(self.shape.count(), into);
38        for member in self.shape.members() {
39            let mut encoded = Vec::new();
40            encode_bytes(member.spelling().as_bytes(), &mut encoded);
41            encode_path(member.held_as(), &mut encoded);
42            encode_bytes(member.shape().name().as_bytes(), &mut encoded);
43            encode_bytes(member.cardinality().name().as_bytes(), &mut encoded);
44            encode_bytes(&encoded, into);
45        }
46        encode_bytes(self.direction.name().as_bytes(), into);
47        match &self.placement {
48            CodecPlacement::AtDeclarationSite => into.push(0),
49            CodecPlacement::PublishedModule { spelling } => {
50                into.push(1);
51                encode_bytes(spelling.spelling().as_bytes(), into);
52            }
53        }
54        encode_owner(self.schema.as_ref(), into);
55        encode_owner(self.byte_role.as_ref(), into);
56        encode_length(self.assumptions.len(), into);
57        for assumption in self.assumptions.as_slice() {
58            encode_bytes(&assumption.citation_bytes(), into);
59        }
60    }
61}
62
63fn encode_path(path: &CodecTypePath, into: &mut Vec<u8>) {
64    encode_bytes(path.rooting().name().as_bytes(), into);
65    encode_length(path.count(), into);
66    for segment in path.segments() {
67        encode_bytes(segment.as_bytes(), into);
68    }
69}
70
71fn encode_owner(owner: Option<&OwnerIdentity>, into: &mut Vec<u8>) {
72    match owner {
73        None => into.push(0),
74        Some(identity) => {
75            into.push(1);
76            encode_bytes(&identity.citation_bytes(), into);
77        }
78    }
79}
80
81impl Kind for CodecProjection {
82    const NAME: &'static str = "codec-projection";
83
84    type Content = CodecContent;
85    type Role = SoleRole;
86    type Question = NoQuestions;
87}
88
89impl CodecDirection {
90    /// Whether this direction covers the road that writes canonical bytes.
91    #[must_use]
92    pub const fn writes(self) -> bool {
93        match self {
94            Self::Encode | Self::RoundTrip => true,
95            Self::Decode => false,
96        }
97    }
98
99    /// Whether this direction covers the road that reads them back.
100    ///
101    /// # Nonclaims
102    ///
103    /// A direction that does not cover it delivers no reader, and that is a stated posture rather than a rendering that fell short: "a codec that refuses on decode is the validator" says exactly as much about the codec that has no decode road.
104    #[must_use]
105    pub const fn reads(self) -> bool {
106        match self {
107            Self::Decode | Self::RoundTrip => true,
108            Self::Encode => false,
109        }
110    }
111}
112
113impl DecodeRefusal {
114    /// Whether this arm names the member the read was standing at.
115    ///
116    /// The two that do not are facts about the whole material and about the assembly, and a member seat on either would name a member no read was standing at.
117    #[must_use]
118    pub const fn carries_member(self) -> bool {
119        match self {
120            Self::Truncated
121            | Self::LengthPastRemaining
122            | Self::LengthPastAddressableWidth
123            | Self::CountPastDeclaredWidth
124            | Self::TextNotUtf8
125            | Self::MemberNotAdmitted
126            | Self::SlotNotAdmitted
127            | Self::NestedMemberRefused
128            | Self::PresenceNotAdmitted => true,
129            Self::TrailingBytes | Self::NotAssembled => false,
130        }
131    }
132
133    /// The sentence this arm is rendered with, for whoever reads the refusal in their own crate.
134    #[must_use]
135    pub const fn sentence(self) -> &'static str {
136        match self {
137            Self::Truncated => "The material ended inside this member.",
138            Self::LengthPastRemaining => {
139                "This member's declared length runs past the material that remains."
140            }
141            Self::LengthPastAddressableWidth => {
142                "This member's declared length does not fit an addressable width."
143            }
144            Self::CountPastDeclaredWidth => {
145                "This member's declared count does not fit the width the member is held at."
146            }
147            Self::TextNotUtf8 => "This member's framed bytes are not UTF-8.",
148            Self::MemberNotAdmitted => "The member's own type refused what was read for it.",
149            Self::SlotNotAdmitted => {
150                "The slot read for this member names no arm of the roster it was declared over."
151            }
152            Self::NestedMemberRefused => {
153                "The nested codec this member carries refused the framed material."
154            }
155            Self::PresenceNotAdmitted => {
156                "This member's presence byte is neither of the two the encode road writes."
157            }
158            Self::TrailingBytes => {
159                "Material remains after the last declared member. A canonical encoding is the \
160                 whole of what a value writes, so a longer input is not this value with something \
161                 after it."
162            }
163            Self::NotAssembled => {
164                "Every member was read, and the road that assembles them refused. The refusal is \
165                 the owner's own, carried exactly."
166            }
167        }
168    }
169}
170
171/// The complete bill, one row per wire shape, in the roster's own order.
172///
173/// Five rows because the roster is five: a row added here without an arm beside it, or an arm without a row, is a length disagreement the declaration itself carries.
174///
175/// The closed-choice row is this compiler's own contract on a caller's roster — a complete roster constant and a position road answering one byte — and not an inheritance from any stamp that happens to emit one.
176pub const MEMBER_CONTRACT: [MemberContract; 5] = [
177    COUNT_CONTRACT.bill,
178    BYTES_CONTRACT.bill,
179    TEXT_CONTRACT.bill,
180    CLOSED_CHOICE_CONTRACT.bill,
181    NESTED_CONTRACT.bill,
182];
183
184/// The write operation one contract row selects.
185#[derive(Clone, Copy)]
186pub(super) enum WriteRoad {
187    /// Widen one count and write its big-endian bytes.
188    Count,
189    /// Borrow bytes through the declared trait road and frame them.
190    Bytes,
191    /// Borrow text through the declared trait road and frame its UTF-8 bytes.
192    Text,
193    /// Write the declared slot of one closed-choice arm.
194    ClosedChoice,
195    /// Call and frame one nested codec.
196    Nested,
197}
198
199/// The read operation one contract row selects.
200#[derive(Clone, Copy)]
201pub(super) enum ReadRoad {
202    /// Read and narrow one count.
203    Count,
204    /// Read framed bytes and ask the member type to admit them.
205    Bytes,
206    /// Read framed UTF-8 text and ask the member type to admit it.
207    Text,
208    /// Elect one arm from the owner's complete roster.
209    ClosedChoice,
210    /// Ask one nested codec to read its framed material.
211    Nested,
212}
213
214/// One authoritative contract row, with its public bill and the two internal operations that consume it.
215#[derive(Clone, Copy)]
216pub(super) struct RenderingContract {
217    /// The public statement of the member roads.
218    pub(super) bill: MemberContract,
219    /// The generated write operation.
220    pub(super) write: WriteRoad,
221    /// The generated read operation.
222    pub(super) read: ReadRoad,
223}
224
225const COUNT_CONTRACT: RenderingContract = RenderingContract {
226    bill: MemberContract {
227        shape: CodecMemberShape::Count,
228        encode_road: "u64::from",
229        decode_road: "<T as ::core::convert::TryFrom<u64>>::try_from",
230    },
231    write: WriteRoad::Count,
232    read: ReadRoad::Count,
233};
234
235const BYTES_CONTRACT: RenderingContract = RenderingContract {
236    bill: MemberContract {
237        shape: CodecMemberShape::Bytes,
238        encode_road: "<T as ::core::convert::AsRef<[u8]>>::as_ref",
239        decode_road: "<T as ::core::convert::TryFrom<::std::vec::Vec<u8>>>::try_from",
240    },
241    write: WriteRoad::Bytes,
242    read: ReadRoad::Bytes,
243};
244
245const TEXT_CONTRACT: RenderingContract = RenderingContract {
246    bill: MemberContract {
247        shape: CodecMemberShape::Text,
248        encode_road: "<T as ::core::convert::AsRef<str>>::as_ref",
249        decode_road: "<T as ::core::convert::TryFrom<::std::string::String>>::try_from",
250    },
251    write: WriteRoad::Text,
252    read: ReadRoad::Text,
253};
254
255const CLOSED_CHOICE_CONTRACT: RenderingContract = RenderingContract {
256    bill: MemberContract {
257        shape: CodecMemberShape::ClosedChoice,
258        encode_road: SLOT_ROAD,
259        decode_road: ROSTER_CONSTANT,
260    },
261    write: WriteRoad::ClosedChoice,
262    read: ReadRoad::ClosedChoice,
263};
264
265const NESTED_CONTRACT: RenderingContract = RenderingContract {
266    bill: MemberContract {
267        shape: CodecMemberShape::Nested,
268        encode_road: ENCODE_ROAD,
269        decode_road: DECODE_ROAD,
270    },
271    write: WriteRoad::Nested,
272    read: ReadRoad::Nested,
273};
274
275/// The authoritative row one member shape selects.
276///
277/// Both the public bill and generated operations read this seat, so adding or reassigning a shape cannot leave an independently selected renderer behind it.
278pub(super) const fn rendering_contract(shape: CodecMemberShape) -> RenderingContract {
279    match shape {
280        CodecMemberShape::Count => COUNT_CONTRACT,
281        CodecMemberShape::Bytes => BYTES_CONTRACT,
282        CodecMemberShape::Text => TEXT_CONTRACT,
283        CodecMemberShape::ClosedChoice => CLOSED_CHOICE_CONTRACT,
284        CodecMemberShape::Nested => NESTED_CONTRACT,
285    }
286}
287
288/// The locals the rendered decode road declares for itself.
289///
290/// # Authority
291///
292/// **A member whose spelling is one of these is refused rather than renamed.**
293/// The decode road binds one local per member under the member's OWN spelling, which is what makes the rendered road readable and what lets the assembly call name its arguments the way the owner named its members.
294/// A member colliding with one of these would shadow the rendering's own binding, and the road would go on reading a value nobody meant — a defect that compiles.
295///
296/// Renaming the rendering's locals to something nobody would write is not the repair: an unreadable rendered road is a road nobody can audit, and the collision would still exist for whatever names were chosen instead.
297pub const RESERVED_BINDINGS: [&str; 12] = [
298    MATERIAL_BINDING,
299    REMAINING_BINDING,
300    INTO_BINDING,
301    NESTED_BINDING,
302    COLLECTED_BINDING,
303    CANDIDATE_BINDING,
304    CHOSEN_BINDING,
305    ELECTED_BINDING,
306    PRESENT_BINDING,
307    CARRIED_BINDING,
308    LENGTH_BINDING,
309    WIDTH_BINDING,
310];
311
312impl CodecIssue {
313    /// This row's position in the declared roster, written ahead of the issue's own material.
314    ///
315    /// Appended and never renumbered: the byte stands inside every identity derived over a refusal that names it.
316    #[must_use]
317    pub const fn slot(&self) -> u8 {
318        match self {
319            Self::PathSegmentsAbsent => 0,
320            Self::SegmentNotAnIdentifier { .. } => 1,
321            Self::PathSegmentsUnbounded { .. } => 2,
322            Self::MemberSpellingAbsent => 3,
323            Self::MemberSpellingNotAnIdentifier { .. } => 4,
324            Self::MemberSpellingDoubled { .. } => 5,
325            Self::MemberShadowsBinding { .. } => 6,
326            Self::AssemblyRoadAbsent => 7,
327            Self::AssemblyRoadNotAnIdentifier { .. } => 8,
328            Self::RefusalSpellingNotAnIdentifier { .. } => 9,
329            Self::ModuleSpellingNotAnIdentifier { .. } => 10,
330            Self::MembersAbsent => 11,
331            Self::MembersUnbounded { .. } => 12,
332        }
333    }
334
335    /// How what this issue observed differs from the contract that was expected.
336    #[must_use]
337    pub const fn observed(&self) -> Observed {
338        match self {
339            Self::PathSegmentsAbsent
340            | Self::MemberSpellingAbsent
341            | Self::AssemblyRoadAbsent
342            | Self::MembersAbsent => Observed::SeatAbsent,
343            Self::PathSegmentsUnbounded { .. } | Self::MembersUnbounded { .. } => {
344                Observed::BoundExceeded
345            }
346            Self::SegmentNotAnIdentifier { .. }
347            | Self::MemberSpellingNotAnIdentifier { .. }
348            | Self::MemberSpellingDoubled { .. }
349            | Self::MemberShadowsBinding { .. }
350            | Self::AssemblyRoadNotAnIdentifier { .. }
351            | Self::RefusalSpellingNotAnIdentifier { .. }
352            | Self::ModuleSpellingNotAnIdentifier { .. } => Observed::ContractDisagreement,
353        }
354    }
355
356    /// Which class of refusal a summary line opens with where this issue is the first established.
357    #[must_use]
358    pub const fn class(&self) -> RefusalClass {
359        match self {
360            Self::PathSegmentsUnbounded { .. } | Self::MembersUnbounded { .. } => {
361                RefusalClass::MagnitudeNotHeld
362            }
363            Self::PathSegmentsAbsent
364            | Self::SegmentNotAnIdentifier { .. }
365            | Self::MemberSpellingAbsent
366            | Self::MemberSpellingNotAnIdentifier { .. }
367            | Self::MemberSpellingDoubled { .. }
368            | Self::MemberShadowsBinding { .. }
369            | Self::AssemblyRoadAbsent
370            | Self::AssemblyRoadNotAnIdentifier { .. }
371            | Self::RefusalSpellingNotAnIdentifier { .. }
372            | Self::ModuleSpellingNotAnIdentifier { .. }
373            | Self::MembersAbsent => RefusalClass::DeclarationNotRead,
374        }
375    }
376}
377
378impl fmt::Display for CodecIssue {
379    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
380        match self {
381            Self::PathSegmentsAbsent => into.write_str("a rendered type path names no segment"),
382            Self::SegmentNotAnIdentifier { segment } => {
383                write!(into, "the path segment {segment} is not one Rust identifier")
384            }
385            Self::PathSegmentsUnbounded { bound, observed } => write!(
386                into,
387                "a rendered type path names {observed} segments where {bound} are declared"
388            ),
389            Self::MemberSpellingAbsent => into.write_str("a codec member states no spelling"),
390            Self::MemberSpellingNotAnIdentifier { spelling } => {
391                write!(into, "the member spelling {spelling} is not one Rust identifier")
392            }
393            Self::MemberSpellingDoubled { spelling } => write!(
394                into,
395                "two members of one shape are both spelled {spelling}, so the decode road would bind one local twice"
396            ),
397            Self::MemberShadowsBinding { spelling, binding } => write!(
398                into,
399                "the member {spelling} is spelled like {binding}, which the decode road binds for itself"
400            ),
401            Self::AssemblyRoadAbsent => into.write_str("a codec assembly road states no spelling"),
402            Self::AssemblyRoadNotAnIdentifier { spelling } => {
403                write!(into, "the assembly road {spelling} is not one Rust identifier")
404            }
405            Self::RefusalSpellingNotAnIdentifier { spelling } => write!(
406                into,
407                "the rendered decode refusal {spelling} is not one Rust identifier"
408            ),
409            Self::ModuleSpellingNotAnIdentifier { spelling } => write!(
410                into,
411                "the published module {spelling} is not one Rust identifier"
412            ),
413            Self::MembersAbsent => into.write_str(
414                "a codec shape declares no member, so its decode road could refuse for one reason and admit every other input",
415            ),
416            Self::MembersUnbounded { bound, observed } => write!(
417                into,
418                "a codec shape declares {observed} members where {bound} are declared"
419            ),
420        }
421    }
422}
423
424impl fmt::Display for CodecError {
425    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
426        write!(into, "{}", self.first_issue())?;
427        let further = self.issues().count().saturating_sub(1);
428        if further > 0 {
429            write!(into, ", and {further} further issues")?;
430        }
431        if let Capping::Truncated { omitted } = self.capping() {
432            write!(into, ", {omitted} of them not carried")?;
433        }
434        Ok(())
435    }
436}
437
438impl core::error::Error for CodecError {}
439
440impl Refused for CodecError {
441    const PHASE: Phase = Phase::Capture;
442    const FAMILY: Family = CODEC_DECLARATION_FAMILY;
443
444    fn class(&self) -> RefusalClass {
445        self.first_issue().class()
446    }
447
448    fn first(&self) -> String {
449        self.first_issue().to_string()
450    }
451
452    fn observed(&self) -> Observed {
453        self.first_issue().observed()
454    }
455
456    fn body(&self) -> LineBody {
457        let further = self.issues().count().saturating_sub(1);
458        let capping = self.capping();
459        if further == 0 && capping == Capping::Complete {
460            LineBody::SingleCause
461        } else {
462            LineBody::Body { further, capping }
463        }
464    }
465
466    /// The issues established beyond the primary cause; the primary is the summary's own subject, never a member of its related set.
467    fn related(&self) -> Vec<Vec<u8>> {
468        self.issues()
469            .iter()
470            .skip(1)
471            .map(CodecIssue::canonical_bytes)
472            .collect()
473    }
474
475    /// This home declares no repair of its own.
476    ///
477    /// Every issue is about what the caller's own declaration states, so the repair is that declaration; a sentence composed here would be this compiler citing a fact nobody declared.
478    fn repairs(&self) -> Bounded<Repair, REPAIR_LIMIT> {
479        Bounded::empty()
480    }
481}