macroonz_compiler/diagnostic/type_guard.rs
1//! The diagnostic home's invariant nucleus: the one road that builds a related set, the private diagnostic assembly, and the roads its own values are read through.
2//!
3//! Declared inside `types.rs` as its own child, so the seats a caller may not write are reachable here and nowhere else.
4//!
5//! What lands here is what is about an ACT rather than about a value.
6//! The related-set road takes the issue material — not a count, and not identities somebody else already derived — and builds the set, so the capping and the identities are two readings of one act.
7//! The caller-placed and intrinsic roads obtain one informed projection from `project.rs` and assemble its private seats here, so a line naming one position beside a seat holding another is unrepresentable.
8
9use super::{
10 Diagnostic, DiagnosticName, DiagnosticNameRefusal, DiagnosticProjection, DiagnosticSeats,
11 Family, IntrinsicRefused, Observed, Phase, Placement, Refused, RelatedIdentity, RelatedSet,
12 Repair, Route, Site, SiteCoordinate,
13};
14use crate::bounded::{Bounded, Capping};
15use crate::diagnostic::project::{diagnostic, intrinsic_site, placement_site};
16use crate::identity::{
17 Contract, Identity, RelatedBody, RelatedIssue, Role, ServiceEntry, Transcript, encode_bytes,
18};
19use crate::request::Door;
20use crate::token::{SourceCoordinate, SpanHandle};
21
22impl Family {
23 /// Declare one family under its owner's namespace.
24 ///
25 /// The shape is `namespace/stem`, checked where the constant is written: a name that carries no namespace is a name two crates could write.
26 ///
27 /// # Panics
28 ///
29 /// Stops const evaluation — the build, for the `const` items this is written for — on a name without an interior `/`.
30 #[must_use]
31 pub const fn declared(name: &'static str) -> Self {
32 assert!(
33 interior_separator(name.as_bytes()),
34 "a family name is namespace/stem, with material on both sides"
35 );
36 Self(name)
37 }
38
39 /// The name, exactly as declared.
40 #[must_use]
41 pub const fn name(self) -> &'static str {
42 self.0
43 }
44}
45
46impl DiagnosticName {
47 /// Declares one lowercase ASCII kebab-case diagnostic name without normalizing it.
48 ///
49 /// # Errors
50 ///
51 /// Returns [`DiagnosticNameRefusal::Empty`] where no name was supplied and [`DiagnosticNameRefusal::NotKebabCase`] where the spelling contains another character or a leading, trailing, or doubled separator.
52 pub const fn declared(name: &'static str) -> Result<Self, DiagnosticNameRefusal> {
53 if name.is_empty() {
54 return Err(DiagnosticNameRefusal::Empty);
55 }
56 if !crate::identity::name_is_grammatical(name) {
57 return Err(DiagnosticNameRefusal::NotKebabCase);
58 }
59 Ok(Self(name))
60 }
61
62 /// The name, exactly as declared.
63 #[must_use]
64 pub const fn spelling(self) -> &'static str {
65 self.0
66 }
67}
68
69/// Whether the first `/` in the bytes has material on both sides.
70const fn interior_separator(name: &[u8]) -> bool {
71 let mut rest = name;
72 let mut ahead = 0_usize;
73 while let Some((byte, remaining)) = rest.split_first() {
74 if *byte == b'/' {
75 return ahead != 0 && !remaining.is_empty();
76 }
77 ahead = ahead.saturating_add(1);
78 rest = remaining;
79 }
80 false
81}
82
83/// The content one related identity is derived over, at either level.
84///
85/// The family name separates two spaces so the same bytes raised under two families never encode alike, and both members are framed so no two compositions share a preimage.
86/// One composition serves both levels deliberately: what separates them is the subject, which is a segment of the derive-key context rather than a discriminant somebody could forget to write into a preimage.
87fn related_content(family: Family, material: &[u8]) -> Vec<u8> {
88 let mut content = Vec::new();
89 encode_bytes(family.name().as_bytes(), &mut content);
90 encode_bytes(material, &mut content);
91 content
92}
93
94/// One related-issue identity over one established issue's material.
95///
96/// Private on purpose: an identity of this subject exists only as part of a set this file built.
97/// The position is zero for every family: the family rides inside the content, framed, where a name fits.
98fn issue_identity(family: Family, material: &[u8]) -> Identity<RelatedIssue> {
99 Identity::derived(Transcript::rooted(
100 Role::DiagnosticRelation,
101 &related_content(family, material),
102 0,
103 ))
104}
105
106/// One related-body identity over the framing of a whole body's issues.
107///
108/// The same private discipline and the same seat, under the other subject.
109/// The separation is deliberately not a byte inside the preimage: the subject rides in the derive-key context, so two levels over identical content are separated before a byte of that content is read.
110fn body_identity(family: Family, material: &[u8]) -> Identity<RelatedBody> {
111 Identity::derived(Transcript::rooted(
112 Role::DiagnosticRelation,
113 &related_content(family, material),
114 0,
115 ))
116}
117
118impl RelatedSet {
119 /// The related set one refusal body amounts to, derived over that body's own issue material.
120 ///
121 /// A road taking a body identity and a set of per-issue identities as two arguments takes two halves that do not check each other: each is honestly derived on its own, so the pair can name one refusal's body over another refusal's issues and still read exactly like a set that belongs together.
122 /// Deriving here removes the pairing instead of policing it.
123 ///
124 /// # Construction
125 ///
126 /// This is a mint site, so its grammar is stated in full.
127 /// Both levels derive at [`Role::DiagnosticRelation`], rooted, at position zero, over
128 ///
129 /// ```text
130 /// content = u64be(len(family)) || family || u64be(material.len()) || material
131 /// ```
132 ///
133 /// where the family is its declared name's UTF-8 bytes, the material of an issue is that issue's own canonical bytes, and the material of the body is `u64be(issue.len()) || issue` for every issue in order, concatenated.
134 /// The per-issue identities are derived first and the body's identity over exactly that material in exactly that order, so two different issue sets cannot reach one body identity and one issue set cannot reach two.
135 /// The two subjects are `related-issue` and `related-body`; everything else an independent reader needs is on [`Transcript`].
136 ///
137 /// # Bounds
138 ///
139 /// The set overruns [`RELATED_ISSUE_LIMIT`](super::RELATED_ISSUE_LIMIT) at the boundary by exactly one, because the body's own identity sits ahead of the per-issue ones.
140 /// Where that happens the body's identity is carried alone — a coarser commitment to the same refusal, never a shorter commitment to a different one — and the capping states how many per-issue identities are not there.
141 ///
142 /// Handed no issues, this road answers with [`RelatedSet::nothing_enumerated`] rather than deriving a body identity over empty material, so "nothing was enumerated" has one representation and two diagnostics that enumerated nothing compare equal.
143 #[must_use]
144 pub fn derived_over(family: Family, issues: &[Vec<u8>]) -> Self {
145 if issues.is_empty() {
146 return Self::nothing_enumerated();
147 }
148 let mut body_material = Vec::new();
149 let mut per_issue = Vec::with_capacity(issues.len());
150 for issue in issues {
151 per_issue.push(RelatedIdentity::Issue(issue_identity(family, issue)));
152 encode_bytes(issue, &mut body_material);
153 }
154 let body = RelatedIdentity::Body(body_identity(family, &body_material));
155 let mut all = Vec::with_capacity(per_issue.len().saturating_add(1));
156 all.push(body);
157 all.append(&mut per_issue);
158 match Bounded::new(all) {
159 Ok(carried) => Self {
160 carried,
161 capping: Capping::Complete,
162 },
163 Err(_) => Self {
164 carried: Bounded::from_array([body]),
165 capping: Capping::Truncated {
166 omitted: issues.len(),
167 },
168 },
169 }
170 }
171
172 /// The canonical empty relation: what a road that enumerated nothing amounts to.
173 ///
174 /// A single-cause refusal establishes one cause and enumerates nothing, so there is no per-issue set to fall short of: zero identities are carried and zero were dropped.
175 ///
176 /// # Nonclaims
177 ///
178 /// Emptiness here is a stated posture about an act that ran — the road looked, and there was nothing to enumerate.
179 /// It is not an absent set, not a set that failed to build, and not a capping that dropped everything.
180 #[must_use]
181 pub const fn nothing_enumerated() -> Self {
182 Self {
183 carried: Bounded::empty(),
184 capping: Capping::Complete,
185 }
186 }
187
188 /// The identities the set carries, body first.
189 #[must_use]
190 pub fn carried(&self) -> &[RelatedIdentity] {
191 self.carried.as_slice()
192 }
193
194 /// Whether that set names every established issue.
195 #[must_use]
196 pub const fn capping(&self) -> Capping {
197 self.capping
198 }
199}
200
201impl Site {
202 /// The site of an observation about one token of a captured declaration.
203 ///
204 /// The handle is the load-bearing half: the producer resolves it to the exact compiler span, and this compiler never does.
205 /// The coordinate beside it is whatever that producer's table answered, including the typed statement that the table does not reach the handle — which is a fact about the TABLE and leaves the observation itself standing.
206 pub const fn at_token(token: SpanHandle, coordinate: SiteCoordinate) -> Self {
207 Self::AtToken { token, coordinate }
208 }
209
210 /// The site of an observation established before any capture existed to issue a handle.
211 ///
212 /// It takes no [`SpanHandle`], which is the whole point of the road: there is no seat here for a caller to fill with handle zero, and no branch in which one is invented.
213 pub const fn before_capture(coordinate: SourceCoordinate) -> Self {
214 Self::BeforeCapture { coordinate }
215 }
216
217 /// The site of an observation about the declaration as a whole.
218 ///
219 /// It takes nothing, which is the statement: there is no narrower token, so no handle and no coordinate exist to be answered — and none is invented for a machine reader to mistake for a resolved one.
220 pub const fn whole_declaration() -> Self {
221 Self::WholeDeclaration
222 }
223
224 /// The token this diagnostic points at, where a capture issued one.
225 ///
226 /// # Nonclaims
227 ///
228 /// It answers with nothing for a site established BEFORE a capture, because no table was built and no handle was issued, and with nothing for a WHOLE-DECLARATION site, because the refusal is not at a token.
229 /// Each is a stated posture rather than a missing value: a handle answered here would index a table that never existed or name a spot the refusal is not about, and either would read exactly like an honest handle.
230 #[must_use]
231 pub const fn token(self) -> Option<SpanHandle> {
232 match self {
233 Self::AtToken { token, .. } => Some(token),
234 Self::BeforeCapture { .. } | Self::WholeDeclaration => None,
235 }
236 }
237
238 /// Where this diagnostic sits, where it sits at a place at all.
239 ///
240 /// The ONE place a pre-capture byte is lifted into the answered posture, and it lifts honestly: nothing was resolved because nothing needed resolving, and the coordinate's own role says which text the position counts into.
241 /// A whole-declaration site answers with nothing — the refusal has no position inside the declaration, and a manufactured one would read exactly like a resolved coordinate.
242 #[must_use]
243 pub const fn coordinate(self) -> Option<SiteCoordinate> {
244 match self {
245 Self::AtToken { coordinate, .. } => Some(coordinate),
246 Self::BeforeCapture { coordinate } => Some(SiteCoordinate::Resolved(coordinate)),
247 Self::WholeDeclaration => None,
248 }
249 }
250}
251
252impl Route {
253 /// The reproduction road one door offers.
254 pub(crate) const fn through(entry: Identity<ServiceEntry>) -> Self {
255 Self { entry }
256 }
257
258 /// The callable entry point that reaches this observation again.
259 #[must_use]
260 pub const fn entry(self) -> Identity<ServiceEntry> {
261 self.entry
262 }
263}
264
265impl Diagnostic {
266 /// Project one caller-placed refused step into the diagnostic its door hands back.
267 ///
268 /// Every seat that could be written two ways is written once on this road: the line through [`composed`](crate::diagnostic::composed), the expected contract and the reproduction route off the door, and the site through the placement the caller states.
269 /// The site is built once and read twice — the prose and the seat are projections of the same value.
270 /// Refusal types whose site is intrinsic expose their own diagnostic road and do not implement this method's public bound.
271 pub fn refused<E: Refused>(refusal: &E, door: &Door, placement: &Placement<'_>) -> Self {
272 assemble_diagnostic(diagnostic(refusal, door, placement_site(placement)))
273 }
274
275 /// The step that was running.
276 #[must_use]
277 pub const fn phase(&self) -> Phase {
278 self.phase
279 }
280
281 /// Where the observation sits.
282 pub const fn site(&self) -> Site {
283 self.site
284 }
285
286 /// The one line this diagnostic projects for a person.
287 ///
288 /// A projection and only a projection: nothing reads it back, and a frontend shows it rather than deciding from it.
289 #[must_use]
290 pub fn summary(&self) -> &str {
291 &self.carried.summary
292 }
293
294 /// The contract that was expected to hold.
295 #[must_use]
296 pub fn expected(&self) -> Identity<Contract> {
297 self.carried.expected
298 }
299
300 /// How what was found differs from it.
301 #[must_use]
302 pub const fn observed(&self) -> Observed {
303 self.observed
304 }
305
306 /// The other issues this one points at, and how that set was capped.
307 #[must_use]
308 pub fn related(&self) -> &RelatedSet {
309 &self.carried.related
310 }
311
312 /// The owner-declared repairs that apply.
313 #[must_use]
314 pub fn repairs(&self) -> &[Repair] {
315 self.carried.repairs.as_slice()
316 }
317
318 /// How to reach this observation again.
319 #[must_use]
320 pub fn route(&self) -> Route {
321 self.carried.route
322 }
323}
324
325/// Compose one intrinsically placed refusal without accepting a second site beside it.
326pub(crate) fn intrinsic_diagnostic<E: IntrinsicRefused>(refusal: &E, door: &Door) -> Diagnostic {
327 assemble_diagnostic(diagnostic(refusal, door, intrinsic_site(refusal)))
328}
329
330/// Assemble one diagnostic from the informed projection without reopening any semantic choice.
331fn assemble_diagnostic(projection: DiagnosticProjection) -> Diagnostic {
332 Diagnostic {
333 phase: projection.phase,
334 site: projection.site,
335 observed: projection.observed,
336 carried: Box::new(DiagnosticSeats {
337 summary: projection.summary,
338 expected: projection.expected,
339 related: projection.related,
340 repairs: projection.repairs,
341 route: projection.route,
342 }),
343 }
344}