1use std::collections::{HashMap, HashSet};
10use std::num::NonZeroUsize;
11use std::sync::Arc;
12
13use miette::NamedSource;
14use petgraph::algo::toposort;
15use petgraph::graph::DiGraph;
16
17use crate::desugar::desugared_ast::{
18 AssertBody, DeclKind, Expr, ExprKind, FigureDecl, File, IndexDeclKind, LayerDecl, PlotDecl,
19 TypeExpr,
20};
21use crate::ir::resolve::{
22 DeclCategory, ExpectedFail, ImportedValueNames, ResolvedFile, resolve_with_imported_values,
23};
24use crate::ir::resolve::{ImportedNames, resolve_with_imports};
25use crate::registry::declared_type::DeclaredType;
26use crate::registry::error::GraphcalError;
27use crate::registry::format::format_unit_expr;
28use crate::registry::prelude::load_prelude;
29use crate::registry::runtime_value::RuntimeValue;
30use crate::registry::types::{
31 self, PositiveFiniteScale, PositiveFiniteScaleError, Registry, RegistryBuilder, UnitScale,
32};
33use crate::syntax::dimension::Rational;
34use crate::syntax::names::{
35 ConstructorName, DeclName, DimName, IndexName, NameAtom, ScopedName, StructTypeName,
36};
37use crate::syntax::span::{Span, Spanned};
38use crate::syntax::visitor::{ExprVisitor, ExprVisitorMut};
39
40#[derive(Debug, Clone, Default)]
46pub struct LoweredPlotBody {
47 pub encodings: Vec<(crate::syntax::ast::EncodingChannel, crate::hir::Expr)>,
49 pub mark_properties: Vec<LoweredPlotField>,
51 pub properties: Vec<LoweredPlotField>,
53}
54
55#[derive(Debug, Clone)]
57pub struct LoweredPlotField {
58 pub name: crate::syntax::names::PlotPropertyName,
59 pub name_span: crate::syntax::span::Span,
61 pub value: crate::hir::Expr,
62}
63
64#[derive(Debug, Clone, Default)]
76pub struct BodySource(Option<NamedSource<Arc<String>>>);
77
78impl BodySource {
79 #[must_use]
82 pub const fn own() -> Self {
83 Self(None)
84 }
85
86 #[must_use]
89 pub const fn dependency(src: NamedSource<Arc<String>>) -> Self {
90 Self(Some(src))
91 }
92
93 #[must_use]
96 pub fn resolve<'a>(
97 &'a self,
98 default: &'a NamedSource<Arc<String>>,
99 ) -> &'a NamedSource<Arc<String>> {
100 self.0.as_ref().unwrap_or(default)
101 }
102
103 #[must_use]
111 pub fn or_dependency(self, dep_src: &NamedSource<Arc<String>>) -> Self {
112 match self.0 {
113 Some(_) => self,
114 None => Self::dependency(dep_src.clone()),
115 }
116 }
117}
118
119#[derive(Debug, Clone)]
121pub struct ConstEntry {
122 pub name: ScopedName,
123 pub type_ann: TypeExpr,
124 pub expr: crate::hir::Expr,
125 pub span: Span,
126 pub src: BodySource,
131}
132
133#[derive(Debug, Clone)]
135pub struct ParamEntry {
136 pub name: ScopedName,
137 pub type_ann: TypeExpr,
138 pub default_expr: Option<crate::hir::Expr>,
139 pub span: Span,
140 pub src: BodySource,
142}
143
144#[derive(Debug, Clone)]
146pub struct NodeEntry {
147 pub name: ScopedName,
148 pub type_ann: TypeExpr,
149 pub expr: crate::hir::Expr,
150 pub span: Span,
151 pub src: BodySource,
153}
154
155#[derive(Debug, Clone)]
157pub struct AssertEntry {
158 pub name: ScopedName,
159 pub body: crate::hir::AssertBody,
160 pub span: Span,
161 pub src: BodySource,
163}
164
165#[derive(Debug, Clone)]
170pub struct UnfrozenConstEntry {
171 pub name: ScopedName,
172 pub type_ann: TypeExpr,
173 pub expr: Expr,
174 pub span: Span,
175 pub src: BodySource,
177}
178
179#[derive(Debug, Clone)]
181pub struct UnfrozenParamEntry {
182 pub name: ScopedName,
183 pub type_ann: TypeExpr,
184 pub default_expr: Option<Expr>,
185 pub span: Span,
186 pub src: BodySource,
188}
189
190#[derive(Debug, Clone)]
192pub struct UnfrozenNodeEntry {
193 pub name: ScopedName,
194 pub type_ann: TypeExpr,
195 pub expr: Expr,
196 pub span: Span,
197 pub src: BodySource,
199}
200
201#[derive(Debug, Clone)]
203pub struct UnfrozenAssertEntry {
204 pub name: ScopedName,
205 pub body: AssertBody,
206 pub span: Span,
207 pub src: BodySource,
209}
210
211#[derive(Debug, Clone)]
213pub struct PlotEntry {
214 pub name: ScopedName,
215 pub mark_type: crate::syntax::ast::MarkType,
217 pub body: Option<LoweredPlotBody>,
221 pub span: Span,
222 pub is_pub: bool,
225 pub displayed: bool,
228}
229
230#[derive(Debug, Clone)]
236pub struct IncludedPlotEntry {
237 pub name: ScopedName,
239 pub span: Span,
241}
242
243#[derive(Debug, Clone)]
245pub struct RequestedPlot {
246 pub alias: DeclName,
248 pub hidden: bool,
250}
251
252#[derive(Debug, Clone)]
254pub struct FigureEntry {
255 pub name: ScopedName,
256 pub plot_names: Vec<Spanned<ScopedName>>,
258 pub fields: Vec<LoweredPlotField>,
261 pub span: Span,
262}
263
264#[derive(Debug, Clone)]
266pub struct LayerEntry {
267 pub name: ScopedName,
268 pub plot_names: Vec<Spanned<ScopedName>>,
270 pub fields: Vec<LoweredPlotField>,
273 pub span: Span,
274}
275
276#[derive(Debug, Clone)]
278pub struct UnfrozenPlotEntry {
279 pub name: ScopedName,
280 pub decl: PlotDecl,
281 pub span: Span,
282 pub is_pub: bool,
284 pub displayed: bool,
286}
287
288#[derive(Debug, Clone)]
290pub struct UnfrozenFigureEntry {
291 pub name: ScopedName,
292 pub decl: FigureDecl,
293 pub span: Span,
294}
295
296#[derive(Debug, Clone)]
298pub struct UnfrozenLayerEntry {
299 pub name: ScopedName,
300 pub decl: LayerDecl,
301 pub span: Span,
302}
303
304#[derive(Debug)]
312pub struct IR {
313 pub registry: Registry,
315 pub consts: Vec<ConstEntry>,
317 pub params: Vec<ParamEntry>,
319 pub nodes: Vec<NodeEntry>,
321 pub asserts: Vec<AssertEntry>,
323 pub plots: Vec<PlotEntry>,
325 pub figures: Vec<FigureEntry>,
327 pub layers: Vec<LayerEntry>,
329 pub included_plots: Vec<IncludedPlotEntry>,
331 pub source_order: Vec<(ScopedName, DeclCategory)>,
333 pub assert_names: HashSet<ScopedName>,
335 pub assumes_map: HashMap<ScopedName, Vec<ScopedName>>,
337 pub expected_fail: HashMap<ScopedName, ExpectedFail>,
339 pub imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
343 pub imported_decl_types: HashMap<ScopedName, DeclaredType>,
350 pub imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
353 pub pub_names: HashSet<DeclName>,
362}
363
364#[derive(Debug, Clone, PartialEq, Eq)]
366pub struct ImportedValueSource {
367 pub dag_id: crate::dag_id::DagId,
369 pub source_name: DeclName,
371}
372
373pub fn lower(ast: &File, src: &NamedSource<Arc<String>>) -> Result<IR, GraphcalError> {
385 let dag_id = crate::dag_id::DagId::from_relative_path(std::path::Path::new(src.name()))
386 .map_err(|e| GraphcalError::EvalError {
387 message: format!("invalid source name `{}`: {e}", src.name()),
388 src: src.clone(),
389 span: crate::syntax::span::Span::new(0, 0).into(),
390 })?;
391 lower_with_imports(ast, src, &ImportedNames::default(), &dag_id)
392}
393
394fn lower_with_imports(
403 ast: &File,
404 src: &NamedSource<Arc<String>>,
405 imported: &ImportedNames,
406 dag_id: &crate::dag_id::DagId,
407) -> Result<IR, GraphcalError> {
408 let (builder, resolved_ir) = lower_to_builder(ast, src, imported, dag_id)?;
409 let resolver = single_module_resolver(ast, dag_id, src)?;
410 resolved_ir.freeze(builder.build(), dag_id, &resolver, src)
411}
412
413fn single_module_resolver(
419 ast: &File,
420 dag_id: &crate::dag_id::DagId,
421 src: &NamedSource<Arc<String>>,
422) -> Result<crate::syntax::module_resolve::ModuleResolver, GraphcalError> {
423 fn add_module_with_dags(
424 target: &mut crate::syntax::module_resolve::ModuleResolver,
425 owner: &crate::dag_id::DagId,
426 declarations: &[crate::desugar::desugared_ast::Declaration],
427 src: &NamedSource<Arc<String>>,
428 ) -> Result<(), GraphcalError> {
429 target
430 .add_module(owner.clone(), declarations)
431 .map_err(|err| GraphcalError::EvalError {
432 message: err.to_string(),
433 src: src.clone(),
434 span: Span::new(0, 0).into(),
435 })?;
436 for decl in declarations {
437 if let crate::desugar::desugared_ast::DeclKind::Dag(dag) = &decl.kind {
438 add_module_with_dags(
439 target,
440 &owner.child(dag.name.value.as_str()),
441 &dag.body,
442 src,
443 )?;
444 }
445 }
446 Ok(())
447 }
448
449 let mut resolver = crate::syntax::module_resolve::ModuleResolver::default();
450 add_module_with_dags(&mut resolver, dag_id, &ast.declarations, src)?;
451 Ok(resolver)
452}
453
454pub(crate) fn lower_to_builder(
464 ast: &File,
465 src: &NamedSource<Arc<String>>,
466 imported: &ImportedNames,
467 dag_id: &crate::dag_id::DagId,
468) -> Result<(RegistryBuilder, UnfrozenIR), GraphcalError> {
469 let resolved = resolve_with_imports(ast, src, imported)?;
471
472 let mut type_anns = extract_type_annotations(ast);
477 for (name, type_ann, _, _) in &imported.consts {
478 type_anns.insert(DeclName::new(name.clone()), type_ann.clone());
479 }
480 for (name, type_ann, _, _) in &imported.params {
481 type_anns.insert(DeclName::new(name.clone()), type_ann.clone());
482 }
483 for (name, type_ann, _, _) in &imported.nodes {
484 type_anns.insert(DeclName::new(name.clone()), type_ann.clone());
485 }
486
487 build_ir_from_resolved(
489 ast,
490 src,
491 resolved,
492 type_anns,
493 HashMap::new(),
494 HashMap::new(),
495 HashMap::new(),
496 dag_id,
497 None,
498 None,
499 )
500}
501
502pub type RegistrySeed<'a> = &'a mut dyn FnMut(&mut RegistryBuilder) -> Result<(), GraphcalError>;
509
510#[expect(
522 clippy::implicit_hasher,
523 reason = "internal API always uses default hasher"
524)]
525pub fn lower_to_builder_with_imported_values(
526 ast: &File,
527 src: &NamedSource<Arc<String>>,
528 imported_names: &ImportedValueNames,
529 imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
530 dag_id: &crate::dag_id::DagId,
531 registry_seed: Option<RegistrySeed<'_>>,
532) -> Result<(RegistryBuilder, UnfrozenIR), GraphcalError> {
533 let imported_decl_types = imported_values
534 .iter()
535 .map(|(name, (_value, ty))| (name.clone(), ty.clone()))
536 .collect();
537 lower_to_builder_with_imported_value_decls(
538 ast,
539 src,
540 imported_names,
541 imported_values,
542 imported_decl_types,
543 HashMap::new(),
544 dag_id,
545 registry_seed,
546 )
547}
548
549#[expect(
560 clippy::implicit_hasher,
561 reason = "internal API always uses default hasher"
562)]
563#[expect(
564 clippy::too_many_arguments,
565 reason = "lowering threads imported value metadata plus the registry seed hook"
566)]
567pub fn lower_to_builder_with_imported_value_decls(
568 ast: &File,
569 src: &NamedSource<Arc<String>>,
570 imported_names: &ImportedValueNames,
571 imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
572 imported_decl_types: HashMap<ScopedName, DeclaredType>,
573 imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
574 dag_id: &crate::dag_id::DagId,
575 registry_seed: Option<RegistrySeed<'_>>,
576) -> Result<(RegistryBuilder, UnfrozenIR), GraphcalError> {
577 let resolved = resolve_with_imported_values(ast, src, imported_names)?;
579
580 let type_anns = extract_type_annotations(ast);
582
583 let (builder, mut unfrozen) = build_ir_from_resolved(
585 ast,
586 src,
587 resolved,
588 type_anns,
589 imported_values,
590 imported_decl_types,
591 imported_value_sources,
592 dag_id,
593 None,
594 registry_seed,
595 )?;
596
597 unfrozen.included_plots = imported_names
600 .plot_names
601 .iter()
602 .map(|(name, span)| IncludedPlotEntry {
603 name: name.clone(),
604 span: *span,
605 })
606 .collect();
607
608 Ok((builder, unfrozen))
609}
610
611#[expect(
644 clippy::implicit_hasher,
645 reason = "internal API always uses default hasher"
646)]
647#[expect(
648 clippy::too_many_arguments,
649 reason = "dag-module lowering threads pre-processed import metadata + optional parent registry"
650)]
651pub fn lower_dag_module_to_builder_with_imported_value_decls(
652 dag_body: &File,
653 parent_registry: Option<&Registry>,
654 imported_names: &ImportedValueNames,
655 imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
656 imported_decl_types: HashMap<ScopedName, DeclaredType>,
657 imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
658 src: &NamedSource<Arc<String>>,
659 dag_id: &crate::dag_id::DagId,
660 registry_seed: Option<RegistrySeed<'_>>,
661) -> Result<(RegistryBuilder, UnfrozenIR), GraphcalError> {
662 let resolved = resolve_with_imported_values(dag_body, src, imported_names)?;
663 let type_anns = extract_type_annotations(dag_body);
664
665 build_ir_from_resolved(
666 dag_body,
667 src,
668 resolved,
669 type_anns,
670 imported_values,
671 imported_decl_types,
672 imported_value_sources,
673 dag_id,
674 parent_registry,
675 registry_seed,
676 )
677}
678
679#[expect(
680 clippy::implicit_hasher,
681 reason = "internal API always uses default hasher"
682)]
683#[expect(
684 clippy::too_many_arguments,
685 reason = "dag-body lowering threads pre-processed import metadata + parent registry"
686)]
687pub fn lower_dag_body_to_ir(
688 dag_name: &str,
689 stripped_body: &[crate::desugar::desugared_ast::Declaration],
690 parent_registry: &Registry,
691 resolver: &crate::syntax::module_resolve::ModuleResolver,
692 imported_names: &ImportedValueNames,
693 imported_decl_types: HashMap<ScopedName, DeclaredType>,
694 imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
695 src: &NamedSource<Arc<String>>,
696 parent_dag_id: &crate::dag_id::DagId,
697) -> Result<IR, GraphcalError> {
698 let virtual_file = File {
699 declarations: stripped_body.to_vec(),
700 };
701 let dag_dag_id = parent_dag_id.child(dag_name);
702 let (builder, unfrozen) = lower_dag_module_to_builder_with_imported_value_decls(
703 &virtual_file,
704 Some(parent_registry),
705 imported_names,
706 HashMap::new(),
707 imported_decl_types,
708 imported_value_sources,
709 src,
710 &dag_dag_id,
711 None,
712 )?;
713 unfrozen.freeze(builder.build(), &dag_dag_id, resolver, src)
714}
715
716pub struct DagBodySelfImports {
719 pub names: ImportedValueNames,
720 pub decl_types: HashMap<ScopedName, DeclaredType>,
721 pub value_sources: HashMap<ScopedName, ImportedValueSource>,
722 pub stripped_body: Vec<crate::desugar::desugared_ast::Declaration>,
723}
724
725fn take_type_ann(
730 type_anns: &mut HashMap<DeclName, TypeExpr>,
731 name: &DeclName,
732 span: Span,
733 src: &NamedSource<Arc<String>>,
734) -> Result<TypeExpr, GraphcalError> {
735 type_anns
736 .remove(name)
737 .ok_or_else(|| GraphcalError::InternalError {
738 message: format!("missing type annotation for `{name}`"),
739 src: src.clone(),
740 span: span.into(),
741 })
742}
743
744#[expect(
749 clippy::too_many_lines,
750 reason = "single linear pipeline — splitting would obscure the flow"
751)]
752#[expect(
753 clippy::too_many_arguments,
754 reason = "IR construction threads imported value type/source metadata"
755)]
756fn build_ir_from_resolved(
757 ast: &File,
758 src: &NamedSource<Arc<String>>,
759 resolved: ResolvedFile,
760 mut type_anns: HashMap<DeclName, TypeExpr>,
761 imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
762 imported_decl_types: HashMap<ScopedName, DeclaredType>,
763 imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
764 dag_id: &crate::dag_id::DagId,
765 parent_registry: Option<&Registry>,
766 registry_seed: Option<RegistrySeed<'_>>,
767) -> Result<(RegistryBuilder, UnfrozenIR), GraphcalError> {
768 let mut builder = RegistryBuilder::new();
774 load_prelude(&mut builder).map_err(|e| GraphcalError::EvalError {
775 message: format!("internal: prelude failed to load: {e}"),
776 src: src.clone(),
777 span: Span::new(0, 0).into(),
778 })?;
779 if let Some(parent) = parent_registry {
780 builder.merge_from_registry(parent);
781 }
782 if let Some(seed) = registry_seed {
786 seed(&mut builder)?;
787 }
788 register_file_declarations(ast, &mut builder, src, dag_id)?;
789
790 let consts = resolved
795 .consts
796 .into_iter()
797 .map(|entry| {
798 let decl_name = DeclName::new(entry.name);
799 let type_ann = take_type_ann(&mut type_anns, &decl_name, entry.span, src)?;
800 Ok(UnfrozenConstEntry {
801 name: ScopedName::from(decl_name),
802 type_ann,
803 expr: entry.expr,
804 span: entry.span,
805 src: BodySource::own(),
806 })
807 })
808 .collect::<Result<Vec<_>, GraphcalError>>()?;
809 let params = resolved
810 .params
811 .into_iter()
812 .map(|entry| {
813 let decl_name = DeclName::new(entry.name);
814 let type_ann = take_type_ann(&mut type_anns, &decl_name, entry.span, src)?;
815 Ok(UnfrozenParamEntry {
816 name: ScopedName::from(decl_name),
817 type_ann,
818 default_expr: entry.default_expr,
819 span: entry.span,
820 src: BodySource::own(),
821 })
822 })
823 .collect::<Result<Vec<_>, GraphcalError>>()?;
824 let nodes = resolved
825 .nodes
826 .into_iter()
827 .map(|entry| {
828 let decl_name = DeclName::new(entry.name);
829 let type_ann = take_type_ann(&mut type_anns, &decl_name, entry.span, src)?;
830 Ok(UnfrozenNodeEntry {
831 name: ScopedName::from(decl_name),
832 type_ann,
833 expr: entry.expr,
834 span: entry.span,
835 src: BodySource::own(),
836 })
837 })
838 .collect::<Result<Vec<_>, GraphcalError>>()?;
839
840 let unfrozen = UnfrozenIR {
841 consts,
842 params,
843 nodes,
844 asserts: resolved
845 .asserts
846 .into_iter()
847 .map(|entry| UnfrozenAssertEntry {
848 name: ScopedName::local(entry.name),
849 body: entry.body,
850 span: entry.span,
851 src: BodySource::own(),
852 })
853 .collect(),
854 plots: resolved
855 .plots
856 .into_iter()
857 .map(|entry| {
858 let is_pub = resolved.pub_names.contains(entry.name.as_str());
859 let displayed = !resolved.hidden_plots.contains(entry.name.as_str());
860 UnfrozenPlotEntry {
861 name: ScopedName::local(entry.name),
862 decl: entry.decl,
863 span: entry.span,
864 is_pub,
865 displayed,
866 }
867 })
868 .collect(),
869 figures: resolved
870 .figures
871 .into_iter()
872 .map(|entry| UnfrozenFigureEntry {
873 name: ScopedName::local(entry.name),
874 decl: entry.decl,
875 span: entry.span,
876 })
877 .collect(),
878 layers: resolved
879 .layers
880 .into_iter()
881 .map(|entry| UnfrozenLayerEntry {
882 name: ScopedName::local(entry.name),
883 decl: entry.decl,
884 span: entry.span,
885 })
886 .collect(),
887 included_plots: Vec::new(),
888 source_order: resolved
889 .source_order
890 .into_iter()
891 .map(|(name, cat)| (ScopedName::from(name), cat))
892 .collect(),
893 assert_names: resolved
894 .assert_names
895 .into_iter()
896 .map(ScopedName::from)
897 .collect(),
898 assumes_map: resolved
899 .assumes_map
900 .into_iter()
901 .map(|(k, v)| {
902 (
903 ScopedName::from(k),
904 v.into_iter().map(ScopedName::from).collect(),
905 )
906 })
907 .collect(),
908 expected_fail: resolved
909 .expected_fail
910 .into_iter()
911 .map(|(k, v)| (ScopedName::from(k), v))
912 .collect(),
913 imported_values,
914 imported_decl_types,
915 imported_value_sources,
916 pub_names: resolved.pub_names,
917 };
918
919 Ok((builder, unfrozen))
920}
921
922pub struct UnfrozenIR {
924 consts: Vec<UnfrozenConstEntry>,
925 params: Vec<UnfrozenParamEntry>,
926 nodes: Vec<UnfrozenNodeEntry>,
927 asserts: Vec<UnfrozenAssertEntry>,
928 plots: Vec<UnfrozenPlotEntry>,
929 figures: Vec<UnfrozenFigureEntry>,
930 layers: Vec<UnfrozenLayerEntry>,
931 pub included_plots: Vec<IncludedPlotEntry>,
933 pub source_order: Vec<(ScopedName, DeclCategory)>,
935 assert_names: HashSet<ScopedName>,
936 assumes_map: HashMap<ScopedName, Vec<ScopedName>>,
938 expected_fail: HashMap<ScopedName, ExpectedFail>,
940 imported_values: HashMap<ScopedName, (RuntimeValue, DeclaredType)>,
942 imported_decl_types: HashMap<ScopedName, DeclaredType>,
944 imported_value_sources: HashMap<ScopedName, ImportedValueSource>,
946 pub_names: HashSet<DeclName>,
950}
951
952impl UnfrozenIR {
953 #[expect(
966 clippy::too_many_lines,
967 reason = "single lowering boundary over every declaration kind"
968 )]
969 pub fn freeze(
970 self,
971 registry: Registry,
972 owner: &crate::dag_id::DagId,
973 resolver: &crate::syntax::module_resolve::ModuleResolver,
974 src: &NamedSource<Arc<String>>,
975 ) -> Result<IR, GraphcalError> {
976 let mut decl_bindings = HashMap::new();
980 for name in self
981 .consts
982 .iter()
983 .map(|entry| &entry.name)
984 .chain(self.params.iter().map(|entry| &entry.name))
985 .chain(self.nodes.iter().map(|entry| &entry.name))
986 {
987 let canonical =
988 crate::hir::diagnostics::resolved_decl_key(owner, name).ok_or_else(|| {
989 GraphcalError::InternalError {
990 message: format!("could not build canonical declaration key for `{name}`"),
991 src: src.clone(),
992 span: Span::new(0, 0).into(),
993 }
994 })?;
995 decl_bindings.insert(name.clone(), canonical);
996 }
997 for (name, source) in &self.imported_value_sources {
998 decl_bindings.insert(
999 name.clone(),
1000 crate::syntax::names::ResolvedName::from_def(
1001 source.dag_id.clone(),
1002 source.source_name.clone(),
1003 ),
1004 );
1005 }
1006
1007 let generic_scope = crate::hir::GenericScope::new();
1008 let prelude = crate::hir::PreludeTypeScope::graphcal();
1009 let expr_ctx = crate::hir::ExprLoweringContext::new(owner, resolver, &generic_scope)
1010 .with_prelude(&prelude)
1011 .with_decl_bindings(&decl_bindings);
1012 let lower_in = |expr: &Expr, body_src: &NamedSource<Arc<String>>| {
1016 crate::hir::lower_expr(expr, expr_ctx).map_err(|err| {
1017 crate::hir::diagnostics::expr_lower_error_to_graphcal(&err, body_src)
1018 })
1019 };
1020
1021 let consts = self
1022 .consts
1023 .iter()
1024 .map(|entry| {
1025 Ok(ConstEntry {
1026 name: entry.name.clone(),
1027 type_ann: entry.type_ann.clone(),
1028 expr: lower_in(&entry.expr, entry.src.resolve(src))?,
1029 span: entry.span,
1030 src: entry.src.clone(),
1031 })
1032 })
1033 .collect::<Result<Vec<_>, GraphcalError>>()?;
1034 let params = self
1035 .params
1036 .iter()
1037 .map(|entry| {
1038 Ok(ParamEntry {
1039 name: entry.name.clone(),
1040 type_ann: entry.type_ann.clone(),
1041 default_expr: entry
1042 .default_expr
1043 .as_ref()
1044 .map(|expr| lower_in(expr, entry.src.resolve(src)))
1045 .transpose()?,
1046 span: entry.span,
1047 src: entry.src.clone(),
1048 })
1049 })
1050 .collect::<Result<Vec<_>, GraphcalError>>()?;
1051 let nodes = self
1052 .nodes
1053 .iter()
1054 .map(|entry| {
1055 Ok(NodeEntry {
1056 name: entry.name.clone(),
1057 type_ann: entry.type_ann.clone(),
1058 expr: lower_in(&entry.expr, entry.src.resolve(src))?,
1059 span: entry.span,
1060 src: entry.src.clone(),
1061 })
1062 })
1063 .collect::<Result<Vec<_>, GraphcalError>>()?;
1064 let asserts = self
1065 .asserts
1066 .iter()
1067 .map(|entry| {
1068 let body_src = entry.src.resolve(src);
1069 Ok(AssertEntry {
1070 name: entry.name.clone(),
1071 body: crate::hir::lower_assert_body(&entry.body, expr_ctx).map_err(|err| {
1072 crate::hir::diagnostics::expr_lower_error_to_graphcal(&err, body_src)
1073 })?,
1074 span: entry.span,
1075 src: entry.src.clone(),
1076 })
1077 })
1078 .collect::<Result<Vec<_>, GraphcalError>>()?;
1079
1080 let lower_optional = |expr: &Expr| crate::hir::lower_expr(expr, expr_ctx).ok();
1084 let plots = self
1085 .plots
1086 .iter()
1087 .map(|entry| {
1088 let mut body = LoweredPlotBody::default();
1089 let mut complete = true;
1090 for encoding in &entry.decl.encodings {
1091 match lower_optional(&encoding.value) {
1092 Some(lowered) => body.encodings.push((encoding.channel, lowered)),
1093 None => complete = false,
1094 }
1095 }
1096 for field in &entry.decl.mark.properties {
1097 match lower_optional(&field.value) {
1098 Some(lowered) => body.mark_properties.push(LoweredPlotField {
1099 name: field.name.value.clone(),
1100 name_span: field.name.span,
1101 value: lowered,
1102 }),
1103 None => complete = false,
1104 }
1105 }
1106 for field in &entry.decl.properties {
1107 match lower_optional(&field.value) {
1108 Some(lowered) => body.properties.push(LoweredPlotField {
1109 name: field.name.value.clone(),
1110 name_span: field.name.span,
1111 value: lowered,
1112 }),
1113 None => complete = false,
1114 }
1115 }
1116 PlotEntry {
1117 name: entry.name.clone(),
1118 mark_type: entry.decl.mark.mark_type,
1119 body: complete.then_some(body),
1120 span: entry.span,
1121 is_pub: entry.is_pub,
1122 displayed: entry.displayed,
1123 }
1124 })
1125 .collect();
1126 let lower_fields = |fields: &[crate::desugar::desugared_ast::PlotField]| {
1127 fields
1128 .iter()
1129 .filter_map(|field| {
1130 Some(LoweredPlotField {
1131 name: field.name.value.clone(),
1132 name_span: field.name.span,
1133 value: lower_optional(&field.value)?,
1134 })
1135 })
1136 .collect::<Vec<_>>()
1137 };
1138 let figures = self
1139 .figures
1140 .iter()
1141 .map(|entry| FigureEntry {
1142 name: entry.name.clone(),
1143 plot_names: entry.decl.plot_names.clone(),
1144 fields: lower_fields(&entry.decl.fields),
1145 span: entry.span,
1146 })
1147 .collect();
1148 let layers = self
1149 .layers
1150 .iter()
1151 .map(|entry| LayerEntry {
1152 name: entry.name.clone(),
1153 plot_names: entry.decl.plot_names.clone(),
1154 fields: lower_fields(&entry.decl.fields),
1155 span: entry.span,
1156 })
1157 .collect();
1158
1159 Ok(IR {
1160 registry,
1161 consts,
1162 params,
1163 nodes,
1164 asserts,
1165 plots,
1166 figures,
1167 layers,
1168 included_plots: self.included_plots,
1169 source_order: self.source_order,
1170 assert_names: self.assert_names,
1171 assumes_map: self.assumes_map,
1172 expected_fail: self.expected_fail,
1173 imported_values: self.imported_values,
1174 imported_decl_types: self.imported_decl_types,
1175 imported_value_sources: self.imported_value_sources,
1176 pub_names: self.pub_names,
1177 })
1178 }
1179
1180 pub fn override_param_default(&mut self, name: &str, expr: Expr) -> bool {
1184 match self
1185 .params
1186 .iter_mut()
1187 .find(|entry| entry.name.member() == name)
1188 {
1189 Some(entry) => {
1190 entry.default_expr = Some(expr);
1191 true
1192 }
1193 None => false,
1194 }
1195 }
1196
1197 pub fn add_const_alias(
1201 &mut self,
1202 name: ScopedName,
1203 type_ann: TypeExpr,
1204 expr: Expr,
1205 span: Span,
1206 ) {
1207 self.consts.push(UnfrozenConstEntry {
1208 name: name.clone(),
1209 type_ann,
1210 expr,
1211 span,
1212 src: BodySource::own(),
1215 });
1216 self.source_order.push((name, DeclCategory::Const));
1217 }
1218
1219 pub fn add_node_alias(&mut self, name: ScopedName, type_ann: TypeExpr, expr: Expr, span: Span) {
1223 self.nodes.push(UnfrozenNodeEntry {
1224 name: name.clone(),
1225 type_ann,
1226 expr,
1227 span,
1228 src: BodySource::own(),
1231 });
1232 self.source_order.push((name, DeclCategory::Node));
1233 }
1234
1235 pub fn check_include_reconciles_overrides(
1250 &self,
1251 bindings: &HashMap<DeclName, Expr>,
1252 index_bindings: &HashMap<IndexName, IndexName>,
1253 type_bindings: &HashMap<StructTypeName, StructTypeName>,
1254 importer_src: &NamedSource<Arc<String>>,
1255 include_span: Span,
1256 ) -> Result<(), GraphcalError> {
1257 if index_bindings.is_empty() && type_bindings.is_empty() {
1258 return Ok(());
1259 }
1260 for param in &self.params {
1261 if bindings.contains_key(param.name.member()) {
1262 continue;
1263 }
1264 let Some(default_expr) = ¶m.default_expr else {
1265 continue;
1266 };
1267 let mut checker = OverrideReconciliationChecker {
1268 index_bindings,
1269 type_bindings,
1270 orphan_decl: param.name.member(),
1271 importer_src,
1272 include_span,
1273 };
1274 checker.visit_expr(default_expr)?;
1275 }
1276 Ok(())
1277 }
1278
1279 #[expect(
1295 clippy::too_many_lines,
1296 reason = "single logical operation: prefix and merge all declaration kinds"
1297 )]
1298 #[expect(
1299 clippy::too_many_arguments,
1300 reason = "merge_dependency coordinates every binding kind plus prefixing state"
1301 )]
1302 pub fn merge_dependency(
1303 &mut self,
1304 dep: Self,
1305 prefix: &str,
1306 bindings: &HashMap<DeclName, Expr>,
1307 dep_names: &HashSet<DeclName>,
1308 index_bindings: &HashMap<IndexName, IndexName>,
1309 type_bindings: &HashMap<StructTypeName, StructTypeName>,
1310 dim_bindings: &HashMap<DimName, DimName>,
1311 import_item_attributes: &HashMap<DeclName, Vec<crate::desugar::desugared_ast::Attribute>>,
1312 requested_plots: &HashMap<DeclName, RequestedPlot>,
1313 importer_src: &NamedSource<Arc<String>>,
1314 dep_src: &NamedSource<Arc<String>>,
1315 ) -> Result<(), GraphcalError> {
1316 fn prefix_dep(d: &ScopedName, prefix: &str, dep_names: &HashSet<DeclName>) -> ScopedName {
1325 if !d.is_qualified() && dep_names.contains(d.member()) {
1326 d.with_prefix(prefix)
1327 } else {
1328 d.clone()
1329 }
1330 }
1331
1332 let mut all_dep_names = dep_names.clone();
1333 all_dep_names.extend(
1334 dep.imported_values
1335 .keys()
1336 .map(|name| DeclName::new(name.member())),
1337 );
1338 all_dep_names.extend(
1339 dep.imported_decl_types
1340 .keys()
1341 .map(|name| DeclName::new(name.member())),
1342 );
1343 all_dep_names.extend(
1344 dep.imported_value_sources
1345 .keys()
1346 .map(|name| DeclName::new(name.member())),
1347 );
1348 let dep_names = &all_dep_names;
1349
1350 for mut entry in dep.consts {
1352 substitute_index_names(&mut entry.expr, index_bindings);
1353 substitute_type_names_in_expr(&mut entry.expr, type_bindings);
1354 prefix_expr_refs(&mut entry.expr, prefix, dep_names);
1355 substitute_type_expr_index_names(&mut entry.type_ann, index_bindings);
1356 substitute_type_expr_nominal_names(&mut entry.type_ann, type_bindings);
1357 substitute_type_expr_nominal_names(&mut entry.type_ann, dim_bindings);
1358 let prefixed = entry.name.with_prefix(prefix);
1359 self.consts.push(UnfrozenConstEntry {
1360 name: prefixed.clone(),
1361 type_ann: entry.type_ann,
1362 expr: entry.expr,
1363 span: entry.span,
1364 src: entry.src.or_dependency(dep_src),
1365 });
1366 self.source_order.push((prefixed, DeclCategory::Const));
1367 }
1368
1369 for mut entry in dep.params {
1371 let prefixed = entry.name.with_prefix(prefix);
1372 if let Some(binding_expr) = bindings.get(entry.name.member()) {
1373 entry.default_expr = Some(binding_expr.clone());
1379 } else if let Some(ref mut expr) = entry.default_expr {
1380 substitute_index_names(expr, index_bindings);
1382 substitute_type_names_in_expr(expr, type_bindings);
1383 prefix_expr_refs(expr, prefix, dep_names);
1384 } else {
1385 }
1387 substitute_type_expr_index_names(&mut entry.type_ann, index_bindings);
1388 substitute_type_expr_nominal_names(&mut entry.type_ann, type_bindings);
1389 substitute_type_expr_nominal_names(&mut entry.type_ann, dim_bindings);
1390 self.params.push(UnfrozenParamEntry {
1391 name: prefixed.clone(),
1392 type_ann: entry.type_ann,
1393 default_expr: entry.default_expr,
1394 span: entry.span,
1395 src: entry.src.or_dependency(dep_src),
1396 });
1397 self.source_order.push((prefixed, DeclCategory::Param));
1398 }
1399
1400 for mut entry in dep.nodes {
1402 substitute_index_names(&mut entry.expr, index_bindings);
1403 substitute_type_names_in_expr(&mut entry.expr, type_bindings);
1404 prefix_expr_refs(&mut entry.expr, prefix, dep_names);
1405 substitute_type_expr_index_names(&mut entry.type_ann, index_bindings);
1406 substitute_type_expr_nominal_names(&mut entry.type_ann, type_bindings);
1407 substitute_type_expr_nominal_names(&mut entry.type_ann, dim_bindings);
1408 let prefixed = entry.name.with_prefix(prefix);
1409 self.nodes.push(UnfrozenNodeEntry {
1410 name: prefixed.clone(),
1411 type_ann: entry.type_ann,
1412 expr: entry.expr,
1413 span: entry.span,
1414 src: entry.src.or_dependency(dep_src),
1415 });
1416 self.source_order.push((prefixed, DeclCategory::Node));
1417 }
1418
1419 for mut entry in dep.asserts {
1421 match &mut entry.body {
1422 crate::desugar::desugared_ast::AssertBody::Expr(e) => {
1423 substitute_index_names(e, index_bindings);
1424 substitute_type_names_in_expr(e, type_bindings);
1425 prefix_expr_refs(e, prefix, dep_names);
1426 }
1427 crate::desugar::desugared_ast::AssertBody::Tolerance {
1428 actual,
1429 expected,
1430 tolerance,
1431 ..
1432 } => {
1433 substitute_index_names(actual, index_bindings);
1434 substitute_type_names_in_expr(actual, type_bindings);
1435 prefix_expr_refs(actual, prefix, dep_names);
1436 substitute_index_names(expected, index_bindings);
1437 substitute_type_names_in_expr(expected, type_bindings);
1438 prefix_expr_refs(expected, prefix, dep_names);
1439 substitute_index_names(tolerance, index_bindings);
1440 substitute_type_names_in_expr(tolerance, type_bindings);
1441 prefix_expr_refs(tolerance, prefix, dep_names);
1442 }
1443 }
1444 let prefixed = entry.name.with_prefix(prefix);
1445 self.asserts.push(UnfrozenAssertEntry {
1446 name: prefixed.clone(),
1447 body: entry.body,
1448 span: entry.span,
1449 src: entry.src.or_dependency(dep_src),
1450 });
1451 self.assert_names.insert(prefixed.clone());
1452 self.source_order.push((prefixed, DeclCategory::Assert));
1453 }
1454
1455 for mut entry in dep.plots {
1461 let Some(requested) = requested_plots.get(entry.name.member()) else {
1462 continue;
1463 };
1464 for encoding in &mut entry.decl.encodings {
1465 substitute_index_names(&mut encoding.value, index_bindings);
1466 substitute_type_names_in_expr(&mut encoding.value, type_bindings);
1467 prefix_expr_refs(&mut encoding.value, prefix, dep_names);
1468 }
1469 for prop in &mut entry.decl.mark.properties {
1470 substitute_index_names(&mut prop.value, index_bindings);
1471 substitute_type_names_in_expr(&mut prop.value, type_bindings);
1472 prefix_expr_refs(&mut prop.value, prefix, dep_names);
1473 }
1474 for prop in &mut entry.decl.properties {
1475 substitute_index_names(&mut prop.value, index_bindings);
1476 substitute_type_names_in_expr(&mut prop.value, type_bindings);
1477 prefix_expr_refs(&mut prop.value, prefix, dep_names);
1478 }
1479 let local = ScopedName::local(requested.alias.as_str());
1480 self.plots.push(UnfrozenPlotEntry {
1481 name: local.clone(),
1482 decl: entry.decl,
1483 span: entry.span,
1484 is_pub: false,
1487 displayed: !requested.hidden,
1488 });
1489 self.source_order.push((local, DeclCategory::Plot));
1490 }
1491
1492 for (assert_name, assumers) in dep.assumes_map {
1497 let prefixed_assert = assert_name.with_prefix(prefix);
1498 let prefixed_assumers: Vec<ScopedName> =
1499 assumers.iter().map(|a| a.with_prefix(prefix)).collect();
1500 self.assumes_map
1501 .entry(prefixed_assert)
1502 .or_default()
1503 .extend(prefixed_assumers);
1504 }
1505 for (assert_name, ef) in dep.expected_fail {
1506 let prefixed = assert_name.with_prefix(prefix);
1507
1508 if index_bindings.is_empty() {
1510 self.expected_fail.insert(prefixed, ef);
1511 } else {
1512 match ef {
1513 ExpectedFail::All => {
1514 self.expected_fail.insert(prefixed, ExpectedFail::All);
1515 }
1516 ExpectedFail::Variants(keys) => {
1517 let filtered: Vec<_> = keys
1518 .into_iter()
1519 .filter(|key| {
1520 !key.iter().any(|part| {
1524 part.named_index().is_some_and(|index| {
1525 index_bindings.contains_key(index.display_name().as_str())
1526 })
1527 })
1528 })
1529 .collect();
1530 if !filtered.is_empty() {
1531 self.expected_fail
1532 .insert(prefixed, ExpectedFail::Variants(filtered));
1533 }
1534 }
1536 }
1537 }
1538 }
1539
1540 for (orig_name, attrs) in import_item_attributes {
1544 for attr in attrs {
1545 if attr
1546 .name
1547 .name
1548 .parse::<crate::syntax::attribute::AttributeName>()
1549 == Ok(crate::syntax::attribute::AttributeName::ExpectedFail)
1550 {
1551 let prefixed_assert = ScopedName::local(orig_name.as_str()).with_prefix(prefix);
1552 let ef = crate::ir::resolve::names::parse_expected_fail_args(
1553 &attr.args,
1554 importer_src,
1555 )?;
1556 self.expected_fail.insert(prefixed_assert, ef);
1557 }
1558 }
1559 }
1560
1561 for (name, value) in dep.imported_values {
1566 self.imported_values
1567 .entry(prefix_dep(&name, prefix, dep_names))
1568 .or_insert(value);
1569 }
1570 for (name, dt) in dep.imported_decl_types {
1571 self.imported_decl_types
1572 .entry(prefix_dep(&name, prefix, dep_names))
1573 .or_insert(dt);
1574 }
1575 for (name, source) in dep.imported_value_sources {
1576 self.imported_value_sources
1577 .entry(prefix_dep(&name, prefix, dep_names))
1578 .or_insert(source);
1579 }
1580 Ok(())
1581 }
1582}
1583
1584struct OverrideReconciliationChecker<'a> {
1593 index_bindings: &'a HashMap<IndexName, IndexName>,
1594 type_bindings: &'a HashMap<StructTypeName, StructTypeName>,
1595 orphan_decl: &'a str,
1596 importer_src: &'a NamedSource<Arc<String>>,
1597 include_span: Span,
1598}
1599
1600impl OverrideReconciliationChecker<'_> {
1601 fn orphan_error(
1602 &self,
1603 overridden_kind: &str,
1604 overridden: &str,
1605 detail: String,
1606 ) -> GraphcalError {
1607 GraphcalError::IncludeMustReconcileOverride {
1608 overridden: overridden.to_string(),
1609 overridden_kind: overridden_kind.to_string(),
1610 orphan_decl: self.orphan_decl.to_string(),
1611 detail,
1612 src: self.importer_src.clone(),
1613 span: self.include_span.into(),
1614 }
1615 }
1616
1617 fn check_type_expr(&self, type_expr: &TypeExpr) -> Result<(), GraphcalError> {
1618 use crate::desugar::desugared_ast::TypeExprKind;
1619 match &type_expr.kind {
1620 TypeExprKind::DimExpr(dim_expr) => {
1621 for item in &dim_expr.terms {
1622 let name = &item.term.name.value;
1623 if let Some(atom) = name.as_bare()
1624 && self.type_bindings.contains_key(atom.as_str())
1625 {
1626 return Err(self.orphan_error(
1627 "type",
1628 atom.as_str(),
1629 format!("type `{name}`"),
1630 ));
1631 }
1632 }
1633 Ok(())
1634 }
1635 TypeExprKind::TypeApplication { name, type_args } => {
1636 if let Some(atom) = name.value.as_bare()
1637 && self.type_bindings.contains_key(atom.as_str())
1638 {
1639 return Err(self.orphan_error(
1640 "type",
1641 atom.as_str(),
1642 format!("type `{}`", name.value),
1643 ));
1644 }
1645 for arg in type_args {
1646 self.check_type_expr(arg)?;
1647 }
1648 Ok(())
1649 }
1650 TypeExprKind::DatetimeApplication { type_args } => {
1651 for arg in type_args {
1652 self.check_type_expr(arg)?;
1653 }
1654 Ok(())
1655 }
1656 TypeExprKind::Indexed { base, .. } => self.check_type_expr(base),
1657 TypeExprKind::Dimensionless
1658 | TypeExprKind::Bool
1659 | TypeExprKind::Int
1660 | TypeExprKind::Datetime => Ok(()),
1661 }
1662 }
1663}
1664
1665impl ExprVisitor<crate::syntax::phase::Desugared> for OverrideReconciliationChecker<'_> {
1666 type Error = GraphcalError;
1667
1668 fn visit_unresolved_ref(&mut self, expr: &Expr) -> Result<(), Self::Error> {
1669 let ExprKind::UnresolvedRef(crate::syntax::ast::UnresolvedRef::Path(path)) = &expr.kind
1670 else {
1671 return Ok(());
1672 };
1673 if let [head, variant] = path.segments()
1676 && self.index_bindings.contains_key(head.name.as_str())
1677 {
1678 return Err(self.orphan_error(
1679 "index",
1680 head.name.as_str(),
1681 format!("`{}.{}`", head.name, variant.name),
1682 ));
1683 }
1684 if let Some(ident) = path.as_bare()
1686 && self.type_bindings.contains_key(ident.name.as_str())
1687 {
1688 let n = ident.name.as_str();
1689 return Err(self.orphan_error("type", n, format!("constructor `{n}`")));
1690 }
1691 Ok(())
1692 }
1693
1694 fn visit_single_child(&mut self, expr: &Expr, inner: &Expr) -> Result<(), Self::Error> {
1695 if let ExprKind::IndexAccess { args, .. } = &expr.kind {
1696 for arg in args {
1697 if let crate::desugar::desugared_ast::IndexArg::Variant { index, variant } = arg
1698 && self
1699 .index_bindings
1700 .contains_key(index.value.leaf().as_str())
1701 {
1702 return Err(self.orphan_error(
1703 "index",
1704 index.value.leaf().as_str(),
1705 format!("`{}.{}`", index.value, variant.value),
1706 ));
1707 }
1708 }
1709 }
1710 self.visit_expr(inner)
1711 }
1712
1713 fn visit_map_entries(
1714 &mut self,
1715 _expr: &Expr,
1716 entries: &[crate::desugar::desugared_ast::MapEntry],
1717 ) -> Result<(), Self::Error> {
1718 for entry in entries {
1719 let key = entry.keys.first();
1720 if let crate::syntax::ast::MapEntryIndex::Named(index_name) = &key.index.value
1721 && self.index_bindings.contains_key(index_name.leaf().as_str())
1722 {
1723 return Err(self.orphan_error(
1724 "index",
1725 index_name.leaf().as_str(),
1726 format!("`{}.{}`", index_name, key.variant.value),
1727 ));
1728 }
1729 self.visit_expr(&entry.value)?;
1730 }
1731 Ok(())
1732 }
1733
1734 fn visit_match(
1735 &mut self,
1736 _expr: &Expr,
1737 scrutinee: &Expr,
1738 arms: &[crate::desugar::desugared_ast::MatchArm],
1739 ) -> Result<(), Self::Error> {
1740 self.visit_expr(scrutinee)?;
1741 for arm in arms {
1742 match &arm.pattern {
1743 crate::desugar::desugared_ast::MatchPattern::IndexLabel {
1744 index, variant, ..
1745 } if self
1746 .index_bindings
1747 .contains_key(index.value.leaf().as_str()) =>
1748 {
1749 return Err(self.orphan_error(
1750 "index",
1751 index.value.leaf().as_str(),
1752 format!("`{}.{}`", index.value, variant.value),
1753 ));
1754 }
1755 crate::desugar::desugared_ast::MatchPattern::Path { path, .. } => {
1756 if let [head, variant] = path.segments()
1757 && self.index_bindings.contains_key(head.name.as_str())
1758 {
1759 return Err(self.orphan_error(
1760 "index",
1761 head.name.as_str(),
1762 format!("`{}.{}`", head.name, variant.name),
1763 ));
1764 }
1765 }
1766 _ => {}
1767 }
1768 self.visit_expr(&arm.body)?;
1769 }
1770 Ok(())
1771 }
1772
1773 fn visit_constructor_call(
1774 &mut self,
1775 expr: &Expr,
1776 fields: &[crate::desugar::desugared_ast::FieldInit],
1777 ) -> Result<(), Self::Error> {
1778 if let ExprKind::ConstructorCall {
1779 callee,
1780 generic_args,
1781 ..
1782 } = &expr.kind
1783 {
1784 if let Some(constructor) = callee.as_bare() {
1785 let n = constructor.name.as_str();
1786 if self.type_bindings.contains_key(n) {
1787 return Err(self.orphan_error("type", n, format!("constructor `{n}(...)`")));
1788 }
1789 }
1790 for arg in generic_args {
1791 if let crate::desugar::desugared_ast::GenericArg::Type(ty) = arg {
1792 self.check_type_expr(ty)?;
1793 }
1794 }
1795 }
1796 for f in fields {
1797 self.visit_expr(&f.value)?;
1798 }
1799 Ok(())
1800 }
1801
1802 fn visit_fn_call(&mut self, expr: &Expr, args: &[Expr]) -> Result<(), Self::Error> {
1803 if let ExprKind::FnCall { type_args, .. } = &expr.kind {
1804 for ga in type_args {
1805 if let crate::desugar::desugared_ast::GenericArg::Type(ty) = ga {
1806 self.check_type_expr(ty)?;
1807 }
1808 }
1809 }
1810 for arg in args {
1811 self.visit_expr(arg)?;
1812 }
1813 Ok(())
1814 }
1815}
1816
1817struct RefPrefixer<'a> {
1825 prefix: &'a str,
1826 prefix_atom: NameAtom,
1827 dep_names: &'a HashSet<DeclName>,
1828}
1829
1830impl RefPrefixer<'_> {
1831 fn rewrite(&self, scoped: &ScopedName) -> Option<ScopedName> {
1832 if !scoped.is_qualified() && self.dep_names.contains(scoped.member()) {
1837 Some(scoped.with_prefix(self.prefix))
1838 } else {
1839 None
1840 }
1841 }
1842}
1843
1844impl ExprVisitorMut<crate::syntax::phase::Desugared> for RefPrefixer<'_> {
1845 type Error = std::convert::Infallible;
1846
1847 fn visit_graph_ref_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1848 if let ExprKind::GraphRef(ident) = &mut expr.kind
1849 && let Some(prefixed) = self.rewrite(&ident.value)
1850 {
1851 ident.value = prefixed;
1852 }
1853 Ok(())
1854 }
1855
1856 fn visit_unresolved_ref_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1857 if let ExprKind::UnresolvedRef(crate::syntax::ast::UnresolvedRef::Path(path)) =
1862 &mut expr.kind
1863 && let Some(ident) = path.as_bare()
1864 && self.dep_names.contains(ident.name.as_str())
1865 {
1866 let leaf = ident.clone();
1867 let prefix_segment = crate::syntax::ast::Ident {
1868 name: self.prefix_atom.clone(),
1869 span: leaf.span,
1870 };
1871 *path = crate::syntax::ast::IdentPath::new(crate::syntax::non_empty::NonEmpty::new(
1872 prefix_segment,
1873 vec![leaf],
1874 ));
1875 }
1876 Ok(())
1877 }
1878
1879 }
1884
1885pub(crate) fn prefix_expr_refs(expr: &mut Expr, prefix: &str, dep_names: &HashSet<DeclName>) {
1893 let Ok(prefix_atom) = NameAtom::parse(prefix) else {
1894 return;
1897 };
1898 let mut prefixer = RefPrefixer {
1899 prefix,
1900 prefix_atom,
1901 dep_names,
1902 };
1903 let _ = prefixer.visit_expr_mut(expr);
1904}
1905
1906struct IndexSubstituter<'a> {
1913 bindings: &'a HashMap<IndexName, IndexName>,
1914}
1915
1916impl ExprVisitorMut<crate::syntax::phase::Desugared> for IndexSubstituter<'_> {
1917 type Error = std::convert::Infallible;
1918
1919 fn visit_unresolved_ref_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1920 if let ExprKind::UnresolvedRef(crate::syntax::ast::UnresolvedRef::Path(path)) =
1924 &mut expr.kind
1925 && let [head, _variant] = path.segments.as_mut_slice()
1926 && let Some(new) = self.bindings.get(head.name.as_str())
1927 && let Ok(new_atom) = NameAtom::parse(new.as_str())
1928 {
1929 head.name = new_atom;
1930 }
1931 Ok(())
1932 }
1933
1934 fn visit_for_comp_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1935 if let ExprKind::ForComp { bindings, body } = &mut expr.kind {
1936 for b in bindings {
1937 if let crate::desugar::desugared_ast::ForBindingIndex::Named(ref mut spanned_idx) =
1938 b.index
1939 && let Some(new) = self.bindings.get(spanned_idx.value.leaf().as_str())
1940 {
1941 spanned_idx.value = new.clone().into();
1942 }
1943 }
1944 self.visit_expr_mut(body)?;
1945 }
1946 Ok(())
1947 }
1948
1949 fn visit_index_access_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1950 use crate::desugar::desugared_ast::IndexArg;
1951 if let ExprKind::IndexAccess { expr: inner, args } = &mut expr.kind {
1952 for arg in args.iter_mut() {
1953 match arg {
1954 IndexArg::Variant { index, .. } => {
1955 if let Some(new) = self.bindings.get(index.value.leaf().as_str()) {
1956 index.value = new.clone().into();
1957 }
1958 }
1959 IndexArg::Expr(e) => {
1960 self.visit_expr_mut(e)?;
1961 }
1962 IndexArg::Var(_) => {}
1963 }
1964 }
1965 self.visit_expr_mut(inner)?;
1966 }
1967 Ok(())
1968 }
1969
1970 fn visit_map_literal_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1971 if let ExprKind::MapLiteral { entries } = &mut expr.kind {
1972 for entry in entries.iter_mut() {
1973 for key in &mut entry.keys {
1974 if let crate::syntax::ast::MapEntryIndex::Named(index_name) = &key.index.value
1975 && let Some(new) = self.bindings.get(index_name.leaf().as_str())
1976 {
1977 key.index.value =
1978 crate::syntax::ast::MapEntryIndex::Named(new.clone().into());
1979 }
1980 }
1981 self.visit_expr_mut(&mut entry.value)?;
1982 }
1983 }
1984 Ok(())
1985 }
1986
1987 fn visit_match_mut(&mut self, expr: &mut Expr) -> Result<(), Self::Error> {
1988 if let ExprKind::Match { scrutinee, arms } = &mut expr.kind {
1989 self.visit_expr_mut(scrutinee)?;
1990 for arm in arms {
1991 match &mut arm.pattern {
1992 crate::desugar::desugared_ast::MatchPattern::IndexLabel { index, .. } => {
1993 if let Some(new) = self.bindings.get(index.value.leaf().as_str()) {
1994 index.value = new.clone().into();
1995 }
1996 }
1997 crate::desugar::desugared_ast::MatchPattern::Path { path, .. } => {
2000 if let [head, _variant] = path.segments.as_mut_slice()
2001 && let Some(new) = self.bindings.get(head.name.as_str())
2002 && let Ok(new_atom) = NameAtom::parse(new.as_str())
2003 {
2004 head.name = new_atom;
2005 }
2006 }
2007 crate::desugar::desugared_ast::MatchPattern::Constructor { .. } => {}
2008 }
2009 self.visit_expr_mut(&mut arm.body)?;
2010 }
2011 }
2012 Ok(())
2013 }
2014}
2015
2016pub(crate) fn substitute_index_names(expr: &mut Expr, bindings: &HashMap<IndexName, IndexName>) {
2025 if bindings.is_empty() {
2026 return;
2027 }
2028 let mut sub = IndexSubstituter { bindings };
2029 let _ = sub.visit_expr_mut(expr);
2030}
2031
2032#[expect(
2039 clippy::implicit_hasher,
2040 reason = "internal API always uses default hasher"
2041)]
2042pub fn substitute_type_expr_index_names(
2043 type_expr: &mut TypeExpr,
2044 bindings: &HashMap<IndexName, IndexName>,
2045) {
2046 use crate::desugar::desugared_ast::TypeExprKind;
2047
2048 if bindings.is_empty() {
2049 return;
2050 }
2051 match &mut type_expr.kind {
2052 TypeExprKind::Indexed { base, indexes } => {
2053 for idx_expr in indexes.iter_mut() {
2054 if let crate::desugar::desugared_ast::IndexExpr::Name(path) = idx_expr
2055 && let Some(atom) = path.value.as_bare()
2056 && let Some(new_name) = bindings.get(atom.as_str())
2057 {
2058 path.value = crate::syntax::names::NamePath::from(new_name.as_str());
2059 }
2060 }
2061 substitute_type_expr_index_names(base, bindings);
2062 }
2063 TypeExprKind::TypeApplication { type_args, .. }
2064 | TypeExprKind::DatetimeApplication { type_args } => {
2065 for arg in type_args {
2066 substitute_type_expr_index_names(arg, bindings);
2067 }
2068 }
2069 TypeExprKind::Dimensionless
2070 | TypeExprKind::Bool
2071 | TypeExprKind::Int
2072 | TypeExprKind::Datetime
2073 | TypeExprKind::DimExpr(_) => {}
2074 }
2075}
2076
2077#[expect(
2086 clippy::implicit_hasher,
2087 reason = "internal API always uses default hasher"
2088)]
2089pub fn substitute_type_expr_nominal_names<K>(type_expr: &mut TypeExpr, bindings: &HashMap<K, K>)
2090where
2091 K: std::hash::Hash + Eq + std::borrow::Borrow<str> + AsRef<str>,
2092{
2093 use crate::desugar::desugared_ast::TypeExprKind;
2094
2095 if bindings.is_empty() {
2096 return;
2097 }
2098 match &mut type_expr.kind {
2099 TypeExprKind::DimExpr(dim_expr) => {
2100 for item in &mut dim_expr.terms {
2101 if let Some(atom) = item.term.name.value.as_bare()
2102 && let Some(new_name) = bindings.get(atom.as_str())
2103 {
2104 item.term.name.value = crate::syntax::names::NamePath::from(new_name.as_ref());
2105 }
2106 }
2107 }
2108 TypeExprKind::Indexed { base, .. } => {
2109 substitute_type_expr_nominal_names(base, bindings);
2110 }
2111 TypeExprKind::TypeApplication { name, type_args } => {
2112 if let Some(atom) = name.value.as_bare()
2113 && let Some(new_name) = bindings.get(atom.as_str())
2114 {
2115 name.value = crate::syntax::names::NamePath::from(new_name.as_ref());
2116 }
2117 for arg in type_args {
2118 substitute_type_expr_nominal_names(arg, bindings);
2119 }
2120 }
2121 TypeExprKind::DatetimeApplication { type_args } => {
2122 for arg in type_args {
2125 substitute_type_expr_nominal_names(arg, bindings);
2126 }
2127 }
2128 TypeExprKind::Dimensionless
2129 | TypeExprKind::Bool
2130 | TypeExprKind::Int
2131 | TypeExprKind::Datetime => {}
2132 }
2133}
2134
2135#[expect(
2141 clippy::too_many_lines,
2142 reason = "single recursion covering every ExprKind variant"
2143)]
2144pub(crate) fn substitute_type_names_in_expr(
2145 expr: &mut Expr,
2146 bindings: &HashMap<StructTypeName, StructTypeName>,
2147) {
2148 use crate::desugar::desugared_ast::{GenericArg, IndexArg};
2149
2150 if bindings.is_empty() {
2151 return;
2152 }
2153 match &mut expr.kind {
2154 ExprKind::Number(_)
2155 | ExprKind::Integer(_)
2156 | ExprKind::Bool(_)
2157 | ExprKind::StringLiteral(_)
2158 | ExprKind::UnitLiteral { .. }
2159 | ExprKind::GraphRef(_) => {}
2160
2161 ExprKind::UnresolvedRef(crate::syntax::ast::UnresolvedRef::Path(path)) => {
2164 if let Some(ident) = path.as_bare_mut()
2165 && let Some(new_name) = bindings.get(ident.name.as_str())
2166 && let Ok(parsed_name) = NameAtom::parse(new_name.as_ref())
2167 {
2168 ident.name = parsed_name;
2169 }
2170 }
2171
2172 ExprKind::InlineDagRef { args, .. } => {
2173 for binding in args {
2174 substitute_type_names_in_expr(&mut binding.value, bindings);
2175 }
2176 }
2177
2178 ExprKind::ConstructorCall {
2179 callee,
2180 generic_args,
2181 fields,
2182 } => {
2183 if let Some(constructor) = callee.as_bare_mut()
2184 && let Some(new_name) = bindings.get(constructor.name.as_str())
2185 && let Ok(parsed_name) = NameAtom::parse(new_name.as_ref())
2186 {
2187 constructor.name = parsed_name;
2188 }
2189 for arg in generic_args.iter_mut() {
2190 if let GenericArg::Type(ty) = arg {
2191 substitute_type_expr_nominal_names(ty, bindings);
2192 }
2193 }
2194 for field in fields {
2195 substitute_type_names_in_expr(&mut field.value, bindings);
2196 }
2197 }
2198
2199 ExprKind::FnCall {
2200 type_args, args, ..
2201 } => {
2202 for ga in type_args.iter_mut() {
2203 if let GenericArg::Type(ty) = ga {
2204 substitute_type_expr_nominal_names(ty, bindings);
2205 }
2206 }
2207 for arg in args {
2208 substitute_type_names_in_expr(arg, bindings);
2209 }
2210 }
2211
2212 ExprKind::BinOp { lhs, rhs, .. } => {
2213 substitute_type_names_in_expr(lhs, bindings);
2214 substitute_type_names_in_expr(rhs, bindings);
2215 }
2216 ExprKind::UnaryOp { operand, .. } => {
2217 substitute_type_names_in_expr(operand, bindings);
2218 }
2219 ExprKind::If {
2220 condition,
2221 then_branch,
2222 else_branch,
2223 } => {
2224 substitute_type_names_in_expr(condition, bindings);
2225 substitute_type_names_in_expr(then_branch, bindings);
2226 substitute_type_names_in_expr(else_branch, bindings);
2227 }
2228 ExprKind::Convert { expr: inner, .. }
2229 | ExprKind::DisplayTimezone { expr: inner, .. }
2230 | ExprKind::FieldAccess { expr: inner, .. } => {
2231 substitute_type_names_in_expr(inner, bindings);
2232 }
2233 ExprKind::IndexAccess { expr: inner, args } => {
2234 substitute_type_names_in_expr(inner, bindings);
2235 for arg in args {
2236 if let IndexArg::Expr(e) = arg {
2237 substitute_type_names_in_expr(e, bindings);
2238 }
2239 }
2240 }
2241 ExprKind::MapLiteral { entries } => {
2242 for entry in entries {
2243 substitute_type_names_in_expr(&mut entry.value, bindings);
2244 }
2245 }
2246 ExprKind::ForComp { body, .. } => {
2247 substitute_type_names_in_expr(body, bindings);
2248 }
2249 ExprKind::Scan {
2250 source, init, body, ..
2251 } => {
2252 substitute_type_names_in_expr(source, bindings);
2253 substitute_type_names_in_expr(init, bindings);
2254 substitute_type_names_in_expr(body, bindings);
2255 }
2256 ExprKind::Unfold { init, body, .. } => {
2257 substitute_type_names_in_expr(init, bindings);
2258 substitute_type_names_in_expr(body, bindings);
2259 }
2260 ExprKind::Match { scrutinee, arms } => {
2261 substitute_type_names_in_expr(scrutinee, bindings);
2262 for arm in arms {
2263 substitute_type_names_in_expr(&mut arm.body, bindings);
2264 }
2265 }
2266 #[expect(
2269 clippy::uninhabited_references,
2270 reason = "Sugar(Infallible) — proof of unreachability"
2271 )]
2272 ExprKind::Sugar(s) => match *s {},
2273 }
2274}
2275
2276pub(crate) fn register_file_declarations(
2283 file: &File,
2284 registry: &mut RegistryBuilder,
2285 src: &NamedSource<Arc<String>>,
2286 dag_id: &crate::dag_id::DagId,
2287) -> Result<(), GraphcalError> {
2288 register_declarations_impl(file, registry, src, None, dag_id)
2289}
2290
2291#[derive(Debug, Default, Clone)]
2297pub struct SelectedDeclarations {
2298 pub default: HashSet<crate::syntax::names::NameAtom>,
2300 pub types: HashSet<crate::syntax::names::NameAtom>,
2302}
2303
2304impl SelectedDeclarations {
2305 #[must_use]
2306 pub fn is_empty(&self) -> bool {
2307 self.default.is_empty() && self.types.is_empty()
2308 }
2309
2310 pub fn insert_default(&mut self, name: impl Into<crate::syntax::names::NameAtom>) {
2311 self.default.insert(name.into());
2312 }
2313
2314 pub fn insert_type(&mut self, name: impl Into<crate::syntax::names::NameAtom>) {
2315 self.types.insert(name.into());
2316 }
2317}
2318
2319pub fn register_selected_declarations(
2330 file: &File,
2331 registry: &mut RegistryBuilder,
2332 src: &NamedSource<Arc<String>>,
2333 names: &SelectedDeclarations,
2334 dag_id: &crate::dag_id::DagId,
2335) -> Result<(), GraphcalError> {
2336 register_declarations_impl(file, registry, src, Some(names), dag_id)
2337}
2338
2339fn register_declarations_impl(
2355 file: &File,
2356 registry: &mut RegistryBuilder,
2357 src: &NamedSource<Arc<String>>,
2358 filter: Option<&SelectedDeclarations>,
2359 dag_id: &crate::dag_id::DagId,
2360) -> Result<(), GraphcalError> {
2361 use crate::desugar::desugared_ast::{DimDecl, IndexDecl, UnitDecl};
2362
2363 let should_register_default =
2364 |name: &str| filter.is_none_or(|names| names.default.contains(name));
2365 let should_register_type = |name: &str| filter.is_none_or(|names| names.types.contains(name));
2366
2367 let mut derived_dims: Vec<&DimDecl> = Vec::new();
2369 let mut units: Vec<&UnitDecl> = Vec::new();
2370 let mut required_range_indexes: Vec<(&IndexDecl, Span)> = Vec::new();
2371 let mut range_indexes: Vec<(&IndexDecl, Span)> = Vec::new();
2372
2373 for decl in &file.declarations {
2376 match &decl.kind {
2377 DeclKind::BaseDimension(d) if should_register_default(d.name.value.as_str()) => {
2378 register_base_dimension_decl(d, registry, dag_id);
2379 }
2380 DeclKind::Dimension(d) if should_register_default(d.name.value.as_str()) => {
2381 if d.definition.is_some() {
2382 derived_dims.push(d);
2383 } else {
2384 register_required_dimension_decl(d, registry, dag_id);
2389 }
2390 }
2391 DeclKind::Unit(u) if should_register_default(u.name.value.as_str()) => {
2392 units.push(u);
2393 }
2394 DeclKind::Index(idx) if should_register_default(idx.name.value.as_str()) => {
2395 match &idx.kind {
2396 IndexDeclKind::RequiredRange { .. } => {
2397 required_range_indexes.push((idx, decl.span));
2398 }
2399 IndexDeclKind::Range { .. } => {
2400 range_indexes.push((idx, decl.span));
2401 }
2402 IndexDeclKind::Named { .. } | IndexDeclKind::RequiredNamed => {
2403 register_index_decl(idx, registry, src, decl.span)?;
2404 }
2405 }
2406 }
2407 DeclKind::Type(t) if should_register_type(t.name.value.as_str()) => {
2408 register_type_decl(t, registry);
2409 }
2410 DeclKind::Dag(d) if should_register_default(d.name.value.as_str()) => {
2411 registry.register_dag(d.name.value.clone(), d.clone());
2412 }
2413 _ => {}
2414 }
2415 }
2416
2417 if !derived_dims.is_empty() {
2419 let sorted = topo_sort_derived_dims(&derived_dims, src)?;
2420 for d in sorted {
2421 register_dimension_decl(d, registry, src)?;
2422 }
2423 }
2424
2425 for (idx, span) in &required_range_indexes {
2427 register_index_decl(idx, registry, src, *span)?;
2428 }
2429
2430 if !units.is_empty() {
2432 let sorted = topo_sort_units(&units, src)?;
2433 for u in sorted {
2434 register_unit_decl(u, registry, src)?;
2435 }
2436 }
2437
2438 for (idx, span) in &range_indexes {
2440 register_index_decl(idx, registry, src, *span)?;
2441 }
2442
2443 for decl in &file.declarations {
2447 match &decl.kind {
2448 DeclKind::Param(d) => {
2449 collect_nat_ranges_from_type_expr(&d.type_ann, registry, src)?;
2450 if let Some(ref value) = d.value {
2451 collect_nat_ranges_from_expr(value, registry, src)?;
2452 }
2453 }
2454 DeclKind::Node(d) => {
2455 collect_nat_ranges_from_type_expr(&d.type_ann, registry, src)?;
2456 collect_nat_ranges_from_expr(&d.value, registry, src)?;
2457 }
2458 DeclKind::ConstNode(d) => {
2459 collect_nat_ranges_from_type_expr(&d.type_ann, registry, src)?;
2460 collect_nat_ranges_from_expr(&d.value, registry, src)?;
2461 }
2462 _ => {}
2463 }
2464 }
2465
2466 Ok(())
2467}
2468
2469fn topo_sort_derived_dims<'a>(
2475 dims: &[&'a crate::desugar::desugared_ast::DimDecl],
2476 src: &NamedSource<Arc<String>>,
2477) -> Result<Vec<&'a crate::desugar::desugared_ast::DimDecl>, GraphcalError> {
2478 let mut graph = DiGraph::<&str, ()>::new();
2479 let mut name_to_idx: HashMap<&str, petgraph::graph::NodeIndex> = HashMap::new();
2480 let mut idx_to_pos: HashMap<petgraph::graph::NodeIndex, usize> = HashMap::new();
2481
2482 for (pos, d) in dims.iter().enumerate() {
2484 let name = d.name.value.as_str();
2485 let idx = graph.add_node(name);
2486 name_to_idx.insert(name, idx);
2487 idx_to_pos.insert(idx, pos);
2488 }
2489
2490 for d in dims {
2494 let self_name = d.name.value.as_str();
2495 let from = name_to_idx[self_name];
2496 let Some(definition) = &d.definition else {
2499 continue;
2500 };
2501 for item in &definition.terms {
2502 let Some(dep_name) = item
2503 .term
2504 .name
2505 .value
2506 .as_bare()
2507 .map(super::super::syntax::names::NameAtom::as_str)
2508 else {
2509 continue;
2510 };
2511 if dep_name != self_name
2512 && let Some(&to) = name_to_idx.get(dep_name)
2513 {
2514 graph.add_edge(from, to, ());
2515 }
2516 }
2517 }
2518
2519 let sorted_indices = toposort(&graph, None).map_err(|cycle| {
2521 let cycle_name = graph[cycle.node_id()];
2522 let pos = idx_to_pos[&cycle.node_id()];
2523 GraphcalError::CyclicDimension {
2524 name: DimName::new(cycle_name),
2525 src: src.clone(),
2526 span: dims[pos].name.span.into(),
2527 }
2528 })?;
2529
2530 Ok(sorted_indices
2532 .into_iter()
2533 .rev()
2534 .map(|idx| dims[idx_to_pos[&idx]])
2535 .collect())
2536}
2537
2538fn topo_sort_units<'a>(
2544 units: &[&'a crate::desugar::desugared_ast::UnitDecl],
2545 src: &NamedSource<Arc<String>>,
2546) -> Result<Vec<&'a crate::desugar::desugared_ast::UnitDecl>, GraphcalError> {
2547 let mut graph = DiGraph::<&str, ()>::new();
2548 let mut name_to_idx: HashMap<&str, petgraph::graph::NodeIndex> = HashMap::new();
2549 let mut idx_to_pos: HashMap<petgraph::graph::NodeIndex, usize> = HashMap::new();
2550
2551 for (pos, u) in units.iter().enumerate() {
2553 let name = u.name.value.as_str();
2554 let idx = graph.add_node(name);
2555 name_to_idx.insert(name, idx);
2556 idx_to_pos.insert(idx, pos);
2557 }
2558
2559 for u in units {
2561 let self_name = u.name.value.as_str();
2562 let from = name_to_idx[self_name];
2563 if let Some(def) = &u.definition {
2564 for item in &def.unit_expr.terms {
2565 if item.name.value.is_qualified() {
2568 continue;
2569 }
2570 let dep_name = item.name.value.name().as_str();
2571 if dep_name != self_name
2572 && let Some(&to) = name_to_idx.get(dep_name)
2573 {
2574 graph.add_edge(from, to, ());
2575 }
2576 }
2577 }
2578 }
2579
2580 let sorted_indices = toposort(&graph, None).map_err(|cycle| {
2581 let pos = idx_to_pos[&cycle.node_id()];
2582 GraphcalError::CyclicUnit {
2583 name: units[pos].name.value.clone(),
2584 src: src.clone(),
2585 span: units[pos].name.span.into(),
2586 }
2587 })?;
2588
2589 Ok(sorted_indices
2591 .into_iter()
2592 .rev()
2593 .map(|idx| units[idx_to_pos[&idx]])
2594 .collect())
2595}
2596
2597fn register_base_dimension_decl(
2598 d: &crate::desugar::desugared_ast::BaseDimDecl,
2599 registry: &mut RegistryBuilder,
2600 dag_id: &crate::dag_id::DagId,
2601) {
2602 let dim_id = crate::syntax::dimension::BaseDimId::UserDefined {
2603 dag: dag_id.clone(),
2604 name: d.name.value.to_string(),
2605 };
2606 registry.register_base_dimension(d.name.value.clone(), dim_id);
2607}
2608
2609fn register_dimension_decl(
2610 d: &crate::desugar::desugared_ast::DimDecl,
2611 registry: &mut RegistryBuilder,
2612 src: &NamedSource<Arc<String>>,
2613) -> Result<(), GraphcalError> {
2614 let Some(definition) = d.definition.as_ref() else {
2618 return Ok(());
2619 };
2620 let dim = registry
2621 .resolve_dim_expr(definition)
2622 .map_err(|_| GraphcalError::DimensionOverflow {
2623 src: src.clone(),
2624 span: d.name.span.into(),
2625 })?
2626 .ok_or_else(|| GraphcalError::UnknownDimension {
2627 name: d.name.value.clone(),
2628 src: src.clone(),
2629 span: d.name.span.into(),
2630 })?;
2631 registry.register_dimension(d.name.value.clone(), dim);
2632 Ok(())
2633}
2634
2635fn register_required_dimension_decl(
2641 d: &crate::desugar::desugared_ast::DimDecl,
2642 registry: &mut RegistryBuilder,
2643 dag_id: &crate::dag_id::DagId,
2644) {
2645 let dim_id = crate::syntax::dimension::BaseDimId::UserDefined {
2646 dag: dag_id.clone(),
2647 name: d.name.value.to_string(),
2648 };
2649 registry.register_base_dimension(d.name.value.clone(), dim_id);
2650}
2651
2652fn eval_error(
2653 message: impl Into<String>,
2654 src: &NamedSource<Arc<String>>,
2655 span: Span,
2656) -> GraphcalError {
2657 GraphcalError::EvalError {
2658 message: message.into(),
2659 src: src.clone(),
2660 span: span.into(),
2661 }
2662}
2663
2664fn validate_positive_finite_scale(
2665 value: f64,
2666 context: &str,
2667 src: &NamedSource<Arc<String>>,
2668 span: Span,
2669) -> Result<PositiveFiniteScale, GraphcalError> {
2670 PositiveFiniteScale::new(value).map_err(|err| {
2671 let reason = match err {
2672 PositiveFiniteScaleError::NonFinite => "must be finite",
2673 PositiveFiniteScaleError::NonPositive => "must be greater than zero",
2674 };
2675 eval_error(format!("{context} {reason}, got {value}"), src, span)
2676 })
2677}
2678
2679fn multiply_positive_scales(
2680 lhs: PositiveFiniteScale,
2681 rhs: PositiveFiniteScale,
2682 context: &str,
2683 src: &NamedSource<Arc<String>>,
2684 span: Span,
2685) -> Result<PositiveFiniteScale, GraphcalError> {
2686 validate_positive_finite_scale(lhs.get() * rhs.get(), context, src, span)
2687}
2688
2689fn register_unit_decl(
2690 u: &crate::desugar::desugared_ast::UnitDecl,
2691 registry: &mut RegistryBuilder,
2692 src: &NamedSource<Arc<String>>,
2693) -> Result<(), GraphcalError> {
2694 let dim = registry
2695 .resolve_dim_expr(&u.dim_type)
2696 .map_err(|_| GraphcalError::DimensionOverflow {
2697 src: src.clone(),
2698 span: u.name.span.into(),
2699 })?
2700 .ok_or_else(|| GraphcalError::UnknownDimension {
2701 name: DimName::new(u.name.value.as_str()),
2702 src: src.clone(),
2703 span: u.name.span.into(),
2704 })?;
2705 if u.definition.is_some() && registry.is_affine_prone(&dim) {
2706 return Err(GraphcalError::AffineProneUnitDefinition {
2707 dim: registry.format_dimension(&dim),
2708 src: src.clone(),
2709 span: u.name.span.into(),
2710 });
2711 }
2712 let scale = if let Some(def) = &u.definition {
2713 if u.constness.is_const() {
2714 if let Some(graph_ref) = first_graph_ref(&def.scale_expr) {
2715 return Err(GraphcalError::GraphRefInConstUnit {
2716 name: graph_ref.value,
2717 src: src.clone(),
2718 span: graph_ref.span.into(),
2719 });
2720 }
2721 if let Some(unit_name) = first_non_const_unit_ref(registry, &def.unit_expr) {
2722 return Err(GraphcalError::NonConstUnitInConst {
2723 name: unit_name.value.clone(),
2724 src: src.clone(),
2725 span: unit_name.span.into(),
2726 });
2727 }
2728 }
2729 if contains_graph_ref(&def.scale_expr) {
2730 let base_scale = resolve_base_unit_static_scale(registry, &def.unit_expr, src)?;
2733 UnitScale::Dynamic {
2734 scale_expr: def.scale_expr.clone(),
2735 base_unit_scale: base_scale,
2736 }
2737 } else {
2738 let (_unit_dim, base_scale) = registry
2742 .resolve_unit_expr(&def.unit_expr)
2743 .map_err(|err| unit_resolve_to_graphcal(err, src, def.span))?;
2744 let scale_expr = validate_positive_finite_scale(
2745 eval_scale_expr(&def.scale_expr, src)?,
2746 "unit scale expression",
2747 src,
2748 def.scale_expr.span,
2749 )?;
2750 let base_scale = validate_positive_finite_scale(
2751 base_scale,
2752 "base unit scale",
2753 src,
2754 def.unit_expr.span,
2755 )?;
2756 let scale =
2757 multiply_positive_scales(scale_expr, base_scale, "unit scale", src, def.span)?;
2758 UnitScale::Static(scale)
2759 }
2760 } else {
2761 UnitScale::Static(validate_positive_finite_scale(
2762 1.0,
2763 "base unit scale",
2764 src,
2765 u.name.span,
2766 )?)
2767 };
2768 if u.definition.is_none() {
2773 let mut iter = dim.iter();
2775 if let Some((id, &exp)) = iter.next()
2776 && iter.next().is_none()
2777 && exp == Rational::ONE
2778 {
2779 registry.set_base_dim_symbol(id.clone(), u.name.value.to_string());
2780 }
2781 }
2782 registry.register_unit_with_scale(u.name.value.clone(), dim, scale, u.constness);
2783 Ok(())
2784}
2785
2786fn first_graph_ref(expr: &Expr) -> Option<Spanned<ScopedName>> {
2787 struct FirstGraphRef(Option<Spanned<ScopedName>>);
2788
2789 impl ExprVisitor<crate::syntax::phase::Desugared> for FirstGraphRef {
2790 type Error = std::convert::Infallible;
2791
2792 fn visit_graph_ref(&mut self, expr: &Expr) -> Result<(), Self::Error> {
2793 if self.0.is_none()
2794 && let ExprKind::GraphRef(name) = &expr.kind
2795 {
2796 self.0 = Some(name.clone());
2797 }
2798 Ok(())
2799 }
2800 }
2801
2802 let mut visitor = FirstGraphRef(None);
2803 let _ = visitor.visit_expr(expr);
2804 visitor.0
2805}
2806
2807fn first_non_const_unit_ref<'a>(
2808 registry: &RegistryBuilder,
2809 unit_expr: &'a crate::desugar::desugared_ast::UnitExpr,
2810) -> Option<&'a Spanned<crate::syntax::names::UnitRef>> {
2811 unit_expr.terms.iter().find_map(|term| {
2812 registry
2813 .get_unit(&term.name.value)
2814 .is_some_and(|info| !info.constness.is_const())
2815 .then_some(&term.name)
2816 })
2817}
2818
2819fn resolve_base_unit_static_scale(
2824 registry: &RegistryBuilder,
2825 unit_expr: &crate::desugar::desugared_ast::UnitExpr,
2826 src: &NamedSource<Arc<String>>,
2827) -> Result<PositiveFiniteScale, GraphcalError> {
2828 let (_dim, base_scale) = registry
2829 .resolve_unit_expr(unit_expr)
2830 .map_err(|err| unit_resolve_to_graphcal(err, src, unit_expr.span))?;
2831 validate_positive_finite_scale(base_scale, "base unit scale", src, unit_expr.span)
2832}
2833
2834fn unit_resolve_to_graphcal(
2836 err: crate::registry::types::UnitResolveError,
2837 src: &NamedSource<Arc<String>>,
2838 span: Span,
2839) -> GraphcalError {
2840 use crate::registry::types::UnitResolveError;
2841 match err {
2842 UnitResolveError::UnknownUnit(name) => GraphcalError::UnknownUnit {
2843 name,
2844 src: src.clone(),
2845 span: span.into(),
2846 },
2847 UnitResolveError::DynamicScale(name) => GraphcalError::EvalError {
2848 message: format!("unit `{name}` has a dynamic scale and cannot be used here"),
2849 src: src.clone(),
2850 span: span.into(),
2851 },
2852 UnitResolveError::Overflow(_) => GraphcalError::DimensionOverflow {
2853 src: src.clone(),
2854 span: span.into(),
2855 },
2856 }
2857}
2858
2859fn contains_graph_ref(expr: &Expr) -> bool {
2861 crate::ir::resolve::contains_graph_ref(expr)
2862}
2863
2864fn nat_size_to_usize(
2868 n: u64,
2869 span: Span,
2870 src: &NamedSource<Arc<String>>,
2871) -> Result<NonZeroUsize, GraphcalError> {
2872 let size = usize::try_from(n).map_err(|_| GraphcalError::EvalError {
2873 message: format!("nat range size {n} does not fit in usize on this target"),
2874 src: src.clone(),
2875 span: span.into(),
2876 })?;
2877 NonZeroUsize::new(size).ok_or_else(|| {
2878 eval_error(
2879 "range(0) is not allowed; indexes must contain at least one element",
2880 src,
2881 span,
2882 )
2883 })
2884}
2885
2886fn collect_nat_ranges_from_type_expr(
2889 type_expr: &crate::desugar::desugared_ast::TypeExpr,
2890 registry: &mut RegistryBuilder,
2891 src: &NamedSource<Arc<String>>,
2892) -> Result<(), GraphcalError> {
2893 if let crate::desugar::desugared_ast::TypeExprKind::Indexed { base, indexes } = &type_expr.kind
2894 {
2895 collect_nat_ranges_from_type_expr(base, registry, src)?;
2896 for idx in indexes {
2897 match idx {
2898 crate::desugar::desugared_ast::IndexExpr::NatExpr(nat_expr) => {
2899 collect_nat_range_literals_from_nat_expr(nat_expr, registry, src)?;
2900 }
2901 crate::desugar::desugared_ast::IndexExpr::Name(_) => {}
2902 }
2903 }
2904 }
2905 if let crate::desugar::desugared_ast::TypeExprKind::TypeApplication { type_args, .. }
2906 | crate::desugar::desugared_ast::TypeExprKind::DatetimeApplication { type_args } =
2907 &type_expr.kind
2908 {
2909 for arg in type_args {
2910 collect_nat_ranges_from_type_expr(arg, registry, src)?;
2911 }
2912 }
2913 Ok(())
2914}
2915
2916fn collect_nat_range_literals_from_nat_expr(
2921 expr: &crate::desugar::desugared_ast::NatExpr,
2922 registry: &mut RegistryBuilder,
2923 src: &NamedSource<Arc<String>>,
2924) -> Result<(), GraphcalError> {
2925 use crate::desugar::desugared_ast::NatExpr;
2926 match expr {
2927 NatExpr::Literal(n, span) => {
2928 let size = nat_size_to_usize(*n, *span, src)?;
2929 registry.ensure_nat_range_index(size);
2930 }
2931 NatExpr::Var(_) => {}
2932 NatExpr::Add(lhs, rhs, _) | NatExpr::Mul(lhs, rhs, _) => {
2933 collect_nat_range_literals_from_nat_expr(lhs, registry, src)?;
2934 collect_nat_range_literals_from_nat_expr(rhs, registry, src)?;
2935 }
2936 }
2937 Ok(())
2938}
2939
2940fn collect_nat_ranges_from_expr(
2943 expr: &crate::desugar::desugared_ast::Expr,
2944 registry: &mut RegistryBuilder,
2945 src: &NamedSource<Arc<String>>,
2946) -> Result<(), GraphcalError> {
2947 use crate::desugar::desugared_ast::{ExprKind, ForBindingIndex};
2948
2949 struct NatRangeCollector<'a> {
2951 registry: &'a mut RegistryBuilder,
2952 src: &'a NamedSource<Arc<String>>,
2953 }
2954
2955 impl crate::syntax::visitor::ExprVisitor<crate::syntax::phase::Desugared>
2956 for NatRangeCollector<'_>
2957 {
2958 type Error = GraphcalError;
2959
2960 fn visit_expr(
2961 &mut self,
2962 expr: &crate::desugar::desugared_ast::Expr,
2963 ) -> Result<(), GraphcalError> {
2964 match &expr.kind {
2965 ExprKind::ForComp { bindings, .. } => {
2966 for binding in bindings {
2967 if let ForBindingIndex::Range { arg, .. } = &binding.index {
2968 collect_nat_range_literals_from_nat_expr(arg, self.registry, self.src)?;
2969 }
2970 }
2971 }
2972 ExprKind::MapLiteral { entries } => {
2973 for entry in entries {
2974 for key in &entry.keys {
2975 if let crate::syntax::ast::MapEntryIndex::NatRange(n) = &key.index.value
2976 {
2977 let size = nat_size_to_usize(*n, key.index.span, self.src)?;
2978 self.registry.ensure_nat_range_index(size);
2979 }
2980 }
2981 }
2982 }
2983 _ => {}
2984 }
2985 self.dispatch(expr)
2986 }
2987 }
2988
2989 let mut collector = NatRangeCollector { registry, src };
2990 collector.visit_expr(expr)
2991}
2992
2993fn register_index_decl(
2994 idx: &crate::desugar::desugared_ast::IndexDecl,
2995 registry: &mut RegistryBuilder,
2996 src: &NamedSource<Arc<String>>,
2997 decl_span: Span,
2998) -> Result<(), GraphcalError> {
2999 let kind = match &idx.kind {
3000 crate::desugar::desugared_ast::IndexDeclKind::Named { variants } => {
3001 types::IndexKind::Named {
3002 variants: variants.iter().map(|v| v.value.clone()).collect(),
3003 }
3004 }
3005 crate::desugar::desugared_ast::IndexDeclKind::Range {
3006 start: start_expr,
3007 end: end_expr,
3008 step: step_expr,
3009 } => lower_range_index(
3010 &idx.name.value,
3011 start_expr,
3012 end_expr,
3013 step_expr,
3014 registry,
3015 src,
3016 decl_span,
3017 )?,
3018 crate::desugar::desugared_ast::IndexDeclKind::RequiredNamed => {
3019 types::IndexKind::RequiredNamed
3020 }
3021 crate::desugar::desugared_ast::IndexDeclKind::RequiredRange { dimension } => {
3022 let dim = registry
3023 .resolve_dim_expr(dimension)
3024 .map_err(|_| GraphcalError::DimensionOverflow {
3025 src: src.clone(),
3026 span: dimension.span.into(),
3027 })?
3028 .ok_or_else(|| GraphcalError::UnknownDimension {
3029 name: crate::syntax::names::DimName::new(idx.name.value.as_str()),
3030 src: src.clone(),
3031 span: dimension.span.into(),
3032 })?;
3033 types::IndexKind::RequiredRange { dimension: dim }
3034 }
3035 };
3036 registry.register_index(types::IndexDef {
3037 name: idx.name.value.clone(),
3038 kind,
3039 });
3040 Ok(())
3041}
3042
3043fn register_type_decl(t: &crate::desugar::desugared_ast::TypeDecl, registry: &mut RegistryBuilder) {
3044 let generic_params: Vec<types::TypeGenericParam> = t
3045 .generic_params
3046 .iter()
3047 .map(|g| types::TypeGenericParam {
3048 name: g.name.value.clone(),
3049 constraint: g.constraint.into(),
3050 default: g.default.clone(),
3051 })
3052 .collect();
3053
3054 let kind = match &t.body {
3055 crate::desugar::desugared_ast::TypeDeclBody::Required => types::TypeDefKind::Required,
3056 crate::desugar::desugared_ast::TypeDeclBody::Constructors(type_members) => {
3057 let members = type_members
3061 .iter()
3062 .map(|m| {
3063 let fields = m.payload.as_ref().map_or_else(Vec::new, |fs| {
3064 fs.iter()
3065 .map(|f| types::StructField {
3066 name: f.name.value.clone(),
3067 type_ann: f.type_ann.clone(),
3068 })
3069 .collect()
3070 });
3071 types::UnionMemberDef {
3072 name: ConstructorName::new(m.name.value.as_str()),
3073 fields,
3074 }
3075 })
3076 .collect();
3077 types::TypeDefKind::Union { members }
3078 }
3079 };
3080
3081 registry.register_type(types::TypeDef {
3082 name: t.name.value.clone(),
3083 generic_params,
3084 kind,
3085 });
3086}
3087
3088fn eval_scale_expr(expr: &Expr, src: &NamedSource<Arc<String>>) -> Result<f64, GraphcalError> {
3093 match &expr.kind {
3094 ExprKind::Number(n) => Ok(*n),
3095 #[expect(clippy::cast_precision_loss, reason = "unit scale constant expression")]
3096 ExprKind::Integer(n) => Ok(*n as f64),
3097 ExprKind::UnresolvedRef(crate::syntax::ast::UnresolvedRef::Path(path)) => {
3098 let builtin = path
3102 .as_bare()
3103 .and_then(|ident| crate::hir::BuiltinConst::parse(ident.name.as_str()));
3104 builtin
3105 .map(crate::hir::BuiltinConst::value)
3106 .ok_or_else(|| GraphcalError::EvalError {
3107 message: format!(
3108 "unknown constant `{}` in scale expression; only built-in \
3109 constants (PI, E, TAU, SQRT2, LN2, LN10) are supported",
3110 path.display_path()
3111 ),
3112 src: src.clone(),
3113 span: path.span().into(),
3114 })
3115 }
3116 ExprKind::BinOp { op, lhs, rhs } => {
3117 use crate::desugar::desugared_ast::BinOp;
3118 let l = eval_scale_expr(lhs, src)?;
3119 let r = eval_scale_expr(rhs, src)?;
3120 match op {
3121 BinOp::Add => Ok(l + r),
3122 BinOp::Sub => Ok(l - r),
3123 BinOp::Mul => Ok(l * r),
3124 BinOp::Div => Ok(l / r),
3125 BinOp::Pow => Ok(l.powf(r)),
3126 _ => Err(GraphcalError::EvalError {
3127 message: format!(
3128 "unsupported operator `{op:?}` in scale expression; \
3129 only `+`, `-`, `*`, `/`, `^` are allowed"
3130 ),
3131 src: src.clone(),
3132 span: expr.span.into(),
3133 }),
3134 }
3135 }
3136 ExprKind::UnaryOp {
3137 op: crate::desugar::desugared_ast::UnaryOp::Neg,
3138 operand,
3139 } => Ok(-eval_scale_expr(operand, src)?),
3140 _ => Err(GraphcalError::EvalError {
3141 message: "scale expression must be a constant expression \
3142 (numbers, PI, E, and arithmetic)"
3143 .to_string(),
3144 src: src.clone(),
3145 span: expr.span.into(),
3146 }),
3147 }
3148}
3149
3150fn eval_range_expr(
3158 expr: &Expr,
3159 registry: &RegistryBuilder,
3160 src: &NamedSource<Arc<String>>,
3161) -> Result<(f64, crate::syntax::dimension::Dimension), GraphcalError> {
3162 use crate::syntax::dimension::Dimension;
3163
3164 let ensure_finite = |value: f64, span: Span| {
3165 if value.is_finite() {
3166 Ok(value)
3167 } else {
3168 Err(eval_error(
3169 format!("range expression must be finite, got {value}"),
3170 src,
3171 span,
3172 ))
3173 }
3174 };
3175
3176 match &expr.kind {
3177 ExprKind::Number(n) => Ok((ensure_finite(*n, expr.span)?, Dimension::dimensionless())),
3178 ExprKind::UnitLiteral { value, unit } => {
3179 let (dim, scale) = registry
3180 .resolve_unit_expr(unit)
3181 .map_err(|err| unit_resolve_to_graphcal(err, src, unit.span))?;
3182 let scale = validate_positive_finite_scale(scale, "range unit scale", src, unit.span)?;
3183 Ok((ensure_finite(*value * scale.get(), expr.span)?, dim))
3184 }
3185 ExprKind::UnaryOp {
3186 op: crate::desugar::desugared_ast::UnaryOp::Neg,
3187 operand,
3188 } => {
3189 let (val, dim) = eval_range_expr(operand, registry, src)?;
3190 Ok((ensure_finite(-val, expr.span)?, dim))
3191 }
3192 _ => Err(GraphcalError::EvalError {
3193 message: "range expression must be a numeric or unit literal".to_string(),
3194 src: src.clone(),
3195 span: expr.span.into(),
3196 }),
3197 }
3198}
3199
3200fn checked_range_step_count(
3201 name: &IndexName,
3202 start: f64,
3203 end: f64,
3204 step: f64,
3205 src: &NamedSource<Arc<String>>,
3206 span: Span,
3207) -> Result<NonZeroUsize, GraphcalError> {
3208 let raw_steps = (end - start) / step;
3209 if !raw_steps.is_finite() {
3210 return Err(GraphcalError::RangeIndexInvalid {
3211 name: name.clone(),
3212 message: "range cardinality is not finite".to_string(),
3213 src: src.clone(),
3214 span: span.into(),
3215 });
3216 }
3217
3218 let nearest = raw_steps.round();
3219 let tolerance = f64::EPSILON.mul_add(raw_steps.abs().max(1.0) * 16.0, 1e-12);
3220 let whole_steps = if (raw_steps - nearest).abs() <= tolerance {
3221 nearest
3222 } else {
3223 raw_steps.floor()
3224 };
3225 if whole_steps < 0.0 {
3226 return Err(GraphcalError::RangeIndexInvalid {
3227 name: name.clone(),
3228 message: "range cardinality is negative".to_string(),
3229 src: src.clone(),
3230 span: span.into(),
3231 });
3232 }
3233
3234 let count = whole_steps + 1.0;
3235 #[expect(
3236 clippy::cast_precision_loss,
3237 reason = "usize upper bound check for f64 range count"
3238 )]
3239 let max_count = usize::MAX as f64;
3240 if count >= max_count {
3241 return Err(GraphcalError::RangeIndexInvalid {
3242 name: name.clone(),
3243 message: format!("range has too many steps ({count})"),
3244 src: src.clone(),
3245 span: span.into(),
3246 });
3247 }
3248
3249 #[expect(
3250 clippy::cast_possible_truncation,
3251 clippy::cast_sign_loss,
3252 reason = "range count is finite, non-negative, and bounded by usize::MAX"
3253 )]
3254 let count = count as usize;
3255 NonZeroUsize::new(count).ok_or_else(|| GraphcalError::RangeIndexInvalid {
3256 name: name.clone(),
3257 message: "range must contain at least one step".to_string(),
3258 src: src.clone(),
3259 span: span.into(),
3260 })
3261}
3262
3263fn lower_range_index(
3265 name: &crate::syntax::names::IndexName,
3266 start_expr: &Expr,
3267 end_expr: &Expr,
3268 step_expr: &Expr,
3269 registry: &RegistryBuilder,
3270 src: &NamedSource<Arc<String>>,
3271 decl_span: crate::syntax::span::Span,
3272) -> Result<types::IndexKind, GraphcalError> {
3273 let (start_val, start_dim) = eval_range_expr(start_expr, registry, src)?;
3274 let (end_val, end_dim) = eval_range_expr(end_expr, registry, src)?;
3275 let (step_val, step_dim) = eval_range_expr(step_expr, registry, src)?;
3276
3277 if start_dim != end_dim || start_dim != step_dim {
3279 return Err(GraphcalError::RangeIndexDimensionMismatch {
3280 name: name.clone(),
3281 start_dim: format!("Dimension({})", registry.format_dimension(&start_dim)),
3282 end_dim: format!("Dimension({})", registry.format_dimension(&end_dim)),
3283 step_dim: format!("Dimension({})", registry.format_dimension(&step_dim)),
3284 src: src.clone(),
3285 span: decl_span.into(),
3286 });
3287 }
3288
3289 for (label, value) in [("start", start_val), ("end", end_val), ("step", step_val)] {
3290 if !value.is_finite() {
3291 return Err(GraphcalError::RangeIndexInvalid {
3292 name: name.clone(),
3293 message: format!("{label} ({value}) must be finite"),
3294 src: src.clone(),
3295 span: decl_span.into(),
3296 });
3297 }
3298 }
3299
3300 if start_val > end_val {
3302 return Err(GraphcalError::RangeIndexInvalid {
3303 name: name.clone(),
3304 message: format!("start ({start_val}) must be <= end ({end_val})"),
3305 src: src.clone(),
3306 span: decl_span.into(),
3307 });
3308 }
3309
3310 if step_val <= 0.0 {
3312 return Err(GraphcalError::RangeIndexInvalid {
3313 name: name.clone(),
3314 message: format!("step ({step_val}) must be > 0"),
3315 src: src.clone(),
3316 span: decl_span.into(),
3317 });
3318 }
3319
3320 let step_count = checked_range_step_count(name, start_val, end_val, step_val, src, decl_span)?;
3321
3322 let (display_label, display_scale) = match &start_expr.kind {
3324 ExprKind::UnitLiteral { unit, .. } => {
3325 match registry.resolve_unit_expr(unit) {
3328 Ok((_dim, scale)) => {
3329 let scale = validate_positive_finite_scale(
3330 scale,
3331 "range display unit scale",
3332 src,
3333 unit.span,
3334 )?;
3335 (Some(format_unit_expr(unit)), scale.get())
3336 }
3337 Err(crate::registry::types::UnitResolveError::Overflow(_)) => {
3338 return Err(GraphcalError::DimensionOverflow {
3339 src: src.clone(),
3340 span: unit.span.into(),
3341 });
3342 }
3343 Err(_) => (None, 1.0),
3344 }
3345 }
3346 _ => (None, 1.0),
3347 };
3348
3349 Ok(types::IndexKind::Range(types::RangeIndexData {
3350 start: start_val,
3351 end: end_val,
3352 step: step_val,
3353 step_count,
3354 dimension: start_dim,
3355 display_label,
3356 display_scale,
3357 }))
3358}
3359
3360fn extract_type_annotations(ast: &File) -> HashMap<DeclName, TypeExpr> {
3363 let mut type_anns = HashMap::new();
3364 for decl in &ast.declarations {
3365 match &decl.kind {
3366 DeclKind::Param(p) => {
3367 type_anns.insert(p.name.value.clone(), p.type_ann.clone());
3368 }
3369 DeclKind::Node(n) => {
3370 type_anns.insert(n.name.value.clone(), n.type_ann.clone());
3371 }
3372 DeclKind::ConstNode(c) => {
3373 type_anns.insert(c.name.value.clone(), c.type_ann.clone());
3374 }
3375 _ => {}
3376 }
3377 }
3378 type_anns
3379}
3380
3381#[cfg(test)]
3382mod tests {
3383 use super::*;
3384 use crate::syntax::parser::Parser;
3385
3386 fn make_src(source: &str) -> NamedSource<Arc<String>> {
3387 NamedSource::new("test.gcl", Arc::new(source.to_string()))
3388 }
3389
3390 fn parse_and_lower(source: &str) -> Result<IR, GraphcalError> {
3391 let raw_file = Parser::new(source).parse_file().unwrap();
3392 let desugared = crate::syntax::desugar::desugar_multi_decls_in_file(raw_file);
3393 let file = desugared;
3394 lower(&file, &make_src(source))
3395 }
3396
3397 #[test]
3398 fn lower_rocket() {
3399 let source = include_str!("../../../../tests/fixtures/valid/rocket.gcl");
3400 let ir = parse_and_lower(source).unwrap();
3401 assert_eq!(ir.consts.len(), 1); assert_eq!(ir.params.len(), 3); assert_eq!(ir.nodes.len(), 3); assert!(ir.registry.dimensions.get_dimension("Length").is_some());
3405 assert!(
3406 ir.registry
3407 .units
3408 .get_unit(&crate::syntax::names::UnitRef::local("km"))
3409 .is_some()
3410 );
3411 }
3412
3413 #[test]
3414 fn lower_constants() {
3415 let source = include_str!("../../../../tests/fixtures/valid/constants.gcl");
3416 let ir = parse_and_lower(source).unwrap();
3417 assert_eq!(ir.consts.len(), 4);
3418 assert_eq!(ir.params.len(), 1);
3419 assert_eq!(ir.nodes.len(), 2);
3420 }
3421
3422 #[test]
3423 fn lower_indexed() {
3424 let source = include_str!("../../../../tests/fixtures/valid/indexed.gcl");
3425 let ir = parse_and_lower(source).unwrap();
3426 assert!(ir.registry.indexes.get_index("Maneuver").is_some());
3427 }
3428
3429 #[test]
3430 fn lower_hohmann() {
3431 let source = include_str!("../../../../tests/fixtures/valid/hohmann.gcl");
3436 let err = parse_and_lower(source).unwrap_err();
3437 assert!(matches!(err, GraphcalError::UnknownGraphRef { .. }));
3438 }
3439
3440 #[test]
3441 fn lower_duplicate_name_error() {
3442 let err = parse_and_lower("param x: Dimensionless = 1.0;\nnode x: Dimensionless = 2.0;")
3443 .unwrap_err();
3444 assert!(matches!(err, GraphcalError::DuplicateName { .. }));
3445 }
3446
3447 #[test]
3448 fn lower_source_order_preserved() {
3449 let ir = parse_and_lower(
3450 "param b: Dimensionless = 2.0;\nparam a: Dimensionless = 1.0;\nnode z: Dimensionless = @a + @b;",
3451 )
3452 .unwrap();
3453 let names: Vec<String> = ir.source_order.iter().map(|(n, _)| n.to_string()).collect();
3454 assert_eq!(names, vec!["b", "a", "z"]);
3455 }
3456
3457 #[test]
3458 fn merge_dependency_keeps_qualified_imported_value_keys() {
3459 let dep_source = "node out: Dimensionless = 2.0;";
3466 let dep_src = make_src(dep_source);
3467 let raw_file = Parser::new(dep_source).parse_file().unwrap();
3468 let dep_file = crate::syntax::desugar::desugar_multi_decls_in_file(raw_file);
3469 let (_dep_builder, mut dep_unfrozen) = lower_to_builder(
3470 &dep_file,
3471 &dep_src,
3472 &ImportedNames {
3473 consts: vec![],
3474 params: vec![],
3475 nodes: vec![],
3476 asserts: vec![],
3477 },
3478 &crate::dag_id::DagId::root("dep"),
3479 )
3480 .unwrap();
3481 let qualified = ScopedName::qualified("mission", "C");
3484 dep_unfrozen.imported_values.insert(
3485 qualified.clone(),
3486 (
3487 RuntimeValue::Scalar(7.0),
3488 DeclaredType::Scalar(crate::syntax::dimension::Dimension::dimensionless()),
3489 ),
3490 );
3491
3492 let importer_source = "node anchor: Dimensionless = 1.0;";
3493 let importer_src = make_src(importer_source);
3494 let raw_importer = Parser::new(importer_source).parse_file().unwrap();
3495 let importer_file = crate::syntax::desugar::desugar_multi_decls_in_file(raw_importer);
3496 let (_importer_builder, mut unfrozen) = lower_to_builder(
3497 &importer_file,
3498 &importer_src,
3499 &ImportedNames {
3500 consts: vec![],
3501 params: vec![],
3502 nodes: vec![],
3503 asserts: vec![],
3504 },
3505 &crate::dag_id::DagId::root("main"),
3506 )
3507 .unwrap();
3508
3509 let dep_names: HashSet<DeclName> = dep_unfrozen
3510 .source_order
3511 .iter()
3512 .map(|(n, _)| DeclName::new(n.member()))
3513 .collect();
3514 unfrozen
3515 .merge_dependency(
3516 dep_unfrozen,
3517 "inst",
3518 &HashMap::new(),
3519 &dep_names,
3520 &HashMap::new(),
3521 &HashMap::new(),
3522 &HashMap::new(),
3523 &HashMap::new(),
3524 &HashMap::new(),
3525 &importer_src,
3526 &dep_src,
3527 )
3528 .unwrap();
3529
3530 assert!(
3531 unfrozen.imported_values.contains_key(&qualified),
3532 "qualified imported value must keep its qualifier"
3533 );
3534 assert!(
3535 !unfrozen
3536 .imported_values
3537 .contains_key(&ScopedName::qualified("inst", "C")),
3538 "imported value must not be re-keyed with the instance prefix"
3539 );
3540 }
3541}