Skip to main content

macroonz_compiler/explanation/
type_contract.rs

1//! The constant answers this home's rosters settle, and the contracts a coverage refusal stands 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 name, position, sentence, and classification are.
4//! The answer-to-question table is what makes the pairing DERIVED rather than supplied: a true answer filed under the wrong question is a value nobody can build.
5
6use super::encode::answer_material;
7use super::project::human_line;
8use super::{
9    ExplanationError, ExplanationIssue, UNIVERSAL_QUESTION_COUNT, UniversalAnswer,
10    UniversalQuestion,
11};
12use crate::bounded::{Bounded, Capping};
13use crate::diagnostic::{
14    EXPLANATION_FAMILY, Family, LineBody, Observed, Phase, REPAIR_LIMIT, RefusalClass, Refused,
15    Repair,
16};
17use crate::identity::encode_bytes;
18use crate::kind::{Answer, Question};
19use core::fmt;
20
21const _: () = assert!(
22    UNIVERSAL_QUESTION_COUNT == UniversalQuestion::ALL.len(),
23    "the universal seat width and the universal roster are one number, stated twice",
24);
25
26impl Question for UniversalQuestion {
27    const ALL: &'static [Self] = &[
28        Self::WhatAreYou,
29        Self::WhichOwnerRequired,
30        Self::WhichDeclarationCaused,
31        Self::WhichProfile,
32        Self::WhichOutputAndDigest,
33        Self::WhichAssumptions,
34        Self::WhatInvalidates,
35        Self::WhyRelatedNotGenerated,
36        Self::WhatRepairsARefusal,
37    ];
38
39    type Answer = UniversalAnswer;
40
41    fn name(self) -> &'static str {
42        match self {
43            Self::WhatAreYou => "what-are-you",
44            Self::WhichOwnerRequired => "which-owner-required",
45            Self::WhichDeclarationCaused => "which-declaration-caused",
46            Self::WhichProfile => "which-profile",
47            Self::WhichOutputAndDigest => "which-output-and-digest",
48            Self::WhichAssumptions => "which-assumptions",
49            Self::WhatInvalidates => "what-invalidates",
50            Self::WhyRelatedNotGenerated => "why-related-not-generated",
51            Self::WhatRepairsARefusal => "what-repairs-a-refusal",
52        }
53    }
54}
55
56impl UniversalQuestion {
57    /// This question in the words a person asks it.
58    #[must_use]
59    pub const fn described(self) -> &'static str {
60        match self {
61            Self::WhatAreYou => "what are you",
62            Self::WhichOwnerRequired => "which owner required you",
63            Self::WhichDeclarationCaused => "which declaration caused you",
64            Self::WhichProfile => "which profile were you decided under",
65            Self::WhichOutputAndDigest => "which output identity and digest are you",
66            Self::WhichAssumptions => "which assumptions do you rest on",
67            Self::WhatInvalidates => "what invalidates you",
68            Self::WhyRelatedNotGenerated => "why was a related projection not generated",
69            Self::WhatRepairsARefusal => "what repairs a refusal",
70        }
71    }
72}
73
74impl Answer for UniversalAnswer {
75    type Question = UniversalQuestion;
76
77    fn question(&self) -> UniversalQuestion {
78        match self {
79            Self::Kind { .. } => UniversalQuestion::WhatAreYou,
80            Self::Owner { .. } => UniversalQuestion::WhichOwnerRequired,
81            Self::CausingDeclarations { .. } => UniversalQuestion::WhichDeclarationCaused,
82            Self::Profile { .. } => UniversalQuestion::WhichProfile,
83            Self::OutputAndDigest { .. } => UniversalQuestion::WhichOutputAndDigest,
84            Self::Assumptions { .. } => UniversalQuestion::WhichAssumptions,
85            Self::Invalidators { .. } => UniversalQuestion::WhatInvalidates,
86            Self::RelatedDispositions { .. } => UniversalQuestion::WhyRelatedNotGenerated,
87            Self::Repairs { .. } => UniversalQuestion::WhatRepairsARefusal,
88        }
89    }
90
91    fn encode_into(&self, into: &mut Vec<u8>) {
92        into.push(self.slot());
93        let mut material = Vec::new();
94        answer_material(self, &mut material);
95        encode_bytes(&material, into);
96    }
97
98    fn human(&self) -> String {
99        human_line(self)
100    }
101}
102
103impl UniversalAnswer {
104    /// This answer's position in the declared roster, written ahead of its own material.
105    ///
106    /// Not the question's position stated twice: the question is what was ASKED and this is which answer SHAPE was given.
107    /// They agree today because the table above is one-to-one, and a roster that ever admitted two shapes for one question would separate them here rather than deriving one preimage for both.
108    /// A position is appended and never renumbered.
109    #[must_use]
110    pub const fn slot(&self) -> u8 {
111        match self {
112            Self::Kind { .. } => 0,
113            Self::Owner { .. } => 1,
114            Self::CausingDeclarations { .. } => 2,
115            Self::Profile { .. } => 3,
116            Self::OutputAndDigest { .. } => 4,
117            Self::Assumptions { .. } => 5,
118            Self::Invalidators { .. } => 6,
119            Self::RelatedDispositions { .. } => 7,
120            Self::Repairs { .. } => 8,
121        }
122    }
123}
124
125impl ExplanationIssue {
126    /// This row's position in the declared roster, written ahead of the issue's own material.
127    #[must_use]
128    pub const fn slot(&self) -> u8 {
129        match self {
130            Self::UniversalUnanswered { .. } => 0,
131            Self::UniversalAnsweredTwice { .. } => 1,
132            Self::DeclaredUnanswered { .. } => 2,
133            Self::DeclaredAnsweredTwice { .. } => 3,
134            Self::QuestionOutsideRoster { .. } => 4,
135            Self::SeatBoundExceeded { .. } => 5,
136            Self::OutputsBesideTheProof { .. } => 6,
137        }
138    }
139
140    /// How what this issue observed differs from the contract that was expected.
141    #[must_use]
142    pub const fn observed(&self) -> Observed {
143        match self {
144            Self::UniversalUnanswered { .. } | Self::DeclaredUnanswered { .. } => {
145                Observed::SeatAbsent
146            }
147            Self::UniversalAnsweredTwice { .. }
148            | Self::DeclaredAnsweredTwice { .. }
149            | Self::QuestionOutsideRoster { .. }
150            | Self::OutputsBesideTheProof { .. } => Observed::ContractDisagreement,
151            Self::SeatBoundExceeded { .. } => Observed::BoundExceeded,
152        }
153    }
154}
155
156impl fmt::Display for ExplanationIssue {
157    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
158        match self {
159            Self::UniversalUnanswered { question } => write!(
160                into,
161                "the universal question \"{}\" has no answer",
162                question.described()
163            ),
164            Self::UniversalAnsweredTwice { question } => write!(
165                into,
166                "the universal question \"{}\" was answered more than once",
167                question.described()
168            ),
169            Self::DeclaredUnanswered { question, slot } => write!(
170                into,
171                "the kind's question \"{question}\" at position {slot} has no answer"
172            ),
173            Self::DeclaredAnsweredTwice { question, slot } => write!(
174                into,
175                "the kind's question \"{question}\" at position {slot} was answered twice or more"
176            ),
177            Self::QuestionOutsideRoster { question } => write!(
178                into,
179                "an answer names the question \"{question}\", which its own roster does not carry"
180            ),
181            Self::SeatBoundExceeded { bound, observed } => {
182                write!(into, "{observed} seats offered where {bound} are declared")
183            }
184            Self::OutputsBesideTheProof {
185                expected,
186                observed,
187                diverges,
188            } => write!(
189                into,
190                "the output answer carries {observed} rows beside the proof's {expected}, diverging at roster position {diverges}"
191            ),
192        }
193    }
194}
195
196impl fmt::Display for ExplanationError {
197    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
198        write!(into, "{}", self.first_issue())?;
199        let further = self.issues().count().saturating_sub(1);
200        if further > 0 {
201            write!(into, ", and {further} further issues")?;
202        }
203        if let Capping::Truncated { omitted } = self.capping() {
204            write!(into, ", {omitted} of them not carried")?;
205        }
206        Ok(())
207    }
208}
209
210impl core::error::Error for ExplanationError {}
211
212impl Refused for ExplanationError {
213    const PHASE: Phase = Phase::Explanation;
214    const FAMILY: Family = EXPLANATION_FAMILY;
215
216    fn class(&self) -> RefusalClass {
217        RefusalClass::ExplanationNotCovered
218    }
219
220    fn first(&self) -> String {
221        self.first_issue().to_string()
222    }
223
224    fn observed(&self) -> Observed {
225        self.first_issue().observed()
226    }
227
228    fn body(&self) -> LineBody {
229        let further = self.issues().count().saturating_sub(1);
230        let capping = self.capping();
231        if further == 0 && capping == Capping::Complete {
232            LineBody::SingleCause
233        } else {
234            LineBody::Body { further, capping }
235        }
236    }
237
238    /// The issues established beyond the primary cause; the primary is the summary's own subject, never a member of its related set.
239    fn related(&self) -> Vec<Vec<u8>> {
240        self.issues()
241            .iter()
242            .skip(1)
243            .map(ExplanationIssue::canonical_bytes)
244            .collect()
245    }
246
247    /// This home declares no repair of its own.
248    ///
249    /// Every issue above is about which questions the caller answered, so the repair is that answer sheet; a sentence composed here would be this compiler citing a fact nobody declared.
250    fn repairs(&self) -> Bounded<Repair, REPAIR_LIMIT> {
251        Bounded::empty()
252    }
253}