1use serde::Serialize;
21use std::collections::BTreeMap;
22use std::fmt;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
26pub struct SrcSpan {
27 pub file: usize,
28 pub line: usize,
29 pub col: usize,
30}
31
32impl SrcSpan {
33 pub const UNKNOWN: SrcSpan = SrcSpan {
34 file: usize::MAX,
35 line: 0,
36 col: 0,
37 };
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
45#[serde(tag = "resolution", rename_all = "snake_case")]
46pub enum Resolved<T> {
47 Exact(T),
49 Ambiguous(Vec<T>),
51 Unresolved(String),
53}
54
55impl<T> Resolved<T> {
56 pub fn exact(&self) -> Option<&T> {
57 match self {
58 Resolved::Exact(v) => Some(v),
59 _ => None,
60 }
61 }
62
63 pub fn is_exact(&self) -> bool {
64 matches!(self, Resolved::Exact(_))
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
73#[serde(tag = "kind", content = "reason", rename_all = "snake_case")]
74pub enum Certainty {
75 Certain,
77 Conditional(String),
80 Unknown(String),
82}
83
84impl Certainty {
85 pub fn is_certain(&self) -> bool {
86 matches!(self, Certainty::Certain)
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
92#[serde(tag = "seg", rename_all = "snake_case")]
93pub enum KeySeg {
94 Literal(String),
96 Dynamic { expr: String },
102 Template { text: String },
108}
109
110impl fmt::Display for KeySeg {
111 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112 match self {
113 KeySeg::Literal(n) => write!(f, "{n}"),
114 KeySeg::Dynamic { expr } => write!(f, "{{{expr}}}"),
115 KeySeg::Template { text } => write!(f, "{text}"),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize)]
124#[serde(transparent)]
125pub struct Key {
126 pub segs: Vec<KeySeg>,
127}
128
129impl Key {
130 pub fn push_literal(&self, text: &str) -> Self {
133 let mut next = self.clone();
134 for part in text.split('.').filter(|p| !p.is_empty()) {
135 next.segs.push(KeySeg::Literal(part.to_string()));
136 }
137 next
138 }
139
140 pub fn push(&self, seg: KeySeg) -> Self {
141 let mut next = self.clone();
142 next.segs.push(seg);
143 next
144 }
145
146 pub fn extend(&self, segs: &[KeySeg]) -> Self {
147 let mut next = self.clone();
148 next.segs.extend_from_slice(segs);
149 next
150 }
151
152 pub fn is_empty(&self) -> bool {
153 self.segs.is_empty()
154 }
155
156 pub fn is_template(&self) -> bool {
158 self.segs
159 .iter()
160 .any(|s| matches!(s, KeySeg::Dynamic { .. } | KeySeg::Template { .. }))
161 }
162
163 pub fn matches(&self, concrete: &str) -> bool {
167 let parts: Vec<&str> = concrete.split('.').filter(|p| !p.is_empty()).collect();
168 if parts.len() != self.segs.len() {
169 return false;
170 }
171 self.segs.iter().zip(parts).all(|(seg, part)| match seg {
172 KeySeg::Literal(n) => n == part,
173 KeySeg::Dynamic { .. } => true,
174 KeySeg::Template { text } => template_segment_matches(text, part),
175 })
176 }
177}
178
179fn template_segment_matches(template: &str, concrete: &str) -> bool {
180 let mut literals = Vec::new();
181 let mut cursor = 0usize;
182 while let Some(open_rel) = template[cursor..].find('{') {
183 let open = cursor + open_rel;
184 let Some(close_rel) = template[open + 1..].find('}') else {
185 return template == concrete;
186 };
187 let close = open + 1 + close_rel;
188 literals.push(&template[cursor..open]);
189 cursor = close + 1;
190 }
191 if literals.is_empty() {
192 return template == concrete;
193 }
194 literals.push(&template[cursor..]);
195
196 let starts_with_wildcard = template.starts_with('{');
197 let ends_with_wildcard = template.ends_with('}');
198 let mut position = 0usize;
199 for (index, literal) in literals.iter().enumerate() {
200 if literal.is_empty() {
201 continue;
202 }
203 if index == 0 && !starts_with_wildcard {
204 if !concrete.starts_with(literal) {
205 return false;
206 }
207 position = literal.len();
208 continue;
209 }
210 let Some(found) = concrete[position..].find(literal) else {
211 return false;
212 };
213 position += found + literal.len();
214 }
215 ends_with_wildcard
216 || literals
217 .last()
218 .is_some_and(|suffix| concrete.ends_with(suffix))
219}
220
221impl fmt::Display for Key {
222 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223 let joined: Vec<String> = self.segs.iter().map(|s| s.to_string()).collect();
224 write!(f, "{}", joined.join("."))
225 }
226}
227
228macro_rules! id_type {
229 ($name:ident) => {
230 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
231 pub struct $name(pub usize);
232 };
233}
234
235id_type!(ModuleDefId);
236id_type!(ModuleInstanceId);
237id_type!(ParamSiteId);
238id_type!(ParamId);
239
240#[derive(Debug, Clone, Serialize)]
242pub struct ModuleDef {
243 pub id: ModuleDefId,
244 pub name: String,
246 pub ctor: Option<String>,
248 pub span: SrcSpan,
249 pub sites: Vec<ParamSiteId>,
250}
251
252#[derive(Debug, Clone, Serialize)]
254pub struct Repeat {
255 pub var: String,
257 pub bound: String,
259}
260
261#[derive(Debug, Clone, Serialize)]
263pub struct ModuleInstance {
264 pub id: ModuleInstanceId,
265 pub def: ModuleDefId,
266 pub parent: Option<ModuleInstanceId>,
267 pub via_field: Option<String>,
270 pub prefix: Key,
272 pub root: String,
277 pub prefix_derived: bool,
281 pub repeat: Option<Repeat>,
282 pub origin: SrcSpan,
283 pub children: Vec<ModuleInstanceId>,
284 pub certainty: Certainty,
285}
286
287#[derive(Debug, Clone, Serialize)]
289#[serde(tag = "via", rename_all = "snake_case")]
290pub enum Acquisition {
291 Constructor { func: String, cite: &'static str },
293 RawGet { method: String },
295}
296
297#[derive(Debug, Clone, Serialize)]
299pub struct ParamSite {
300 pub id: ParamSiteId,
301 pub owner: ModuleDefId,
302 pub acquisition: Acquisition,
303 pub relative_key: Key,
305 pub kind: crate::known::ParamKind,
306 pub shape: Option<String>,
309 pub span: SrcSpan,
310 pub certainty: Certainty,
311}
312
313#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
317#[serde(tag = "match", rename_all = "snake_case")]
318pub enum CheckpointMatch {
319 NotChecked,
320 Found {
322 name: String,
323 shape: Vec<usize>,
324 dtype: String,
325 },
326 FoundMany {
328 count: usize,
329 sample: String,
330 },
331 Missing,
332}
333
334#[derive(Debug, Clone, Serialize)]
336pub struct Param {
337 pub id: ParamId,
338 pub site: ParamSiteId,
339 pub owner: ModuleInstanceId,
340 pub key: Key,
342 pub root: String,
344 pub certainty: Certainty,
345 pub checkpoint: CheckpointMatch,
346}
347
348#[derive(Debug, Clone, Serialize)]
350pub struct Diagnostic {
351 pub span: SrcSpan,
352 pub message: String,
353 pub key: Option<Key>,
354}
355
356#[derive(Debug, Clone, Default, Serialize)]
359pub struct Coverage {
360 pub instances: usize,
361 pub params: usize,
362 pub params_certain: usize,
363 pub params_conditional: usize,
364 pub params_unknown: usize,
365 pub diagnostics: usize,
366}
367
368#[derive(Debug, Default, Serialize)]
369pub struct Structure {
370 pub defs: Vec<ModuleDef>,
371 pub instances: Vec<ModuleInstance>,
372 pub sites: Vec<ParamSite>,
373 pub params: Vec<Param>,
374 pub root: Option<ModuleInstanceId>,
375 pub diagnostics: Vec<Diagnostic>,
376}
377
378impl Structure {
379 pub fn def(&self, id: ModuleDefId) -> &ModuleDef {
380 &self.defs[id.0]
381 }
382
383 pub fn instance(&self, id: ModuleInstanceId) -> &ModuleInstance {
384 &self.instances[id.0]
385 }
386
387 pub fn site(&self, id: ParamSiteId) -> &ParamSite {
388 &self.sites[id.0]
389 }
390
391 pub fn add_def(&mut self, name: String, ctor: Option<String>, span: SrcSpan) -> ModuleDefId {
392 let id = ModuleDefId(self.defs.len());
393 self.defs.push(ModuleDef {
394 id,
395 name,
396 ctor,
397 span,
398 sites: Vec::new(),
399 });
400 id
401 }
402
403 #[allow(clippy::too_many_arguments)]
404 pub fn add_instance(
405 &mut self,
406 def: ModuleDefId,
407 parent: Option<ModuleInstanceId>,
408 via_field: Option<String>,
409 prefix: Key,
410 root: String,
411 prefix_derived: bool,
412 repeat: Option<Repeat>,
413 origin: SrcSpan,
414 certainty: Certainty,
415 ) -> ModuleInstanceId {
416 let id = ModuleInstanceId(self.instances.len());
417 self.instances.push(ModuleInstance {
418 id,
419 def,
420 parent,
421 via_field,
422 prefix,
423 root,
424 prefix_derived,
425 repeat,
426 origin,
427 children: Vec::new(),
428 certainty,
429 });
430 if let Some(p) = parent {
431 self.instances[p.0].children.push(id);
432 }
433 id
434 }
435
436 pub fn derive_prefixes(&mut self) {
445 let order: Vec<ModuleInstanceId> =
446 (0..self.instances.len()).map(ModuleInstanceId).collect();
447 for id in order.into_iter().rev() {
448 if !self.instances[id.0].prefix_derived {
449 continue;
450 }
451
452 let mut by_root: BTreeMap<String, Vec<Key>> = BTreeMap::new();
453 for p in self.params.iter().filter(|p| p.owner == id) {
454 by_root
455 .entry(p.root.clone())
456 .or_default()
457 .push(p.key.clone());
458 }
459 for child in self.instances[id.0].children.clone() {
460 let child = &self.instances[child.0];
461 if !child.prefix.is_empty() && !child.root.is_empty() {
462 by_root
463 .entry(child.root.clone())
464 .or_default()
465 .push(child.prefix.clone());
466 }
467 }
468
469 let Some((root, keys)) = by_root.into_iter().max_by_key(|(_, k)| k.len()) else {
470 continue;
471 };
472 if let Some(common) = longest_common_prefix(&keys) {
473 self.instances[id.0].prefix = common;
474 self.instances[id.0].root = root;
475 }
476 }
477 }
478
479 #[allow(clippy::too_many_arguments)]
480 pub fn add_site(
481 &mut self,
482 owner: ModuleDefId,
483 acquisition: Acquisition,
484 relative_key: Key,
485 kind: crate::known::ParamKind,
486 shape: Option<String>,
487 span: SrcSpan,
488 certainty: Certainty,
489 ) -> ParamSiteId {
490 let id = ParamSiteId(self.sites.len());
491 self.sites.push(ParamSite {
492 id,
493 owner,
494 acquisition,
495 relative_key,
496 kind,
497 shape,
498 span,
499 certainty,
500 });
501 self.defs[owner.0].sites.push(id);
502 id
503 }
504
505 pub fn add_param(
506 &mut self,
507 site: ParamSiteId,
508 owner: ModuleInstanceId,
509 key: Key,
510 root: String,
511 certainty: Certainty,
512 ) -> ParamId {
513 let id = ParamId(self.params.len());
514 self.params.push(Param {
515 id,
516 site,
517 owner,
518 key,
519 root,
520 certainty,
521 checkpoint: CheckpointMatch::NotChecked,
522 });
523 id
524 }
525
526 pub fn diagnose(&mut self, span: SrcSpan, message: impl Into<String>, key: Option<Key>) {
527 self.diagnostics.push(Diagnostic {
528 span,
529 message: message.into(),
530 key,
531 });
532 }
533
534 pub fn dedupe_params(&mut self) {
541 let mut seen: BTreeMap<(String, String), ParamId> = BTreeMap::new();
542 let mut keep: Vec<bool> = vec![true; self.params.len()];
543
544 for (index, should_keep) in keep.iter_mut().enumerate() {
545 let ident = (
546 self.params[index].root.clone(),
547 self.params[index].key.to_string(),
548 );
549 match seen.get(&ident) {
550 Some(first) => {
551 let first = *first;
552 *should_keep = false;
553 let incoming = self.params[index].certainty.clone();
554 let existing = self.params[first.0].certainty.clone();
555 self.params[first.0].certainty = least_certain(existing, incoming);
556 }
557 None => {
558 seen.insert(ident, ParamId(index));
559 }
560 }
561 }
562
563 let mut next = 0usize;
564 let mut remap: Vec<Option<ParamId>> = vec![None; self.params.len()];
565 let mut kept = Vec::new();
566 for (index, param) in self.params.drain(..).enumerate() {
567 if keep[index] {
568 remap[index] = Some(ParamId(next));
569 let mut param = param;
570 param.id = ParamId(next);
571 kept.push(param);
572 next += 1;
573 }
574 }
575 self.params = kept;
576 let _ = remap;
577 }
578
579 pub fn roots(&self) -> Vec<String> {
581 let mut seen = Vec::new();
582 for p in &self.params {
583 if !seen.contains(&p.root) {
584 seen.push(p.root.clone());
585 }
586 }
587 seen
588 }
589
590 pub fn coverage(&self) -> Coverage {
591 let mut c = Coverage {
592 instances: self.instances.len(),
593 params: self.params.len(),
594 diagnostics: self.diagnostics.len(),
595 ..Default::default()
596 };
597 for p in &self.params {
598 match p.certainty {
599 Certainty::Certain => c.params_certain += 1,
600 Certainty::Conditional(_) => c.params_conditional += 1,
601 Certainty::Unknown(_) => c.params_unknown += 1,
602 }
603 }
604 c
605 }
606}
607
608fn least_certain(a: Certainty, b: Certainty) -> Certainty {
610 match (&a, &b) {
611 (Certainty::Unknown(_), _) => a,
612 (_, Certainty::Unknown(_)) => b,
613 (Certainty::Conditional(_), _) => a,
614 (_, Certainty::Conditional(_)) => b,
615 _ => Certainty::Certain,
616 }
617}
618
619fn longest_common_prefix(keys: &[Key]) -> Option<Key> {
621 let first = keys.first()?;
622 let mut len = first.segs.len();
623 for key in &keys[1..] {
624 let shared = first
625 .segs
626 .iter()
627 .zip(&key.segs)
628 .take_while(|(a, b)| a == b)
629 .count();
630 len = len.min(shared);
631 }
632 (len > 0).then(|| Key {
633 segs: first.segs[..len].to_vec(),
634 })
635}