1mod deps;
2pub(crate) mod names;
3#[cfg(test)]
4mod tests;
5
6use std::collections::{HashMap, HashSet};
7use std::sync::Arc;
8
9use miette::NamedSource;
10
11use crate::desugar::desugared_ast::{
12 AssertBody, AttributeArg, DeclKind, DimExpr, Expr, ExprKind, File, IndexExpr, TypeDeclBody,
13 TypeExpr, TypeExprKind,
14};
15use crate::registry::error::GraphcalError;
16use crate::registry::prelude::{
17 PRELUDE_BUILTIN_TYPE_NAMES, PRELUDE_DIMENSION_NAMES, PRELUDE_UNIT_NAMES,
18};
19use crate::registry::resolve_types::{
20 ResolvedAssertEntry, ResolvedConstEntry, ResolvedFigureEntry, ResolvedLayerEntry,
21 ResolvedNodeEntry, ResolvedParamEntry, ResolvedPlotEntry,
22};
23use crate::syntax::attribute::AttributeName;
24use crate::syntax::names::{DeclName, NameAtom};
25use crate::syntax::span::Span;
26
27pub use crate::registry::resolve_types::{
29 DeclCategory, ExpectedFail, ExpectedFailKey, ExpectedFailKeyPart, ImportedValueNames,
30 ResolvedFile, is_aggregation_fn, is_time_scale_name,
31};
32pub use crate::syntax::names::ScopedName;
33
34pub use deps::{collect_graph_ref_names, collect_graph_refs, contains_graph_ref};
36
37use names::parse_expected_fail_args;
39
40fn register_value_namespace_name(
41 value_names: &mut HashMap<ScopedName, Span>,
42 name: String,
43 span: Span,
44 src: &NamedSource<Arc<String>>,
45) -> Result<(), GraphcalError> {
46 let scoped_name = ScopedName::local(name.clone());
47 if let Some(first_span) = value_names.get(&scoped_name) {
48 return Err(GraphcalError::DuplicateName {
49 name,
50 src: src.clone(),
51 duplicate: span.into(),
52 first: (*first_span).into(),
53 });
54 }
55 value_names.insert(scoped_name, span);
56 Ok(())
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60enum ExclusiveUniverse {
61 Type,
62 Index,
63 Value,
64}
65
66type ExclusiveUniverseBinding = (ExclusiveUniverse, Span);
67
68fn register_exclusive_universe_name(
69 occupied: &mut HashMap<NameAtom, ExclusiveUniverseBinding>,
70 atom: &NameAtom,
71 universe: ExclusiveUniverse,
72 span: Span,
73 src: &NamedSource<Arc<String>>,
74) -> Result<(), GraphcalError> {
75 occupied
76 .insert(atom.clone(), (universe, span))
77 .map_or(Ok(()), |first| {
78 Err(GraphcalError::DuplicateName {
79 name: atom.to_string(),
80 src: src.clone(),
81 duplicate: span.into(),
82 first: first.1.into(),
83 })
84 })
85}
86
87fn check_builtin_name_shadowing(
88 file: &File,
89 src: &NamedSource<Arc<String>>,
90) -> Result<(), GraphcalError> {
91 for decl in &file.declarations {
92 let shadowed = match &decl.kind {
93 DeclKind::BaseDimension(d) if is_builtin_type_name(d.name.value.as_str()) => {
94 Some(("dimension", d.name.value.to_string(), d.name.span))
95 }
96 DeclKind::Dimension(d) if is_builtin_type_name(d.name.value.as_str()) => {
97 Some(("dimension", d.name.value.to_string(), d.name.span))
98 }
99 DeclKind::Type(t) if is_builtin_type_name(t.name.value.as_str()) => {
100 Some(("type", t.name.value.to_string(), t.name.span))
101 }
102 DeclKind::Index(i) if is_builtin_type_name(i.name.value.as_str()) => {
103 Some(("index", i.name.value.to_string(), i.name.span))
104 }
105 DeclKind::Unit(u) if PRELUDE_UNIT_NAMES.contains(&u.name.value.as_str()) => {
106 Some(("unit", u.name.value.to_string(), u.name.span))
107 }
108 _ => None,
109 };
110
111 if let Some((kind, name, span)) = shadowed {
112 return Err(GraphcalError::BuiltinNameShadowed {
113 kind,
114 name,
115 src: src.clone(),
116 span: span.into(),
117 });
118 }
119 }
120
121 Ok(())
122}
123
124fn is_builtin_type_name(name: &str) -> bool {
125 PRELUDE_DIMENSION_NAMES.contains(&name) || PRELUDE_BUILTIN_TYPE_NAMES.contains(&name)
126}
127
128fn check_exclusive_universe_collisions(
129 file: &File,
130 src: &NamedSource<Arc<String>>,
131 names: &HashMap<ScopedName, Span>,
132) -> Result<(), GraphcalError> {
133 let mut occupied = names
134 .iter()
135 .filter(|(name, _)| !name.is_qualified())
136 .map(|(name, span)| {
137 (
138 DeclName::new(name.member()).into_atom(),
139 (ExclusiveUniverse::Value, *span),
140 )
141 })
142 .collect::<HashMap<_, _>>();
143
144 for (atom, universe, span) in file
145 .declarations
146 .iter()
147 .filter_map(|decl| exclusive_universe_decl(&decl.kind))
148 {
149 register_exclusive_universe_name(&mut occupied, atom, universe, span, src)?;
150 }
151
152 Ok(())
153}
154
155fn exclusive_universe_decl(decl: &DeclKind) -> Option<(&NameAtom, ExclusiveUniverse, Span)> {
156 match decl {
157 DeclKind::Param(p) => Some((p.name.value.atom(), ExclusiveUniverse::Value, p.name.span)),
158 DeclKind::Node(n) => Some((n.name.value.atom(), ExclusiveUniverse::Value, n.name.span)),
159 DeclKind::ConstNode(c) => {
160 Some((c.name.value.atom(), ExclusiveUniverse::Value, c.name.span))
161 }
162 DeclKind::Assert(a) => Some((a.name.value.atom(), ExclusiveUniverse::Value, a.name.span)),
163 DeclKind::Plot(p) => Some((p.name.value.atom(), ExclusiveUniverse::Value, p.name.span)),
164 DeclKind::Figure(f) => Some((f.name.value.atom(), ExclusiveUniverse::Value, f.name.span)),
165 DeclKind::Layer(l) => Some((l.name.value.atom(), ExclusiveUniverse::Value, l.name.span)),
166 DeclKind::Dag(d) => Some((d.name.value.atom(), ExclusiveUniverse::Value, d.name.span)),
167 DeclKind::BaseDimension(d) => {
168 Some((d.name.value.atom(), ExclusiveUniverse::Type, d.name.span))
169 }
170 DeclKind::Dimension(d) => Some((d.name.value.atom(), ExclusiveUniverse::Type, d.name.span)),
171 DeclKind::Type(t) => Some((t.name.value.atom(), ExclusiveUniverse::Type, t.name.span)),
172 DeclKind::Index(i) => Some((i.name.value.atom(), ExclusiveUniverse::Index, i.name.span)),
173 DeclKind::Unit(_) | DeclKind::Import(_) | DeclKind::Include(_) => None,
174 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
175 }
176}
177
178fn check_value_namespace_collisions(
179 file: &File,
180 src: &NamedSource<Arc<String>>,
181 names: &HashMap<ScopedName, Span>,
182) -> Result<(), GraphcalError> {
183 let mut value_names: HashMap<ScopedName, Span> = names.clone();
184
185 for decl in &file.declarations {
186 match &decl.kind {
187 DeclKind::Param(p) => register_value_namespace_name(
188 &mut value_names,
189 p.name.value.to_string(),
190 p.name.span,
191 src,
192 )?,
193 DeclKind::Node(n) => register_value_namespace_name(
194 &mut value_names,
195 n.name.value.to_string(),
196 n.name.span,
197 src,
198 )?,
199 DeclKind::ConstNode(c) => register_value_namespace_name(
200 &mut value_names,
201 c.name.value.to_string(),
202 c.name.span,
203 src,
204 )?,
205 DeclKind::Assert(a) => register_value_namespace_name(
206 &mut value_names,
207 a.name.value.to_string(),
208 a.name.span,
209 src,
210 )?,
211 DeclKind::Plot(p) => register_value_namespace_name(
212 &mut value_names,
213 p.name.value.to_string(),
214 p.name.span,
215 src,
216 )?,
217 DeclKind::Figure(f) => register_value_namespace_name(
218 &mut value_names,
219 f.name.value.to_string(),
220 f.name.span,
221 src,
222 )?,
223 DeclKind::Layer(l) => register_value_namespace_name(
224 &mut value_names,
225 l.name.value.to_string(),
226 l.name.span,
227 src,
228 )?,
229 DeclKind::Type(t) => {
230 if let TypeDeclBody::Constructors(members) = &t.body {
231 for member in members {
232 register_value_namespace_name(
233 &mut value_names,
234 member.name.value.to_string(),
235 member.name.span,
236 src,
237 )?;
238 }
239 }
240 }
241 DeclKind::BaseDimension(_)
242 | DeclKind::Dimension(_)
243 | DeclKind::Unit(_)
244 | DeclKind::Index(_)
245 | DeclKind::Import(_)
246 | DeclKind::Include(_)
247 | DeclKind::Dag(_) => {}
248 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
249 }
250 }
251
252 Ok(())
253}
254
255struct CollectedDeclarations {
257 consts: Vec<ResolvedConstEntry>,
258 params: Vec<ResolvedParamEntry>,
259 nodes: Vec<ResolvedNodeEntry>,
260 asserts: Vec<ResolvedAssertEntry>,
261 plots: Vec<ResolvedPlotEntry>,
262 figures: Vec<ResolvedFigureEntry>,
263 layers: Vec<ResolvedLayerEntry>,
264 source_order: Vec<(DeclName, DeclCategory)>,
265 assert_names: HashSet<DeclName>,
266 pub_names: HashSet<DeclName>,
267}
268
269#[expect(
273 clippy::too_many_lines,
274 reason = "complex declaration collection with multiple passes"
275)]
276fn collect_local_declarations(
277 file: &File,
278 src: &NamedSource<Arc<String>>,
279 names: &mut HashMap<ScopedName, Span>,
280) -> Result<CollectedDeclarations, GraphcalError> {
281 let mut consts = Vec::new();
282 let mut params = Vec::new();
283 let mut nodes = Vec::new();
284 let mut asserts = Vec::new();
285 let mut plots = Vec::new();
286 let mut figures = Vec::new();
287 let mut layers = Vec::new();
288 let mut source_order: Vec<(DeclName, DeclCategory)> = Vec::new();
289 let mut assert_names: HashSet<DeclName> = HashSet::new();
290
291 check_builtin_name_shadowing(file, src)?;
292 check_exclusive_universe_collisions(file, src, names)?;
293 check_value_namespace_collisions(file, src, names)?;
294
295 let mut pub_names: HashSet<DeclName> = HashSet::new();
299 for decl in &file.declarations {
300 let is_visible = match &decl.kind {
301 DeclKind::Param(_) => true,
302 DeclKind::Node(d) => d.visibility.is_public(),
303 DeclKind::ConstNode(d) => d.visibility.is_public(),
304 DeclKind::BaseDimension(d) => d.visibility.is_public(),
305 DeclKind::Dimension(d) => d.visibility.is_public(),
306 DeclKind::Unit(d) => d.visibility.is_public(),
307 DeclKind::Type(d) => d.visibility.is_public(),
308 DeclKind::Index(d) => d.visibility.is_public(),
309 DeclKind::Import(d) => d.visibility.is_public(),
310 DeclKind::Include(d) => d.visibility.is_public(),
311 DeclKind::Dag(d) => d.visibility.is_public(),
312 DeclKind::Assert(d) => d.visibility.is_public(),
313 DeclKind::Plot(d) => d.visibility.is_public(),
314 DeclKind::Figure(d) => d.visibility.is_public(),
315 DeclKind::Layer(d) => d.visibility.is_public(),
316 DeclKind::Sugar(_) => false,
317 };
318 if !is_visible {
319 continue;
320 }
321 let Some((name, _)) = decl.kind.name_and_span() else {
322 continue;
323 };
324 pub_names.insert(DeclName::new(name));
325 }
326
327 for decl in &file.declarations {
334 match &decl.kind {
335 DeclKind::Index(idx) if idx.kind.is_required() && !idx.visibility.is_bindable() => {
336 return Err(GraphcalError::RequiredItemMustBeBindable {
337 kind: "index".to_string(),
338 name: idx.name.value.to_string(),
339 src: src.clone(),
340 span: idx.name.span.into(),
341 });
342 }
343 DeclKind::Type(t)
344 if matches!(t.body, TypeDeclBody::Required) && !t.visibility.is_bindable() =>
345 {
346 return Err(GraphcalError::RequiredItemMustBeBindable {
347 kind: "type".to_string(),
348 name: t.name.value.to_string(),
349 src: src.clone(),
350 span: t.name.span.into(),
351 });
352 }
353 DeclKind::Dimension(d) if d.definition.is_none() && !d.visibility.is_bindable() => {
354 return Err(GraphcalError::RequiredItemMustBeBindable {
355 kind: "dim".to_string(),
356 name: d.name.value.to_string(),
357 src: src.clone(),
358 span: d.name.span.into(),
359 });
360 }
361 _ => {}
362 }
363 }
364
365 for decl in &file.declarations {
367 let (name, name_span) = match &decl.kind {
369 DeclKind::Param(p) => (p.name.value.to_string(), p.name.span),
370 DeclKind::Node(n) => (n.name.value.to_string(), n.name.span),
371 DeclKind::ConstNode(c) => (c.name.value.to_string(), c.name.span),
372 DeclKind::Assert(a) => (a.name.value.to_string(), a.name.span),
373 DeclKind::Plot(p) => (p.name.value.to_string(), p.name.span),
374 DeclKind::Figure(f) => (f.name.value.to_string(), f.name.span),
375 DeclKind::Layer(l) => (l.name.value.to_string(), l.name.span),
376 DeclKind::BaseDimension(_)
377 | DeclKind::Dimension(_)
378 | DeclKind::Unit(_)
379 | DeclKind::Type(_)
380 | DeclKind::Index(_)
381 | DeclKind::Import(_)
382 | DeclKind::Include(_)
383 | DeclKind::Dag(_) => {
384 continue;
385 }
386 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
387 };
388
389 let scoped_name = ScopedName::local(name.clone());
390 names.insert(scoped_name, name_span);
391
392 let category = match &decl.kind {
394 DeclKind::Param(_) => DeclCategory::Param,
395 DeclKind::ConstNode(_) => DeclCategory::Const,
396 DeclKind::Node(_) => DeclCategory::Node,
397 DeclKind::Assert(_) => {
398 assert_names.insert(DeclName::new(name.as_str()));
399 DeclCategory::Assert
400 }
401 DeclKind::Plot(_) => DeclCategory::Plot,
402 DeclKind::Figure(_) => DeclCategory::Figure,
403 DeclKind::Layer(_) => DeclCategory::Layer,
404 DeclKind::BaseDimension(_)
405 | DeclKind::Dimension(_)
406 | DeclKind::Unit(_)
407 | DeclKind::Type(_)
408 | DeclKind::Index(_)
409 | DeclKind::Import(_)
410 | DeclKind::Include(_)
411 | DeclKind::Dag(_) => {
412 continue;
414 }
415 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
416 };
417 source_order.push((DeclName::new(name.as_str()), category));
418 }
419
420 for decl in &file.declarations {
424 match &decl.kind {
425 DeclKind::BaseDimension(_)
426 | DeclKind::Dimension(_)
427 | DeclKind::Unit(_)
428 | DeclKind::Type(_)
429 | DeclKind::Index(_)
430 | DeclKind::Import(_)
431 | DeclKind::Include(_)
432 | DeclKind::Dag(_) => {}
433 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
434 DeclKind::Assert(a) => {
435 asserts.push(ResolvedAssertEntry {
436 name: a.name.value.to_string(),
437 body: a.body.clone(),
438 span: decl.span,
439 });
440 }
441 DeclKind::Plot(p) => {
442 plots.push(ResolvedPlotEntry {
443 name: p.name.value.to_string(),
444 decl: p.clone(),
445 span: decl.span,
446 });
447 }
448 DeclKind::Figure(f) => {
449 figures.push(ResolvedFigureEntry {
450 name: f.name.value.to_string(),
451 decl: f.clone(),
452 span: decl.span,
453 });
454 }
455 DeclKind::Layer(l) => {
456 layers.push(ResolvedLayerEntry {
457 name: l.name.value.to_string(),
458 decl: l.clone(),
459 span: decl.span,
460 });
461 }
462 DeclKind::Param(p) => {
463 params.push(ResolvedParamEntry {
464 name: p.name.value.to_string(),
465 default_expr: p.value.clone(),
466 span: decl.span,
467 });
468 }
469 DeclKind::ConstNode(c) => {
470 consts.push(ResolvedConstEntry {
471 name: c.name.value.to_string(),
472 expr: c.value.clone(),
473 span: decl.span,
474 });
475 }
476 DeclKind::Node(n) => {
477 nodes.push(ResolvedNodeEntry {
478 name: n.name.value.to_string(),
479 expr: n.value.clone(),
480 span: decl.span,
481 });
482 }
483 }
484 }
485
486 Ok(CollectedDeclarations {
487 consts,
488 params,
489 nodes,
490 asserts,
491 plots,
492 figures,
493 layers,
494 source_order,
495 assert_names,
496 pub_names,
497 })
498}
499
500struct ValidatedAttributes {
502 assumes_map: HashMap<DeclName, Vec<DeclName>>,
503 expected_fail_map: HashMap<DeclName, ExpectedFail>,
504 hidden_plots: HashSet<DeclName>,
507}
508
509#[expect(clippy::too_many_lines, reason = "comprehensive attribute validation")]
511fn validate_attributes(
512 file: &File,
513 src: &NamedSource<Arc<String>>,
514 assert_names: &HashSet<DeclName>,
515) -> Result<ValidatedAttributes, GraphcalError> {
516 let mut assumes_map: HashMap<DeclName, Vec<DeclName>> = HashMap::new();
517 let mut expected_fail_map: HashMap<DeclName, ExpectedFail> = HashMap::new();
518 let mut hidden_plots: HashSet<DeclName> = HashSet::new();
519
520 for decl in &file.declarations {
521 let decl_name: Option<DeclName> = match &decl.kind {
522 DeclKind::Param(p) => Some(p.name.value.clone()),
523 DeclKind::Node(n) => Some(n.name.value.clone()),
524 DeclKind::ConstNode(c) => Some(c.name.value.clone()),
525 DeclKind::Assert(a) => Some(a.name.value.clone()),
526 DeclKind::Plot(p) => Some(p.name.value.clone()),
527 DeclKind::Figure(f) => Some(f.name.value.clone()),
528 _ => None,
529 };
530 for attr in &decl.attributes {
531 let attr_name_str = attr.name.name.as_str();
532 let attr_name = attr_name_str.parse::<AttributeName>().map_err(|err| {
533 GraphcalError::UnknownAttribute {
534 name: err.into_raw(),
535 src: src.clone(),
536 span: attr.span.into(),
537 }
538 })?;
539
540 match attr_name {
541 AttributeName::Assumes => {
542 let kind = match &decl.kind {
544 DeclKind::ConstNode(_) => Some("const node"),
545 DeclKind::Param(_) | DeclKind::Node(_) => None,
546 DeclKind::Assert(_) => Some("assert"),
547 DeclKind::Plot(_) => Some("plot"),
548 DeclKind::Figure(_) => Some("figure"),
549 DeclKind::Layer(_) => Some("layer"),
550
551 DeclKind::BaseDimension(_) | DeclKind::Dimension(_) => Some("dim"),
552 DeclKind::Unit(_) => Some("unit"),
553 DeclKind::Type(_) => Some("type"),
554 DeclKind::Index(_) => Some("cat/range"),
555 DeclKind::Import(_) => Some("import"),
556 DeclKind::Include(_) => Some("include"),
557 DeclKind::Dag(_) => Some("dag"),
558 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
559 };
560 if let Some(kind) = kind {
561 return Err(GraphcalError::InvalidAssumesTarget {
562 kind: kind.to_string(),
563 src: src.clone(),
564 span: attr.span.into(),
565 });
566 }
567 for arg in &attr.args {
569 let ident = match arg {
570 AttributeArg::Path { segments, .. } if segments.len() == 1 => {
571 segments.first()
572 }
573 AttributeArg::Path { .. }
574 | AttributeArg::RangeStep { .. }
575 | AttributeArg::Group { .. } => {
576 return Err(GraphcalError::EvalError {
577 message:
578 "`#[assumes(...)]` arguments must be plain identifiers"
579 .to_string(),
580 src: src.clone(),
581 span: arg.span().into(),
582 });
583 }
584 };
585 let arg_name = ident.name.as_str();
586 if !assert_names.contains(arg_name) {
587 return Err(GraphcalError::UnknownAssertInAssumes {
588 name: arg_name.to_string(),
589 src: src.clone(),
590 span: ident.span.into(),
591 });
592 }
593 if let Some(ref dname) = decl_name {
594 assumes_map
595 .entry(DeclName::new(arg_name))
596 .or_default()
597 .push(dname.clone());
598 }
599 }
600 }
601 AttributeName::ExpectedFail => {
602 let kind = match &decl.kind {
603 DeclKind::Assert(a) => {
604 let ef = parse_expected_fail_args(&attr.args, src)?;
606 if matches!(ef, ExpectedFail::All) {
609 let is_indexed = matches!(
610 &a.body,
611 AssertBody::Expr(expr) if matches!(expr.kind, ExprKind::ForComp { .. })
612 );
613 if is_indexed {
614 return Err(GraphcalError::ExpectedFailAllOnIndexed {
615 src: src.clone(),
616 span: attr.span.into(),
617 });
618 }
619 }
620 if let Some(ref dname) = decl_name {
621 expected_fail_map.insert(dname.clone(), ef);
622 }
623 continue;
624 }
625 DeclKind::Param(_) => "param",
626 DeclKind::ConstNode(_) => "const node",
627 DeclKind::Node(_) => "node",
628 DeclKind::Plot(_) => "plot",
629 DeclKind::Figure(_) => "figure",
630 DeclKind::Layer(_) => "layer",
631
632 DeclKind::BaseDimension(_) | DeclKind::Dimension(_) => "dim",
633 DeclKind::Unit(_) => "unit",
634 DeclKind::Type(_) => "type",
635 DeclKind::Index(_) => "cat/range",
636 DeclKind::Import(_) => "import",
637 DeclKind::Include(_) => "include",
638 DeclKind::Dag(_) => "dag",
639 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
640 };
641 return Err(GraphcalError::InvalidExpectedFailTarget {
642 kind: kind.to_string(),
643 src: src.clone(),
644 span: attr.span.into(),
645 });
646 }
647 AttributeName::Hidden => {
648 let kind = match &decl.kind {
652 DeclKind::Plot(_) => None,
653 DeclKind::Param(_) => Some("param"),
654 DeclKind::ConstNode(_) => Some("const node"),
655 DeclKind::Node(_) => Some("node"),
656 DeclKind::Assert(_) => Some("assert"),
657 DeclKind::Figure(_) => Some("figure"),
658 DeclKind::Layer(_) => Some("layer"),
659 DeclKind::BaseDimension(_) | DeclKind::Dimension(_) => Some("dim"),
660 DeclKind::Unit(_) => Some("unit"),
661 DeclKind::Type(_) => Some("type"),
662 DeclKind::Index(_) => Some("cat/range"),
663 DeclKind::Import(_) => Some("import"),
664 DeclKind::Include(_) => Some("include"),
665 DeclKind::Dag(_) => Some("dag"),
666 DeclKind::Sugar(_) => crate::syntax::desugar::unreachable_post_desugar(),
667 };
668 if let Some(kind) = kind {
669 return Err(GraphcalError::InvalidHiddenTarget {
670 kind: kind.to_string(),
671 src: src.clone(),
672 span: attr.span.into(),
673 });
674 }
675 if !attr.args.is_empty() {
676 return Err(GraphcalError::EvalError {
677 message: "`#[hidden]` takes no arguments".to_string(),
678 src: src.clone(),
679 span: attr.span.into(),
680 });
681 }
682 if let Some(ref dname) = decl_name {
683 hidden_plots.insert(dname.clone());
684 }
685 }
686 AttributeName::Lazy => {
687 }
689 }
690 }
691 }
692
693 Ok(ValidatedAttributes {
694 assumes_map,
695 expected_fail_map,
696 hidden_plots,
697 })
698}
699
700#[expect(
711 clippy::too_many_lines,
712 reason = "exhaustive declaration-kind validation is clearer in one pass"
713)]
714fn validate_private_in_public(
715 file: &File,
716 src: &NamedSource<Arc<String>>,
717 pub_names: &HashSet<DeclName>,
718) -> Result<(), GraphcalError> {
719 use crate::desugar::desugared_ast::IndexDeclKind;
720
721 let mut local_type_names: HashMap<String, Span> = HashMap::new();
723 for decl in &file.declarations {
724 let (name, span) = match &decl.kind {
725 DeclKind::BaseDimension(d) => (d.name.value.to_string(), d.name.span),
726 DeclKind::Dimension(d) => (d.name.value.to_string(), d.name.span),
727 DeclKind::Index(idx) => (idx.name.value.to_string(), idx.name.span),
728 DeclKind::Type(t) => (t.name.value.to_string(), t.name.span),
729 _ => continue,
730 };
731 local_type_names.insert(name, span);
732 }
733
734 if local_type_names.is_empty() {
736 return Ok(());
737 }
738
739 let emit = |pub_kind: &str,
740 pub_name: String,
741 pub_span: Span,
742 refs: &[(crate::syntax::names::NamePath, Span)]|
743 -> Result<(), GraphcalError> {
744 for (ref_path, ref_span) in refs {
745 let Some(ref_name) = ref_path.as_bare() else {
748 continue;
749 };
750 if local_type_names.contains_key(ref_name.as_str())
751 && !pub_names.contains(ref_name.as_str())
752 {
753 return Err(GraphcalError::PrivateInPublic {
754 pub_kind: pub_kind.to_string(),
755 pub_name,
756 ref_kind: ref_kind_for(file, ref_name.as_str()).to_string(),
757 ref_name: ref_name.to_string(),
758 src: src.clone(),
759 ref_span: (*ref_span).into(),
760 pub_span: pub_span.into(),
761 });
762 }
763 }
764 Ok(())
765 };
766
767 for decl in &file.declarations {
768 let is_visible = match &decl.kind {
771 DeclKind::Param(_) => true,
772 DeclKind::Node(d) => d.visibility.is_public(),
773 DeclKind::ConstNode(d) => d.visibility.is_public(),
774 DeclKind::BaseDimension(d) => d.visibility.is_public(),
775 DeclKind::Dimension(d) => d.visibility.is_public(),
776 DeclKind::Unit(d) => d.visibility.is_public(),
777 DeclKind::Type(d) => d.visibility.is_public(),
778 DeclKind::Index(d) => d.visibility.is_public(),
779 DeclKind::Import(d) => d.visibility.is_public(),
780 DeclKind::Include(d) => d.visibility.is_public(),
781 DeclKind::Dag(d) => d.visibility.is_public(),
782 DeclKind::Assert(d) => d.visibility.is_public(),
783 DeclKind::Plot(d) => d.visibility.is_public(),
784 DeclKind::Figure(d) => d.visibility.is_public(),
785 DeclKind::Layer(d) => d.visibility.is_public(),
786 DeclKind::Sugar(_) => false,
787 };
788 if !is_visible {
789 continue;
790 }
791
792 let mut refs: Vec<(crate::syntax::names::NamePath, Span)> = Vec::new();
793 let (kind, name): (&str, String) = match &decl.kind {
794 DeclKind::Param(p) => {
795 collect_type_refs(&p.type_ann, &mut refs);
796 ("param", p.name.value.to_string())
797 }
798 DeclKind::Node(n) => {
799 collect_type_refs(&n.type_ann, &mut refs);
800 ("node", n.name.value.to_string())
801 }
802 DeclKind::ConstNode(c) => {
803 collect_type_refs(&c.type_ann, &mut refs);
804 ("const node", c.name.value.to_string())
805 }
806 DeclKind::Dimension(d) => {
807 if let Some(def) = &d.definition {
808 collect_dim_refs(def, &mut refs);
809 }
810 ("dim", d.name.value.to_string())
811 }
812 DeclKind::Unit(u) => {
813 collect_dim_refs(&u.dim_type, &mut refs);
814 ("unit", u.name.value.to_string())
815 }
816 DeclKind::Type(t) => {
817 if let TypeDeclBody::Constructors(members) = &t.body {
820 for member in members {
821 if let Some(fields) = &member.payload {
822 for field in fields {
823 collect_type_refs(&field.type_ann, &mut refs);
824 }
825 }
826 }
827 }
828 ("type", t.name.value.to_string())
829 }
830 DeclKind::Index(idx) => {
831 if let IndexDeclKind::RequiredRange { dimension } = &idx.kind {
832 collect_dim_refs(dimension, &mut refs);
833 }
834 ("index", idx.name.value.to_string())
835 }
836 _ => continue,
840 };
841
842 emit(kind, name, decl.span, &refs)?;
843 }
844 Ok(())
845}
846
847fn collect_type_refs(type_expr: &TypeExpr, refs: &mut Vec<(crate::syntax::names::NamePath, Span)>) {
849 match &type_expr.kind {
850 TypeExprKind::DimExpr(dim_expr) => collect_dim_refs(dim_expr, refs),
851 TypeExprKind::Indexed { base, indexes } => {
852 collect_type_refs(base, refs);
853 for idx in indexes {
854 if let IndexExpr::Name(path) = idx {
855 refs.push((path.value.clone(), path.span));
856 }
857 }
858 }
859 TypeExprKind::TypeApplication { name, type_args } => {
860 refs.push((name.value.clone(), name.span));
861 for arg in type_args {
862 collect_type_refs(arg, refs);
863 }
864 }
865 TypeExprKind::DatetimeApplication { type_args } => {
866 for arg in type_args {
870 collect_type_refs(arg, refs);
871 }
872 }
873 TypeExprKind::Dimensionless
874 | TypeExprKind::Bool
875 | TypeExprKind::Int
876 | TypeExprKind::Datetime => {}
877 }
878}
879
880fn collect_dim_refs(dim_expr: &DimExpr, refs: &mut Vec<(crate::syntax::names::NamePath, Span)>) {
882 for item in &dim_expr.terms {
883 refs.push((item.term.name.value.clone(), item.term.span));
884 }
885}
886
887fn ref_kind_for(file: &File, ref_name: &str) -> &'static str {
889 match file
890 .declarations
891 .iter()
892 .find(|d| match &d.kind {
893 DeclKind::BaseDimension(bd) => bd.name.value.as_str() == ref_name,
894 DeclKind::Dimension(d) => d.name.value.as_str() == ref_name,
895 DeclKind::Index(idx) => idx.name.value.as_str() == ref_name,
896 DeclKind::Type(t) => t.name.value.as_str() == ref_name,
897 _ => false,
898 })
899 .map(|d| &d.kind)
900 {
901 Some(DeclKind::BaseDimension(_) | DeclKind::Dimension(_)) => "dim",
902 Some(DeclKind::Index(_)) => "index",
903 Some(DeclKind::Type(_)) => "type",
904 _ => "item",
905 }
906}
907
908#[derive(Debug, Default)]
912pub(crate) struct ImportedNames {
913 pub consts: Vec<(String, TypeExpr, Expr, Span)>,
914 pub params: Vec<(String, TypeExpr, Expr, Span)>,
915 pub nodes: Vec<(String, TypeExpr, Expr, Span)>,
916 pub asserts: Vec<(String, AssertBody, Span)>,
917}
918
919pub fn resolve(file: &File, src: &NamedSource<Arc<String>>) -> Result<ResolvedFile, GraphcalError> {
929 resolve_with_imports(file, src, &ImportedNames::default())
930}
931
932pub(crate) fn resolve_with_imports(
943 file: &File,
944 src: &NamedSource<Arc<String>>,
945 imported: &ImportedNames,
946) -> Result<ResolvedFile, GraphcalError> {
947 let mut names: HashMap<ScopedName, Span> = HashMap::new();
948
949 for (name, _, _, span) in &imported.consts {
952 names.insert(ScopedName::local(name.as_str()), *span);
953 }
954 for (name, _, _, span) in &imported.params {
955 names.insert(ScopedName::local(name.as_str()), *span);
956 }
957 for (name, _, _, span) in &imported.nodes {
958 names.insert(ScopedName::local(name.as_str()), *span);
959 }
960 for (name, _, span) in &imported.asserts {
961 names.insert(ScopedName::local(name.as_str()), *span);
962 }
963
964 let local = collect_local_declarations(file, src, &mut names)?;
966
967 let mut all_assert_names: HashSet<DeclName> = HashSet::new();
969 for (name, _, _) in &imported.asserts {
970 all_assert_names.insert(DeclName::new(name.as_str()));
971 }
972 all_assert_names.extend(local.assert_names.iter().cloned());
973
974 let mut all_consts: Vec<ResolvedConstEntry> = imported
977 .consts
978 .iter()
979 .map(|(name, _, expr, span)| ResolvedConstEntry {
980 name: name.clone(),
981 expr: expr.clone(),
982 span: *span,
983 })
984 .collect();
985 all_consts.extend(local.consts);
986 let mut all_params: Vec<ResolvedParamEntry> = imported
987 .params
988 .iter()
989 .map(|(name, _, expr, span)| ResolvedParamEntry {
990 name: name.clone(),
991 default_expr: Some(expr.clone()),
992 span: *span,
993 })
994 .collect();
995 all_params.extend(local.params);
996 let mut all_nodes: Vec<ResolvedNodeEntry> = imported
997 .nodes
998 .iter()
999 .map(|(name, _, expr, span)| ResolvedNodeEntry {
1000 name: name.clone(),
1001 expr: expr.clone(),
1002 span: *span,
1003 })
1004 .collect();
1005 all_nodes.extend(local.nodes);
1006 let mut all_asserts: Vec<ResolvedAssertEntry> = imported
1007 .asserts
1008 .iter()
1009 .map(|(name, body, span)| ResolvedAssertEntry {
1010 name: name.clone(),
1011 body: body.clone(),
1012 span: *span,
1013 })
1014 .collect();
1015 all_asserts.extend(local.asserts);
1016
1017 let mut all_source_order: Vec<(DeclName, DeclCategory)> = Vec::new();
1019 for (name, _, _, _) in &imported.consts {
1020 all_source_order.push((DeclName::new(name.as_str()), DeclCategory::Const));
1021 }
1022 for (name, _, _, _) in &imported.params {
1023 all_source_order.push((DeclName::new(name.as_str()), DeclCategory::Param));
1024 }
1025 for (name, _, _, _) in &imported.nodes {
1026 all_source_order.push((DeclName::new(name.as_str()), DeclCategory::Node));
1027 }
1028 for (name, _, _) in &imported.asserts {
1029 all_source_order.push((DeclName::new(name.as_str()), DeclCategory::Assert));
1030 }
1031 all_source_order.extend(local.source_order);
1032
1033 let validated = validate_attributes(file, src, &all_assert_names)?;
1035
1036 validate_private_in_public(file, src, &local.pub_names)?;
1038
1039 Ok(ResolvedFile {
1040 consts: all_consts,
1041 params: all_params,
1042 nodes: all_nodes,
1043 asserts: all_asserts,
1044 plots: local.plots,
1045 figures: local.figures,
1046 layers: local.layers,
1047 source_order: all_source_order,
1048 assert_names: all_assert_names,
1049 assumes_map: validated.assumes_map,
1050 expected_fail: validated.expected_fail_map,
1051 hidden_plots: validated.hidden_plots,
1052 pub_names: local.pub_names,
1053 })
1054}
1055
1056pub(crate) fn resolve_with_imported_values(
1068 file: &File,
1069 src: &NamedSource<Arc<String>>,
1070 imported: &ImportedValueNames,
1071) -> Result<ResolvedFile, GraphcalError> {
1072 let mut names: HashMap<ScopedName, Span> = HashMap::new();
1073
1074 for (name, span) in &imported.const_names {
1079 names.insert(name.clone(), *span);
1080 }
1081 for (name, span) in &imported.param_names {
1082 names.insert(name.clone(), *span);
1083 }
1084 for (name, span) in &imported.node_names {
1085 names.insert(name.clone(), *span);
1086 }
1087 for (name, span) in &imported.assert_names {
1088 names.insert(ScopedName::local(name.as_str()), *span);
1089 }
1090 for (name, span) in &imported.plot_names {
1091 names.insert(name.clone(), *span);
1092 }
1093
1094 let local = collect_local_declarations(file, src, &mut names)?;
1096
1097 let mut all_assert_names: HashSet<DeclName> = HashSet::new();
1099 for (name, _) in &imported.assert_names {
1100 all_assert_names.insert(name.clone());
1101 }
1102 all_assert_names.extend(local.assert_names.iter().cloned());
1103
1104 let validated = validate_attributes(file, src, &all_assert_names)?;
1106
1107 validate_private_in_public(file, src, &local.pub_names)?;
1109
1110 Ok(ResolvedFile {
1111 consts: local.consts,
1112 params: local.params,
1113 nodes: local.nodes,
1114 asserts: local.asserts,
1115 plots: local.plots,
1116 figures: local.figures,
1117 layers: local.layers,
1118 source_order: local.source_order,
1119 assert_names: all_assert_names,
1120 assumes_map: validated.assumes_map,
1121 expected_fail: validated.expected_fail_map,
1122 hidden_plots: validated.hidden_plots,
1123 pub_names: local.pub_names,
1124 })
1125}