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::{
6    AssemblyPosture, CodecContent, CodecDirection, CodecError, CodecIssue, CodecPlacement,
7    CodecProjection, CodecTypePath, DecodeRefusal,
8};
9use crate::bounded::{Bounded, Capping};
10use crate::diagnostic::{
11    CODEC_DECLARATION_FAMILY, Family, LineBody, Observed, Phase, REPAIR_LIMIT, RefusalClass,
12    Refused, Repair,
13};
14use crate::identity::{OwnerIdentity, encode_bytes, encode_length};
15use crate::kind::{CanonicalContent, Kind, NoQuestions, SoleRole};
16use core::fmt;
17
18impl CanonicalContent for CodecContent {
19    fn encode_content_into(&self, into: &mut Vec<u8>) {
20        encode_path(self.shape.owner(), into);
21        encode_bytes(self.shape.refusal().as_bytes(), into);
22        let assembly = self.shape.assembly();
23        encode_bytes(assembly.road().as_bytes(), into);
24        match assembly.posture() {
25            AssemblyPosture::Total => into.push(0),
26            AssemblyPosture::Checked { refusal } => {
27                into.push(1);
28                encode_path(refusal, into);
29            }
30        }
31        encode_length(self.shape.count(), into);
32        for member in self.shape.members() {
33            let mut encoded = Vec::new();
34            encode_bytes(member.spelling().as_bytes(), &mut encoded);
35            encode_path(member.held_as(), &mut encoded);
36            encode_bytes(member.shape().name().as_bytes(), &mut encoded);
37            encode_bytes(member.cardinality().name().as_bytes(), &mut encoded);
38            encode_bytes(&encoded, into);
39        }
40        encode_bytes(self.direction.name().as_bytes(), into);
41        match &self.placement {
42            CodecPlacement::AtDeclarationSite => into.push(0),
43            CodecPlacement::PublishedModule { spelling } => {
44                into.push(1);
45                encode_bytes(spelling.spelling().as_bytes(), into);
46            }
47        }
48        encode_owner(self.schema.as_ref(), into);
49        encode_owner(self.byte_role.as_ref(), into);
50        encode_length(self.assumptions.len(), into);
51        for assumption in self.assumptions.as_slice() {
52            encode_bytes(&assumption.citation_bytes(), into);
53        }
54    }
55}
56
57fn encode_path(path: &CodecTypePath, into: &mut Vec<u8>) {
58    encode_bytes(path.rooting().name().as_bytes(), into);
59    encode_length(path.count(), into);
60    for segment in path.segments() {
61        encode_bytes(segment.as_bytes(), into);
62    }
63}
64
65fn encode_owner(owner: Option<&OwnerIdentity>, into: &mut Vec<u8>) {
66    match owner {
67        None => into.push(0),
68        Some(identity) => {
69            into.push(1);
70            encode_bytes(&identity.citation_bytes(), into);
71        }
72    }
73}
74
75impl Kind for CodecProjection {
76    const NAME: &'static str = "codec-projection";
77
78    type Content = CodecContent;
79    type Role = SoleRole;
80    type Question = NoQuestions;
81}
82
83impl CodecDirection {
84    /// Whether this direction covers the road that writes canonical bytes.
85    #[must_use]
86    pub const fn writes(self) -> bool {
87        match self {
88            Self::Encode | Self::RoundTrip => true,
89            Self::Decode => false,
90        }
91    }
92
93    /// Whether this direction covers the road that reads them back.
94    ///
95    /// # Nonclaims
96    ///
97    /// 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.
98    #[must_use]
99    pub const fn reads(self) -> bool {
100        match self {
101            Self::Decode | Self::RoundTrip => true,
102            Self::Encode => false,
103        }
104    }
105}
106
107impl DecodeRefusal {
108    /// Whether this arm names the member the read was standing at.
109    ///
110    /// 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.
111    #[must_use]
112    pub const fn carries_member(self) -> bool {
113        match self {
114            Self::Truncated
115            | Self::LengthPastRemaining
116            | Self::LengthPastAddressableWidth
117            | Self::CountPastDeclaredWidth
118            | Self::TextNotUtf8
119            | Self::MemberNotAdmitted
120            | Self::SlotNotAdmitted
121            | Self::NestedMemberRefused
122            | Self::PresenceNotAdmitted => true,
123            Self::TrailingBytes | Self::NotAssembled => false,
124        }
125    }
126
127    /// The sentence this arm is rendered with, for whoever reads the refusal in their own crate.
128    #[must_use]
129    pub const fn sentence(self) -> &'static str {
130        match self {
131            Self::Truncated => "The material ended inside this member.",
132            Self::LengthPastRemaining => {
133                "This member's declared length runs past the material that remains."
134            }
135            Self::LengthPastAddressableWidth => {
136                "This member's declared length does not fit an addressable width."
137            }
138            Self::CountPastDeclaredWidth => {
139                "This member's declared count does not fit the width the member is held at."
140            }
141            Self::TextNotUtf8 => "This member's framed bytes are not UTF-8.",
142            Self::MemberNotAdmitted => "The member's own type refused what was read for it.",
143            Self::SlotNotAdmitted => {
144                "The slot read for this member names no arm of the roster it was declared over."
145            }
146            Self::NestedMemberRefused => {
147                "The nested codec this member carries refused the framed material."
148            }
149            Self::PresenceNotAdmitted => {
150                "This member's presence byte is neither of the two the encode road writes."
151            }
152            Self::TrailingBytes => {
153                "Material remains after the last declared member. A canonical encoding is the \
154                 whole of what a value writes, so a longer input is not this value with something \
155                 after it."
156            }
157            Self::NotAssembled => {
158                "Every member was read, and the road that assembles them refused. The refusal is \
159                 the owner's own, carried exactly."
160            }
161        }
162    }
163}
164
165impl CodecIssue {
166    /// This row's position in the declared roster, written ahead of the issue's own material.
167    ///
168    /// Appended and never renumbered: the byte stands inside every identity derived over a refusal that names it.
169    #[must_use]
170    pub const fn slot(&self) -> u8 {
171        match self {
172            Self::PathSegmentsAbsent => 0,
173            Self::SegmentNotAnIdentifier { .. } => 1,
174            Self::PathSegmentsUnbounded { .. } => 2,
175            Self::MemberSpellingAbsent => 3,
176            Self::MemberSpellingNotAnIdentifier { .. } => 4,
177            Self::MemberSpellingDoubled { .. } => 5,
178            Self::MemberShadowsBinding { .. } => 6,
179            Self::AssemblyRoadAbsent => 7,
180            Self::AssemblyRoadNotAnIdentifier { .. } => 8,
181            Self::RefusalSpellingNotAnIdentifier { .. } => 9,
182            Self::ModuleSpellingNotAnIdentifier { .. } => 10,
183            Self::MembersAbsent => 11,
184            Self::MembersUnbounded { .. } => 12,
185        }
186    }
187
188    /// How what this issue observed differs from the contract that was expected.
189    #[must_use]
190    pub const fn observed(&self) -> Observed {
191        match self {
192            Self::PathSegmentsAbsent
193            | Self::MemberSpellingAbsent
194            | Self::AssemblyRoadAbsent
195            | Self::MembersAbsent => Observed::SeatAbsent,
196            Self::PathSegmentsUnbounded { .. } | Self::MembersUnbounded { .. } => {
197                Observed::BoundExceeded
198            }
199            Self::SegmentNotAnIdentifier { .. }
200            | Self::MemberSpellingNotAnIdentifier { .. }
201            | Self::MemberSpellingDoubled { .. }
202            | Self::MemberShadowsBinding { .. }
203            | Self::AssemblyRoadNotAnIdentifier { .. }
204            | Self::RefusalSpellingNotAnIdentifier { .. }
205            | Self::ModuleSpellingNotAnIdentifier { .. } => Observed::ContractDisagreement,
206        }
207    }
208
209    /// Which class of refusal a summary line opens with where this issue is the first established.
210    #[must_use]
211    pub const fn class(&self) -> RefusalClass {
212        match self {
213            Self::PathSegmentsUnbounded { .. } | Self::MembersUnbounded { .. } => {
214                RefusalClass::MagnitudeNotHeld
215            }
216            Self::PathSegmentsAbsent
217            | Self::SegmentNotAnIdentifier { .. }
218            | Self::MemberSpellingAbsent
219            | Self::MemberSpellingNotAnIdentifier { .. }
220            | Self::MemberSpellingDoubled { .. }
221            | Self::MemberShadowsBinding { .. }
222            | Self::AssemblyRoadAbsent
223            | Self::AssemblyRoadNotAnIdentifier { .. }
224            | Self::RefusalSpellingNotAnIdentifier { .. }
225            | Self::ModuleSpellingNotAnIdentifier { .. }
226            | Self::MembersAbsent => RefusalClass::DeclarationNotRead,
227        }
228    }
229}
230
231impl fmt::Display for CodecIssue {
232    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
233        match self {
234            Self::PathSegmentsAbsent => into.write_str("a rendered type path names no segment"),
235            Self::SegmentNotAnIdentifier { segment } => {
236                write!(into, "the path segment {segment} is not one Rust identifier")
237            }
238            Self::PathSegmentsUnbounded { bound, observed } => write!(
239                into,
240                "a rendered type path names {observed} segments where {bound} are declared"
241            ),
242            Self::MemberSpellingAbsent => into.write_str("a codec member states no spelling"),
243            Self::MemberSpellingNotAnIdentifier { spelling } => {
244                write!(into, "the member spelling {spelling} is not one Rust identifier")
245            }
246            Self::MemberSpellingDoubled { spelling } => write!(
247                into,
248                "two members of one shape are both spelled {spelling}, so the decode road would bind one local twice"
249            ),
250            Self::MemberShadowsBinding { spelling, binding } => write!(
251                into,
252                "the member {spelling} is spelled like {binding}, which the decode road binds for itself"
253            ),
254            Self::AssemblyRoadAbsent => into.write_str("a codec assembly road states no spelling"),
255            Self::AssemblyRoadNotAnIdentifier { spelling } => {
256                write!(into, "the assembly road {spelling} is not one Rust identifier")
257            }
258            Self::RefusalSpellingNotAnIdentifier { spelling } => write!(
259                into,
260                "the rendered decode refusal {spelling} is not one Rust identifier"
261            ),
262            Self::ModuleSpellingNotAnIdentifier { spelling } => write!(
263                into,
264                "the published module {spelling} is not one Rust identifier"
265            ),
266            Self::MembersAbsent => into.write_str(
267                "a codec shape declares no member, so its decode road could refuse for one reason and admit every other input",
268            ),
269            Self::MembersUnbounded { bound, observed } => write!(
270                into,
271                "a codec shape declares {observed} members where {bound} are declared"
272            ),
273        }
274    }
275}
276
277impl fmt::Display for CodecError {
278    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(into, "{}", self.first_issue())?;
280        let further = self.issues().count().saturating_sub(1);
281        if further > 0 {
282            write!(into, ", and {further} further issues")?;
283        }
284        if let Capping::Truncated { omitted } = self.capping() {
285            write!(into, ", {omitted} of them not carried")?;
286        }
287        Ok(())
288    }
289}
290
291impl core::error::Error for CodecError {}
292
293impl Refused for CodecError {
294    const PHASE: Phase = Phase::Capture;
295    const FAMILY: Family = CODEC_DECLARATION_FAMILY;
296
297    fn class(&self) -> RefusalClass {
298        self.first_issue().class()
299    }
300
301    fn first(&self) -> String {
302        self.first_issue().to_string()
303    }
304
305    fn observed(&self) -> Observed {
306        self.first_issue().observed()
307    }
308
309    fn body(&self) -> LineBody {
310        let further = self.issues().count().saturating_sub(1);
311        let capping = self.capping();
312        if further == 0 && capping == Capping::Complete {
313            LineBody::SingleCause
314        } else {
315            LineBody::Body { further, capping }
316        }
317    }
318
319    /// The issues established beyond the primary cause; the primary is the summary's own subject, never a member of its related set.
320    fn related(&self) -> Vec<Vec<u8>> {
321        self.issues()
322            .iter()
323            .skip(1)
324            .map(CodecIssue::canonical_bytes)
325            .collect()
326    }
327
328    /// This home declares no repair of its own.
329    ///
330    /// 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.
331    fn repairs(&self) -> Bounded<Repair, REPAIR_LIMIT> {
332        Bounded::empty()
333    }
334}