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