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 from_dotted(text: &str) -> Self {
132 Key {
133 segs: text
134 .split('.')
135 .filter(|part| !part.is_empty())
136 .map(|part| {
137 if part.contains('{') && part.contains('}') {
138 KeySeg::Template {
139 text: part.to_string(),
140 }
141 } else {
142 KeySeg::Literal(part.to_string())
143 }
144 })
145 .collect(),
146 }
147 }
148
149 pub fn push_literal(&self, text: &str) -> Self {
152 let mut next = self.clone();
153 for part in text.split('.').filter(|p| !p.is_empty()) {
154 next.segs.push(KeySeg::Literal(part.to_string()));
155 }
156 next
157 }
158
159 pub fn push(&self, seg: KeySeg) -> Self {
160 let mut next = self.clone();
161 next.segs.push(seg);
162 next
163 }
164
165 pub fn extend(&self, segs: &[KeySeg]) -> Self {
166 let mut next = self.clone();
167 next.segs.extend_from_slice(segs);
168 next
169 }
170
171 pub fn is_empty(&self) -> bool {
172 self.segs.is_empty()
173 }
174
175 pub fn is_template(&self) -> bool {
177 self.segs
178 .iter()
179 .any(|s| matches!(s, KeySeg::Dynamic { .. } | KeySeg::Template { .. }))
180 }
181
182 pub fn matches(&self, concrete: &str) -> bool {
186 let parts: Vec<&str> = concrete.split('.').filter(|p| !p.is_empty()).collect();
187 if parts.len() != self.segs.len() {
188 return false;
189 }
190 self.segs.iter().zip(parts).all(|(seg, part)| match seg {
191 KeySeg::Literal(n) => n == part,
192 KeySeg::Dynamic { .. } => true,
193 KeySeg::Template { text } => template_segment_matches(text, part),
194 })
195 }
196}
197
198fn template_segment_matches(template: &str, concrete: &str) -> bool {
199 let mut literals = Vec::new();
200 let mut cursor = 0usize;
201 while let Some(open_rel) = template[cursor..].find('{') {
202 let open = cursor + open_rel;
203 let Some(close_rel) = template[open + 1..].find('}') else {
204 return template == concrete;
205 };
206 let close = open + 1 + close_rel;
207 literals.push(&template[cursor..open]);
208 cursor = close + 1;
209 }
210 if literals.is_empty() {
211 return template == concrete;
212 }
213 literals.push(&template[cursor..]);
214
215 let starts_with_wildcard = template.starts_with('{');
216 let ends_with_wildcard = template.ends_with('}');
217 let mut position = 0usize;
218 for (index, literal) in literals.iter().enumerate() {
219 if literal.is_empty() {
220 continue;
221 }
222 if index == 0 && !starts_with_wildcard {
223 if !concrete.starts_with(literal) {
224 return false;
225 }
226 position = literal.len();
227 continue;
228 }
229 let Some(found) = concrete[position..].find(literal) else {
230 return false;
231 };
232 position += found + literal.len();
233 }
234 ends_with_wildcard
235 || literals
236 .last()
237 .is_some_and(|suffix| concrete.ends_with(suffix))
238}
239
240impl fmt::Display for Key {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 let joined: Vec<String> = self.segs.iter().map(|s| s.to_string()).collect();
243 write!(f, "{}", joined.join("."))
244 }
245}
246
247macro_rules! id_type {
248 ($name:ident) => {
249 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize)]
250 pub struct $name(pub usize);
251 };
252}
253
254id_type!(ModuleDefId);
255id_type!(ModuleInstanceId);
256id_type!(ParamSiteId);
257id_type!(ParamId);
258
259#[derive(Debug, Clone, Serialize)]
261pub struct ModuleDef {
262 pub id: ModuleDefId,
263 pub name: String,
265 pub ctor: Option<String>,
267 pub span: SrcSpan,
268 pub sites: Vec<ParamSiteId>,
269}
270
271#[derive(Debug, Clone, Serialize)]
273pub struct Repeat {
274 pub var: String,
276 pub bound: String,
278}
279
280#[derive(Debug, Clone, Serialize)]
282pub struct ModuleInstance {
283 pub id: ModuleInstanceId,
284 pub def: ModuleDefId,
285 pub parent: Option<ModuleInstanceId>,
286 pub via_field: Option<String>,
289 pub prefix: Key,
291 pub root: String,
296 pub prefix_derived: bool,
300 pub repeat: Option<Repeat>,
301 pub origin: SrcSpan,
302 pub children: Vec<ModuleInstanceId>,
303 pub certainty: Certainty,
304}
305
306#[derive(Debug, Clone, Serialize)]
308#[serde(tag = "via", rename_all = "snake_case")]
309pub enum Acquisition {
310 Constructor { func: String, cite: &'static str },
312 RawGet { method: String },
314}
315
316#[derive(Debug, Clone, Serialize)]
318pub struct ParamSite {
319 pub id: ParamSiteId,
320 pub owner: ModuleDefId,
321 pub acquisition: Acquisition,
322 pub relative_key: Key,
324 pub kind: crate::known::ParamKind,
325 pub shape: Option<String>,
328 pub span: SrcSpan,
329 pub certainty: Certainty,
330}
331
332#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
336#[serde(tag = "match", rename_all = "snake_case")]
337pub enum CheckpointMatch {
338 NotChecked,
339 Found {
341 name: String,
342 shape: Vec<usize>,
343 dtype: String,
344 },
345 FoundMany {
347 count: usize,
348 sample: String,
349 },
350 Missing,
351}
352
353#[derive(Debug, Clone, Serialize)]
355pub struct Param {
356 pub id: ParamId,
357 pub site: ParamSiteId,
358 pub owner: ModuleInstanceId,
359 pub key: Key,
361 pub root: String,
363 pub certainty: Certainty,
364 pub checkpoint: CheckpointMatch,
365}
366
367#[derive(Debug, Clone, Serialize)]
369pub struct Diagnostic {
370 pub span: SrcSpan,
371 pub message: String,
372 pub key: Option<Key>,
373}
374
375#[derive(Debug, Clone, Default, Serialize)]
378pub struct Coverage {
379 pub instances: usize,
380 pub params: usize,
381 pub params_certain: usize,
382 pub params_conditional: usize,
383 pub params_unknown: usize,
384 pub diagnostics: usize,
385}
386
387#[derive(Debug, Default, Serialize)]
388pub struct Structure {
389 pub defs: Vec<ModuleDef>,
390 pub instances: Vec<ModuleInstance>,
391 pub sites: Vec<ParamSite>,
392 pub params: Vec<Param>,
393 pub root: Option<ModuleInstanceId>,
394 pub diagnostics: Vec<Diagnostic>,
395}
396
397impl Structure {
398 pub fn def(&self, id: ModuleDefId) -> &ModuleDef {
399 &self.defs[id.0]
400 }
401
402 pub fn instance(&self, id: ModuleInstanceId) -> &ModuleInstance {
403 &self.instances[id.0]
404 }
405
406 pub fn site(&self, id: ParamSiteId) -> &ParamSite {
407 &self.sites[id.0]
408 }
409
410 pub fn add_def(&mut self, name: String, ctor: Option<String>, span: SrcSpan) -> ModuleDefId {
411 let id = ModuleDefId(self.defs.len());
412 self.defs.push(ModuleDef {
413 id,
414 name,
415 ctor,
416 span,
417 sites: Vec::new(),
418 });
419 id
420 }
421
422 #[allow(clippy::too_many_arguments)]
423 pub fn add_instance(
424 &mut self,
425 def: ModuleDefId,
426 parent: Option<ModuleInstanceId>,
427 via_field: Option<String>,
428 prefix: Key,
429 root: String,
430 prefix_derived: bool,
431 repeat: Option<Repeat>,
432 origin: SrcSpan,
433 certainty: Certainty,
434 ) -> ModuleInstanceId {
435 let id = ModuleInstanceId(self.instances.len());
436 self.instances.push(ModuleInstance {
437 id,
438 def,
439 parent,
440 via_field,
441 prefix,
442 root,
443 prefix_derived,
444 repeat,
445 origin,
446 children: Vec::new(),
447 certainty,
448 });
449 if let Some(p) = parent {
450 self.instances[p.0].children.push(id);
451 }
452 id
453 }
454
455 pub fn derive_prefixes(&mut self) {
464 let order: Vec<ModuleInstanceId> =
465 (0..self.instances.len()).map(ModuleInstanceId).collect();
466 for id in order.into_iter().rev() {
467 if !self.instances[id.0].prefix_derived {
468 continue;
469 }
470
471 let mut by_root: BTreeMap<String, Vec<Key>> = BTreeMap::new();
472 for p in self.params.iter().filter(|p| p.owner == id) {
473 by_root
474 .entry(p.root.clone())
475 .or_default()
476 .push(p.key.clone());
477 }
478 for child in self.instances[id.0].children.clone() {
479 let child = &self.instances[child.0];
480 if !child.prefix.is_empty() && !child.root.is_empty() {
481 by_root
482 .entry(child.root.clone())
483 .or_default()
484 .push(child.prefix.clone());
485 }
486 }
487
488 let Some((root, keys)) = by_root.into_iter().max_by_key(|(_, k)| k.len()) else {
489 continue;
490 };
491 if let Some(common) = longest_common_prefix(&keys) {
492 self.instances[id.0].prefix = common;
493 self.instances[id.0].root = root;
494 }
495 }
496 }
497
498 #[allow(clippy::too_many_arguments)]
499 pub fn add_site(
500 &mut self,
501 owner: ModuleDefId,
502 acquisition: Acquisition,
503 relative_key: Key,
504 kind: crate::known::ParamKind,
505 shape: Option<String>,
506 span: SrcSpan,
507 certainty: Certainty,
508 ) -> ParamSiteId {
509 let id = ParamSiteId(self.sites.len());
510 self.sites.push(ParamSite {
511 id,
512 owner,
513 acquisition,
514 relative_key,
515 kind,
516 shape,
517 span,
518 certainty,
519 });
520 self.defs[owner.0].sites.push(id);
521 id
522 }
523
524 pub fn add_param(
525 &mut self,
526 site: ParamSiteId,
527 owner: ModuleInstanceId,
528 key: Key,
529 root: String,
530 certainty: Certainty,
531 ) -> ParamId {
532 let id = ParamId(self.params.len());
533 self.params.push(Param {
534 id,
535 site,
536 owner,
537 key,
538 root,
539 certainty,
540 checkpoint: CheckpointMatch::NotChecked,
541 });
542 id
543 }
544
545 pub fn diagnose(&mut self, span: SrcSpan, message: impl Into<String>, key: Option<Key>) {
546 self.diagnostics.push(Diagnostic {
547 span,
548 message: message.into(),
549 key,
550 });
551 }
552
553 pub fn dedupe_params(&mut self) {
560 let mut seen: BTreeMap<(String, String), ParamId> = BTreeMap::new();
561 let mut keep: Vec<bool> = vec![true; self.params.len()];
562
563 for (index, should_keep) in keep.iter_mut().enumerate() {
564 let ident = (
565 self.params[index].root.clone(),
566 self.params[index].key.to_string(),
567 );
568 match seen.get(&ident) {
569 Some(first) => {
570 let first = *first;
571 *should_keep = false;
572 let incoming = self.params[index].certainty.clone();
573 let existing = self.params[first.0].certainty.clone();
574 self.params[first.0].certainty = least_certain(existing, incoming);
575 }
576 None => {
577 seen.insert(ident, ParamId(index));
578 }
579 }
580 }
581
582 let mut next = 0usize;
583 let mut remap: Vec<Option<ParamId>> = vec![None; self.params.len()];
584 let mut kept = Vec::new();
585 for (index, param) in self.params.drain(..).enumerate() {
586 if keep[index] {
587 remap[index] = Some(ParamId(next));
588 let mut param = param;
589 param.id = ParamId(next);
590 kept.push(param);
591 next += 1;
592 }
593 }
594 self.params = kept;
595 let _ = remap;
596 }
597
598 pub fn roots(&self) -> Vec<String> {
600 let mut seen = Vec::new();
601 for p in &self.params {
602 if !seen.contains(&p.root) {
603 seen.push(p.root.clone());
604 }
605 }
606 seen
607 }
608
609 pub fn coverage(&self) -> Coverage {
610 let mut c = Coverage {
611 instances: self.instances.len(),
612 params: self.params.len(),
613 diagnostics: self.diagnostics.len(),
614 ..Default::default()
615 };
616 for p in &self.params {
617 match p.certainty {
618 Certainty::Certain => c.params_certain += 1,
619 Certainty::Conditional(_) => c.params_conditional += 1,
620 Certainty::Unknown(_) => c.params_unknown += 1,
621 }
622 }
623 c
624 }
625}
626
627fn least_certain(a: Certainty, b: Certainty) -> Certainty {
629 match (&a, &b) {
630 (Certainty::Unknown(_), _) => a,
631 (_, Certainty::Unknown(_)) => b,
632 (Certainty::Conditional(_), _) => a,
633 (_, Certainty::Conditional(_)) => b,
634 _ => Certainty::Certain,
635 }
636}
637
638fn longest_common_prefix(keys: &[Key]) -> Option<Key> {
640 let first = keys.first()?;
641 let mut len = first.segs.len();
642 for key in &keys[1..] {
643 let shared = first
644 .segs
645 .iter()
646 .zip(&key.segs)
647 .take_while(|(a, b)| a == b)
648 .count();
649 len = len.min(shared);
650 }
651 (len > 0).then(|| Key {
652 segs: first.segs[..len].to_vec(),
653 })
654}