macroonz_compiler/stamp/type_guard.rs
1//! The stamp home's invariant nucleus: every road that reaches a private field, and the one road that composes a published artifact.
2//!
3//! Declared inside `types.rs` as its own child, which is what makes this home's claims structural rather than remembered.
4//! A spelling is admitted against the alphabet here, so a name the consumer's compiler would read as something else is not a value anybody can hold.
5//! A pattern's seats and a stamp's sites are closed here, so a definition that binds one metavariable twice, or a manifest that names one site twice, is refused before a token exists.
6//! And an artifact is composed here, so there is no half-rendered publication unit for a reader to mistake for a whole one.
7
8use super::super::render;
9use super::{
10 Landing, PART_LIMIT, PATH_SEGMENT_LIMIT, Part, Pattern, PublicationGround, PublicationRecord,
11 PublishedStamp, SITE_LIMIT, Seat, Seating, Site, SiteRoot, Stamp, StampError, StampName,
12 StampedPlan, Visibility,
13};
14use crate::bounded::{Bounded, NonEmpty, NonEmptyError, first_duplicate_position};
15use crate::identity::{self, Identity};
16use crate::plan::DigestContract;
17use crate::token::{GeneratedTree, rendered_name};
18
19impl Seat {
20 /// Declare one metavariable seat.
21 ///
22 /// # Errors
23 ///
24 /// Returns [`StampError::NotAnIdentifier`] where the name cannot seat a metavariable — not one Rust identifier, or a keyword the language already took, either of which a matcher refuses.
25 pub fn declared(name: &str, seating: Seating) -> Result<Self, StampError> {
26 if !rendered_name(name) {
27 return Err(StampError::NotAnIdentifier);
28 }
29 Ok(Self {
30 name: name.to_owned(),
31 seating,
32 })
33 }
34
35 /// The name material travels under.
36 #[must_use]
37 pub fn name(&self) -> &str {
38 self.name.as_str()
39 }
40
41 /// The shape it travels in.
42 #[must_use]
43 pub const fn seating(&self) -> Seating {
44 self.seating
45 }
46}
47
48impl Pattern {
49 /// Declare one pattern: the sentence its definition is documented with, the shape it is invoked in, and the body that shape expands into.
50 ///
51 /// # Errors
52 ///
53 /// Returns [`StampError::SeatNameDoubled`] where two seats carry one name, [`StampError::PatternEmpty`] where no part was stated, and [`StampError::PatternUnbounded`] where the parts outgrow the declared magnitude.
54 ///
55 /// The namespace is closed before the magnitude, because a collision is a defect in what was declared and a caller repairing a magnitude first would repair the collision second.
56 pub fn declared(note: &str, parts: Vec<Part>, body: GeneratedTree) -> Result<Self, StampError> {
57 seat_names_closed(&parts)?;
58 let admitted: NonEmpty<Part, PART_LIMIT> =
59 NonEmpty::new(parts).map_err(|refusal| match refusal {
60 NonEmptyError::Empty(_) => StampError::PatternEmpty,
61 NonEmptyError::Overflow(overflow) => StampError::PatternUnbounded { overflow },
62 })?;
63 Ok(Self {
64 note: note.to_owned(),
65 parts: admitted,
66 body,
67 })
68 }
69
70 /// The sentence the definition is documented with.
71 #[must_use]
72 pub fn note(&self) -> &str {
73 self.note.as_str()
74 }
75
76 /// The declared shape, in the order it is written; structurally at least one part.
77 ///
78 /// # Ordering
79 ///
80 /// This order is meaning: a matcher and every invocation are walks over it, so the same parts stated in another order are another grammar.
81 #[must_use]
82 pub fn parts(&self) -> &NonEmpty<Part, PART_LIMIT> {
83 &self.parts
84 }
85
86 /// The body the shape expands into.
87 #[must_use]
88 pub const fn body(&self) -> &GeneratedTree {
89 &self.body
90 }
91
92 /// The seats of the shape, in the order a site supplies arguments for them.
93 pub fn seats(&self) -> impl Iterator<Item = &Seat> {
94 self.parts.iter().filter_map(|part| match part {
95 Part::Seat(seat) => Some(seat),
96 Part::Literal(_) | Part::Reach => None,
97 })
98 }
99
100 /// How many seats the shape declares.
101 #[must_use]
102 pub fn seat_count(&self) -> usize {
103 self.seats().count()
104 }
105
106 /// Whether the shape gives a site's visibility a coordinate.
107 #[must_use]
108 pub fn reaches(&self) -> bool {
109 self.parts.iter().any(|part| match part {
110 Part::Reach => true,
111 Part::Literal(_) | Part::Seat(_) => false,
112 })
113 }
114}
115
116impl StampName {
117 /// The name one published stamp is exported under.
118 ///
119 /// # Errors
120 ///
121 /// Returns [`StampError::NotAnIdentifier`] where the spelling cannot name an exported item — not one Rust identifier, or a keyword the language already took.
122 pub fn declared(spelling: &str) -> Result<Self, StampError> {
123 if !rendered_name(spelling) {
124 return Err(StampError::NotAnIdentifier);
125 }
126 Ok(Self {
127 spelling: spelling.to_owned(),
128 })
129 }
130
131 /// The exported spelling a site invokes this stamp by.
132 #[must_use]
133 pub fn spelling(&self) -> &str {
134 self.spelling.as_str()
135 }
136}
137
138impl SiteRoot {
139 /// The path one site reaches its stamp by, parsed from the segments the caller stated.
140 ///
141 /// # Errors
142 ///
143 /// Returns [`StampError::NotAnIdentifier`] where a segment cannot name a step of a site's path, [`StampError::PathEmpty`] where no segment was stated, and [`StampError::PathUnbounded`] where the segments outgrow the declared magnitude.
144 ///
145 /// The checks are in that order, so exactly one cause is true of any refused root.
146 /// The reading is position-aware the way the language's own path grammar is: the root position admits the qualifiers a site lawfully roots itself under — `crate`, `self`, or a leading run of `super` — and every segment past the qualifiers names an item, read against the composed law that refuses the keyword roster.
147 pub fn spelled(segments: Vec<String>) -> Result<Self, StampError> {
148 let names = match segments.split_first() {
149 Some((root, rest)) if root.as_str() == "crate" || root.as_str() == "self" => rest,
150 Some(_) | None => {
151 let qualifiers = segments
152 .iter()
153 .take_while(|segment| segment.as_str() == "super")
154 .count();
155 segments.get(qualifiers..).unwrap_or(&[])
156 }
157 };
158 for segment in names {
159 if !rendered_name(segment.as_str()) {
160 return Err(StampError::NotAnIdentifier);
161 }
162 }
163 let admitted: NonEmpty<String, PATH_SEGMENT_LIMIT> =
164 NonEmpty::new(segments).map_err(|refusal| match refusal {
165 NonEmptyError::Empty(_) => StampError::PathEmpty,
166 NonEmptyError::Overflow(overflow) => StampError::PathUnbounded { overflow },
167 })?;
168 Ok(Self { segments: admitted })
169 }
170
171 /// The segments, in the order they were stated; structurally at least one.
172 #[must_use]
173 pub fn segments(&self) -> &NonEmpty<String, PATH_SEGMENT_LIMIT> {
174 &self.segments
175 }
176
177 /// How many segments the root carries; structurally at least one.
178 #[must_use]
179 pub fn count(&self) -> usize {
180 self.segments.count()
181 }
182}
183
184impl Site {
185 /// Declare one site that adopts a stamp.
186 ///
187 /// The name is a label rather than a spelling: it is what the manifest calls this landing, and no token is ever written from it.
188 ///
189 /// # Errors
190 ///
191 /// Returns [`StampError::ArgumentsUnbounded`] where the arguments outgrow the declared magnitude.
192 /// Whether they are the RIGHT arguments is settled where the site meets its pattern, in [`Stamp::declared`].
193 pub fn declared(
194 name: &str,
195 root: SiteRoot,
196 reach: Visibility,
197 arguments: Vec<GeneratedTree>,
198 ) -> Result<Self, StampError> {
199 let admitted: Bounded<GeneratedTree, PART_LIMIT> = Bounded::new(arguments)
200 .map_err(|overflow| StampError::ArgumentsUnbounded { overflow })?;
201 Ok(Self {
202 name: name.to_owned(),
203 root,
204 reach,
205 arguments: admitted,
206 })
207 }
208
209 /// What the manifest calls this landing.
210 #[must_use]
211 pub fn name(&self) -> &str {
212 self.name.as_str()
213 }
214
215 /// The path this site reaches its stamp by.
216 #[must_use]
217 pub const fn root(&self) -> &SiteRoot {
218 &self.root
219 }
220
221 /// The reach this site writes.
222 #[must_use]
223 pub const fn reach(&self) -> Visibility {
224 self.reach
225 }
226
227 /// The material this site supplies, one argument per declared seat, in seat order.
228 #[must_use]
229 pub fn arguments(&self) -> &[GeneratedTree] {
230 self.arguments.as_slice()
231 }
232}
233
234impl Stamp {
235 /// Declare the complete payload one published stamp is rendered from.
236 ///
237 /// # Errors
238 ///
239 /// Returns [`StampError::SiteNameDoubled`] where two sites carry one name, [`StampError::ArgumentsUnmatched`] where a site supplies a different number of arguments than the pattern declares seats, [`StampError::ReachUnseated`] where a site declares a reach the pattern gives no coordinate to, [`StampError::SitesAbsent`] where no site was stated, and [`StampError::SitesUnbounded`] where the sites outgrow the declared magnitude.
240 ///
241 /// The namespace is closed first, then each site is settled against the pattern in the order the sites were stated, and the magnitude last.
242 pub fn declared(
243 name: StampName,
244 pattern: Pattern,
245 sites: Vec<Site>,
246 ) -> Result<Self, StampError> {
247 site_names_closed(&sites)?;
248 sites_seated(&pattern, &sites)?;
249 let admitted: NonEmpty<Site, SITE_LIMIT> =
250 NonEmpty::new(sites).map_err(|refusal| match refusal {
251 NonEmptyError::Empty(_) => StampError::SitesAbsent,
252 NonEmptyError::Overflow(overflow) => StampError::SitesUnbounded { overflow },
253 })?;
254 Ok(Self {
255 name,
256 pattern,
257 sites: admitted,
258 })
259 }
260
261 /// The name this stamp is exported under.
262 #[must_use]
263 pub const fn name(&self) -> &StampName {
264 &self.name
265 }
266
267 /// The pattern this stamp stamps.
268 #[must_use]
269 pub const fn pattern(&self) -> &Pattern {
270 &self.pattern
271 }
272
273 /// The sites covered, in the order they were declared; structurally at least one.
274 ///
275 /// # Ordering
276 ///
277 /// This order is meaning for a migration: one invocation is rendered per site in the order this yields.
278 #[must_use]
279 pub fn sites(&self) -> &NonEmpty<Site, SITE_LIMIT> {
280 &self.sites
281 }
282
283 /// How many sites this stamp covers; structurally at least one.
284 #[must_use]
285 pub fn count(&self) -> usize {
286 self.sites.count()
287 }
288}
289
290impl Landing {
291 /// The site this landing is for.
292 #[must_use]
293 pub fn site(&self) -> &str {
294 self.site.as_str()
295 }
296
297 /// The invocation written there.
298 #[must_use]
299 pub const fn invocation(&self) -> &GeneratedTree {
300 &self.invocation
301 }
302}
303
304impl PublicationRecord {
305 /// Why neither of the lighter roads expresses this output.
306 #[must_use]
307 pub const fn ground(&self) -> PublicationGround {
308 self.ground
309 }
310
311 /// The planned member this artifact materializes.
312 #[must_use]
313 pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
314 self.unit
315 }
316
317 /// What the eventual staged bytes' digest must satisfy.
318 #[must_use]
319 pub const fn staged(&self) -> DigestContract {
320 self.staged
321 }
322
323 /// The stamp the artifact was rendered from, whole.
324 #[must_use]
325 pub const fn covered(&self) -> &Stamp {
326 &self.stamp
327 }
328
329 /// What the unit contains, row by row.
330 pub fn manifest(&self) -> impl Iterator<Item = &str> {
331 self.stamp.sites().iter().map(Site::name)
332 }
333}
334
335impl StampedPlan {
336 /// One reading, from the one road that checked the seat it states.
337 ///
338 /// `pub` only within the stamp home: the checks live in [`planned`](crate::stamp::planned), and this is how that road writes down what it proved.
339 pub(in crate::stamp) const fn read(
340 unit: Identity<identity::GeneratedUnit>,
341 staged: DigestContract,
342 ) -> Self {
343 Self { unit, staged }
344 }
345
346 /// The planned member's semantic key.
347 #[must_use]
348 pub const fn unit(&self) -> Identity<identity::GeneratedUnit> {
349 self.unit
350 }
351
352 /// What the eventual staged bytes' digest must satisfy.
353 #[must_use]
354 pub const fn staged(&self) -> DigestContract {
355 self.staged
356 }
357}
358
359impl PublishedStamp {
360 /// Render one published stamp over what the plan decided, what the caller declared, and why the lighter roads are insufficient.
361 ///
362 /// The order is the road: the definition first, then one invocation per covered site, then the record — and the artifact only after all three, so no half-rendered publication unit exists.
363 ///
364 /// # Errors
365 ///
366 /// Returns [`StampError::TokensUnbounded`] where the definition, one invocation, or the tree either is assembled into outgrows the declared token magnitude.
367 pub fn rendered(
368 planned: &StampedPlan,
369 stamp: &Stamp,
370 ground: PublicationGround,
371 ) -> Result<Self, StampError> {
372 let definition = GeneratedTree::assembled(render::definition(stamp)?)?;
373 let mut landings: Vec<Landing> = Vec::new();
374 for site in stamp.sites() {
375 let invocation = GeneratedTree::assembled(render::invocation(stamp, site)?)?;
376 landings.push(Landing {
377 site: site.name().to_owned(),
378 invocation,
379 });
380 }
381 Ok(Self {
382 definition,
383 landings,
384 record: PublicationRecord {
385 ground,
386 unit: planned.unit,
387 staged: planned.staged,
388 stamp: stamp.clone(),
389 },
390 })
391 }
392
393 /// The name the stamp is exported under, read out of the record.
394 #[must_use]
395 pub const fn name(&self) -> &StampName {
396 self.record.covered().name()
397 }
398
399 /// The definition a publication road lands as visible source.
400 #[must_use]
401 pub const fn definition(&self) -> &GeneratedTree {
402 &self.definition
403 }
404
405 /// Every covered site's landing, in the order the stamp declares them.
406 ///
407 /// # Bounds
408 ///
409 /// Exactly as many as the stamp declares sites, because the road that built them walked that stamp once.
410 #[must_use]
411 pub fn landings(&self) -> &[Landing] {
412 self.landings.as_slice()
413 }
414
415 /// How many landings this artifact carries; structurally at least one.
416 #[must_use]
417 pub fn count(&self) -> usize {
418 self.landings.len()
419 }
420
421 /// This side's record of the publication act.
422 pub const fn record(&self) -> &PublicationRecord {
423 &self.record
424 }
425}
426
427/// The seat namespace one pattern closes.
428///
429/// Two seats under one name bind one metavariable twice, which the consumer's compiler would report inside an expansion nobody wrote.
430fn seat_names_closed(parts: &[Part]) -> Result<(), StampError> {
431 let doubled = first_duplicate_position(parts, |left, right| match (left, right) {
432 (Part::Seat(left), Part::Seat(right)) => left.name() == right.name(),
433 (Part::Literal(_) | Part::Reach, _) | (_, Part::Literal(_) | Part::Reach) => false,
434 });
435 if let Some(position) = doubled {
436 return Err(StampError::SeatNameDoubled {
437 at: counted(position),
438 });
439 }
440 Ok(())
441}
442
443/// The site namespace one stamp closes.
444///
445/// Two sites under one name are one manifest row written twice, and nothing downstream could tell which landing a row is about.
446fn site_names_closed(sites: &[Site]) -> Result<(), StampError> {
447 if let Some(position) =
448 first_duplicate_position(sites, |left, right| left.name() == right.name())
449 {
450 return Err(StampError::SiteNameDoubled {
451 at: counted(position),
452 });
453 }
454 Ok(())
455}
456
457/// Every site settled against the pattern it adopts: one argument per seat, and a reach only where the pattern writes one.
458fn sites_seated(pattern: &Pattern, sites: &[Site]) -> Result<(), StampError> {
459 let seats = counted(pattern.seat_count());
460 let reaches = pattern.reaches();
461 for (position, site) in sites.iter().enumerate() {
462 let supplied = counted(site.arguments().len());
463 if supplied != seats {
464 return Err(StampError::ArgumentsUnmatched {
465 at: counted(position),
466 seats,
467 supplied,
468 });
469 }
470 if !reaches && site.reach() != Visibility::Private {
471 return Err(StampError::ReachUnseated {
472 at: counted(position),
473 });
474 }
475 }
476 Ok(())
477}
478
479/// One count as a refusal carries it.
480fn counted(value: usize) -> u32 {
481 u32::try_from(value).unwrap_or(u32::MAX)
482}