Skip to main content

macroonz_compiler/plan/
type_contract.rs

1//! The constant answers this home's rosters settle, and the contracts a planning 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 position, sentence, and classification are.
4//! Nothing here decides anything: the passes that establish a planning issue live where the refusal is raised.
5
6use super::{BoundAxis, InvalidationTrigger, PlanError, PlanIssue};
7use crate::bounded::{Bounded, Capping};
8use crate::diagnostic::{
9    Family, LineBody, Observed, PLANNING_FAMILY, Phase, REPAIR_LIMIT, RefusalClass, Refused, Repair,
10};
11use core::fmt;
12
13impl BoundAxis {
14    /// The position a canonical encoding writes for this axis.
15    ///
16    /// Appended and never renumbered: the byte stands inside every identity derived over a refusal that names it.
17    #[must_use]
18    pub const fn slot(self) -> u8 {
19        match self {
20            Self::Declarations => 0,
21            Self::Outputs => 1,
22            Self::Triggers => 2,
23            Self::TraceEntries => 3,
24            Self::OriginEdges => 4,
25        }
26    }
27
28    /// What this axis governs, as a refusal states it.
29    #[must_use]
30    pub const fn described(self) -> &'static str {
31        match self {
32            Self::Declarations => "the captured declarations one account may name",
33            Self::Outputs => "the outputs one plan may declare",
34            Self::Triggers => "the triggers one plan may watch",
35            Self::TraceEntries => "the entries one decision trace may record",
36            Self::OriginEdges => "the edges one origin trail may draw",
37        }
38    }
39}
40
41impl InvalidationTrigger {
42    /// The discriminant byte a canonical encoding writes ahead of what this row watches.
43    #[must_use]
44    pub const fn slot(&self) -> u8 {
45        match self {
46            Self::CapturedDeclaration { .. } => 0,
47            Self::Profile { .. } => 1,
48            Self::Generator { .. } => 2,
49            Self::Declared { .. } => 3,
50            Self::ProjectionContent { .. } => 4,
51        }
52    }
53}
54
55impl PlanIssue {
56    /// This row's position in the declared roster, written ahead of the issue's own material.
57    #[must_use]
58    pub const fn slot(&self) -> u8 {
59        match self {
60            Self::ContradictoryFacts { .. } => 1,
61            Self::UnknownKind { .. } => 2,
62            Self::ProfileUnsupported { .. } => 3,
63            Self::BoundExceeded { .. } => 4,
64            Self::MembershipIncomplete { .. } => 5,
65            Self::OrphanGeneratedNode { .. } => 6,
66            Self::MembershipDoubled { .. } => 7,
67            Self::TrailDiscontinuous { .. } => 8,
68            Self::CauseSetUnwatchable { .. } => 9,
69            Self::MembershipForeign { .. } => 10,
70            Self::AddressInert { .. } => 11,
71        }
72    }
73
74    /// How what this issue observed differs from the contract that was expected.
75    #[must_use]
76    pub const fn observed(&self) -> Observed {
77        match self {
78            Self::ContradictoryFacts { .. }
79            | Self::UnknownKind { .. }
80            | Self::MembershipDoubled { .. }
81            | Self::MembershipForeign { .. }
82            | Self::AddressInert { .. } => Observed::ContractDisagreement,
83            Self::ProfileUnsupported { .. } => Observed::ProfileDisagreement,
84            Self::BoundExceeded { .. } | Self::CauseSetUnwatchable { .. } => {
85                Observed::BoundExceeded
86            }
87            Self::MembershipIncomplete { .. } => Observed::SeatAbsent,
88            Self::OrphanGeneratedNode { .. } | Self::TrailDiscontinuous { .. } => {
89                Observed::OriginAbsent
90            }
91        }
92    }
93}
94
95impl fmt::Display for PlanIssue {
96    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
97        match self {
98            Self::ContradictoryFacts { between } => write!(
99                into,
100                "two facts that decided this plan disagree: {}.{} and {}.{}",
101                between.left.home, between.left.name, between.right.home, between.right.name
102            ),
103            Self::UnknownKind { .. } => {
104                into.write_str("the plan names a kind this compiler was not handed")
105            }
106            Self::ProfileUnsupported { profile } => write!(
107                into,
108                "the profile {}/{} at version {} offers no such projection",
109                profile.stem(),
110                profile.name(),
111                profile.version().position()
112            ),
113            Self::BoundExceeded {
114                axis,
115                bound,
116                observed,
117            } => write!(
118                into,
119                "{}: {observed} offered where {bound} are declared",
120                axis.described()
121            ),
122            Self::MembershipIncomplete { .. } => {
123                into.write_str("a declared output is absent from the plan's membership")
124            }
125            Self::OrphanGeneratedNode { .. } => {
126                into.write_str("a generated unit arrived with no origin")
127            }
128            Self::MembershipDoubled {
129                role_slot,
130                observed,
131            } => write!(
132                into,
133                "{observed} members stand under the seat at roster position {role_slot}"
134            ),
135            Self::TrailDiscontinuous { at } => write!(
136                into,
137                "the origin trail's edge at position {at} does not start where the edge before it ended"
138            ),
139            Self::CauseSetUnwatchable { named, watchable } => write!(
140                into,
141                "the account names {named} independent causes and this reading watches {watchable}"
142            ),
143            Self::MembershipForeign { seat } => write!(
144                into,
145                "the member under the seat {seat} stands outside the kind's declared roster"
146            ),
147            Self::AddressInert { seat } => write!(
148                into,
149                "an address was stated for the seat {seat}, which no publication act consumes"
150            ),
151        }
152    }
153}
154
155impl fmt::Display for PlanError {
156    fn fmt(&self, into: &mut fmt::Formatter<'_>) -> fmt::Result {
157        write!(into, "{}", self.first_issue())?;
158        let further = self.issues().count().saturating_sub(1);
159        if further > 0 {
160            write!(into, ", and {further} further issues")?;
161        }
162        if let Capping::Truncated { omitted } = self.capping() {
163            write!(into, ", {omitted} of them not carried")?;
164        }
165        Ok(())
166    }
167}
168
169impl core::error::Error for PlanError {}
170
171impl Refused for PlanError {
172    const PHASE: Phase = Phase::Planning;
173    const FAMILY: Family = PLANNING_FAMILY;
174
175    fn class(&self) -> RefusalClass {
176        RefusalClass::PlanNotStated
177    }
178
179    fn first(&self) -> String {
180        self.first_issue().to_string()
181    }
182
183    fn observed(&self) -> Observed {
184        self.first_issue().observed()
185    }
186
187    fn body(&self) -> LineBody {
188        let further = self.issues().count().saturating_sub(1);
189        let capping = self.capping();
190        if further == 0 && capping == Capping::Complete {
191            LineBody::SingleCause
192        } else {
193            LineBody::Body { further, capping }
194        }
195    }
196
197    /// The issues established beyond the primary cause; the primary is the summary's own subject, never a member of its related set.
198    fn related(&self) -> Vec<Vec<u8>> {
199        self.issues()
200            .iter()
201            .skip(1)
202            .map(PlanIssue::canonical_bytes)
203            .collect()
204    }
205
206    /// This home declares no repair of its own.
207    ///
208    /// Every issue above 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.
209    fn repairs(&self) -> Bounded<Repair, REPAIR_LIMIT> {
210        Bounded::empty()
211    }
212}