1use std::collections::{BTreeMap, BTreeSet, HashMap};
9use std::sync::Arc;
10
11use miette::NamedSource;
12
13use crate::desugar::desugared_ast::{MulDivOp, TypeExpr, TypeExprKind};
14use crate::hir;
15use crate::hir::diagnostics::{
16 expr_lower_error_to_graphcal, hir_lower_error_to_graphcal, resolved_decl_key,
17};
18pub use crate::ir::lower::{LoweredPlotBody, LoweredPlotField};
19use crate::syntax::dimension::{Dimension, Rational};
20use crate::syntax::names::{
21 ConstructorName, DeclName, DimName, FieldName, GenericParamName, IndexName, ModuleAliasName,
22 NameAtom, NamePath, StructTypeName,
23};
24use crate::syntax::nat::Monomial;
25pub use crate::syntax::nat::{NatLinearForm, NatPolyForm};
26use crate::syntax::span::{Span, Spanned};
27
28use crate::ir::lower::IR;
29use crate::ir::resolve::{DeclCategory, ExpectedFail};
30use crate::registry::declared_type::IndexTypeRef;
31use crate::registry::error::GraphcalError;
32use crate::registry::time_scale::TimeScale;
33use crate::registry::types::{
34 IndexDef, Registry, RegistryBuilder, TypeDef, TypeGenericConstraint, UnionMemberDef,
35};
36use crate::syntax::module_resolve::{ModuleResolveError, ModuleResolver};
37use crate::syntax::names::{ResolvedName, ScopedName, namespace};
38
39#[derive(Debug, Clone, PartialEq, Eq)]
48pub enum ResolvedTypeExpr {
49 Dimensionless,
51 Bool,
53 Int,
55 Datetime(TimeScale),
57 IndexArg(ResolvedIndex),
62 Scalar(Dimension),
64 Struct(ResolvedName<namespace::StructType>, Span),
66 GenericStruct {
68 name: ResolvedName<namespace::StructType>,
69 type_args: Vec<Self>,
70 span: Span,
71 },
72 GenericDimParam(GenericParamName, Span),
74 GenericTypeParam(GenericParamName, Span),
76 GenericDimExpr {
78 terms: Vec<ResolvedDimTerm>,
79 span: Span,
80 },
81 Indexed {
83 base: Box<Self>,
84 indexes: Vec<ResolvedIndex>,
85 },
86}
87
88impl ResolvedTypeExpr {
89 #[must_use]
91 pub fn format(&self, registry: &Registry) -> String {
92 match self {
93 Self::Dimensionless => "Dimensionless".to_string(),
94 Self::Bool => "Bool".to_string(),
95 Self::Int => "Int".to_string(),
96 Self::Datetime(scale) => {
97 if scale.is_utc() {
98 "Datetime".to_string()
99 } else {
100 format!("Datetime<{scale}>")
101 }
102 }
103 Self::IndexArg(index) => format!("index {}", format_resolved_index(index)),
104 Self::Scalar(dim) => {
105 let formatted = registry.dimensions.format_dimension(dim);
106 if formatted.is_empty() {
107 "Dimensionless".to_string()
108 } else {
109 formatted
110 }
111 }
112 Self::Struct(name, _) => name.as_str().to_string(),
113 Self::GenericStruct {
114 name, type_args, ..
115 } => {
116 let args: Vec<String> = type_args.iter().map(|a| a.format(registry)).collect();
117 format!("{}<{}>", name.as_str(), args.join(", "))
118 }
119 Self::GenericDimParam(name, _) | Self::GenericTypeParam(name, _) => name.to_string(),
120 Self::GenericDimExpr { terms, .. } => {
121 let parts: Vec<String> = terms.iter().map(|t| t.format(registry)).collect();
122 parts.join(" ")
123 }
124 Self::Indexed { base, indexes } => {
125 let base_str = base.format(registry);
126 let idx_strs: Vec<String> = indexes.iter().map(format_resolved_index).collect();
127 format!("{base_str}[{}]", idx_strs.join(", "))
128 }
129 }
130 }
131}
132
133fn format_resolved_index(index: &ResolvedIndex) -> String {
134 match index {
135 ResolvedIndex::Concrete(name, _) => name.as_str().to_string(),
136 ResolvedIndex::GenericParam(name, _) => name.to_string(),
137 ResolvedIndex::NatExpr(form, _) => format!("range({})", form.format()),
138 }
139}
140
141#[derive(Debug, Clone, PartialEq, Eq)]
143pub enum ResolvedDimTerm {
144 Concrete {
146 dim: Dimension,
147 power: Rational,
148 op: MulDivOp,
149 },
150 GenericParam {
152 name: GenericParamName,
153 power: Rational,
154 op: MulDivOp,
155 span: Span,
156 },
157}
158
159impl ResolvedDimTerm {
160 #[must_use]
162 pub const fn op(&self) -> MulDivOp {
163 match self {
164 Self::Concrete { op, .. } | Self::GenericParam { op, .. } => *op,
165 }
166 }
167
168 #[must_use]
170 pub fn format(&self, registry: &Registry) -> String {
171 let (name, power, op) = match self {
172 Self::Concrete { dim, power, op } => {
173 (registry.dimensions.format_dimension(dim), *power, *op)
174 }
175 Self::GenericParam {
176 name, power, op, ..
177 } => (name.to_string(), *power, *op),
178 };
179 let prefix = match op {
180 MulDivOp::Mul => "",
181 MulDivOp::Div => "/ ",
182 };
183 if power == Rational::ONE {
184 format!("{prefix}{name}")
185 } else {
186 format!(
187 "{prefix}{name}{}",
188 crate::registry::format::format_exponent(power)
189 )
190 }
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
200pub struct NatRangeIndexIdentity {
201 form: NatPolyForm,
202}
203
204impl NatRangeIndexIdentity {
205 pub fn try_from_form(
212 form: NatPolyForm,
213 ) -> Result<Self, crate::registry::types::NatRangeIndexError> {
214 if form.is_constant() {
215 crate::registry::types::NatRangeIndex::try_from_u64(form.constant())?;
216 }
217 Ok(Self { form })
218 }
219
220 #[must_use]
222 pub const fn form(&self) -> &NatPolyForm {
223 &self.form
224 }
225
226 #[must_use]
228 pub fn into_form(self) -> NatPolyForm {
229 self.form
230 }
231
232 pub fn to_index_type_ref(
240 &self,
241 ) -> Result<IndexTypeRef, crate::registry::types::NatRangeIndexError> {
242 IndexTypeRef::from_nat_range_form(self.form.clone())
243 }
244}
245
246impl NatPolyForm {
247 pub fn to_nat_range_identity(
253 &self,
254 ) -> Result<NatRangeIndexIdentity, crate::registry::types::NatRangeIndexError> {
255 NatRangeIndexIdentity::try_from_form(self.clone())
256 }
257}
258pub fn normalize_nat_expr(
263 expr: &crate::desugar::desugared_ast::NatExpr,
264 nat_params: &[GenericParamName],
265 src: &NamedSource<Arc<String>>,
266) -> Result<NatPolyForm, GraphcalError> {
267 use crate::desugar::desugared_ast::NatExpr;
268 match expr {
269 NatExpr::Literal(n, _) => Ok(NatPolyForm::from_constant(*n)),
270 NatExpr::Var(ident) => {
271 let gp = nat_params
272 .iter()
273 .find(|p| p.as_str() == ident.name.as_str())
274 .ok_or_else(|| GraphcalError::UnknownIndex {
275 name: IndexName::new(&ident.name),
276 src: src.clone(),
277 span: ident.span.into(),
278 })?;
279 Ok(NatPolyForm::from_var(gp.clone()))
280 }
281 NatExpr::Add(lhs, rhs, span) => {
282 let l = normalize_nat_expr(lhs, nat_params, src)?;
283 let r = normalize_nat_expr(rhs, nat_params, src)?;
284 l.add(&r).map_err(|err| nat_overflow_error(err, src, *span))
285 }
286 NatExpr::Mul(lhs, rhs, span) => {
287 let l = normalize_nat_expr(lhs, nat_params, src)?;
288 let r = normalize_nat_expr(rhs, nat_params, src)?;
289 l.mul(&r).map_err(|err| nat_overflow_error(err, src, *span))
290 }
291 }
292}
293
294#[must_use]
297pub fn nat_overflow_error(
298 err: crate::syntax::nat::NatOverflowError,
299 src: &NamedSource<Arc<String>>,
300 span: Span,
301) -> GraphcalError {
302 GraphcalError::EvalError {
303 message: err.to_string(),
304 src: src.clone(),
305 span: span.into(),
306 }
307}
308
309#[derive(Debug, Clone, PartialEq, Eq)]
311pub enum ResolvedIndex {
312 Concrete(ResolvedName<namespace::Index>, Span),
314 GenericParam(GenericParamName, Span),
316 NatExpr(NatPolyForm, Span),
321}
322
323impl ResolvedIndex {
324 #[must_use]
325 pub fn format_for_diagnostic(&self) -> String {
326 match self {
327 Self::Concrete(name, _) => name.as_str().to_string(),
328 Self::GenericParam(name, _) => name.to_string(),
329 Self::NatExpr(form, _) => format!("range({})", form.format()),
330 }
331 }
332}
333
334#[derive(Debug, Clone)]
342pub struct ModuleConstructorDef {
343 pub owning_type: ResolvedName<namespace::StructType>,
344 pub type_def: TypeDef,
345 pub variant: UnionMemberDef,
346}
347
348#[derive(Debug, Default, Clone)]
349pub struct ModuleTypeRegistry {
350 dimensions: HashMap<ResolvedName<namespace::Dim>, Dimension>,
351 indexes: HashMap<ResolvedName<namespace::Index>, IndexDef>,
352 struct_types: HashMap<ResolvedName<namespace::StructType>, TypeDef>,
353 constructors: HashMap<ResolvedName<namespace::Constructor>, ModuleConstructorDef>,
354}
355
356impl ModuleTypeRegistry {
357 pub fn insert_graphcal_prelude(
364 &mut self,
365 ) -> Result<(), crate::syntax::dimension::RationalError> {
366 let mut builder = RegistryBuilder::new();
367 crate::registry::prelude::load_prelude(&mut builder)?;
368 let registry = builder.build();
369 let owner = crate::registry::prelude::prelude_dag_id();
370 for name in crate::registry::prelude::PRELUDE_DIMENSION_NAMES {
371 if let Some(dim) = registry.dimensions.get_dimension(name) {
372 self.dimensions.insert(
373 ResolvedName::from_def(owner.clone(), DimName::new(*name)),
374 dim.clone(),
375 );
376 }
377 }
378 Ok(())
379 }
380
381 pub fn insert_registry(&mut self, owner: &crate::dag_id::DagId, registry: &Registry) {
388 for (name, dim) in registry.dimensions.all_dimensions() {
389 self.dimensions.insert(
390 ResolvedName::from_def(owner.clone(), name.clone()),
391 dim.clone(),
392 );
393 }
394 for index in registry.indexes.all_indexes() {
395 self.indexes.insert(
396 ResolvedName::from_def(owner.clone(), index.name.clone()),
397 index.clone(),
398 );
399 }
400 for type_def in registry.types.all_types() {
401 let type_name = ResolvedName::from_def(owner.clone(), type_def.name.clone());
402 self.struct_types
403 .insert(type_name.clone(), type_def.clone());
404 if let Some(members) = type_def.union_members() {
405 for member in members {
406 self.constructors.insert(
407 ResolvedName::from_def(owner.clone(), member.name.clone()),
408 ModuleConstructorDef {
409 owning_type: type_name.clone(),
410 type_def: type_def.clone(),
411 variant: member.clone(),
412 },
413 );
414 }
415 }
416 }
417 }
418
419 #[must_use]
420 pub fn get_dimension(&self, name: &ResolvedName<namespace::Dim>) -> Option<&Dimension> {
421 self.dimensions.get(name)
422 }
423
424 #[must_use]
425 pub fn get_index(&self, name: &ResolvedName<namespace::Index>) -> Option<&IndexDef> {
426 self.indexes.get(name)
427 }
428
429 #[must_use]
430 pub fn get_struct_type(&self, name: &ResolvedName<namespace::StructType>) -> Option<&TypeDef> {
431 self.struct_types.get(name)
432 }
433
434 #[must_use]
436 pub fn lookup_constructor(
437 &self,
438 constructor: &ResolvedName<namespace::Constructor>,
439 ) -> Option<&ModuleConstructorDef> {
440 self.constructors.get(constructor)
441 }
442}
443
444#[derive(Debug, Clone, Copy)]
446pub struct ModuleTypeContext<'a> {
447 owner: &'a crate::dag_id::DagId,
448 resolver: &'a ModuleResolver,
449 types: &'a ModuleTypeRegistry,
450}
451
452impl<'a> ModuleTypeContext<'a> {
453 #[must_use]
454 pub const fn new(
455 owner: &'a crate::dag_id::DagId,
456 resolver: &'a ModuleResolver,
457 types: &'a ModuleTypeRegistry,
458 ) -> Self {
459 Self {
460 owner,
461 resolver,
462 types,
463 }
464 }
465
466 #[must_use]
467 pub const fn owner(self) -> &'a crate::dag_id::DagId {
468 self.owner
469 }
470}
471
472#[derive(Debug, Clone)]
481pub struct ResolvedDomainConstraint {
482 pub min: Option<f64>,
484 pub max: Option<f64>,
486 pub min_display: Option<String>,
488 pub max_display: Option<String>,
490 pub span: Span,
492}
493
494#[derive(Debug, Clone, PartialEq, Eq, Hash)]
500pub struct StructFieldConstraintKey {
501 pub owning_type: crate::registry::declared_type::StructTypeRef,
502 pub constructor: ConstructorName,
503 pub field: FieldName,
504}
505
506impl StructFieldConstraintKey {
507 #[must_use]
508 pub const fn new(
509 owning_type: crate::registry::declared_type::StructTypeRef,
510 constructor: ConstructorName,
511 field: FieldName,
512 ) -> Self {
513 Self {
514 owning_type,
515 constructor,
516 field,
517 }
518 }
519}
520
521pub type DagRegistry = HashMap<crate::dag_id::DagId, DagTIR>;
533
534#[derive(Debug, Clone, Default, PartialEq, Eq)]
536pub struct ResolvedDagDependencies {
537 pub runtime_deps:
539 HashMap<ResolvedName<namespace::Decl>, BTreeSet<ResolvedName<namespace::Decl>>>,
540 pub const_deps: HashMap<ResolvedName<namespace::Decl>, BTreeSet<ResolvedName<namespace::Decl>>>,
542}
543
544#[derive(Debug, Clone, Default)]
546pub struct ResolvedExpressions {
547 pub consts: HashMap<ResolvedName<namespace::Decl>, hir::Expr>,
549 pub param_defaults: HashMap<ResolvedName<namespace::Decl>, hir::Expr>,
551 pub nodes: HashMap<ResolvedName<namespace::Decl>, hir::Expr>,
553 pub asserts: HashMap<ResolvedName<namespace::Decl>, hir::AssertBody>,
555}
556
557impl ResolvedExpressions {
558 #[must_use]
560 pub fn runtime_expr(&self, key: &ResolvedName<namespace::Decl>) -> Option<&hir::Expr> {
561 self.param_defaults.get(key).or_else(|| self.nodes.get(key))
562 }
563}
564
565#[derive(Debug, Clone, Default)]
567pub struct ResolvedCollectionRefs {
568 pub index_defs: HashMap<ResolvedName<namespace::Index>, IndexDef>,
572}
573
574#[derive(Debug, Clone, Default)]
576pub struct ResolvedConstructorRefs {
577 pub constructor_defs: HashMap<ResolvedName<namespace::Constructor>, ResolvedConstructorTarget>,
581}
582
583#[derive(Debug, Clone, Default)]
585pub struct ResolvedInlineDagRefs {
586 pub calls: HashMap<Span, ResolvedInlineDagCall>,
588}
589
590#[derive(Debug, Clone, PartialEq, Eq, Hash)]
592pub struct ResolvedStructFieldTypeKey {
593 pub owning_type: ResolvedName<namespace::StructType>,
595 pub constructor: ConstructorName,
597 pub field: FieldName,
599}
600
601#[derive(Debug, Clone, Default)]
603pub struct ResolvedTypeDefs {
604 pub struct_types: HashMap<ResolvedName<namespace::StructType>, TypeDef>,
606 pub field_types: HashMap<ResolvedStructFieldTypeKey, ResolvedTypeExpr>,
608 pub field_bounds: HashMap<ResolvedStructFieldTypeKey, Vec<ResolvedDomainBound>>,
610 pub generic_defaults:
612 HashMap<(ResolvedName<namespace::StructType>, GenericParamName), ResolvedTypeExpr>,
613}
614
615#[derive(Debug, Clone)]
622pub struct ResolvedDomainBound {
623 pub kind: crate::syntax::ast::DomainBoundKind,
625 pub kind_span: Span,
627 pub value: hir::Expr,
629 pub span: Span,
631}
632
633#[derive(Debug, Clone, Default)]
639pub struct DagSemanticBody {
640 pub expressions: ResolvedExpressions,
642 pub domain_bounds: HashMap<ResolvedName<namespace::Decl>, Vec<ResolvedDomainBound>>,
644 pub plot_exprs: ResolvedPlotExprs,
646 pub dynamic_unit_scales: HashMap<crate::syntax::names::UnitRef, hir::Expr>,
653 pub dependencies: ResolvedDagDependencies,
655 pub collection_refs: ResolvedCollectionRefs,
657 pub constructor_refs: ResolvedConstructorRefs,
659 pub inline_dag_refs: ResolvedInlineDagRefs,
661 pub type_defs: ResolvedTypeDefs,
663 pub decl_bindings: HashMap<ScopedName, ResolvedName<namespace::Decl>>,
665}
666
667#[derive(Debug, Clone, Default)]
673pub struct ResolvedPlotExprs {
674 pub plots: HashMap<ScopedName, LoweredPlotBody>,
676 pub figures: HashMap<ScopedName, Vec<LoweredPlotField>>,
678 pub layers: HashMap<ScopedName, Vec<LoweredPlotField>>,
680}
681
682#[derive(Debug, Clone)]
684pub struct ResolvedInlineDagCall {
685 pub target: crate::dag_id::DagId,
686 pub arg_targets: HashMap<Span, ResolvedName<namespace::Decl>>,
688 pub output: Spanned<ResolvedName<namespace::Decl>>,
690}
691
692#[derive(Debug, Clone)]
694pub struct ResolvedConstructorTarget {
695 pub constructor: ResolvedName<namespace::Constructor>,
696 pub owning_type: ResolvedName<namespace::StructType>,
697 pub type_def: TypeDef,
698 pub variant: UnionMemberDef,
699}
700
701#[derive(Debug, Clone)]
714pub struct TIR {
715 pub registry: Registry,
718 pub root_dag_id: crate::dag_id::DagId,
721 pub dags: DagRegistry,
725 pub module_aliases: HashMap<ModuleAliasName, crate::dag_id::DagId>,
731}
732
733impl TIR {
734 #[must_use]
742 #[expect(
743 clippy::expect_used,
744 reason = "TIR invariant: root entry always present"
745 )]
746 pub fn root(&self) -> &DagTIR {
747 self.dags
748 .get(&self.root_dag_id)
749 .expect("TIR.dags must contain root_dag_id")
750 }
751
752 #[expect(
758 clippy::expect_used,
759 reason = "TIR invariant: root entry always present"
760 )]
761 pub fn root_mut(&mut self) -> &mut DagTIR {
762 self.dags
763 .get_mut(&self.root_dag_id)
764 .expect("TIR.dags must contain root_dag_id")
765 }
766
767 #[must_use]
772 pub fn is_library(&self) -> bool {
773 self.root().params.iter().any(|p| p.default_expr.is_none())
774 || self
775 .registry
776 .indexes
777 .all_indexes()
778 .any(crate::registry::types::IndexDef::is_required)
779 }
780
781 pub fn build_declared_types(
790 &self,
791 src: &NamedSource<Arc<String>>,
792 ) -> Result<HashMap<ScopedName, crate::registry::declared_type::DeclaredType>, GraphcalError>
793 {
794 self.root().build_declared_types(src)
795 }
796
797 #[must_use]
809 pub fn lookup_call_target(&self, path: &crate::syntax::ast::ModulePath) -> Option<&DagTIR> {
810 let id = self.resolve_call_path(path)?;
811 self.dags.get(&id)
812 }
813
814 #[must_use]
821 pub fn resolve_call_path(
822 &self,
823 path: &crate::syntax::ast::ModulePath,
824 ) -> Option<crate::dag_id::DagId> {
825 if path.segments.len() == 1 {
826 return Some(self.root_dag_id.child(path.segments[0].name.as_str()));
827 }
828 let alias = path.segments[0].name.as_str();
829 let dep_id = self.module_aliases.get(alias)?;
830 let mut id = dep_id.clone();
831 for seg in &path.segments.as_slice()[1..] {
832 id = id.child(seg.name.as_str());
833 }
834 Some(id)
835 }
836
837 #[must_use]
844 pub fn empty_for_eval_helpers(registry: Registry) -> Self {
845 let root_dag_id = crate::dag_id::DagId::root("<eval-helper>");
846 let mut dags = DagRegistry::new();
847 dags.insert(
848 root_dag_id.clone(),
849 DagTIR {
850 dag_id: root_dag_id.clone(),
851 consts: Vec::new(),
852 params: Vec::new(),
853 nodes: Vec::new(),
854 asserts: Vec::new(),
855 plots: Vec::new(),
856 figures: Vec::new(),
857 layers: Vec::new(),
858 included_plots: Vec::new(),
859 semantic: DagSemanticBody::default(),
860 source_order: Vec::new(),
861 assert_names: std::collections::HashSet::new(),
862 assumes_map: HashMap::new(),
863 expected_fail: HashMap::new(),
864 resolved_decl_types: HashMap::new(),
865 domain_constraints: HashMap::new(),
866 imported_values: HashMap::new(),
867 imported_decl_types: HashMap::new(),
868 imported_value_sources: HashMap::new(),
869 pub_nodes: std::collections::HashSet::new(),
870 },
871 );
872 Self {
873 registry,
874 root_dag_id,
875 dags,
876 module_aliases: HashMap::new(),
877 }
878 }
879}
880
881#[derive(Debug, Clone)]
888pub struct DagTIR {
889 pub dag_id: crate::dag_id::DagId,
893 pub consts: Vec<crate::ir::lower::ConstEntry>,
895 pub params: Vec<crate::ir::lower::ParamEntry>,
897 pub nodes: Vec<crate::ir::lower::NodeEntry>,
899 pub asserts: Vec<crate::ir::lower::AssertEntry>,
901 pub plots: Vec<crate::ir::lower::PlotEntry>,
903 pub figures: Vec<crate::ir::lower::FigureEntry>,
905 pub layers: Vec<crate::ir::lower::LayerEntry>,
907 pub included_plots: Vec<crate::ir::lower::IncludedPlotEntry>,
909 pub semantic: DagSemanticBody,
911 pub source_order: Vec<(ScopedName, DeclCategory)>,
913 pub assert_names: std::collections::HashSet<ScopedName>,
915 pub assumes_map: HashMap<ScopedName, Vec<ScopedName>>,
917 pub expected_fail: HashMap<ScopedName, ExpectedFail>,
919 pub resolved_decl_types: HashMap<ScopedName, ResolvedTypeExpr>,
921 pub domain_constraints: HashMap<ScopedName, ResolvedDomainConstraint>,
923 pub imported_values: HashMap<
925 ScopedName,
926 (
927 crate::registry::runtime_value::RuntimeValue,
928 crate::registry::declared_type::DeclaredType,
929 ),
930 >,
931 pub imported_decl_types: HashMap<ScopedName, crate::registry::declared_type::DeclaredType>,
934 pub imported_value_sources: HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
936 pub pub_nodes: std::collections::HashSet<DeclName>,
943}
944
945impl DagTIR {
946 pub fn build_declared_types(
955 &self,
956 src: &NamedSource<Arc<String>>,
957 ) -> Result<HashMap<ScopedName, crate::registry::declared_type::DeclaredType>, GraphcalError>
958 {
959 let mut declared_types = HashMap::new();
966 for name in crate::registry::builtins::builtin_constants().keys() {
967 declared_types.insert(
968 ScopedName::local(*name),
969 crate::registry::declared_type::DeclaredType::Scalar(Dimension::dimensionless()),
970 );
971 }
972 for (name, dt) in &self.imported_decl_types {
973 declared_types.insert(name.clone(), dt.clone());
974 }
975 for (name, (_rv, dt)) in &self.imported_values {
976 declared_types.insert(name.clone(), dt.clone());
977 }
978 for (name, resolved) in &self.resolved_decl_types {
979 let dt = resolved_to_declared_type(resolved, src)?;
980 declared_types.insert(name.clone(), dt);
981 }
982 Ok(declared_types)
983 }
984
985 pub fn populate_pub_nodes(&mut self, body: &[crate::desugar::desugared_ast::Declaration]) {
987 use crate::desugar::desugared_ast::DeclKind;
988
989 for decl in body {
990 if let DeclKind::Node(n) = &decl.kind
991 && n.visibility.is_public()
992 {
993 self.pub_nodes.insert(n.name.value.clone());
994 }
995 }
996 }
997
998 #[must_use]
1004 pub fn resolved_decl_key_for_local(
1005 &self,
1006 name: &ScopedName,
1007 ) -> Option<ResolvedName<namespace::Decl>> {
1008 if let Some(resolved) = self.semantic.decl_bindings.get(name) {
1009 return Some(resolved.clone());
1010 }
1011 if self.resolved_decl_types.contains_key(name)
1012 || self
1013 .source_order
1014 .iter()
1015 .any(|(source_name, _)| source_name == name)
1016 {
1017 return resolved_decl_key(&self.dag_id, name);
1018 }
1019 if !name.is_qualified() {
1020 let mut candidates = self
1021 .resolved_decl_types
1022 .keys()
1023 .filter(|candidate| candidate.member() == name.member())
1024 .filter_map(|candidate| resolved_decl_key(&self.dag_id, candidate));
1025 if let Some(candidate) = candidates.next()
1026 && candidates.next().is_none()
1027 {
1028 return Some(candidate);
1029 }
1030 }
1031 resolved_decl_key(&self.dag_id, name)
1032 }
1033}
1034
1035pub fn type_resolve_with_modules(
1045 ir: IR,
1046 root_dag_id: crate::dag_id::DagId,
1047 src: &NamedSource<Arc<String>>,
1048 module_resolver: &ModuleResolver,
1049 module_types: &ModuleTypeRegistry,
1050) -> Result<TIR, GraphcalError> {
1051 let owner_for_ctx = root_dag_id.clone();
1052 let ctx = ModuleTypeContext::new(&owner_for_ctx, module_resolver, module_types);
1053 type_resolve_impl(ir, root_dag_id, src, ctx)
1054}
1055
1056fn type_resolve_impl(
1057 ir: IR,
1058 root_dag_id: crate::dag_id::DagId,
1059 src: &NamedSource<Arc<String>>,
1060 module_ctx: ModuleTypeContext<'_>,
1061) -> Result<TIR, GraphcalError> {
1062 let imported_value_sources_for_hir = ir.imported_value_sources.clone();
1063 let asserts_for_hir = ir.asserts.clone();
1064 let mut root_dag = type_resolve_dag(
1065 ir.consts,
1066 ir.params,
1067 ir.nodes,
1068 &asserts_for_hir,
1069 &ir.registry,
1070 src,
1071 &root_dag_id,
1072 module_ctx,
1073 &imported_value_sources_for_hir,
1074 )?
1075 .with_body(
1076 ir.asserts,
1077 ir.plots,
1078 ir.figures,
1079 ir.layers,
1080 ir.included_plots,
1081 ir.source_order,
1082 ir.assert_names,
1083 ir.assumes_map,
1084 ir.expected_fail,
1085 ir.imported_values,
1086 ir.imported_decl_types,
1087 ir.imported_value_sources,
1088 module_ctx,
1089 src,
1090 )?;
1091 lower_dynamic_unit_scales(&ir.registry, module_ctx, &mut root_dag.semantic);
1092 augment_runtime_deps_for_dynamic_units(&mut root_dag.semantic);
1093 check_hir_body_policies(
1094 &root_dag.semantic,
1095 &ir.registry,
1096 &ir.pub_names,
1097 module_ctx,
1098 src,
1099 )?;
1100 let mut dags = DagRegistry::new();
1101 dags.insert(root_dag_id.clone(), root_dag);
1102 Ok(TIR {
1103 registry: ir.registry,
1104 root_dag_id,
1105 dags,
1106 module_aliases: HashMap::new(),
1107 })
1108}
1109
1110pub fn type_resolve_single_with_modules(
1113 ir: IR,
1114 dag_id: &crate::dag_id::DagId,
1115 src: &NamedSource<Arc<String>>,
1116 module_resolver: &ModuleResolver,
1117 module_types: &ModuleTypeRegistry,
1118) -> Result<DagTIR, GraphcalError> {
1119 let ctx = ModuleTypeContext::new(dag_id, module_resolver, module_types);
1120 type_resolve_single_impl(ir, dag_id, src, ctx)
1121}
1122
1123fn type_resolve_single_impl(
1124 ir: IR,
1125 dag_id: &crate::dag_id::DagId,
1126 src: &NamedSource<Arc<String>>,
1127 module_ctx: ModuleTypeContext<'_>,
1128) -> Result<DagTIR, GraphcalError> {
1129 let imported_value_sources_for_hir = ir.imported_value_sources.clone();
1130 let asserts_for_hir = ir.asserts.clone();
1131 let mut dag = type_resolve_dag(
1132 ir.consts,
1133 ir.params,
1134 ir.nodes,
1135 &asserts_for_hir,
1136 &ir.registry,
1137 src,
1138 dag_id,
1139 module_ctx,
1140 &imported_value_sources_for_hir,
1141 )?
1142 .with_body(
1143 ir.asserts,
1144 ir.plots,
1145 ir.figures,
1146 ir.layers,
1147 ir.included_plots,
1148 ir.source_order,
1149 ir.assert_names,
1150 ir.assumes_map,
1151 ir.expected_fail,
1152 ir.imported_values,
1153 ir.imported_decl_types,
1154 ir.imported_value_sources,
1155 module_ctx,
1156 src,
1157 )?;
1158 lower_dynamic_unit_scales(&ir.registry, module_ctx, &mut dag.semantic);
1159 augment_runtime_deps_for_dynamic_units(&mut dag.semantic);
1160 check_hir_body_policies(&dag.semantic, &ir.registry, &ir.pub_names, module_ctx, src)?;
1161 Ok(dag)
1162}
1163
1164fn lower_dynamic_unit_scales(
1172 registry: &Registry,
1173 ctx: ModuleTypeContext<'_>,
1174 semantic: &mut DagSemanticBody,
1175) {
1176 let generic_scope = hir::GenericScope::new();
1177 let prelude = hir::PreludeTypeScope::graphcal();
1178 let expr_ctx = hir::ExprLoweringContext::new(ctx.owner, ctx.resolver, &generic_scope)
1179 .with_prelude(&prelude)
1180 .with_decl_bindings(&semantic.decl_bindings);
1181 for (name, _dim, scale) in registry.units.all_units() {
1182 if let crate::registry::types::UnitScale::Dynamic { scale_expr, .. } = scale
1183 && let Ok(lowered) = hir::lower_expr(scale_expr, expr_ctx)
1184 {
1185 semantic.dynamic_unit_scales.insert(name.clone(), lowered);
1186 }
1187 }
1188}
1189
1190#[expect(
1193 clippy::too_many_arguments,
1194 reason = "orchestrates per-DAG type resolution across IR declarations and semantic body data"
1195)]
1196fn type_resolve_dag(
1197 consts: Vec<crate::ir::lower::ConstEntry>,
1198 params: Vec<crate::ir::lower::ParamEntry>,
1199 nodes: Vec<crate::ir::lower::NodeEntry>,
1200 asserts: &[crate::ir::lower::AssertEntry],
1201 registry: &Registry,
1202 src: &NamedSource<Arc<String>>,
1203 dag_id: &crate::dag_id::DagId,
1204 module_ctx: ModuleTypeContext<'_>,
1205 imported_value_sources: &HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
1206) -> Result<DagTIRSeed, GraphcalError> {
1207 let mut resolved_decl_types = HashMap::new();
1208 let no_generic_params: &[GenericParamName] = &[];
1209
1210 for entry in &consts {
1214 let resolved = resolve_type_expr_inner(
1215 &entry.type_ann,
1216 registry,
1217 dag_id,
1218 no_generic_params,
1219 no_generic_params,
1220 no_generic_params,
1221 entry.src.resolve(src),
1222 Some(module_ctx),
1223 )?;
1224 resolved_decl_types.insert(entry.name.clone(), resolved);
1225 }
1226 for entry in ¶ms {
1227 let resolved = resolve_type_expr_inner(
1228 &entry.type_ann,
1229 registry,
1230 dag_id,
1231 no_generic_params,
1232 no_generic_params,
1233 no_generic_params,
1234 entry.src.resolve(src),
1235 Some(module_ctx),
1236 )?;
1237 resolved_decl_types.insert(entry.name.clone(), resolved);
1238 }
1239 for entry in &nodes {
1240 let resolved = resolve_type_expr_inner(
1241 &entry.type_ann,
1242 registry,
1243 dag_id,
1244 no_generic_params,
1245 no_generic_params,
1246 no_generic_params,
1247 entry.src.resolve(src),
1248 Some(module_ctx),
1249 )?;
1250 resolved_decl_types.insert(entry.name.clone(), resolved);
1251 }
1252
1253 let LoweredDagExpressions {
1254 exprs: expressions,
1255 domain_bounds,
1256 } = lower_resolved_expressions(
1257 &consts,
1258 ¶ms,
1259 &nodes,
1260 asserts,
1261 module_ctx,
1262 imported_value_sources,
1263 src,
1264 )?;
1265 let dependencies =
1266 collect_resolved_dag_dependencies(&consts, ¶ms, &nodes, &expressions, module_ctx, src)?;
1267 let collection_refs = collect_resolved_collection_refs(
1268 &expressions,
1269 &domain_bounds,
1270 &resolved_decl_types,
1271 module_ctx,
1272 src,
1273 )?;
1274 let constructor_refs =
1275 collect_resolved_constructor_refs(&expressions, &domain_bounds, module_ctx, src)?;
1276 let inline_dag_refs = collect_resolved_inline_dag_refs(&expressions);
1277 let type_defs = collect_resolved_type_defs(
1278 &resolved_decl_types,
1279 &constructor_refs,
1280 module_ctx,
1281 registry,
1282 src,
1283 )?;
1284
1285 let semantic = DagSemanticBody {
1286 expressions,
1287 domain_bounds,
1288 plot_exprs: ResolvedPlotExprs::default(),
1289 dynamic_unit_scales: HashMap::new(),
1290 dependencies,
1291 collection_refs,
1292 constructor_refs,
1293 inline_dag_refs,
1294 type_defs,
1295 decl_bindings: HashMap::new(),
1296 };
1297
1298 Ok(DagTIRSeed {
1299 dag_id: dag_id.clone(),
1300 consts,
1301 params,
1302 nodes,
1303 resolved_decl_types,
1304 semantic,
1305 })
1306}
1307
1308fn collect_resolved_type_defs(
1309 resolved_decl_types: &HashMap<ScopedName, ResolvedTypeExpr>,
1310 constructor_refs: &ResolvedConstructorRefs,
1311 ctx: ModuleTypeContext<'_>,
1312 registry: &Registry,
1313 src: &NamedSource<Arc<String>>,
1314) -> Result<ResolvedTypeDefs, GraphcalError> {
1315 let mut defs = ResolvedTypeDefs::default();
1316 if let Some(symbols) = ctx.resolver.modules().get(ctx.owner) {
1317 for symbol in symbols.struct_types().values() {
1318 record_resolved_struct_type_def(symbol.resolved(), ctx, registry, src, &mut defs)?;
1319 }
1320 }
1321 for resolved in resolved_decl_types.values() {
1322 collect_struct_type_defs_from_resolved_type(resolved, ctx, registry, src, &mut defs)?;
1323 }
1324 for target in constructor_refs.constructor_defs.values() {
1325 record_resolved_struct_type_def(&target.owning_type, ctx, registry, src, &mut defs)?;
1326 }
1327 Ok(defs)
1328}
1329
1330fn collect_struct_type_defs_from_resolved_type(
1331 resolved: &ResolvedTypeExpr,
1332 ctx: ModuleTypeContext<'_>,
1333 registry: &Registry,
1334 src: &NamedSource<Arc<String>>,
1335 defs: &mut ResolvedTypeDefs,
1336) -> Result<(), GraphcalError> {
1337 match resolved {
1338 ResolvedTypeExpr::Struct(name, _) => {
1339 record_resolved_struct_type_def(name, ctx, registry, src, defs)?;
1340 }
1341 ResolvedTypeExpr::GenericStruct {
1342 name, type_args, ..
1343 } => {
1344 record_resolved_struct_type_def(name, ctx, registry, src, defs)?;
1345 for arg in type_args {
1346 collect_struct_type_defs_from_resolved_type(arg, ctx, registry, src, defs)?;
1347 }
1348 }
1349 ResolvedTypeExpr::Indexed { base, indexes: _ } => {
1350 collect_struct_type_defs_from_resolved_type(base, ctx, registry, src, defs)?;
1351 }
1352 ResolvedTypeExpr::Dimensionless
1353 | ResolvedTypeExpr::Bool
1354 | ResolvedTypeExpr::Int
1355 | ResolvedTypeExpr::Datetime(_)
1356 | ResolvedTypeExpr::IndexArg(_)
1357 | ResolvedTypeExpr::Scalar(_)
1358 | ResolvedTypeExpr::GenericDimParam(_, _)
1359 | ResolvedTypeExpr::GenericTypeParam(_, _)
1360 | ResolvedTypeExpr::GenericDimExpr { .. } => {}
1361 }
1362 Ok(())
1363}
1364
1365fn record_resolved_struct_type_def(
1366 name: &ResolvedName<namespace::StructType>,
1367 ctx: ModuleTypeContext<'_>,
1368 registry: &Registry,
1369 src: &NamedSource<Arc<String>>,
1370 defs: &mut ResolvedTypeDefs,
1371) -> Result<(), GraphcalError> {
1372 if defs.struct_types.contains_key(name) {
1373 return Ok(());
1374 }
1375 let Some(type_def) = ctx.types.get_struct_type(name) else {
1376 return Ok(());
1377 };
1378
1379 for param in &type_def.generic_params {
1380 if let Some(default) = ¶m.default {
1381 let resolved =
1382 resolve_type_expr_in_struct_scope(default, name, type_def, ctx, registry, src)?;
1383 defs.generic_defaults
1384 .insert((name.clone(), param.name.clone()), resolved);
1385 }
1386 }
1387
1388 if let Some(members) = type_def.union_members() {
1389 let generic_scope = generic_scope_for_type_def(name, type_def, src)?;
1390 let prelude = hir::PreludeTypeScope::graphcal();
1391 let bound_expr_ctx =
1392 hir::ExprLoweringContext::new(name.owner(), ctx.resolver, &generic_scope)
1393 .with_prelude(&prelude);
1394 for member in members {
1395 for field in &member.fields {
1396 let key = ResolvedStructFieldTypeKey {
1397 owning_type: name.clone(),
1398 constructor: member.name.clone(),
1399 field: field.name.clone(),
1400 };
1401 let resolved = resolve_type_expr_in_struct_scope(
1402 &field.type_ann,
1403 name,
1404 type_def,
1405 ctx,
1406 registry,
1407 src,
1408 )?;
1409 let bounds = lower_domain_bounds(&field.type_ann, bound_expr_ctx, src)?;
1410 if !bounds.is_empty() {
1411 defs.field_bounds.insert(key.clone(), bounds);
1412 }
1413 defs.field_types.insert(key, resolved);
1414 }
1415 }
1416 }
1417
1418 defs.struct_types.insert(name.clone(), type_def.clone());
1419 Ok(())
1420}
1421
1422fn generic_scope_for_type_def(
1425 name: &ResolvedName<namespace::StructType>,
1426 type_def: &TypeDef,
1427 src: &NamedSource<Arc<String>>,
1428) -> Result<hir::GenericScope, GraphcalError> {
1429 let owner = hir::GenericParamOwner::Type(name.clone());
1430 let mut scope = hir::GenericScope::new();
1431 for param in &type_def.generic_params {
1432 let constraint = match param.constraint {
1433 TypeGenericConstraint::Dim => crate::syntax::ast::GenericConstraint::Dim,
1434 TypeGenericConstraint::Index => crate::syntax::ast::GenericConstraint::Index,
1435 TypeGenericConstraint::Nat => crate::syntax::ast::GenericConstraint::Nat,
1436 TypeGenericConstraint::Unconstrained => crate::syntax::ast::GenericConstraint::Type,
1437 };
1438 scope
1439 .insert_binding(hir::GenericParamBinding::new(
1440 hir::GenericParamId::new(owner.clone(), param.name.clone()),
1441 constraint,
1442 Span::new(0, 0),
1443 ))
1444 .map_err(|err| GraphcalError::InternalError {
1445 message: format!("duplicate generic param while scoping `{name}`: {err}"),
1446 src: src.clone(),
1447 span: Span::new(0, 0).into(),
1448 })?;
1449 }
1450 Ok(scope)
1451}
1452
1453fn resolve_type_expr_in_struct_scope(
1454 type_expr: &TypeExpr,
1455 type_owner: &ResolvedName<namespace::StructType>,
1456 type_def: &TypeDef,
1457 ctx: ModuleTypeContext<'_>,
1458 registry: &Registry,
1459 src: &NamedSource<Arc<String>>,
1460) -> Result<ResolvedTypeExpr, GraphcalError> {
1461 let prelude = hir::PreludeTypeScope::graphcal();
1462 let resolve_ctx = HirTypeResolutionContext {
1463 src,
1464 resolver: ctx.resolver,
1465 module_types: ctx.types,
1466 registry: Some(registry),
1467 prelude: &prelude,
1468 };
1469 let hir_type = lower_type_generic_default(type_expr, type_owner, type_def, resolve_ctx)?;
1470 resolve_hir_type_expr_inner(&hir_type, resolve_ctx)
1471}
1472
1473fn lower_resolved_expressions(
1481 consts: &[crate::ir::lower::ConstEntry],
1482 params: &[crate::ir::lower::ParamEntry],
1483 nodes: &[crate::ir::lower::NodeEntry],
1484 asserts: &[crate::ir::lower::AssertEntry],
1485 ctx: ModuleTypeContext<'_>,
1486 imported_value_sources: &HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
1487 src: &NamedSource<Arc<String>>,
1488) -> Result<LoweredDagExpressions, GraphcalError> {
1489 let generic_scope = hir::GenericScope::new();
1490 let prelude = hir::PreludeTypeScope::graphcal();
1491 let decl_bindings = collect_hir_decl_bindings(
1492 ctx.owner,
1493 consts,
1494 params,
1495 nodes,
1496 imported_value_sources,
1497 src,
1498 )?;
1499 let expr_ctx = hir::ExprLoweringContext::new(ctx.owner, ctx.resolver, &generic_scope)
1500 .with_prelude(&prelude)
1501 .with_decl_bindings(&decl_bindings);
1502 let mut exprs = ResolvedExpressions::default();
1503 let mut domain_bounds = HashMap::new();
1504
1505 for entry in consts {
1508 let body_src = entry.src.resolve(src);
1509 let key = decl_key_or_internal_error(ctx.owner, &entry.name, entry.span, body_src)?;
1510 let bounds = lower_domain_bounds(&entry.type_ann, expr_ctx, body_src)?;
1511 if !bounds.is_empty() {
1512 domain_bounds.insert(key.clone(), bounds);
1513 }
1514 exprs.consts.insert(key, entry.expr.clone());
1515 }
1516 for entry in params {
1517 let body_src = entry.src.resolve(src);
1518 let key = decl_key_or_internal_error(ctx.owner, &entry.name, entry.span, body_src)?;
1519 let bounds = lower_domain_bounds(&entry.type_ann, expr_ctx, body_src)?;
1520 if !bounds.is_empty() {
1521 domain_bounds.insert(key.clone(), bounds);
1522 }
1523 let Some(expr) = &entry.default_expr else {
1524 continue;
1525 };
1526 exprs.param_defaults.insert(key, expr.clone());
1527 }
1528 for entry in nodes {
1529 let body_src = entry.src.resolve(src);
1530 let key = decl_key_or_internal_error(ctx.owner, &entry.name, entry.span, body_src)?;
1531 let bounds = lower_domain_bounds(&entry.type_ann, expr_ctx, body_src)?;
1532 if !bounds.is_empty() {
1533 domain_bounds.insert(key.clone(), bounds);
1534 }
1535 exprs.nodes.insert(key, entry.expr.clone());
1536 }
1537 for entry in asserts {
1538 let key =
1539 decl_key_or_internal_error(ctx.owner, &entry.name, entry.span, entry.src.resolve(src))?;
1540 exprs.asserts.insert(key, entry.body.clone());
1541 }
1542
1543 Ok(LoweredDagExpressions {
1544 exprs,
1545 domain_bounds,
1546 })
1547}
1548
1549fn collect_plot_exprs(
1557 plots: &[crate::ir::lower::PlotEntry],
1558 figures: &[crate::ir::lower::FigureEntry],
1559 layers: &[crate::ir::lower::LayerEntry],
1560 ctx: ModuleTypeContext<'_>,
1561 src: &NamedSource<Arc<String>>,
1562 semantic: &mut DagSemanticBody,
1563) -> Result<(), GraphcalError> {
1564 let mut plot_exprs = ResolvedPlotExprs::default();
1565
1566 let collect = |expr: &hir::Expr,
1567 collection_refs: &mut ResolvedCollectionRefs,
1568 constructor_refs: &mut ResolvedConstructorRefs|
1569 -> Result<(), GraphcalError> {
1570 collect_resolved_collection_refs_from_expr(expr, ctx, src, collection_refs)?;
1571 collect_resolved_constructor_refs_from_expr(expr, ctx, src, constructor_refs)
1572 };
1573
1574 for entry in plots {
1575 let Some(body) = &entry.body else {
1576 continue;
1577 };
1578 for (_, expr) in &body.encodings {
1579 collect(
1580 expr,
1581 &mut semantic.collection_refs,
1582 &mut semantic.constructor_refs,
1583 )?;
1584 }
1585 for field in body.mark_properties.iter().chain(&body.properties) {
1586 collect(
1587 &field.value,
1588 &mut semantic.collection_refs,
1589 &mut semantic.constructor_refs,
1590 )?;
1591 }
1592 plot_exprs.plots.insert(entry.name.clone(), body.clone());
1593 }
1594
1595 for (name, fields, is_figure) in figures
1596 .iter()
1597 .map(|entry| (&entry.name, &entry.fields, true))
1598 .chain(
1599 layers
1600 .iter()
1601 .map(|entry| (&entry.name, &entry.fields, false)),
1602 )
1603 {
1604 for field in fields {
1605 collect(
1606 &field.value,
1607 &mut semantic.collection_refs,
1608 &mut semantic.constructor_refs,
1609 )?;
1610 }
1611 if is_figure {
1612 plot_exprs.figures.insert(name.clone(), fields.clone());
1613 } else {
1614 plot_exprs.layers.insert(name.clone(), fields.clone());
1615 }
1616 }
1617
1618 semantic.plot_exprs = plot_exprs;
1619 Ok(())
1620}
1621
1622fn decl_key_or_internal_error(
1625 owner: &crate::dag_id::DagId,
1626 name: &ScopedName,
1627 span: Span,
1628 src: &NamedSource<Arc<String>>,
1629) -> Result<ResolvedName<namespace::Decl>, GraphcalError> {
1630 resolved_decl_key(owner, name).ok_or_else(|| {
1631 internal_error(
1632 format!("could not build canonical declaration key for `{name}`"),
1633 src,
1634 span,
1635 )
1636 })
1637}
1638
1639fn lower_domain_bounds(
1641 type_ann: &crate::desugar::desugared_ast::TypeExpr,
1642 expr_ctx: hir::ExprLoweringContext<'_>,
1643 src: &NamedSource<Arc<String>>,
1644) -> Result<Vec<ResolvedDomainBound>, GraphcalError> {
1645 type_ann
1646 .domain_bounds()
1647 .iter()
1648 .map(|bound| {
1649 let value = hir::lower_expr(&bound.value, expr_ctx)
1650 .map_err(|err| expr_lower_error_to_graphcal(&err, src))?;
1651 Ok(ResolvedDomainBound {
1652 kind: bound.kind,
1653 kind_span: bound.kind_span,
1654 value,
1655 span: bound.span,
1656 })
1657 })
1658 .collect()
1659}
1660
1661struct LoweredDagExpressions {
1664 exprs: ResolvedExpressions,
1665 domain_bounds: HashMap<ResolvedName<namespace::Decl>, Vec<ResolvedDomainBound>>,
1666}
1667
1668fn check_hir_body_policies(
1680 semantic: &DagSemanticBody,
1681 registry: &Registry,
1682 pub_names: &std::collections::HashSet<DeclName>,
1683 ctx: ModuleTypeContext<'_>,
1684 src: &NamedSource<Arc<String>>,
1685) -> Result<(), GraphcalError> {
1686 let checker = HirPolicyChecker { registry, ctx, src };
1687 let local = |key: &ResolvedName<namespace::Decl>| key.owner() == ctx.owner;
1688 let is_pub = |leaf: &str| pub_names.contains(&DeclName::new(leaf));
1689
1690 for (key, expr) in &semantic.expressions.consts {
1691 checker.check_expr(expr, true, local(key))?;
1692 }
1693 for (key, bounds) in &semantic.domain_bounds {
1694 if semantic.expressions.consts.contains_key(key) {
1695 for bound in bounds {
1696 checker.check_expr(&bound.value, true, local(key))?;
1697 }
1698 }
1699 }
1700 for (key, expr) in &semantic.expressions.nodes {
1701 checker.check_expr(expr, false, local(key))?;
1702 }
1703 for expr in semantic.expressions.param_defaults.values() {
1704 checker.check_expr(expr, false, false)?;
1707 }
1708 for (key, body) in &semantic.expressions.asserts {
1709 let check_literals = local(key) && is_pub(key.as_str());
1710 match body {
1711 hir::AssertBody::Expr(expr) => checker.check_expr(expr, false, check_literals)?,
1712 hir::AssertBody::Tolerance {
1713 actual,
1714 expected,
1715 tolerance,
1716 ..
1717 } => {
1718 checker.check_expr(actual, false, check_literals)?;
1719 checker.check_expr(expected, false, check_literals)?;
1720 checker.check_expr(tolerance, false, check_literals)?;
1721 }
1722 }
1723 }
1724 for (name, body) in &semantic.plot_exprs.plots {
1725 let check_literals = !name.is_qualified() && is_pub(name.member());
1726 for (_, expr) in &body.encodings {
1727 checker.check_expr(expr, false, check_literals)?;
1728 }
1729 for field in body.mark_properties.iter().chain(&body.properties) {
1730 checker.check_expr(&field.value, false, check_literals)?;
1731 }
1732 }
1733 for (name, fields) in semantic
1734 .plot_exprs
1735 .figures
1736 .iter()
1737 .chain(&semantic.plot_exprs.layers)
1738 {
1739 let check_literals = !name.is_qualified() && is_pub(name.member());
1740 for field in fields {
1741 checker.check_expr(&field.value, false, check_literals)?;
1742 }
1743 }
1744 Ok(())
1745}
1746
1747struct HirPolicyChecker<'a> {
1748 registry: &'a Registry,
1749 ctx: ModuleTypeContext<'a>,
1750 src: &'a NamedSource<Arc<String>>,
1751}
1752
1753impl HirPolicyChecker<'_> {
1754 fn check_expr(
1755 &self,
1756 expr: &hir::Expr,
1757 const_body: bool,
1758 check_pub_bind_literals: bool,
1759 ) -> Result<(), GraphcalError> {
1760 crate::stack::with_stack_growth(|| {
1762 self.check_expr_inner(expr, const_body, check_pub_bind_literals)
1763 })
1764 }
1765
1766 fn check_expr_inner(
1767 &self,
1768 expr: &hir::Expr,
1769 const_body: bool,
1770 check_pub_bind_literals: bool,
1771 ) -> Result<(), GraphcalError> {
1772 let recurse =
1773 |inner: &hir::Expr| self.check_expr(inner, const_body, check_pub_bind_literals);
1774 match &expr.kind {
1775 hir::ExprKind::Error
1776 | hir::ExprKind::Number(_)
1777 | hir::ExprKind::Integer(_)
1778 | hir::ExprKind::Bool(_)
1779 | hir::ExprKind::StringLiteral(_)
1780 | hir::ExprKind::TypeSystemRef(_)
1781 | hir::ExprKind::ConstRef(_)
1782 | hir::ExprKind::LocalRef(_) => Ok(()),
1783 hir::ExprKind::UnitLiteral { unit, .. } => self.check_const_unit_expr(unit, const_body),
1784 hir::ExprKind::GraphRef(target) => {
1785 self.check_graph_ref(target, expr.span, const_body)
1788 }
1789 hir::ExprKind::VariantLiteral(variant) => {
1790 self.check_variant_literal(variant, check_pub_bind_literals)
1791 }
1792 hir::ExprKind::BinOp { lhs, rhs, .. } => {
1793 recurse(lhs)?;
1794 recurse(rhs)
1795 }
1796 hir::ExprKind::UnaryOp { operand, .. }
1797 | hir::ExprKind::DisplayTimezone { expr: operand, .. }
1798 | hir::ExprKind::FieldAccess { expr: operand, .. } => recurse(operand),
1799 hir::ExprKind::Convert {
1800 expr: operand,
1801 target,
1802 } => {
1803 self.check_const_unit_expr(target, const_body)?;
1804 recurse(operand)
1805 }
1806 hir::ExprKind::FnCall { args, .. } => args.iter().try_for_each(recurse),
1807 hir::ExprKind::If {
1808 condition,
1809 then_branch,
1810 else_branch,
1811 } => {
1812 recurse(condition)?;
1813 recurse(then_branch)?;
1814 recurse(else_branch)
1815 }
1816 hir::ExprKind::ConstructorCall { fields, .. } => {
1817 fields.iter().try_for_each(|field| recurse(&field.value))
1818 }
1819 hir::ExprKind::MapLiteral { entries } => {
1820 for entry in entries {
1821 for key in &entry.keys {
1822 if let hir::expr::MapEntryKey::IndexVariant(variant) = key {
1823 self.check_variant_literal(variant, check_pub_bind_literals)?;
1824 }
1825 }
1826 recurse(&entry.value)?;
1827 }
1828 Ok(())
1829 }
1830 hir::ExprKind::ForComp { body, .. } => recurse(body),
1831 hir::ExprKind::IndexAccess { expr: inner, args } => {
1832 recurse(inner)?;
1833 for arg in args {
1834 match arg {
1835 hir::expr::IndexArg::Variant(variant) => {
1836 self.check_variant_literal(variant, check_pub_bind_literals)?;
1837 }
1838 hir::expr::IndexArg::Expr(arg_expr) => recurse(arg_expr)?,
1839 hir::expr::IndexArg::Var(_) => {}
1840 }
1841 }
1842 Ok(())
1843 }
1844 hir::ExprKind::Scan {
1845 source, init, body, ..
1846 } => {
1847 recurse(source)?;
1848 recurse(init)?;
1849 recurse(body)
1850 }
1851 hir::ExprKind::Unfold { init, body, .. } => {
1852 recurse(init)?;
1853 recurse(body)
1854 }
1855 hir::ExprKind::Match { scrutinee, arms } => {
1856 recurse(scrutinee)?;
1857 for arm in arms {
1858 if let hir::expr::MatchPattern::IndexLabel { variant, .. } = &arm.pattern {
1859 self.check_variant_literal(variant, check_pub_bind_literals)?;
1860 }
1861 recurse(&arm.body)?;
1862 }
1863 Ok(())
1864 }
1865 hir::ExprKind::InlineDagRef { args, .. } => {
1866 args.iter().try_for_each(|arg| recurse(&arg.value))
1867 }
1868 }
1869 }
1870
1871 fn check_const_unit_expr(
1872 &self,
1873 unit: &crate::desugar::desugared_ast::UnitExpr,
1874 const_body: bool,
1875 ) -> Result<(), GraphcalError> {
1876 if !const_body {
1877 return Ok(());
1878 }
1879 for term in &unit.terms {
1880 let Some(info) = self.registry.units.get_unit(&term.name.value) else {
1881 continue;
1883 };
1884 if !info.constness.is_const() {
1885 return Err(GraphcalError::NonConstUnitInConst {
1886 name: term.name.value.clone(),
1887 src: self.src.clone(),
1888 span: term.name.span.into(),
1889 });
1890 }
1891 }
1892 Ok(())
1893 }
1894
1895 fn check_graph_ref(
1896 &self,
1897 target: &Spanned<ResolvedName<namespace::Decl>>,
1898 ref_span: Span,
1899 const_body: bool,
1900 ) -> Result<(), GraphcalError> {
1901 let Ok(kind) = self.ctx.resolver.decl_symbol_kind(&target.value) else {
1902 return Ok(());
1905 };
1906 if matches!(kind, crate::syntax::module_resolve::DeclSymbolKind::Assert) {
1907 return Err(GraphcalError::GraphRefToAssert {
1908 name: DeclName::new(target.value.as_str()),
1909 src: self.src.clone(),
1910 span: ref_span.into(),
1911 });
1912 }
1913 if const_body && !kind.is_const() {
1914 return Err(GraphcalError::GraphRefInConst {
1915 name: ScopedName::local(target.value.as_str()),
1916 src: self.src.clone(),
1917 span: ref_span.into(),
1918 });
1919 }
1920 Ok(())
1921 }
1922
1923 fn check_variant_literal(
1924 &self,
1925 variant: &hir::expr::IndexVariantRef,
1926 check_pub_bind_literals: bool,
1927 ) -> Result<(), GraphcalError> {
1928 if !check_pub_bind_literals {
1929 return Ok(());
1930 }
1931 let index = variant.variant.index();
1932 if index.owner() != self.ctx.owner {
1933 return Ok(());
1934 }
1935 let is_pub_bind = self
1936 .ctx
1937 .resolver
1938 .modules()
1939 .get(self.ctx.owner)
1940 .and_then(|symbols| symbols.indexes().get(&IndexName::new(index.as_str())))
1941 .is_some_and(|symbol| {
1942 symbol.visibility().is_bindable() && !symbol.variants().is_empty()
1943 });
1944 if is_pub_bind {
1945 return Err(GraphcalError::PubIndexVariantLiteral {
1946 index: index.as_str().to_string(),
1947 variant: variant.variant.variant().as_str().to_string(),
1948 src: self.src.clone(),
1949 span: variant.path_span().into(),
1950 });
1951 }
1952 Ok(())
1953 }
1954}
1955
1956fn augment_runtime_deps_for_dynamic_units(semantic: &mut DagSemanticBody) {
1963 if semantic.dynamic_unit_scales.is_empty() {
1964 return;
1965 }
1966 let scale_deps: HashMap<
1967 crate::syntax::names::UnitRef,
1968 BTreeSet<ResolvedName<namespace::Decl>>,
1969 > = semantic
1970 .dynamic_unit_scales
1971 .iter()
1972 .map(|(name, expr)| {
1973 (
1974 name.clone(),
1975 hir::collect_expr_dependencies(expr).graph_refs,
1976 )
1977 })
1978 .collect();
1979
1980 let DagSemanticBody {
1981 expressions,
1982 dependencies,
1983 ..
1984 } = semantic;
1985 for (key, expr) in expressions.param_defaults.iter().chain(&expressions.nodes) {
1986 let mut unit_names = std::collections::HashSet::new();
1987 collect_unit_names_from_hir(expr, &mut unit_names);
1988 let extra: BTreeSet<ResolvedName<namespace::Decl>> = unit_names
1989 .iter()
1990 .filter_map(|unit| scale_deps.get(unit))
1991 .flatten()
1992 .cloned()
1993 .collect();
1994 if !extra.is_empty() {
1995 dependencies
1996 .runtime_deps
1997 .entry(key.clone())
1998 .or_default()
1999 .extend(extra);
2000 }
2001 }
2002}
2003
2004fn collect_unit_names_from_hir(
2006 expr: &hir::Expr,
2007 names: &mut std::collections::HashSet<crate::syntax::names::UnitRef>,
2008) {
2009 crate::stack::with_stack_growth(|| match &expr.kind {
2011 hir::ExprKind::UnitLiteral { unit, .. } => {
2012 for term in &unit.terms {
2013 names.insert(term.name.value.clone());
2014 }
2015 }
2016 hir::ExprKind::Convert {
2017 expr: inner,
2018 target,
2019 } => {
2020 for term in &target.terms {
2021 names.insert(term.name.value.clone());
2022 }
2023 collect_unit_names_from_hir(inner, names);
2024 }
2025 hir::ExprKind::Error
2026 | hir::ExprKind::Number(_)
2027 | hir::ExprKind::Integer(_)
2028 | hir::ExprKind::Bool(_)
2029 | hir::ExprKind::StringLiteral(_)
2030 | hir::ExprKind::TypeSystemRef(_)
2031 | hir::ExprKind::GraphRef(_)
2032 | hir::ExprKind::ConstRef(_)
2033 | hir::ExprKind::LocalRef(_)
2034 | hir::ExprKind::VariantLiteral(_) => {}
2035 hir::ExprKind::BinOp { lhs, rhs, .. } => {
2036 collect_unit_names_from_hir(lhs, names);
2037 collect_unit_names_from_hir(rhs, names);
2038 }
2039 hir::ExprKind::UnaryOp { operand, .. }
2040 | hir::ExprKind::DisplayTimezone { expr: operand, .. }
2041 | hir::ExprKind::FieldAccess { expr: operand, .. } => {
2042 collect_unit_names_from_hir(operand, names);
2043 }
2044 hir::ExprKind::FnCall { args, .. } => {
2045 for arg in args {
2046 collect_unit_names_from_hir(arg, names);
2047 }
2048 }
2049 hir::ExprKind::If {
2050 condition,
2051 then_branch,
2052 else_branch,
2053 } => {
2054 collect_unit_names_from_hir(condition, names);
2055 collect_unit_names_from_hir(then_branch, names);
2056 collect_unit_names_from_hir(else_branch, names);
2057 }
2058 hir::ExprKind::ConstructorCall { fields, .. } => {
2059 for field in fields {
2060 collect_unit_names_from_hir(&field.value, names);
2061 }
2062 }
2063 hir::ExprKind::MapLiteral { entries } => {
2064 for entry in entries {
2065 collect_unit_names_from_hir(&entry.value, names);
2066 }
2067 }
2068 hir::ExprKind::ForComp { body, .. } => collect_unit_names_from_hir(body, names),
2069 hir::ExprKind::IndexAccess { expr: inner, args } => {
2070 collect_unit_names_from_hir(inner, names);
2071 for arg in args {
2072 if let hir::expr::IndexArg::Expr(arg_expr) = arg {
2073 collect_unit_names_from_hir(arg_expr, names);
2074 }
2075 }
2076 }
2077 hir::ExprKind::Scan {
2078 source, init, body, ..
2079 } => {
2080 collect_unit_names_from_hir(source, names);
2081 collect_unit_names_from_hir(init, names);
2082 collect_unit_names_from_hir(body, names);
2083 }
2084 hir::ExprKind::Unfold { init, body, .. } => {
2085 collect_unit_names_from_hir(init, names);
2086 collect_unit_names_from_hir(body, names);
2087 }
2088 hir::ExprKind::Match { scrutinee, arms } => {
2089 collect_unit_names_from_hir(scrutinee, names);
2090 for arm in arms {
2091 collect_unit_names_from_hir(&arm.body, names);
2092 }
2093 }
2094 hir::ExprKind::InlineDagRef { args, .. } => {
2095 for arg in args {
2096 collect_unit_names_from_hir(&arg.value, names);
2097 }
2098 }
2099 });
2100}
2101
2102fn collect_resolved_dag_dependencies(
2103 consts: &[crate::ir::lower::ConstEntry],
2104 params: &[crate::ir::lower::ParamEntry],
2105 nodes: &[crate::ir::lower::NodeEntry],
2106 exprs: &ResolvedExpressions,
2107 ctx: ModuleTypeContext<'_>,
2108 src: &NamedSource<Arc<String>>,
2109) -> Result<ResolvedDagDependencies, GraphcalError> {
2110 let mut resolved = ResolvedDagDependencies::default();
2111
2112 for entry in consts {
2113 let body_src = entry.src.resolve(src);
2114 let key = resolved_decl_key(ctx.owner, &entry.name).ok_or_else(|| {
2115 internal_error(
2116 format!(
2117 "could not build canonical declaration key for `{}`",
2118 entry.name
2119 ),
2120 body_src,
2121 entry.span,
2122 )
2123 })?;
2124 let hir_expr = exprs.consts.get(&key).ok_or_else(|| {
2125 internal_error(
2126 format!(
2127 "missing HIR expression for const declaration `{}`",
2128 entry.name
2129 ),
2130 body_src,
2131 entry.span,
2132 )
2133 })?;
2134 let mut deps = hir::collect_expr_dependencies(hir_expr);
2135 for graph_ref in &deps.graph_refs {
2136 let kind = ctx
2140 .resolver
2141 .decl_symbol_kind(graph_ref)
2142 .map_err(|err| module_resolve_error(&err, body_src, entry.span))?;
2143 if kind.is_const() {
2144 deps.const_refs.insert(graph_ref.clone());
2145 }
2146 }
2147 resolved.const_deps.insert(key, deps.const_refs);
2148 }
2149
2150 for entry in params {
2151 let key = resolved_decl_key(ctx.owner, &entry.name).ok_or_else(|| {
2152 internal_error(
2153 format!(
2154 "could not build canonical declaration key for `{}`",
2155 entry.name
2156 ),
2157 entry.src.resolve(src),
2158 entry.span,
2159 )
2160 })?;
2161 let deps = exprs.param_defaults.get(&key).map_or_else(
2162 hir::ExprDependencies::default,
2163 hir::collect_expr_dependencies,
2164 );
2165 resolved.runtime_deps.insert(key, deps.graph_refs);
2166 }
2167
2168 for entry in nodes {
2169 let body_src = entry.src.resolve(src);
2170 let key = resolved_decl_key(ctx.owner, &entry.name).ok_or_else(|| {
2171 internal_error(
2172 format!(
2173 "could not build canonical declaration key for `{}`",
2174 entry.name
2175 ),
2176 body_src,
2177 entry.span,
2178 )
2179 })?;
2180 let hir_expr = exprs.nodes.get(&key).ok_or_else(|| {
2181 internal_error(
2182 format!(
2183 "missing HIR expression for node declaration `{}`",
2184 entry.name
2185 ),
2186 body_src,
2187 entry.span,
2188 )
2189 })?;
2190 let mut deps = hir::collect_expr_dependencies(hir_expr);
2191 if !hir::has_ref_outside_unfold(hir_expr, &key) {
2198 deps.graph_refs.remove(&key);
2199 }
2200 resolved.runtime_deps.insert(key, deps.graph_refs);
2201 }
2202
2203 Ok(resolved)
2204}
2205
2206fn collect_resolved_collection_refs(
2207 exprs: &ResolvedExpressions,
2208 domain_bounds: &HashMap<ResolvedName<namespace::Decl>, Vec<ResolvedDomainBound>>,
2209 resolved_decl_types: &HashMap<ScopedName, ResolvedTypeExpr>,
2210 ctx: ModuleTypeContext<'_>,
2211 src: &NamedSource<Arc<String>>,
2212) -> Result<ResolvedCollectionRefs, GraphcalError> {
2213 let mut refs = ResolvedCollectionRefs::default();
2214
2215 for resolved_type in resolved_decl_types.values() {
2216 collect_resolved_collection_indexes_from_type(resolved_type, ctx, src, &mut refs)?;
2217 }
2218
2219 for hir_expr in exprs
2220 .consts
2221 .values()
2222 .chain(exprs.param_defaults.values())
2223 .chain(exprs.nodes.values())
2224 .chain(domain_bounds.values().flatten().map(|bound| &bound.value))
2225 {
2226 collect_resolved_collection_refs_from_expr(hir_expr, ctx, src, &mut refs)?;
2227 }
2228 for body in exprs.asserts.values() {
2229 collect_resolved_collection_refs_from_assert_body(body, ctx, src, &mut refs)?;
2230 }
2231
2232 Ok(refs)
2233}
2234
2235fn record_resolved_collection_index(
2236 index: &ResolvedName<namespace::Index>,
2237 ctx: ModuleTypeContext<'_>,
2238 src: &NamedSource<Arc<String>>,
2239 span: Span,
2240 refs: &mut ResolvedCollectionRefs,
2241) -> Result<(), GraphcalError> {
2242 if refs.index_defs.contains_key(index) {
2243 return Ok(());
2244 }
2245 let def = ctx.types.get_index(index).cloned().ok_or_else(|| {
2246 internal_error(
2247 format!("semantic collection metadata references unknown index `{index}`"),
2248 src,
2249 span,
2250 )
2251 })?;
2252 refs.index_defs.insert(index.clone(), def);
2253 Ok(())
2254}
2255
2256fn collect_resolved_collection_indexes_from_type(
2257 resolved_type: &ResolvedTypeExpr,
2258 ctx: ModuleTypeContext<'_>,
2259 src: &NamedSource<Arc<String>>,
2260 refs: &mut ResolvedCollectionRefs,
2261) -> Result<(), GraphcalError> {
2262 match resolved_type {
2263 ResolvedTypeExpr::IndexArg(ResolvedIndex::Concrete(index, span)) => {
2264 record_resolved_collection_index(index, ctx, src, *span, refs)
2265 }
2266 ResolvedTypeExpr::Indexed { base, indexes } => {
2267 collect_resolved_collection_indexes_from_type(base, ctx, src, refs)?;
2268 for index in indexes {
2269 if let ResolvedIndex::Concrete(resolved, span) = index {
2270 record_resolved_collection_index(resolved, ctx, src, *span, refs)?;
2271 }
2272 }
2273 Ok(())
2274 }
2275 ResolvedTypeExpr::GenericStruct { type_args, .. } => {
2276 for arg in type_args {
2277 collect_resolved_collection_indexes_from_type(arg, ctx, src, refs)?;
2278 }
2279 Ok(())
2280 }
2281 ResolvedTypeExpr::Dimensionless
2282 | ResolvedTypeExpr::Bool
2283 | ResolvedTypeExpr::Int
2284 | ResolvedTypeExpr::Datetime(_)
2285 | ResolvedTypeExpr::IndexArg(_)
2286 | ResolvedTypeExpr::Scalar(_)
2287 | ResolvedTypeExpr::Struct(_, _)
2288 | ResolvedTypeExpr::GenericDimParam(_, _)
2289 | ResolvedTypeExpr::GenericTypeParam(_, _)
2290 | ResolvedTypeExpr::GenericDimExpr { .. } => Ok(()),
2291 }
2292}
2293
2294fn collect_resolved_collection_refs_from_expr(
2295 expr: &hir::Expr,
2296 ctx: ModuleTypeContext<'_>,
2297 src: &NamedSource<Arc<String>>,
2298 refs: &mut ResolvedCollectionRefs,
2299) -> Result<(), GraphcalError> {
2300 crate::stack::with_stack_growth(|| {
2303 collect_resolved_collection_refs_from_expr_inner(expr, ctx, src, refs)
2304 })
2305}
2306
2307#[expect(
2308 clippy::too_many_lines,
2309 reason = "expression traversal mirrors HIR variants"
2310)]
2311fn collect_resolved_collection_refs_from_expr_inner(
2312 expr: &hir::Expr,
2313 ctx: ModuleTypeContext<'_>,
2314 src: &NamedSource<Arc<String>>,
2315 refs: &mut ResolvedCollectionRefs,
2316) -> Result<(), GraphcalError> {
2317 match &expr.kind {
2318 hir::ExprKind::Error
2319 | hir::ExprKind::Number(_)
2320 | hir::ExprKind::Integer(_)
2321 | hir::ExprKind::Bool(_)
2322 | hir::ExprKind::StringLiteral(_)
2323 | hir::ExprKind::TypeSystemRef(_)
2324 | hir::ExprKind::GraphRef(_)
2325 | hir::ExprKind::LocalRef(_)
2326 | hir::ExprKind::ConstRef(_)
2327 | hir::ExprKind::UnitLiteral { .. } => Ok(()),
2328 hir::ExprKind::VariantLiteral(variant) => record_resolved_collection_index(
2329 variant.variant.index(),
2330 ctx,
2331 src,
2332 variant.path_span(),
2333 refs,
2334 ),
2335 hir::ExprKind::BinOp { lhs, rhs, .. } => {
2336 collect_resolved_collection_refs_from_expr(lhs, ctx, src, refs)?;
2337 collect_resolved_collection_refs_from_expr(rhs, ctx, src, refs)
2338 }
2339 hir::ExprKind::UnaryOp { operand, .. } => {
2340 collect_resolved_collection_refs_from_expr(operand, ctx, src, refs)
2341 }
2342 hir::ExprKind::FnCall { args, .. } => {
2343 for arg in args {
2344 collect_resolved_collection_refs_from_expr(arg, ctx, src, refs)?;
2345 }
2346 Ok(())
2347 }
2348 hir::ExprKind::If {
2349 condition,
2350 then_branch,
2351 else_branch,
2352 } => {
2353 collect_resolved_collection_refs_from_expr(condition, ctx, src, refs)?;
2354 collect_resolved_collection_refs_from_expr(then_branch, ctx, src, refs)?;
2355 collect_resolved_collection_refs_from_expr(else_branch, ctx, src, refs)
2356 }
2357 hir::ExprKind::Convert { expr, .. }
2358 | hir::ExprKind::DisplayTimezone { expr, .. }
2359 | hir::ExprKind::FieldAccess { expr, .. } => {
2360 collect_resolved_collection_refs_from_expr(expr, ctx, src, refs)
2361 }
2362 hir::ExprKind::ConstructorCall { fields, .. } => {
2363 for field in fields {
2364 collect_resolved_collection_refs_from_expr(&field.value, ctx, src, refs)?;
2365 }
2366 Ok(())
2367 }
2368 hir::ExprKind::MapLiteral { entries } => {
2369 for entry in entries {
2370 for key in &entry.keys {
2371 match key {
2372 hir::expr::MapEntryKey::IndexVariant(variant) => {
2373 record_resolved_collection_index(
2374 variant.variant.index(),
2375 ctx,
2376 src,
2377 variant.variant_span,
2378 refs,
2379 )?;
2380 }
2381 hir::expr::MapEntryKey::NatRangeVariant { .. } => {}
2382 }
2383 }
2384 collect_resolved_collection_refs_from_expr(&entry.value, ctx, src, refs)?;
2385 }
2386 Ok(())
2387 }
2388 hir::ExprKind::ForComp { bindings, body } => {
2389 for binding in bindings {
2390 match &binding.index {
2391 hir::expr::ForBindingIndex::Named(index) => {
2392 record_resolved_collection_index(&index.value, ctx, src, index.span, refs)?;
2393 }
2394 hir::expr::ForBindingIndex::Range { .. } => {}
2395 }
2396 }
2397 collect_resolved_collection_refs_from_expr(body, ctx, src, refs)
2398 }
2399 hir::ExprKind::IndexAccess { expr, args } => {
2400 collect_resolved_collection_refs_from_expr(expr, ctx, src, refs)?;
2401 for arg in args {
2402 match arg {
2403 hir::expr::IndexArg::Variant(variant) => {
2404 record_resolved_collection_index(
2405 variant.variant.index(),
2406 ctx,
2407 src,
2408 variant.path_span(),
2409 refs,
2410 )?;
2411 }
2412 hir::expr::IndexArg::Expr(expr) => {
2413 collect_resolved_collection_refs_from_expr(expr, ctx, src, refs)?;
2414 }
2415 hir::expr::IndexArg::Var(_) => {}
2416 }
2417 }
2418 Ok(())
2419 }
2420 hir::ExprKind::Scan {
2421 source, init, body, ..
2422 } => {
2423 collect_resolved_collection_refs_from_expr(source, ctx, src, refs)?;
2424 collect_resolved_collection_refs_from_expr(init, ctx, src, refs)?;
2425 collect_resolved_collection_refs_from_expr(body, ctx, src, refs)
2426 }
2427 hir::ExprKind::Unfold { init, body, .. } => {
2428 collect_resolved_collection_refs_from_expr(init, ctx, src, refs)?;
2429 collect_resolved_collection_refs_from_expr(body, ctx, src, refs)
2430 }
2431 hir::ExprKind::Match { scrutinee, arms } => {
2432 collect_resolved_collection_refs_from_expr(scrutinee, ctx, src, refs)?;
2433 for arm in arms {
2434 if let hir::expr::MatchPattern::IndexLabel { variant, span: _ } = &arm.pattern {
2435 record_resolved_collection_index(
2436 variant.variant.index(),
2437 ctx,
2438 src,
2439 variant.path_span(),
2440 refs,
2441 )?;
2442 }
2443 collect_resolved_collection_refs_from_expr(&arm.body, ctx, src, refs)?;
2444 }
2445 Ok(())
2446 }
2447 hir::ExprKind::InlineDagRef { args, .. } => {
2448 for arg in args {
2449 collect_resolved_collection_refs_from_expr(&arg.value, ctx, src, refs)?;
2450 }
2451 Ok(())
2452 }
2453 }
2454}
2455
2456fn collect_resolved_collection_refs_from_assert_body(
2457 body: &hir::AssertBody,
2458 ctx: ModuleTypeContext<'_>,
2459 src: &NamedSource<Arc<String>>,
2460 refs: &mut ResolvedCollectionRefs,
2461) -> Result<(), GraphcalError> {
2462 match body {
2463 hir::AssertBody::Expr(expr) => {
2464 collect_resolved_collection_refs_from_expr(expr, ctx, src, refs)
2465 }
2466 hir::AssertBody::Tolerance {
2467 actual,
2468 expected,
2469 tolerance,
2470 is_relative: _,
2471 } => {
2472 collect_resolved_collection_refs_from_expr(actual, ctx, src, refs)?;
2473 collect_resolved_collection_refs_from_expr(expected, ctx, src, refs)?;
2474 collect_resolved_collection_refs_from_expr(tolerance, ctx, src, refs)
2475 }
2476 }
2477}
2478
2479fn collect_resolved_constructor_refs(
2480 exprs: &ResolvedExpressions,
2481 domain_bounds: &HashMap<ResolvedName<namespace::Decl>, Vec<ResolvedDomainBound>>,
2482 ctx: ModuleTypeContext<'_>,
2483 src: &NamedSource<Arc<String>>,
2484) -> Result<ResolvedConstructorRefs, GraphcalError> {
2485 let mut refs = ResolvedConstructorRefs::default();
2486
2487 for hir_expr in exprs
2488 .consts
2489 .values()
2490 .chain(exprs.param_defaults.values())
2491 .chain(exprs.nodes.values())
2492 .chain(domain_bounds.values().flatten().map(|bound| &bound.value))
2493 {
2494 collect_resolved_constructor_refs_from_expr(hir_expr, ctx, src, &mut refs)?;
2495 }
2496 for body in exprs.asserts.values() {
2497 collect_resolved_constructor_refs_from_assert_body(body, ctx, src, &mut refs)?;
2498 }
2499
2500 Ok(refs)
2501}
2502
2503fn record_resolved_constructor_target(
2504 constructor: &ResolvedName<namespace::Constructor>,
2505 ctx: ModuleTypeContext<'_>,
2506 src: &NamedSource<Arc<String>>,
2507 span: Span,
2508 refs: &mut ResolvedConstructorRefs,
2509) -> Result<ResolvedConstructorTarget, GraphcalError> {
2510 if let Some(target) = refs.constructor_defs.get(constructor) {
2511 return Ok(target.clone());
2512 }
2513
2514 let def = ctx.types.lookup_constructor(constructor).ok_or_else(|| {
2515 internal_error(
2516 format!("semantic constructor metadata references unknown constructor `{constructor}`"),
2517 src,
2518 span,
2519 )
2520 })?;
2521 let target = ResolvedConstructorTarget {
2522 constructor: constructor.clone(),
2523 owning_type: def.owning_type.clone(),
2524 type_def: def.type_def.clone(),
2525 variant: def.variant.clone(),
2526 };
2527 refs.constructor_defs
2528 .insert(constructor.clone(), target.clone());
2529 Ok(target)
2530}
2531
2532fn collect_resolved_constructor_refs_from_expr(
2533 expr: &hir::Expr,
2534 ctx: ModuleTypeContext<'_>,
2535 src: &NamedSource<Arc<String>>,
2536 refs: &mut ResolvedConstructorRefs,
2537) -> Result<(), GraphcalError> {
2538 crate::stack::with_stack_growth(|| {
2541 collect_resolved_constructor_refs_from_expr_inner(expr, ctx, src, refs)
2542 })
2543}
2544
2545#[expect(
2546 clippy::too_many_lines,
2547 reason = "expression traversal mirrors HIR variants"
2548)]
2549fn collect_resolved_constructor_refs_from_expr_inner(
2550 expr: &hir::Expr,
2551 ctx: ModuleTypeContext<'_>,
2552 src: &NamedSource<Arc<String>>,
2553 refs: &mut ResolvedConstructorRefs,
2554) -> Result<(), GraphcalError> {
2555 match &expr.kind {
2556 hir::ExprKind::Error
2557 | hir::ExprKind::Number(_)
2558 | hir::ExprKind::Integer(_)
2559 | hir::ExprKind::Bool(_)
2560 | hir::ExprKind::StringLiteral(_)
2561 | hir::ExprKind::TypeSystemRef(_)
2562 | hir::ExprKind::GraphRef(_)
2563 | hir::ExprKind::LocalRef(_)
2564 | hir::ExprKind::UnitLiteral { .. }
2565 | hir::ExprKind::VariantLiteral(_) => Ok(()),
2566 hir::ExprKind::ConstRef(target) => {
2567 if let hir::ConstRef::Constructor(constructor) = &target.value {
2568 record_resolved_constructor_target(constructor, ctx, src, target.span, refs)?;
2569 }
2570 Ok(())
2571 }
2572 hir::ExprKind::BinOp { lhs, rhs, .. } => {
2573 collect_resolved_constructor_refs_from_expr(lhs, ctx, src, refs)?;
2574 collect_resolved_constructor_refs_from_expr(rhs, ctx, src, refs)
2575 }
2576 hir::ExprKind::UnaryOp { operand, .. } => {
2577 collect_resolved_constructor_refs_from_expr(operand, ctx, src, refs)
2578 }
2579 hir::ExprKind::FnCall { args, .. } => {
2580 for arg in args {
2581 collect_resolved_constructor_refs_from_expr(arg, ctx, src, refs)?;
2582 }
2583 Ok(())
2584 }
2585 hir::ExprKind::If {
2586 condition,
2587 then_branch,
2588 else_branch,
2589 } => {
2590 collect_resolved_constructor_refs_from_expr(condition, ctx, src, refs)?;
2591 collect_resolved_constructor_refs_from_expr(then_branch, ctx, src, refs)?;
2592 collect_resolved_constructor_refs_from_expr(else_branch, ctx, src, refs)
2593 }
2594 hir::ExprKind::Convert { expr, .. }
2595 | hir::ExprKind::DisplayTimezone { expr, .. }
2596 | hir::ExprKind::FieldAccess { expr, .. } => {
2597 collect_resolved_constructor_refs_from_expr(expr, ctx, src, refs)
2598 }
2599 hir::ExprKind::ConstructorCall { callee, fields, .. } => {
2600 record_resolved_constructor_target(&callee.value, ctx, src, callee.span, refs)?;
2601 for field in fields {
2602 collect_resolved_constructor_refs_from_expr(&field.value, ctx, src, refs)?;
2603 }
2604 Ok(())
2605 }
2606 hir::ExprKind::MapLiteral { entries } => {
2607 for entry in entries {
2608 collect_resolved_constructor_refs_from_expr(&entry.value, ctx, src, refs)?;
2609 }
2610 Ok(())
2611 }
2612 hir::ExprKind::ForComp { body, .. } => {
2613 collect_resolved_constructor_refs_from_expr(body, ctx, src, refs)
2614 }
2615 hir::ExprKind::IndexAccess { expr, args } => {
2616 collect_resolved_constructor_refs_from_expr(expr, ctx, src, refs)?;
2617 for arg in args {
2618 if let hir::expr::IndexArg::Expr(expr) = arg {
2619 collect_resolved_constructor_refs_from_expr(expr, ctx, src, refs)?;
2620 }
2621 }
2622 Ok(())
2623 }
2624 hir::ExprKind::Scan {
2625 source, init, body, ..
2626 } => {
2627 collect_resolved_constructor_refs_from_expr(source, ctx, src, refs)?;
2628 collect_resolved_constructor_refs_from_expr(init, ctx, src, refs)?;
2629 collect_resolved_constructor_refs_from_expr(body, ctx, src, refs)
2630 }
2631 hir::ExprKind::Unfold { init, body, .. } => {
2632 collect_resolved_constructor_refs_from_expr(init, ctx, src, refs)?;
2633 collect_resolved_constructor_refs_from_expr(body, ctx, src, refs)
2634 }
2635 hir::ExprKind::Match { scrutinee, arms } => {
2636 collect_resolved_constructor_refs_from_expr(scrutinee, ctx, src, refs)?;
2637 for arm in arms {
2638 if let hir::expr::MatchPattern::Constructor { constructor, .. } = &arm.pattern {
2639 record_resolved_constructor_target(
2640 &constructor.value,
2641 ctx,
2642 src,
2643 constructor.span,
2644 refs,
2645 )?;
2646 }
2647 collect_resolved_constructor_refs_from_expr(&arm.body, ctx, src, refs)?;
2648 }
2649 Ok(())
2650 }
2651 hir::ExprKind::InlineDagRef { args, .. } => {
2652 for arg in args {
2653 collect_resolved_constructor_refs_from_expr(&arg.value, ctx, src, refs)?;
2654 }
2655 Ok(())
2656 }
2657 }
2658}
2659
2660fn collect_resolved_constructor_refs_from_assert_body(
2661 body: &hir::AssertBody,
2662 ctx: ModuleTypeContext<'_>,
2663 src: &NamedSource<Arc<String>>,
2664 refs: &mut ResolvedConstructorRefs,
2665) -> Result<(), GraphcalError> {
2666 match body {
2667 hir::AssertBody::Expr(expr) => {
2668 collect_resolved_constructor_refs_from_expr(expr, ctx, src, refs)
2669 }
2670 hir::AssertBody::Tolerance {
2671 actual,
2672 expected,
2673 tolerance,
2674 is_relative: _,
2675 } => {
2676 collect_resolved_constructor_refs_from_expr(actual, ctx, src, refs)?;
2677 collect_resolved_constructor_refs_from_expr(expected, ctx, src, refs)?;
2678 collect_resolved_constructor_refs_from_expr(tolerance, ctx, src, refs)
2679 }
2680 }
2681}
2682
2683fn collect_resolved_inline_dag_refs(exprs: &ResolvedExpressions) -> ResolvedInlineDagRefs {
2684 let mut refs = ResolvedInlineDagRefs::default();
2685
2686 for hir_expr in exprs
2687 .consts
2688 .values()
2689 .chain(exprs.param_defaults.values())
2690 .chain(exprs.nodes.values())
2691 {
2692 collect_resolved_inline_dag_refs_from_expr(hir_expr, &mut refs);
2693 }
2694 for body in exprs.asserts.values() {
2695 collect_resolved_inline_dag_refs_from_assert_body(body, &mut refs);
2696 }
2697
2698 refs
2699}
2700
2701fn collect_resolved_inline_dag_refs_from_expr(expr: &hir::Expr, refs: &mut ResolvedInlineDagRefs) {
2702 crate::stack::with_stack_growth(|| {
2705 collect_resolved_inline_dag_refs_from_expr_inner(expr, refs);
2706 });
2707}
2708
2709fn collect_resolved_inline_dag_refs_from_expr_inner(
2710 expr: &hir::Expr,
2711 refs: &mut ResolvedInlineDagRefs,
2712) {
2713 match &expr.kind {
2714 hir::ExprKind::Error
2715 | hir::ExprKind::Number(_)
2716 | hir::ExprKind::Integer(_)
2717 | hir::ExprKind::Bool(_)
2718 | hir::ExprKind::StringLiteral(_)
2719 | hir::ExprKind::TypeSystemRef(_)
2720 | hir::ExprKind::GraphRef(_)
2721 | hir::ExprKind::ConstRef(_)
2722 | hir::ExprKind::LocalRef(_)
2723 | hir::ExprKind::UnitLiteral { .. }
2724 | hir::ExprKind::VariantLiteral(_) => {}
2725 hir::ExprKind::BinOp { lhs, rhs, .. } => {
2726 collect_resolved_inline_dag_refs_from_expr(lhs, refs);
2727 collect_resolved_inline_dag_refs_from_expr(rhs, refs);
2728 }
2729 hir::ExprKind::UnaryOp { operand, .. }
2730 | hir::ExprKind::Convert { expr: operand, .. }
2731 | hir::ExprKind::DisplayTimezone { expr: operand, .. }
2732 | hir::ExprKind::FieldAccess { expr: operand, .. } => {
2733 collect_resolved_inline_dag_refs_from_expr(operand, refs);
2734 }
2735 hir::ExprKind::FnCall { args, .. } => {
2736 for arg in args {
2737 collect_resolved_inline_dag_refs_from_expr(arg, refs);
2738 }
2739 }
2740 hir::ExprKind::If {
2741 condition,
2742 then_branch,
2743 else_branch,
2744 } => {
2745 collect_resolved_inline_dag_refs_from_expr(condition, refs);
2746 collect_resolved_inline_dag_refs_from_expr(then_branch, refs);
2747 collect_resolved_inline_dag_refs_from_expr(else_branch, refs);
2748 }
2749 hir::ExprKind::ConstructorCall { fields, .. } => {
2750 for field in fields {
2751 collect_resolved_inline_dag_refs_from_expr(&field.value, refs);
2752 }
2753 }
2754 hir::ExprKind::MapLiteral { entries } => {
2755 for entry in entries {
2756 collect_resolved_inline_dag_refs_from_expr(&entry.value, refs);
2757 }
2758 }
2759 hir::ExprKind::ForComp { body, .. } => {
2760 collect_resolved_inline_dag_refs_from_expr(body, refs);
2761 }
2762 hir::ExprKind::IndexAccess { expr, args } => {
2763 collect_resolved_inline_dag_refs_from_expr(expr, refs);
2764 for arg in args {
2765 if let hir::expr::IndexArg::Expr(expr) = arg {
2766 collect_resolved_inline_dag_refs_from_expr(expr, refs);
2767 }
2768 }
2769 }
2770 hir::ExprKind::Scan {
2771 source, init, body, ..
2772 } => {
2773 collect_resolved_inline_dag_refs_from_expr(source, refs);
2774 collect_resolved_inline_dag_refs_from_expr(init, refs);
2775 collect_resolved_inline_dag_refs_from_expr(body, refs);
2776 }
2777 hir::ExprKind::Unfold { init, body, .. } => {
2778 collect_resolved_inline_dag_refs_from_expr(init, refs);
2779 collect_resolved_inline_dag_refs_from_expr(body, refs);
2780 }
2781 hir::ExprKind::Match { scrutinee, arms } => {
2782 collect_resolved_inline_dag_refs_from_expr(scrutinee, refs);
2783 for arm in arms {
2784 collect_resolved_inline_dag_refs_from_expr(&arm.body, refs);
2785 }
2786 }
2787 hir::ExprKind::InlineDagRef {
2788 target,
2789 args,
2790 output,
2791 } => {
2792 let arg_targets = args
2793 .iter()
2794 .map(|arg| (arg.target.span, arg.target.value.clone()))
2795 .collect();
2796 refs.calls.insert(
2797 expr.span,
2798 ResolvedInlineDagCall {
2799 target: target.value.clone(),
2800 arg_targets,
2801 output: output.clone(),
2802 },
2803 );
2804 for arg in args {
2805 collect_resolved_inline_dag_refs_from_expr(&arg.value, refs);
2806 }
2807 }
2808 }
2809}
2810
2811fn collect_resolved_inline_dag_refs_from_assert_body(
2812 body: &hir::AssertBody,
2813 refs: &mut ResolvedInlineDagRefs,
2814) {
2815 match body {
2816 hir::AssertBody::Expr(expr) => collect_resolved_inline_dag_refs_from_expr(expr, refs),
2817 hir::AssertBody::Tolerance {
2818 actual,
2819 expected,
2820 tolerance,
2821 is_relative: _,
2822 } => {
2823 collect_resolved_inline_dag_refs_from_expr(actual, refs);
2824 collect_resolved_inline_dag_refs_from_expr(expected, refs);
2825 collect_resolved_inline_dag_refs_from_expr(tolerance, refs);
2826 }
2827 }
2828}
2829
2830fn collect_hir_decl_bindings(
2831 owner: &crate::dag_id::DagId,
2832 consts: &[crate::ir::lower::ConstEntry],
2833 params: &[crate::ir::lower::ParamEntry],
2834 nodes: &[crate::ir::lower::NodeEntry],
2835 imported_value_sources: &HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
2836 src: &NamedSource<Arc<String>>,
2837) -> Result<HashMap<ScopedName, ResolvedName<namespace::Decl>>, GraphcalError> {
2838 let mut bindings = HashMap::new();
2839
2840 for name in consts
2841 .iter()
2842 .map(|entry| &entry.name)
2843 .chain(params.iter().map(|entry| &entry.name))
2844 .chain(nodes.iter().map(|entry| &entry.name))
2845 {
2846 let resolved = resolved_decl_key(owner, name).ok_or_else(|| {
2847 internal_error(
2848 format!("could not build canonical declaration key for `{name}`"),
2849 src,
2850 Span::new(0, 0),
2851 )
2852 })?;
2853 bindings.insert(name.clone(), resolved);
2854 }
2855
2856 for (name, source) in imported_value_sources {
2857 bindings.insert(
2858 name.clone(),
2859 ResolvedName::from_def(source.dag_id.clone(), source.source_name.clone()),
2860 );
2861 }
2862
2863 Ok(bindings)
2864}
2865
2866#[expect(
2867 clippy::too_many_arguments,
2868 reason = "collects local and imported declaration binding sources for a completed DAG"
2869)]
2870fn collect_resolved_decl_bindings(
2871 ctx: ModuleTypeContext<'_>,
2872 consts: &[crate::ir::lower::ConstEntry],
2873 params: &[crate::ir::lower::ParamEntry],
2874 nodes: &[crate::ir::lower::NodeEntry],
2875 imported_values: &HashMap<
2876 ScopedName,
2877 (
2878 crate::registry::runtime_value::RuntimeValue,
2879 crate::registry::declared_type::DeclaredType,
2880 ),
2881 >,
2882 imported_decl_types: &HashMap<ScopedName, crate::registry::declared_type::DeclaredType>,
2883 imported_value_sources: &HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
2884 src: &NamedSource<Arc<String>>,
2885) -> Result<HashMap<ScopedName, ResolvedName<namespace::Decl>>, GraphcalError> {
2886 let mut bindings = collect_hir_decl_bindings(
2887 ctx.owner,
2888 consts,
2889 params,
2890 nodes,
2891 imported_value_sources,
2892 src,
2893 )?;
2894
2895 for name in imported_values
2896 .keys()
2897 .chain(imported_decl_types.keys())
2898 .chain(imported_value_sources.keys())
2899 {
2900 if bindings.contains_key(name) {
2901 continue;
2902 }
2903 let path = scoped_name_to_name_path(name).ok_or_else(|| {
2904 internal_error(
2905 format!("could not convert visible declaration `{name}` to a name path"),
2906 src,
2907 Span::new(0, 0),
2908 )
2909 })?;
2910 let resolved = match ctx.resolver.resolve_decl_path(ctx.owner, &path) {
2911 Ok(resolved) => resolved,
2912 Err(_err)
2913 if imported_values.contains_key(name) || imported_decl_types.contains_key(name) =>
2914 {
2915 let synthetic_owner = name
2921 .qualifier()
2922 .iter()
2923 .fold(ctx.owner.clone(), |owner, segment| {
2924 owner.child(segment.as_ref())
2925 });
2926 ResolvedName::from_def(synthetic_owner, DeclName::new(name.member()))
2927 }
2928 Err(err) => return Err(module_resolve_error(&err, src, Span::new(0, 0))),
2929 };
2930 bindings.insert(name.clone(), resolved);
2931 }
2932
2933 Ok(bindings)
2934}
2935
2936fn scoped_name_to_name_path(name: &ScopedName) -> Option<NamePath> {
2937 let qualifier = name
2938 .qualifier()
2939 .iter()
2940 .map(|segment| NameAtom::parse(segment.as_ref()).ok())
2941 .collect::<Option<Vec<_>>>()?;
2942 let leaf = NameAtom::parse(name.member()).ok()?;
2943 Some(if qualifier.is_empty() {
2944 NamePath::local(leaf)
2945 } else {
2946 NamePath::qualified_path(qualifier, leaf)
2947 })
2948}
2949
2950fn resolve_expected_fail_keys(
2951 expected_fail: HashMap<ScopedName, ExpectedFail>,
2952 ctx: ModuleTypeContext<'_>,
2953 src: &NamedSource<Arc<String>>,
2954) -> Result<HashMap<ScopedName, ExpectedFail>, GraphcalError> {
2955 expected_fail
2956 .into_iter()
2957 .map(|(assert_name, expected)| {
2958 let resolved = match expected {
2959 ExpectedFail::All => ExpectedFail::All,
2960 ExpectedFail::Variants(keys) => {
2961 let resolved_keys = keys
2962 .into_iter()
2963 .map(|key| {
2964 key.into_iter()
2965 .map(|part| {
2966 let Some(index_path) = part.source_index_path().cloned() else {
2967 return Ok(part);
2968 };
2969 let resolved = ctx
2970 .resolver
2971 .resolve_index_variant_parts(
2972 ctx.owner,
2973 &index_path,
2974 &part.variant(),
2975 )
2976 .map_err(|err| {
2977 module_resolve_error(&err, src, part.span())
2978 })?;
2979 Ok(part.with_resolved_variant(resolved))
2980 })
2981 .collect::<Result<_, GraphcalError>>()
2982 })
2983 .collect::<Result<_, GraphcalError>>()?;
2984 ExpectedFail::Variants(resolved_keys)
2985 }
2986 };
2987 Ok((assert_name, resolved))
2988 })
2989 .collect()
2990}
2991
2992struct DagTIRSeed {
2996 dag_id: crate::dag_id::DagId,
2997 consts: Vec<crate::ir::lower::ConstEntry>,
2998 params: Vec<crate::ir::lower::ParamEntry>,
2999 nodes: Vec<crate::ir::lower::NodeEntry>,
3000 resolved_decl_types: HashMap<ScopedName, ResolvedTypeExpr>,
3001 semantic: DagSemanticBody,
3002}
3003
3004impl DagTIRSeed {
3005 #[expect(
3006 clippy::too_many_arguments,
3007 reason = "single conversion that absorbs every IR field beyond the resolved decls"
3008 )]
3009 fn with_body(
3010 self,
3011 asserts: Vec<crate::ir::lower::AssertEntry>,
3012 plots: Vec<crate::ir::lower::PlotEntry>,
3013 figures: Vec<crate::ir::lower::FigureEntry>,
3014 layers: Vec<crate::ir::lower::LayerEntry>,
3015 included_plots: Vec<crate::ir::lower::IncludedPlotEntry>,
3016 source_order: Vec<(ScopedName, DeclCategory)>,
3017 assert_names: std::collections::HashSet<ScopedName>,
3018 assumes_map: HashMap<ScopedName, Vec<ScopedName>>,
3019 expected_fail: HashMap<ScopedName, ExpectedFail>,
3020 imported_values: HashMap<
3021 ScopedName,
3022 (
3023 crate::registry::runtime_value::RuntimeValue,
3024 crate::registry::declared_type::DeclaredType,
3025 ),
3026 >,
3027 imported_decl_types: HashMap<ScopedName, crate::registry::declared_type::DeclaredType>,
3028 imported_value_sources: HashMap<ScopedName, crate::ir::lower::ImportedValueSource>,
3029 module_ctx: ModuleTypeContext<'_>,
3030 src: &NamedSource<Arc<String>>,
3031 ) -> Result<DagTIR, GraphcalError> {
3032 let decl_bindings = collect_resolved_decl_bindings(
3033 module_ctx,
3034 &self.consts,
3035 &self.params,
3036 &self.nodes,
3037 &imported_values,
3038 &imported_decl_types,
3039 &imported_value_sources,
3040 src,
3041 )?;
3042 let expected_fail = resolve_expected_fail_keys(expected_fail, module_ctx, src)?;
3043
3044 let mut semantic = self.semantic;
3045 semantic.decl_bindings = decl_bindings;
3046 collect_plot_exprs(&plots, &figures, &layers, module_ctx, src, &mut semantic)?;
3047
3048 Ok(DagTIR {
3049 dag_id: self.dag_id,
3050 consts: self.consts,
3051 params: self.params,
3052 nodes: self.nodes,
3053 asserts,
3054 plots,
3055 figures,
3056 layers,
3057 included_plots,
3058 semantic,
3059 source_order,
3060 assert_names,
3061 assumes_map,
3062 expected_fail,
3063 resolved_decl_types: self.resolved_decl_types,
3064 domain_constraints: HashMap::new(), imported_values,
3066 imported_decl_types,
3067 imported_value_sources,
3068 pub_nodes: std::collections::HashSet::new(),
3069 })
3070 }
3071}
3072
3073pub fn resolved_to_declared_type(
3088 resolved: &ResolvedTypeExpr,
3089 src: &NamedSource<Arc<String>>,
3090) -> Result<crate::registry::declared_type::DeclaredType, GraphcalError> {
3091 use crate::registry::declared_type::{DeclaredType, StructTypeRef};
3092
3093 match resolved {
3094 ResolvedTypeExpr::Dimensionless => Ok(DeclaredType::Scalar(Dimension::dimensionless())),
3095 ResolvedTypeExpr::Bool => Ok(DeclaredType::Bool),
3096 ResolvedTypeExpr::Int => Ok(DeclaredType::Int),
3097 ResolvedTypeExpr::Datetime(scale) => Ok(DeclaredType::Datetime(*scale)),
3098 ResolvedTypeExpr::IndexArg(index) => Err(GraphcalError::EvalError {
3099 message: format!(
3100 "index `{}` cannot be used as a value type",
3101 format_resolved_index(index)
3102 ),
3103 src: src.clone(),
3104 span: resolved_type_expr_span(resolved).into(),
3105 }),
3106 ResolvedTypeExpr::Scalar(dim) => Ok(DeclaredType::Scalar(dim.clone())),
3107 ResolvedTypeExpr::Struct(name, _) => Ok(DeclaredType::Struct(
3108 StructTypeRef::from_resolved(name.clone()),
3109 vec![],
3110 )),
3111 ResolvedTypeExpr::GenericStruct {
3112 name, type_args, ..
3113 } => {
3114 let mut declared_args = Vec::with_capacity(type_args.len());
3115 for arg in type_args {
3116 declared_args.push(resolved_type_arg_to_declared_type(arg, src)?);
3117 }
3118 Ok(DeclaredType::Struct(
3119 StructTypeRef::from_resolved(name.clone()),
3120 declared_args,
3121 ))
3122 }
3123 ResolvedTypeExpr::GenericDimParam(name, span) => Err(GraphcalError::EvalError {
3124 message: format!("cannot use generic dimension parameter `{name}` as a concrete type"),
3125 src: src.clone(),
3126 span: (*span).into(),
3127 }),
3128 ResolvedTypeExpr::GenericTypeParam(name, span) => Err(GraphcalError::EvalError {
3129 message: format!("cannot use generic type parameter `{name}` as a concrete type"),
3130 src: src.clone(),
3131 span: (*span).into(),
3132 }),
3133 ResolvedTypeExpr::GenericDimExpr { span, .. } => Err(GraphcalError::EvalError {
3134 message: "cannot use generic dimension expression as a concrete type".to_string(),
3135 src: src.clone(),
3136 span: (*span).into(),
3137 }),
3138 ResolvedTypeExpr::Indexed { base, indexes } => {
3139 let mut result = resolved_to_declared_type(base, src)?;
3140 for idx in indexes.iter().rev() {
3141 match idx {
3142 ResolvedIndex::Concrete(name, _) => {
3143 result = DeclaredType::Indexed {
3144 element: Box::new(result),
3145 index: IndexTypeRef::from_resolved(name.clone()),
3146 };
3147 }
3148 ResolvedIndex::NatExpr(form, span) => {
3149 if !form.is_constant() {
3150 return Err(GraphcalError::EvalError {
3151 message: format!(
3152 "cannot use generic nat expression `{}` as a concrete type",
3153 form.format()
3154 ),
3155 src: src.clone(),
3156 span: (*span).into(),
3157 });
3158 }
3159 let nat_range =
3160 crate::registry::types::NatRangeIndex::try_from_u64(form.constant())
3161 .map_err(|err| GraphcalError::EvalError {
3162 message: err.to_string(),
3163 src: src.clone(),
3164 span: (*span).into(),
3165 })?;
3166 result = DeclaredType::Indexed {
3167 element: Box::new(result),
3168 index: IndexTypeRef::from_nat_range(nat_range),
3169 };
3170 }
3171 ResolvedIndex::GenericParam(name, span) => {
3172 return Err(GraphcalError::EvalError {
3173 message: format!(
3174 "cannot use generic index parameter `{name}` as a concrete type"
3175 ),
3176 src: src.clone(),
3177 span: (*span).into(),
3178 });
3179 }
3180 }
3181 }
3182 Ok(result)
3183 }
3184 }
3185}
3186
3187fn resolved_type_arg_to_declared_type(
3188 resolved: &ResolvedTypeExpr,
3189 src: &NamedSource<Arc<String>>,
3190) -> Result<crate::registry::declared_type::DeclaredType, GraphcalError> {
3191 match resolved {
3192 ResolvedTypeExpr::IndexArg(index) => resolved_index_to_declared_arg(index, src),
3193 _ => resolved_to_declared_type(resolved, src),
3194 }
3195}
3196
3197fn resolved_type_expr_span(resolved: &ResolvedTypeExpr) -> Span {
3198 match resolved {
3199 ResolvedTypeExpr::Dimensionless
3200 | ResolvedTypeExpr::Bool
3201 | ResolvedTypeExpr::Int
3202 | ResolvedTypeExpr::Datetime(_)
3203 | ResolvedTypeExpr::Scalar(_) => Span::new(0, 0),
3204 ResolvedTypeExpr::IndexArg(index) => resolved_index_span(index),
3205 ResolvedTypeExpr::Struct(_, span)
3206 | ResolvedTypeExpr::GenericDimParam(_, span)
3207 | ResolvedTypeExpr::GenericTypeParam(_, span)
3208 | ResolvedTypeExpr::GenericDimExpr { span, .. }
3209 | ResolvedTypeExpr::GenericStruct { span, .. } => *span,
3210 ResolvedTypeExpr::Indexed { base, .. } => resolved_type_expr_span(base),
3211 }
3212}
3213
3214const fn resolved_index_span(index: &ResolvedIndex) -> Span {
3215 match index {
3216 ResolvedIndex::Concrete(_, span)
3217 | ResolvedIndex::GenericParam(_, span)
3218 | ResolvedIndex::NatExpr(_, span) => *span,
3219 }
3220}
3221
3222fn resolved_index_to_declared_arg(
3223 index: &ResolvedIndex,
3224 src: &NamedSource<Arc<String>>,
3225) -> Result<crate::registry::declared_type::DeclaredType, GraphcalError> {
3226 let reference = match index {
3227 ResolvedIndex::Concrete(name, _) => IndexTypeRef::from_resolved(name.clone()),
3228 ResolvedIndex::NatExpr(form, span) => IndexTypeRef::from_nat_range_form(form.clone())
3229 .map_err(|err| GraphcalError::EvalError {
3230 message: err.to_string(),
3231 src: src.clone(),
3232 span: (*span).into(),
3233 })?,
3234 ResolvedIndex::GenericParam(name, span) => {
3235 return Err(GraphcalError::EvalError {
3236 message: format!("generic index parameter `{name}` is not bound"),
3237 src: src.clone(),
3238 span: (*span).into(),
3239 });
3240 }
3241 };
3242 Ok(crate::registry::declared_type::DeclaredType::IndexArg(
3243 reference,
3244 ))
3245}
3246
3247fn resolved_index_to_inferred(
3248 index: &ResolvedIndex,
3249 src: &NamedSource<Arc<String>>,
3250) -> Result<crate::tir::dim_check::InferredIndex, GraphcalError> {
3251 let reference = match index {
3252 ResolvedIndex::Concrete(name, _) => IndexTypeRef::from_resolved(name.clone()),
3253 ResolvedIndex::NatExpr(form, span) => IndexTypeRef::from_nat_range_form(form.clone())
3254 .map_err(|err| GraphcalError::EvalError {
3255 message: err.to_string(),
3256 src: src.clone(),
3257 span: (*span).into(),
3258 })?,
3259 ResolvedIndex::GenericParam(name, span) => {
3260 return Err(GraphcalError::EvalError {
3261 message: format!("generic index parameter `{name}` is not bound"),
3262 src: src.clone(),
3263 span: (*span).into(),
3264 });
3265 }
3266 };
3267 Ok(crate::tir::dim_check::InferredIndex::from_ref(reference))
3268}
3269
3270fn resolved_index_matches_inferred(
3271 expected: &ResolvedIndex,
3272 actual: &crate::tir::dim_check::InferredIndex,
3273) -> bool {
3274 match expected {
3275 ResolvedIndex::Concrete(name, _) => actual.matches_resolved(name),
3276 ResolvedIndex::GenericParam(_, _) => false,
3277 ResolvedIndex::NatExpr(form, _) => actual.nat_range_form().as_ref() == Some(form),
3278 }
3279}
3280
3281fn resolved_index_display_name(index: &ResolvedIndex) -> IndexName {
3282 match index {
3283 ResolvedIndex::Concrete(name, _) => name.to_unowned_def_name(),
3284 ResolvedIndex::GenericParam(name, _) => IndexName::from_atom(name.atom().clone()),
3285 ResolvedIndex::NatExpr(form, _) => IndexName::new(format!("range({})", form.format())),
3286 }
3287}
3288
3289fn unify_nat_poly_form(
3300 form: &NatPolyForm,
3301 target: u64,
3302 nat_sub: &mut HashMap<GenericParamName, u64>,
3303 actual_idx: &IndexName,
3304 src: &NamedSource<Arc<String>>,
3305 span: Span,
3306) -> Result<(), GraphcalError> {
3307 let mut reduced_constant: u64 = 0;
3310 let mut reduced_terms: BTreeMap<Monomial, u64> = BTreeMap::new();
3312
3313 let form_mismatch = || GraphcalError::IndexMismatch {
3317 expected: IndexName::new(format!("range({})", form.format())),
3318 found: actual_idx.clone(),
3319 src: src.clone(),
3320 span: span.into(),
3321 };
3322
3323 for (mono, coeff) in &form.terms {
3324 let (remaining_mono, factor) = mono.substitute(nat_sub).ok_or_else(form_mismatch)?;
3325 let term_value = coeff.checked_mul(factor).ok_or_else(form_mismatch)?;
3326 if remaining_mono.is_constant() {
3327 reduced_constant = reduced_constant
3328 .checked_add(term_value)
3329 .ok_or_else(form_mismatch)?;
3330 } else {
3331 let entry = reduced_terms.entry(remaining_mono).or_insert(0);
3332 *entry = entry.checked_add(term_value).ok_or_else(form_mismatch)?;
3333 }
3334 }
3335 reduced_terms.retain(|_, c| *c != 0);
3337
3338 if reduced_terms.is_empty() {
3339 if reduced_constant != target {
3341 let expected = match form.evaluate(nat_sub) {
3342 Some(n) => crate::registry::types::NatRangeIndex::try_from_u64(n)
3343 .map_err(|err| GraphcalError::EvalError {
3344 message: err.to_string(),
3345 src: src.clone(),
3346 span: span.into(),
3347 })?
3348 .display_name(),
3349 None => IndexName::new(format!("range({})", form.format())),
3350 };
3351 return Err(GraphcalError::IndexMismatch {
3352 expected,
3353 found: actual_idx.clone(),
3354 src: src.clone(),
3355 span: span.into(),
3356 });
3357 }
3358 return Ok(());
3359 }
3360
3361 let mut unbound_vars = std::collections::BTreeSet::new();
3363 for mono in reduced_terms.keys() {
3364 for var in mono.0.keys() {
3365 unbound_vars.insert(var.clone());
3366 }
3367 }
3368
3369 if let [var] = unbound_vars.iter().collect::<Vec<_>>().as_slice() {
3370 let var = (*var).clone();
3371 let all_linear = reduced_terms
3373 .keys()
3374 .all(|m| m.0.len() == 1 && m.0.get(&var) == Some(&1));
3375
3376 if all_linear {
3377 let total_coeff = reduced_terms
3379 .values()
3380 .try_fold(0u64, |acc, c| acc.checked_add(*c))
3381 .ok_or_else(form_mismatch)?;
3382 if target < reduced_constant {
3383 return Err(form_mismatch());
3384 }
3385 let remainder = target - reduced_constant;
3386 if total_coeff == 0 || !remainder.is_multiple_of(total_coeff) {
3387 return Err(form_mismatch());
3388 }
3389 let value = remainder / total_coeff;
3390 bind_or_check(nat_sub, var, value, |prev, _| {
3391 match crate::registry::types::NatRangeIndex::try_from_u64(*prev) {
3392 Ok(index) => GraphcalError::IndexMismatch {
3393 expected: index.display_name(),
3394 found: actual_idx.clone(),
3395 src: src.clone(),
3396 span: span.into(),
3397 },
3398 Err(err) => GraphcalError::EvalError {
3399 message: err.to_string(),
3400 src: src.clone(),
3401 span: span.into(),
3402 },
3403 }
3404 })?;
3405 return Ok(());
3406 }
3407 }
3408
3409 let var_names: Vec<&str> = unbound_vars.iter().map(GenericParamName::as_str).collect();
3411 Err(GraphcalError::EvalError {
3412 message: format!(
3413 "cannot infer Nat parameters [{}] from a single index — \
3414 provide more arguments or use explicit type annotations",
3415 var_names.join(", ")
3416 ),
3417 src: src.clone(),
3418 span: span.into(),
3419 })
3420}
3421
3422fn bind_or_check<K, V, E>(
3432 sub: &mut HashMap<K, V>,
3433 key: K,
3434 value: V,
3435 on_conflict: impl FnOnce(&V, &V) -> E,
3436) -> Result<(), E>
3437where
3438 K: Eq + std::hash::Hash,
3439 V: PartialEq,
3440{
3441 if let Some(prev) = sub.get(&key) {
3442 if *prev != value {
3443 return Err(on_conflict(prev, &value));
3444 }
3445 } else {
3446 sub.insert(key, value);
3447 }
3448 Ok(())
3449}
3450
3451#[expect(
3461 clippy::too_many_lines,
3462 reason = "complex generic unification requires many match arms"
3463)]
3464#[expect(
3465 clippy::implicit_hasher,
3466 reason = "always called with standard HashMap"
3467)]
3468#[expect(
3469 clippy::too_many_arguments,
3470 reason = "unification needs all substitution maps, registry, and source context"
3471)]
3472pub fn unify_resolved_type(
3473 resolved: &ResolvedTypeExpr,
3474 actual: &crate::tir::dim_check::InferredType,
3475 dim_sub: &mut HashMap<GenericParamName, Dimension>,
3476 index_sub: &mut HashMap<GenericParamName, IndexTypeRef>,
3477 nat_sub: &mut HashMap<GenericParamName, u64>,
3478 registry: &Registry,
3479 src: &NamedSource<Arc<String>>,
3480 span: Span,
3481) -> Result<(), GraphcalError> {
3482 use crate::tir::dim_check::InferredType;
3483
3484 match resolved {
3485 ResolvedTypeExpr::Indexed { base, indexes } => {
3486 let mut current = actual;
3489 for idx in indexes {
3490 let InferredType::Indexed {
3491 element,
3492 index: actual_idx,
3493 } = current
3494 else {
3495 return Err(GraphcalError::DimensionMismatch {
3496 expected: "indexed type".to_string(),
3497 found: crate::tir::dim_check::format_inferred_type(current, registry),
3498 help: "expected an indexed value".to_string(),
3499 src: src.clone(),
3500 span: span.into(),
3501 });
3502 };
3503 match idx {
3504 ResolvedIndex::GenericParam(gp, _) => {
3505 bind_or_check(
3506 index_sub,
3507 gp.clone(),
3508 actual_idx.type_ref().clone(),
3509 |prev, _| GraphcalError::IndexMismatch {
3510 expected: prev.display_name(),
3511 found: actual_idx.name(),
3512 src: src.clone(),
3513 span: span.into(),
3514 },
3515 )?;
3516 }
3517 ResolvedIndex::Concrete(name, _) => {
3518 if !actual_idx.matches_resolved(name) {
3519 return Err(GraphcalError::IndexMismatch {
3520 expected: name.to_unowned_def_name(),
3521 found: actual_idx.name(),
3522 src: src.clone(),
3523 span: span.into(),
3524 });
3525 }
3526 }
3527 ResolvedIndex::NatExpr(form, _) => {
3528 let actual_nat = actual_idx
3530 .nat_range_form()
3531 .filter(NatPolyForm::is_constant)
3532 .map(|actual_form| actual_form.constant())
3533 .ok_or_else(|| GraphcalError::IndexMismatch {
3534 expected: IndexName::new(format!("range({})", form.format())),
3535 found: actual_idx.name(),
3536 src: src.clone(),
3537 span: span.into(),
3538 })?;
3539 let actual_idx_name = actual_idx.name();
3541 unify_nat_poly_form(
3542 form,
3543 actual_nat,
3544 nat_sub,
3545 &actual_idx_name,
3546 src,
3547 span,
3548 )?;
3549 }
3550 }
3551 current = element;
3552 }
3553 unify_resolved_type(
3554 base, current, dim_sub, index_sub, nat_sub, registry, src, span,
3555 )
3556 }
3557
3558 ResolvedTypeExpr::Bool => {
3559 if *actual != InferredType::Bool {
3560 return Err(GraphcalError::DimensionMismatch {
3561 expected: "Bool".to_string(),
3562 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3563 help: "expected Bool argument".to_string(),
3564 src: src.clone(),
3565 span: span.into(),
3566 });
3567 }
3568 Ok(())
3569 }
3570
3571 ResolvedTypeExpr::Int => {
3572 if !actual.is_int_like() {
3573 return Err(GraphcalError::DimensionMismatch {
3574 expected: "Int".to_string(),
3575 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3576 help: "expected Int argument".to_string(),
3577 src: src.clone(),
3578 span: span.into(),
3579 });
3580 }
3581 Ok(())
3582 }
3583
3584 ResolvedTypeExpr::Datetime(expected_scale) => {
3585 if *actual != InferredType::Datetime(*expected_scale) {
3586 let expected_str = if expected_scale.is_utc() {
3587 "Datetime".to_string()
3588 } else {
3589 format!("Datetime<{expected_scale}>")
3590 };
3591 return Err(GraphcalError::DimensionMismatch {
3592 expected: expected_str,
3593 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3594 help: "expected Datetime argument".to_string(),
3595 src: src.clone(),
3596 span: span.into(),
3597 });
3598 }
3599 Ok(())
3600 }
3601
3602 ResolvedTypeExpr::IndexArg(expected_index) => {
3603 let InferredType::NamedIndex(actual_index) = actual else {
3604 return Err(GraphcalError::DimensionMismatch {
3605 expected: format!("index {}", format_resolved_index(expected_index)),
3606 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3607 help: "expected an index generic argument".to_string(),
3608 src: src.clone(),
3609 span: span.into(),
3610 });
3611 };
3612 if !resolved_index_matches_inferred(expected_index, actual_index) {
3613 return Err(GraphcalError::IndexMismatch {
3614 expected: resolved_index_display_name(expected_index),
3615 found: actual_index.name(),
3616 src: src.clone(),
3617 span: span.into(),
3618 });
3619 }
3620 Ok(())
3621 }
3622
3623 ResolvedTypeExpr::Dimensionless => {
3624 let actual_dim = crate::tir::dim_check::expect_scalar(actual, registry, src, span)?;
3625 if !actual_dim.is_dimensionless() {
3626 return Err(GraphcalError::DimensionMismatch {
3627 expected: "Dimensionless".to_string(),
3628 found: registry.dimensions.format_dimension(&actual_dim),
3629 help: "expected Dimensionless argument".to_string(),
3630 src: src.clone(),
3631 span: span.into(),
3632 });
3633 }
3634 Ok(())
3635 }
3636
3637 ResolvedTypeExpr::Scalar(expected_dim) => {
3638 let actual_dim = crate::tir::dim_check::expect_scalar(actual, registry, src, span)?;
3639 if *expected_dim != actual_dim {
3640 return Err(GraphcalError::DimensionMismatch {
3641 expected: registry.dimensions.format_dimension(expected_dim),
3642 found: registry.dimensions.format_dimension(&actual_dim),
3643 help: "dimension mismatch in function argument".to_string(),
3644 src: src.clone(),
3645 span: span.into(),
3646 });
3647 }
3648 Ok(())
3649 }
3650
3651 ResolvedTypeExpr::GenericStruct {
3652 name, type_args, ..
3653 } => {
3654 let InferredType::Struct(actual_name, actual_args) = actual else {
3657 return Err(GraphcalError::DimensionMismatch {
3658 expected: name.as_str().to_string(),
3659 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3660 help: format!("expected struct type `{}`", name.as_str()),
3661 src: src.clone(),
3662 span: span.into(),
3663 });
3664 };
3665 if actual_name.resolved() != name {
3666 return Err(GraphcalError::DimensionMismatch {
3667 expected: name.as_str().to_string(),
3668 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3669 help: format!("expected struct type `{}`", name.as_str()),
3670 src: src.clone(),
3671 span: span.into(),
3672 });
3673 }
3674 for (declared_arg, actual_arg) in type_args.iter().zip(actual_args) {
3679 unify_resolved_type(
3680 declared_arg,
3681 actual_arg,
3682 dim_sub,
3683 index_sub,
3684 nat_sub,
3685 registry,
3686 src,
3687 span,
3688 )?;
3689 }
3690 Ok(())
3691 }
3692
3693 ResolvedTypeExpr::Struct(name, _) => {
3694 let InferredType::Struct(actual_name, _) = actual else {
3697 return Err(GraphcalError::DimensionMismatch {
3698 expected: name.as_str().to_string(),
3699 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3700 help: format!("expected struct type `{}`", name.as_str()),
3701 src: src.clone(),
3702 span: span.into(),
3703 });
3704 };
3705 if actual_name.resolved() != name {
3706 return Err(GraphcalError::DimensionMismatch {
3707 expected: name.as_str().to_string(),
3708 found: crate::tir::dim_check::format_inferred_type(actual, registry),
3709 help: format!("expected struct type `{}`", name.as_str()),
3710 src: src.clone(),
3711 span: span.into(),
3712 });
3713 }
3714 Ok(())
3715 }
3716
3717 ResolvedTypeExpr::GenericDimParam(gp, _) => {
3718 let actual_dim = crate::tir::dim_check::expect_scalar(actual, registry, src, span)?;
3719 bind_or_check(dim_sub, gp.clone(), actual_dim, |prev, new| {
3720 GraphcalError::DimensionMismatch {
3721 expected: registry.dimensions.format_dimension(prev),
3722 found: registry.dimensions.format_dimension(new),
3723 help: format!(
3724 "generic `{gp}` was bound to {} but this argument requires {}",
3725 registry.dimensions.format_dimension(prev),
3726 registry.dimensions.format_dimension(new),
3727 ),
3728 src: src.clone(),
3729 span: span.into(),
3730 }
3731 })
3732 }
3733
3734 ResolvedTypeExpr::GenericTypeParam(gp, gp_span) => Err(GraphcalError::EvalError {
3735 message: format!(
3736 "cannot infer unconstrained generic type parameter `{gp}` in this position yet"
3737 ),
3738 src: src.clone(),
3739 span: (*gp_span).into(),
3740 }),
3741
3742 ResolvedTypeExpr::GenericDimExpr { terms, .. } => {
3743 let actual_dim = crate::tir::dim_check::expect_scalar(actual, registry, src, span)?;
3744
3745 if terms.len() == 1
3747 && let ResolvedDimTerm::GenericParam {
3748 name: gp,
3749 power,
3750 op: MulDivOp::Mul,
3751 ..
3752 } = &terms[0]
3753 {
3754 let bound_dim = if *power == Rational::ONE {
3755 actual_dim
3756 } else {
3757 let exponent = Rational::try_new(power.den(), power.num()).map_err(|_| {
3759 GraphcalError::InternalError {
3760 message: format!("generic dimension parameter `{gp}` has zero power"),
3761 src: src.clone(),
3762 span: span.into(),
3763 }
3764 })?;
3765 actual_dim
3766 .pow(exponent)
3767 .map_err(|_| GraphcalError::DimensionOverflow {
3768 src: src.clone(),
3769 span: span.into(),
3770 })?
3771 };
3772 bind_or_check(dim_sub, gp.clone(), bound_dim, |prev, new| {
3773 GraphcalError::DimensionMismatch {
3774 expected: registry.dimensions.format_dimension(prev),
3775 found: registry.dimensions.format_dimension(new),
3776 help: format!(
3777 "generic `{gp}` was bound to {} but this argument requires {}",
3778 registry.dimensions.format_dimension(prev),
3779 registry.dimensions.format_dimension(new),
3780 ),
3781 src: src.clone(),
3782 span: span.into(),
3783 }
3784 })?;
3785 return Ok(());
3786 }
3787
3788 let mut expected_dim = Dimension::dimensionless();
3790 for term in terms {
3791 let overflow_err = || GraphcalError::DimensionOverflow {
3792 src: src.clone(),
3793 span: span.into(),
3794 };
3795 let term_dim = match term {
3796 ResolvedDimTerm::Concrete { dim, power, .. } => {
3797 dim.pow(*power).map_err(|_| overflow_err())?
3798 }
3799 ResolvedDimTerm::GenericParam {
3800 name: gp, power, ..
3801 } => {
3802 if let Some(prev) = dim_sub.get(gp) {
3803 prev.pow(*power).map_err(|_| overflow_err())?
3804 } else {
3805 return Err(GraphcalError::DimensionMismatch {
3806 expected: format!("generic `{gp}` (unresolved)"),
3807 found: registry.dimensions.format_dimension(&actual_dim),
3808 help: format!(
3809 "generic `{gp}` could not be inferred from this argument"
3810 ),
3811 src: src.clone(),
3812 span: span.into(),
3813 });
3814 }
3815 }
3816 };
3817 expected_dim = match term.op() {
3818 MulDivOp::Mul => (expected_dim * term_dim).map_err(|_| overflow_err())?,
3819 MulDivOp::Div => (expected_dim / term_dim).map_err(|_| overflow_err())?,
3820 };
3821 }
3822
3823 if expected_dim != actual_dim {
3824 return Err(GraphcalError::DimensionMismatch {
3825 expected: registry.dimensions.format_dimension(&expected_dim),
3826 found: registry.dimensions.format_dimension(&actual_dim),
3827 help: "dimension mismatch in function argument".to_string(),
3828 src: src.clone(),
3829 span: span.into(),
3830 });
3831 }
3832 Ok(())
3833 }
3834 }
3835}
3836
3837#[expect(
3845 clippy::implicit_hasher,
3846 reason = "always called with standard HashMap"
3847)]
3848pub fn substitute_resolved_type(
3849 resolved: &ResolvedTypeExpr,
3850 dim_sub: &HashMap<GenericParamName, Dimension>,
3851 index_sub: &HashMap<GenericParamName, IndexTypeRef>,
3852 nat_sub: &HashMap<GenericParamName, u64>,
3853 src: &NamedSource<Arc<String>>,
3854) -> Result<crate::tir::dim_check::InferredType, GraphcalError> {
3855 let no_type_sub = HashMap::new();
3856 substitute_resolved_type_with_types(resolved, dim_sub, index_sub, nat_sub, &no_type_sub, src)
3857}
3858
3859#[expect(
3865 clippy::implicit_hasher,
3866 reason = "always called with standard HashMap"
3867)]
3868#[expect(
3869 clippy::too_many_lines,
3870 reason = "single dispatch over ResolvedTypeExpr variants with per-variant generic-substitution + dimension-arithmetic overflow handling"
3871)]
3872pub fn substitute_resolved_type_with_types(
3873 resolved: &ResolvedTypeExpr,
3874 dim_sub: &HashMap<GenericParamName, Dimension>,
3875 index_sub: &HashMap<GenericParamName, IndexTypeRef>,
3876 nat_sub: &HashMap<GenericParamName, u64>,
3877 type_sub: &HashMap<GenericParamName, crate::tir::dim_check::InferredType>,
3878 src: &NamedSource<Arc<String>>,
3879) -> Result<crate::tir::dim_check::InferredType, GraphcalError> {
3880 use crate::tir::dim_check::InferredType;
3881
3882 match resolved {
3883 ResolvedTypeExpr::Dimensionless => Ok(InferredType::Scalar(Dimension::dimensionless())),
3884 ResolvedTypeExpr::Bool => Ok(InferredType::Bool),
3885 ResolvedTypeExpr::Int => Ok(InferredType::Int),
3886 ResolvedTypeExpr::Datetime(scale) => Ok(InferredType::Datetime(*scale)),
3887 ResolvedTypeExpr::IndexArg(index) => {
3888 resolved_index_to_inferred(index, src).map(InferredType::NamedIndex)
3889 }
3890 ResolvedTypeExpr::Scalar(dim) => Ok(InferredType::Scalar(dim.clone())),
3891 ResolvedTypeExpr::Struct(name, _) => Ok(InferredType::Struct(
3892 crate::tir::dim_check::InferredStructType::from_resolved(name.clone()),
3893 vec![],
3894 )),
3895 ResolvedTypeExpr::GenericStruct {
3896 name, type_args, ..
3897 } => {
3898 let mut inferred_args = Vec::with_capacity(type_args.len());
3899 for arg in type_args {
3900 inferred_args.push(substitute_resolved_type_with_types(
3901 arg, dim_sub, index_sub, nat_sub, type_sub, src,
3902 )?);
3903 }
3904 Ok(InferredType::Struct(
3905 crate::tir::dim_check::InferredStructType::from_resolved(name.clone()),
3906 inferred_args,
3907 ))
3908 }
3909
3910 ResolvedTypeExpr::GenericDimParam(gp, span) => dim_sub.get(gp).map_or_else(
3911 || {
3912 Err(GraphcalError::EvalError {
3913 message: format!("generic `{gp}` not bound during substitution"),
3914 src: src.clone(),
3915 span: (*span).into(),
3916 })
3917 },
3918 |dim| Ok(InferredType::Scalar(dim.clone())),
3919 ),
3920
3921 ResolvedTypeExpr::GenericTypeParam(gp, span) => type_sub.get(gp).map_or_else(
3922 || {
3923 Err(GraphcalError::EvalError {
3924 message: format!("generic type parameter `{gp}` not bound during substitution"),
3925 src: src.clone(),
3926 span: (*span).into(),
3927 })
3928 },
3929 |ty| Ok(ty.clone()),
3930 ),
3931
3932 ResolvedTypeExpr::GenericDimExpr { terms, span } => {
3933 let overflow_err = || GraphcalError::DimensionOverflow {
3934 src: src.clone(),
3935 span: (*span).into(),
3936 };
3937 let mut result = Dimension::dimensionless();
3938 for term in terms {
3939 let term_dim = match term {
3940 ResolvedDimTerm::Concrete { dim, power, .. } => {
3941 dim.pow(*power).map_err(|_| overflow_err())?
3942 }
3943 ResolvedDimTerm::GenericParam {
3944 name: gp,
3945 power,
3946 span: term_span,
3947 ..
3948 } => {
3949 let base = dim_sub.get(gp).ok_or_else(|| GraphcalError::EvalError {
3950 message: format!("generic `{gp}` not bound during substitution"),
3951 src: src.clone(),
3952 span: (*term_span).into(),
3953 })?;
3954 base.pow(*power).map_err(|_| overflow_err())?
3955 }
3956 };
3957 result = match term.op() {
3958 MulDivOp::Mul => (result * term_dim).map_err(|_| overflow_err())?,
3959 MulDivOp::Div => (result / term_dim).map_err(|_| overflow_err())?,
3960 };
3961 }
3962 Ok(InferredType::Scalar(result))
3963 }
3964
3965 ResolvedTypeExpr::Indexed { base, indexes } => {
3966 let mut result = substitute_resolved_type_with_types(
3967 base, dim_sub, index_sub, nat_sub, type_sub, src,
3968 )?;
3969 for idx in indexes.iter().rev() {
3970 let resolved_idx = match idx {
3971 ResolvedIndex::Concrete(name, _) => {
3972 result = InferredType::Indexed {
3973 element: Box::new(result),
3974 index: crate::tir::dim_check::InferredIndex::from_resolved(
3975 name.clone(),
3976 ),
3977 };
3978 continue;
3979 }
3980 ResolvedIndex::GenericParam(gp, span) => {
3981 crate::tir::dim_check::InferredIndex::from_ref(
3982 index_sub
3983 .get(gp)
3984 .cloned()
3985 .ok_or_else(|| GraphcalError::EvalError {
3986 message: format!(
3987 "generic index `{gp}` not bound during substitution"
3988 ),
3989 src: src.clone(),
3990 span: (*span).into(),
3991 })?,
3992 )
3993 }
3994 ResolvedIndex::NatExpr(form, span) => {
3995 let n = form.evaluate(nat_sub).ok_or_else(|| {
3996 let vars = form.variables();
3997 let unbound: Vec<&str> = vars
3998 .iter()
3999 .filter(|k| !nat_sub.contains_key(*k))
4000 .map(GenericParamName::as_str)
4001 .collect();
4002 GraphcalError::EvalError {
4003 message: format!(
4004 "generic nat parameter(s) [{}] not bound during substitution",
4005 unbound.join(", ")
4006 ),
4007 src: src.clone(),
4008 span: (*span).into(),
4009 }
4010 })?;
4011 crate::tir::dim_check::InferredIndex::from_nat_range_form(
4012 NatPolyForm::from_constant(n),
4013 )
4014 .map_err(|err| GraphcalError::EvalError {
4015 message: err.to_string(),
4016 src: src.clone(),
4017 span: (*span).into(),
4018 })?
4019 }
4020 };
4021 result = InferredType::Indexed {
4022 element: Box::new(result),
4023 index: resolved_idx,
4024 };
4025 }
4026 Ok(result)
4027 }
4028 }
4029}
4030
4031fn require_local_type_level_path<'a>(
4040 path: &'a NamePath,
4041 span: Span,
4042 src: &NamedSource<Arc<String>>,
4043) -> Result<&'a str, GraphcalError> {
4044 path.as_bare()
4045 .map(super::super::syntax::names::NameAtom::as_str)
4046 .ok_or_else(|| GraphcalError::EvalError {
4047 message: format!(
4048 "qualified type-level reference `{path}` needs module-aware resolution"
4049 ),
4050 src: src.clone(),
4051 span: span.into(),
4052 })
4053}
4054
4055fn module_resolve_error(
4056 err: &ModuleResolveError,
4057 src: &NamedSource<Arc<String>>,
4058 span: Span,
4059) -> GraphcalError {
4060 GraphcalError::EvalError {
4061 message: err.to_string(),
4062 src: src.clone(),
4063 span: span.into(),
4064 }
4065}
4066
4067fn internal_error(message: String, src: &NamedSource<Arc<String>>, span: Span) -> GraphcalError {
4068 GraphcalError::InternalError {
4069 message,
4070 src: src.clone(),
4071 span: span.into(),
4072 }
4073}
4074
4075const fn module_lookup_is_absent(err: &ModuleResolveError) -> bool {
4076 matches!(err, ModuleResolveError::UnknownName { .. })
4077}
4078
4079fn type_lower_error_to_graphcal(
4080 err: &hir::HirLowerError,
4081 type_ann: &TypeExpr,
4082 src: &NamedSource<Arc<String>>,
4083) -> GraphcalError {
4084 if let hir::HirLowerError::UnknownTypePath { path, span } = err {
4085 if type_expr_has_index_name_at_span(type_ann, *span)
4086 && let Ok(name) = IndexName::try_new(path.clone())
4087 {
4088 return GraphcalError::UnknownIndex {
4089 name,
4090 src: src.clone(),
4091 span: (*span).into(),
4092 };
4093 }
4094 if type_expr_has_dim_term_at_span(type_ann, *span)
4095 && let Ok(name) = DimName::try_new(path.clone())
4096 {
4097 return GraphcalError::UnknownDimension {
4098 name,
4099 src: src.clone(),
4100 span: (*span).into(),
4101 };
4102 }
4103 }
4104 hir_lower_error_to_graphcal(err, src)
4105}
4106
4107fn type_expr_has_index_name_at_span(type_ann: &TypeExpr, span: Span) -> bool {
4108 match &type_ann.kind {
4109 TypeExprKind::Indexed { base, indexes } => {
4110 type_expr_has_index_name_at_span(base, span)
4111 || indexes.iter().any(|index| match index {
4112 crate::desugar::desugared_ast::IndexExpr::Name(name) => name.span == span,
4113 crate::desugar::desugared_ast::IndexExpr::NatExpr(_) => false,
4114 })
4115 }
4116 TypeExprKind::TypeApplication { type_args, .. }
4117 | TypeExprKind::DatetimeApplication { type_args } => type_args
4118 .iter()
4119 .any(|arg| type_expr_has_index_name_at_span(arg, span)),
4120 TypeExprKind::Dimensionless
4121 | TypeExprKind::Bool
4122 | TypeExprKind::Int
4123 | TypeExprKind::Datetime
4124 | TypeExprKind::DimExpr(_) => false,
4125 }
4126}
4127
4128fn type_expr_has_dim_term_at_span(type_ann: &TypeExpr, span: Span) -> bool {
4129 match &type_ann.kind {
4130 TypeExprKind::DimExpr(dim_expr) => dim_expr
4131 .terms
4132 .iter()
4133 .any(|item| item.term.name.span == span),
4134 TypeExprKind::Indexed { base, .. } => type_expr_has_dim_term_at_span(base, span),
4135 TypeExprKind::TypeApplication { type_args, .. }
4136 | TypeExprKind::DatetimeApplication { type_args } => type_args
4137 .iter()
4138 .any(|arg| type_expr_has_dim_term_at_span(arg, span)),
4139 TypeExprKind::Dimensionless
4140 | TypeExprKind::Bool
4141 | TypeExprKind::Int
4142 | TypeExprKind::Datetime => false,
4143 }
4144}
4145
4146#[derive(Clone, Copy)]
4147struct HirTypeResolutionContext<'a> {
4148 src: &'a NamedSource<Arc<String>>,
4149 resolver: &'a ModuleResolver,
4150 module_types: &'a ModuleTypeRegistry,
4151 registry: Option<&'a Registry>,
4152 prelude: &'a hir::PreludeTypeScope,
4153}
4154
4155pub fn resolve_hir_type_expr(
4163 type_ann: &hir::TypeExpr,
4164 _registry: &Registry,
4165 src: &NamedSource<Arc<String>>,
4166 module_ctx: ModuleTypeContext<'_>,
4167) -> Result<ResolvedTypeExpr, GraphcalError> {
4168 let prelude = hir::PreludeTypeScope::graphcal();
4169 let ctx = HirTypeResolutionContext {
4170 src,
4171 resolver: module_ctx.resolver,
4172 module_types: module_ctx.types,
4173 registry: None,
4174 prelude: &prelude,
4175 };
4176 resolve_hir_type_expr_inner(type_ann, ctx)
4177}
4178
4179fn resolve_ast_type_expr_via_hir(
4180 type_ann: &TypeExpr,
4181 registry: &Registry,
4182 src: &NamedSource<Arc<String>>,
4183 module_ctx: ModuleTypeContext<'_>,
4184) -> Result<ResolvedTypeExpr, GraphcalError> {
4185 let generic_scope = hir::GenericScope::new();
4186 let prelude = hir::PreludeTypeScope::graphcal();
4187 let lower_ctx =
4188 hir::TypeLoweringContext::new(module_ctx.owner, module_ctx.resolver, &generic_scope)
4189 .with_prelude(&prelude);
4190 let hir_type = hir::lower_type_expr(type_ann, lower_ctx)
4191 .map_err(|err| type_lower_error_to_graphcal(&err, type_ann, src))?;
4192 let resolve_ctx = HirTypeResolutionContext {
4193 src,
4194 resolver: module_ctx.resolver,
4195 module_types: module_ctx.types,
4196 registry: Some(registry),
4197 prelude: &prelude,
4198 };
4199 resolve_hir_type_expr_inner(&hir_type, resolve_ctx)
4200}
4201
4202fn resolve_hir_type_expr_inner(
4203 type_ann: &hir::TypeExpr,
4204 ctx: HirTypeResolutionContext<'_>,
4205) -> Result<ResolvedTypeExpr, GraphcalError> {
4206 match &type_ann.kind {
4207 hir::TypeExprKind::Builtin(builtin) => Ok(resolve_hir_builtin_type(*builtin)),
4208 hir::TypeExprKind::DimExpr(dim_expr) => resolve_hir_dim_expr(dim_expr, ctx),
4209 hir::TypeExprKind::Index(index) => Err(GraphcalError::EvalError {
4210 message: format!(
4211 "index `{}` cannot be used as a type",
4212 format_hir_index_ref(index)
4213 ),
4214 src: ctx.src.clone(),
4215 span: hir_index_ref_span(index).into(),
4216 }),
4217 hir::TypeExprKind::Struct(name) => {
4218 hir_struct_type_def(&name.value, name.span, ctx)?;
4219 Ok(ResolvedTypeExpr::Struct(name.value.clone(), name.span))
4220 }
4221 hir::TypeExprKind::GenericTypeParam(param) => Ok(ResolvedTypeExpr::GenericTypeParam(
4222 param.value.name.clone(),
4223 param.span,
4224 )),
4225 hir::TypeExprKind::TypeApplication { name, type_args } => {
4226 resolve_hir_type_application(type_ann, name, type_args, ctx)
4227 }
4228 hir::TypeExprKind::Indexed { base, indexes } => {
4229 let resolved_base = resolve_hir_type_expr_inner(base, ctx)?;
4230 let resolved_indexes = indexes
4231 .iter()
4232 .map(|index| resolve_hir_index_ref(index, ctx))
4233 .collect::<Result<Vec<_>, _>>()?;
4234 Ok(ResolvedTypeExpr::Indexed {
4235 base: Box::new(resolved_base),
4236 indexes: resolved_indexes,
4237 })
4238 }
4239 }
4240}
4241
4242const fn hir_index_ref_span(index: &hir::IndexRef) -> Span {
4243 match index {
4244 hir::IndexRef::Concrete(name) => name.span,
4245 hir::IndexRef::GenericParam(param) => param.span,
4246 hir::IndexRef::NatExpr(nat_expr) => nat_expr.span(),
4247 }
4248}
4249
4250fn format_hir_index_ref(index: &hir::IndexRef) -> String {
4251 match index {
4252 hir::IndexRef::Concrete(name) => name.value.as_str().to_string(),
4253 hir::IndexRef::GenericParam(param) => param.value.name.to_string(),
4254 hir::IndexRef::NatExpr(nat_expr) => format!("range({})", format_hir_nat_expr(nat_expr)),
4255 }
4256}
4257
4258fn format_hir_nat_expr(nat_expr: &hir::NatExpr) -> String {
4259 match nat_expr {
4260 hir::NatExpr::Literal(n, _) => n.to_string(),
4261 hir::NatExpr::Param(param) => param.value.name.to_string(),
4262 hir::NatExpr::Add(lhs, rhs, _) => {
4263 format!(
4264 "{} + {}",
4265 format_hir_nat_expr(lhs),
4266 format_hir_nat_expr(rhs)
4267 )
4268 }
4269 hir::NatExpr::Mul(lhs, rhs, _) => {
4270 format!(
4271 "{} * {}",
4272 format_hir_nat_expr(lhs),
4273 format_hir_nat_expr(rhs)
4274 )
4275 }
4276 }
4277}
4278
4279const fn resolve_hir_builtin_type(builtin: hir::BuiltinType) -> ResolvedTypeExpr {
4280 match builtin {
4281 hir::BuiltinType::Dimensionless => ResolvedTypeExpr::Dimensionless,
4282 hir::BuiltinType::Bool => ResolvedTypeExpr::Bool,
4283 hir::BuiltinType::Int => ResolvedTypeExpr::Int,
4284 hir::BuiltinType::Datetime(scale) => ResolvedTypeExpr::Datetime(scale.scale()),
4285 }
4286}
4287
4288fn hir_dimension(
4289 name: &ResolvedName<namespace::Dim>,
4290 span: Span,
4291 ctx: HirTypeResolutionContext<'_>,
4292) -> Result<Dimension, GraphcalError> {
4293 ctx.module_types
4294 .get_dimension(name)
4295 .cloned()
4296 .or_else(|| {
4297 ctx.registry.and_then(|registry| {
4298 registry
4299 .dimensions
4300 .get_dimension(name.to_unowned_def_name().as_str())
4301 .cloned()
4302 })
4303 })
4304 .ok_or_else(|| GraphcalError::UnknownDimension {
4305 name: name.to_unowned_def_name(),
4306 src: ctx.src.clone(),
4307 span: span.into(),
4308 })
4309}
4310
4311fn hir_index_name(
4312 name: &ResolvedName<namespace::Index>,
4313 span: Span,
4314 ctx: HirTypeResolutionContext<'_>,
4315) -> Result<IndexName, GraphcalError> {
4316 if ctx.module_types.get_index(name).is_some() {
4317 Ok(name.to_unowned_def_name())
4318 } else {
4319 Err(GraphcalError::UnknownIndex {
4320 name: name.to_unowned_def_name(),
4321 src: ctx.src.clone(),
4322 span: span.into(),
4323 })
4324 }
4325}
4326
4327fn hir_struct_type_def<'a>(
4328 name: &ResolvedName<namespace::StructType>,
4329 span: Span,
4330 ctx: HirTypeResolutionContext<'a>,
4331) -> Result<&'a TypeDef, GraphcalError> {
4332 ctx.module_types
4333 .get_struct_type(name)
4334 .ok_or_else(|| GraphcalError::UnknownStructType {
4335 name: name.to_string(),
4336 src: ctx.src.clone(),
4337 span: span.into(),
4338 })
4339}
4340
4341fn resolve_hir_dim_expr(
4342 dim_expr: &hir::DimExpr,
4343 ctx: HirTypeResolutionContext<'_>,
4344) -> Result<ResolvedTypeExpr, GraphcalError> {
4345 let terms = dim_expr
4346 .terms
4347 .iter()
4348 .map(|item| resolve_hir_dim_expr_item(item, ctx))
4349 .collect::<Result<Vec<_>, _>>()?;
4350
4351 if let [
4352 ResolvedDimTerm::GenericParam {
4353 name,
4354 power,
4355 op: MulDivOp::Mul,
4356 span,
4357 },
4358 ] = terms.as_slice()
4359 && *power == Rational::ONE
4360 {
4361 return Ok(ResolvedTypeExpr::GenericDimParam(name.clone(), *span));
4362 }
4363
4364 let has_generic = terms
4365 .iter()
4366 .any(|term| matches!(term, ResolvedDimTerm::GenericParam { .. }));
4367 if has_generic {
4368 return Ok(ResolvedTypeExpr::GenericDimExpr {
4369 terms,
4370 span: dim_expr.span,
4371 });
4372 }
4373
4374 let result = terms.iter().try_fold(
4375 Dimension::dimensionless(),
4376 |acc, term| -> Result<Dimension, GraphcalError> {
4377 let ResolvedDimTerm::Concrete { dim, power, op } = term else {
4378 return Err(GraphcalError::InternalError {
4379 message: "generic dimension term reached concrete dimension folding"
4380 .to_string(),
4381 src: ctx.src.clone(),
4382 span: dim_expr.span.into(),
4383 });
4384 };
4385 let overflow_err = || GraphcalError::DimensionOverflow {
4386 src: ctx.src.clone(),
4387 span: dim_expr.span.into(),
4388 };
4389 let powered = dim.pow(*power).map_err(|_| overflow_err())?;
4390 match op {
4391 MulDivOp::Mul => (acc * powered).map_err(|_| overflow_err()),
4392 MulDivOp::Div => (acc / powered).map_err(|_| overflow_err()),
4393 }
4394 },
4395 )?;
4396 Ok(ResolvedTypeExpr::Scalar(result))
4397}
4398
4399fn resolve_hir_dim_expr_item(
4400 item: &hir::DimExprItem,
4401 ctx: HirTypeResolutionContext<'_>,
4402) -> Result<ResolvedDimTerm, GraphcalError> {
4403 let power = item.term.power.unwrap_or(Rational::ONE);
4404 match &item.term.target {
4405 hir::DimTermTarget::Dimension(name) => Ok(ResolvedDimTerm::Concrete {
4406 dim: hir_dimension(&name.value, name.span, ctx)?,
4407 power,
4408 op: item.op,
4409 }),
4410 hir::DimTermTarget::GenericParam(param) => Ok(ResolvedDimTerm::GenericParam {
4411 name: param.value.name.clone(),
4412 power,
4413 op: item.op,
4414 span: item.term.span,
4415 }),
4416 }
4417}
4418
4419fn resolve_hir_index_ref(
4420 index: &hir::IndexRef,
4421 ctx: HirTypeResolutionContext<'_>,
4422) -> Result<ResolvedIndex, GraphcalError> {
4423 match index {
4424 hir::IndexRef::Concrete(name) => {
4425 hir_index_name(&name.value, name.span, ctx)?;
4426 Ok(ResolvedIndex::Concrete(name.value.clone(), name.span))
4427 }
4428 hir::IndexRef::GenericParam(param) => Ok(ResolvedIndex::GenericParam(
4429 param.value.name.clone(),
4430 param.span,
4431 )),
4432 hir::IndexRef::NatExpr(nat_expr) => Ok(ResolvedIndex::NatExpr(
4433 normalize_hir_nat_expr(nat_expr)
4434 .map_err(|err| nat_overflow_error(err, ctx.src, nat_expr.span()))?,
4435 nat_expr.span(),
4436 )),
4437 }
4438}
4439
4440fn normalize_hir_nat_expr(
4441 expr: &hir::NatExpr,
4442) -> Result<NatPolyForm, crate::syntax::nat::NatOverflowError> {
4443 match expr {
4444 hir::NatExpr::Literal(value, _) => Ok(NatPolyForm::from_constant(*value)),
4445 hir::NatExpr::Param(param) => Ok(NatPolyForm::from_var(param.value.name.clone())),
4446 hir::NatExpr::Add(lhs, rhs, _) => {
4447 normalize_hir_nat_expr(lhs)?.add(&normalize_hir_nat_expr(rhs)?)
4448 }
4449 hir::NatExpr::Mul(lhs, rhs, _) => {
4450 normalize_hir_nat_expr(lhs)?.mul(&normalize_hir_nat_expr(rhs)?)
4451 }
4452 }
4453}
4454
4455fn check_type_application_arity(
4459 type_name: &str,
4460 type_def: &TypeDef,
4461 arg_count: usize,
4462 span: Span,
4463 src: &NamedSource<Arc<String>>,
4464) -> Result<(), GraphcalError> {
4465 let total_params = type_def.generic_params.len();
4466 let required_count = type_def
4467 .generic_params
4468 .iter()
4469 .take_while(|p| p.default.is_none())
4470 .count();
4471 if arg_count < required_count || arg_count > total_params {
4472 let hint = if required_count == total_params {
4473 format!("{total_params}")
4474 } else {
4475 format!("{required_count}..{total_params}")
4476 };
4477 return Err(GraphcalError::EvalError {
4478 message: format!("type `{type_name}` expects {hint} type argument(s), got {arg_count}"),
4479 src: src.clone(),
4480 span: span.into(),
4481 });
4482 }
4483 Ok(())
4484}
4485
4486fn resolve_hir_type_application(
4487 type_ann: &hir::TypeExpr,
4488 name: &crate::syntax::span::Spanned<ResolvedName<namespace::StructType>>,
4489 type_args: &[hir::TypeExpr],
4490 ctx: HirTypeResolutionContext<'_>,
4491) -> Result<ResolvedTypeExpr, GraphcalError> {
4492 let type_def = hir_struct_type_def(&name.value, name.span, ctx)?;
4493 check_type_application_arity(
4494 name.value.as_str(),
4495 type_def,
4496 type_args.len(),
4497 type_ann.span,
4498 ctx.src,
4499 )?;
4500
4501 let mut resolved_args = Vec::with_capacity(type_def.generic_params.len());
4502 for (param, arg) in type_def.generic_params.iter().zip(type_args) {
4503 resolved_args.push(resolve_hir_type_arg_for_param(param, arg, ctx)?);
4504 }
4505
4506 for param in type_def.generic_params.iter().skip(type_args.len()) {
4507 let default_expr = param
4508 .default
4509 .as_ref()
4510 .ok_or_else(|| GraphcalError::EvalError {
4511 message: format!(
4512 "internal: generic parameter `{}` has no default",
4513 param.name
4514 ),
4515 src: ctx.src.clone(),
4516 span: type_ann.span.into(),
4517 })?;
4518 let default_hir = lower_type_generic_default(default_expr, &name.value, type_def, ctx)?;
4519 resolved_args.push(resolve_hir_type_arg_for_param(param, &default_hir, ctx)?);
4520 }
4521
4522 Ok(ResolvedTypeExpr::GenericStruct {
4523 name: name.value.clone(),
4524 type_args: resolved_args,
4525 span: type_ann.span,
4526 })
4527}
4528
4529fn resolve_hir_type_arg_for_param(
4530 param: &crate::registry::types::TypeGenericParam,
4531 arg: &hir::TypeExpr,
4532 ctx: HirTypeResolutionContext<'_>,
4533) -> Result<ResolvedTypeExpr, GraphcalError> {
4534 match param.constraint {
4535 TypeGenericConstraint::Index => match &arg.kind {
4536 hir::TypeExprKind::Index(index) => {
4537 resolve_hir_index_ref(index, ctx).map(ResolvedTypeExpr::IndexArg)
4538 }
4539 _ => Err(GraphcalError::EvalError {
4540 message: format!(
4541 "generic parameter `{}` expects an Index argument",
4542 param.name
4543 ),
4544 src: ctx.src.clone(),
4545 span: arg.span.into(),
4546 }),
4547 },
4548 TypeGenericConstraint::Nat => Err(GraphcalError::EvalError {
4549 message: format!(
4550 "generic parameter `{}` expects a Nat argument, got a type argument",
4551 param.name
4552 ),
4553 src: ctx.src.clone(),
4554 span: arg.span.into(),
4555 }),
4556 TypeGenericConstraint::Dim | TypeGenericConstraint::Unconstrained => {
4557 resolve_hir_type_expr_inner(arg, ctx)
4558 }
4559 }
4560}
4561
4562fn lower_type_generic_default(
4563 default_expr: &TypeExpr,
4564 type_owner: &ResolvedName<namespace::StructType>,
4565 type_def: &TypeDef,
4566 ctx: HirTypeResolutionContext<'_>,
4567) -> Result<hir::TypeExpr, GraphcalError> {
4568 let mut scope = hir::GenericScope::new();
4569 for param in &type_def.generic_params {
4570 let constraint = match param.constraint {
4571 TypeGenericConstraint::Dim => crate::syntax::ast::GenericConstraint::Dim,
4572 TypeGenericConstraint::Index => crate::syntax::ast::GenericConstraint::Index,
4573 TypeGenericConstraint::Nat => crate::syntax::ast::GenericConstraint::Nat,
4574 TypeGenericConstraint::Unconstrained => crate::syntax::ast::GenericConstraint::Type,
4575 };
4576 let id = hir::GenericParamId::new(
4577 hir::GenericParamOwner::Type(type_owner.clone()),
4578 param.name.clone(),
4579 );
4580 scope
4581 .insert_binding(hir::GenericParamBinding::new(
4582 id,
4583 constraint,
4584 default_expr.span,
4585 ))
4586 .map_err(|err| hir_lower_error_to_graphcal(&err, ctx.src))?;
4587 }
4588
4589 let lower_ctx = hir::TypeLoweringContext::new(type_owner.owner(), ctx.resolver, &scope)
4590 .with_prelude(ctx.prelude);
4591 hir::lower_type_expr(default_expr, lower_ctx)
4592 .map_err(|err| hir_lower_error_to_graphcal(&err, ctx.src))
4593}
4594
4595#[expect(
4596 clippy::too_many_arguments,
4597 reason = "resolves one AST index path against generic params, local registry, and module context"
4598)]
4599fn resolve_index_expr_name(
4600 path: &NamePath,
4601 span: Span,
4602 registry: &Registry,
4603 owner: &crate::dag_id::DagId,
4604 index_params: &[GenericParamName],
4605 nat_params: &[GenericParamName],
4606 src: &NamedSource<Arc<String>>,
4607 module_ctx: Option<ModuleTypeContext<'_>>,
4608) -> Result<ResolvedIndex, GraphcalError> {
4609 if let Some(atom) = path.as_bare() {
4610 let text = atom.as_str();
4611 if let Some(gp) = nat_params.iter().find(|p| p.as_str() == text) {
4612 return Ok(ResolvedIndex::NatExpr(
4613 NatPolyForm::from_var(gp.clone()),
4614 span,
4615 ));
4616 }
4617 if let Some(gp) = index_params.iter().find(|p| p.as_str() == text) {
4618 return Ok(ResolvedIndex::GenericParam(gp.clone(), span));
4619 }
4620 }
4621
4622 if let Some(ctx) = module_ctx {
4623 match ctx.resolver.resolve_index_path(ctx.owner, path) {
4624 Ok(resolved) => {
4625 if ctx.types.get_index(&resolved).is_some() {
4626 return Ok(ResolvedIndex::Concrete(resolved, span));
4627 }
4628 return Err(GraphcalError::UnknownIndex {
4629 name: resolved.to_unowned_def_name(),
4630 src: src.clone(),
4631 span: span.into(),
4632 });
4633 }
4634 Err(err) if path.is_bare() && module_lookup_is_absent(&err) => {}
4635 Err(err) => return Err(module_resolve_error(&err, src, span)),
4636 }
4637 }
4638
4639 let text = require_local_type_level_path(path, span, src)?;
4640 if registry.indexes.get_index(text).is_some() {
4641 Ok(ResolvedIndex::Concrete(
4642 ResolvedName::from_def(owner.clone(), IndexName::new(text)),
4643 span,
4644 ))
4645 } else {
4646 Err(GraphcalError::UnknownIndex {
4647 name: IndexName::new(text),
4648 src: src.clone(),
4649 span: span.into(),
4650 })
4651 }
4652}
4653
4654fn resolve_concrete_index_path(
4655 path: &NamePath,
4656 span: Span,
4657 registry: &Registry,
4658 owner: &crate::dag_id::DagId,
4659 src: &NamedSource<Arc<String>>,
4660 module_ctx: Option<ModuleTypeContext<'_>>,
4661) -> Result<Option<ResolvedName<namespace::Index>>, GraphcalError> {
4662 if let Some(ctx) = module_ctx {
4663 match ctx.resolver.resolve_index_path(ctx.owner, path) {
4664 Ok(resolved) => {
4665 let Some(index) = ctx.types.get_index(&resolved) else {
4666 return Err(GraphcalError::UnknownIndex {
4667 name: resolved.to_unowned_def_name(),
4668 src: src.clone(),
4669 span: span.into(),
4670 });
4671 };
4672 if matches!(
4673 index.kind,
4674 crate::registry::types::IndexKind::Named { .. }
4675 | crate::registry::types::IndexKind::RequiredNamed
4676 ) {
4677 return Ok(Some(resolved));
4678 }
4679 return Ok(None);
4680 }
4681 Err(err) if module_lookup_is_absent(&err) => {}
4682 Err(_) if path.is_bare() => {
4683 }
4686 Err(err) => return Err(module_resolve_error(&err, src, span)),
4687 }
4688 }
4689
4690 let Some(atom) = path.as_bare() else {
4691 return Ok(None);
4692 };
4693 let Some(index) = registry.indexes.get_index(atom.as_str()) else {
4694 return Ok(None);
4695 };
4696 Ok(matches!(
4697 index.kind,
4698 crate::registry::types::IndexKind::Named { .. }
4699 | crate::registry::types::IndexKind::RequiredNamed
4700 )
4701 .then(|| ResolvedName::from_def(owner.clone(), IndexName::from_atom(atom.clone()))))
4702}
4703
4704type ResolvedStructTypeLookup<'a> = Option<(ResolvedName<namespace::StructType>, &'a TypeDef)>;
4705
4706fn resolve_struct_type_path<'a>(
4707 path: &NamePath,
4708 span: Span,
4709 registry: &'a Registry,
4710 owner: &crate::dag_id::DagId,
4711 src: &NamedSource<Arc<String>>,
4712 module_ctx: Option<ModuleTypeContext<'a>>,
4713) -> Result<ResolvedStructTypeLookup<'a>, GraphcalError> {
4714 if let Some(ctx) = module_ctx {
4715 match ctx.resolver.resolve_struct_type_path(ctx.owner, path) {
4716 Ok(resolved) => {
4717 if let Some(type_def) = ctx.types.get_struct_type(&resolved) {
4718 return Ok(Some((resolved, type_def)));
4719 }
4720 return Err(GraphcalError::UnknownStructType {
4721 name: resolved.to_string(),
4722 src: src.clone(),
4723 span: span.into(),
4724 });
4725 }
4726 Err(err) if module_lookup_is_absent(&err) => {}
4727 Err(_err) if path.is_bare() => {}
4728 Err(err) => return Err(module_resolve_error(&err, src, span)),
4729 }
4730 }
4731
4732 let Some(atom) = path.as_bare() else {
4733 return Ok(None);
4734 };
4735 Ok(registry.types.get_type(atom.as_str()).map(|type_def| {
4736 (
4737 ResolvedName::from_def(owner.clone(), StructTypeName::from_atom(atom.clone())),
4738 type_def,
4739 )
4740 }))
4741}
4742
4743fn resolve_dimension_path(
4744 path: &NamePath,
4745 span: Span,
4746 registry: &Registry,
4747 src: &NamedSource<Arc<String>>,
4748 module_ctx: Option<ModuleTypeContext<'_>>,
4749) -> Result<Option<Dimension>, GraphcalError> {
4750 if let Some(ctx) = module_ctx {
4751 match ctx.resolver.resolve_dimension_path(ctx.owner, path) {
4752 Ok(resolved) => {
4753 return ctx
4754 .types
4755 .get_dimension(&resolved)
4756 .cloned()
4757 .map(Some)
4758 .ok_or_else(|| GraphcalError::UnknownDimension {
4759 name: resolved.to_unowned_def_name(),
4760 src: src.clone(),
4761 span: span.into(),
4762 });
4763 }
4764 Err(err) if path.is_bare() && module_lookup_is_absent(&err) => {}
4765 Err(err) => return Err(module_resolve_error(&err, src, span)),
4766 }
4767 }
4768
4769 let text = require_local_type_level_path(path, span, src)?;
4770 Ok(registry.dimensions.get_dimension(text).cloned())
4771}
4772
4773pub fn resolve_type_expr(
4783 type_ann: &TypeExpr,
4784 registry: &Registry,
4785 dim_params: &[GenericParamName],
4786 index_params: &[GenericParamName],
4787 nat_params: &[GenericParamName],
4788 src: &NamedSource<Arc<String>>,
4789) -> Result<ResolvedTypeExpr, GraphcalError> {
4790 let owner = crate::dag_id::DagId::root("<type-resolution>");
4791 resolve_type_expr_inner(
4792 type_ann,
4793 registry,
4794 &owner,
4795 dim_params,
4796 index_params,
4797 nat_params,
4798 src,
4799 None,
4800 )
4801}
4802
4803pub fn resolve_type_expr_with_modules(
4805 type_ann: &TypeExpr,
4806 registry: &Registry,
4807 dim_params: &[GenericParamName],
4808 index_params: &[GenericParamName],
4809 nat_params: &[GenericParamName],
4810 src: &NamedSource<Arc<String>>,
4811 module_ctx: ModuleTypeContext<'_>,
4812) -> Result<ResolvedTypeExpr, GraphcalError> {
4813 resolve_type_expr_inner(
4814 type_ann,
4815 registry,
4816 module_ctx.owner,
4817 dim_params,
4818 index_params,
4819 nat_params,
4820 src,
4821 Some(module_ctx),
4822 )
4823}
4824
4825#[expect(
4826 clippy::too_many_arguments,
4827 reason = "recursive resolver threads generic parameter scopes and optional module context"
4828)]
4829fn resolve_type_expr_inner(
4830 type_ann: &TypeExpr,
4831 registry: &Registry,
4832 owner: &crate::dag_id::DagId,
4833 dim_params: &[GenericParamName],
4834 index_params: &[GenericParamName],
4835 nat_params: &[GenericParamName],
4836 src: &NamedSource<Arc<String>>,
4837 module_ctx: Option<ModuleTypeContext<'_>>,
4838) -> Result<ResolvedTypeExpr, GraphcalError> {
4839 if let Some(ctx) = module_ctx
4840 && dim_params.is_empty()
4841 && index_params.is_empty()
4842 && nat_params.is_empty()
4843 {
4844 return resolve_ast_type_expr_via_hir(type_ann, registry, src, ctx);
4845 }
4846
4847 match &type_ann.kind {
4848 TypeExprKind::Dimensionless => Ok(ResolvedTypeExpr::Dimensionless),
4849 TypeExprKind::Bool => Ok(ResolvedTypeExpr::Bool),
4850 TypeExprKind::Int => Ok(ResolvedTypeExpr::Int),
4851 TypeExprKind::Datetime => Ok(ResolvedTypeExpr::Datetime(TimeScale::UTC)),
4852 TypeExprKind::DatetimeApplication { type_args } => {
4853 resolve_datetime_application(type_ann, type_args, src)
4854 }
4855
4856 TypeExprKind::Indexed { base, indexes } => {
4857 let resolved_base = resolve_type_expr_inner(
4858 base,
4859 registry,
4860 owner,
4861 dim_params,
4862 index_params,
4863 nat_params,
4864 src,
4865 module_ctx,
4866 )?;
4867 let mut resolved_indexes = Vec::with_capacity(indexes.len());
4868 for idx in indexes {
4869 match idx {
4870 crate::desugar::desugared_ast::IndexExpr::NatExpr(nat_expr) => {
4871 let form = normalize_nat_expr(nat_expr, nat_params, src)?;
4872 resolved_indexes.push(ResolvedIndex::NatExpr(form, nat_expr.span()));
4873 }
4874 crate::desugar::desugared_ast::IndexExpr::Name(path) => {
4875 resolved_indexes.push(resolve_index_expr_name(
4876 &path.value,
4877 path.span,
4878 registry,
4879 owner,
4880 index_params,
4881 nat_params,
4882 src,
4883 module_ctx,
4884 )?);
4885 }
4886 }
4887 }
4888 Ok(ResolvedTypeExpr::Indexed {
4889 base: Box::new(resolved_base),
4890 indexes: resolved_indexes,
4891 })
4892 }
4893
4894 TypeExprKind::DimExpr(dim_expr) => resolve_dim_expr(
4895 dim_expr,
4896 registry,
4897 owner,
4898 dim_params,
4899 index_params,
4900 src,
4901 module_ctx,
4902 ),
4903
4904 TypeExprKind::TypeApplication { name, type_args } => resolve_type_application(
4905 type_ann,
4906 name,
4907 type_args,
4908 registry,
4909 owner,
4910 dim_params,
4911 index_params,
4912 nat_params,
4913 src,
4914 module_ctx,
4915 ),
4916 }
4917}
4918
4919fn resolve_dim_expr(
4927 dim_expr: &crate::desugar::desugared_ast::DimExpr,
4928 registry: &Registry,
4929 owner: &crate::dag_id::DagId,
4930 dim_params: &[GenericParamName],
4931 index_params: &[GenericParamName],
4932 src: &NamedSource<Arc<String>>,
4933 module_ctx: Option<ModuleTypeContext<'_>>,
4934) -> Result<ResolvedTypeExpr, GraphcalError> {
4935 if dim_expr.terms.len() == 1 && dim_expr.terms[0].term.power.is_none() {
4938 let term = &dim_expr.terms[0].term;
4939 if let Some(index) = resolve_concrete_index_path(
4940 &term.name.value,
4941 term.name.span,
4942 registry,
4943 owner,
4944 src,
4945 module_ctx,
4946 )? {
4947 return Ok(ResolvedTypeExpr::IndexArg(ResolvedIndex::Concrete(
4948 index, term.span,
4949 )));
4950 }
4951 if let Some(atom) = term.name.value.as_bare()
4952 && let Some(gp) = index_params.iter().find(|p| p.as_str() == atom.as_str())
4953 {
4954 return Ok(ResolvedTypeExpr::IndexArg(ResolvedIndex::GenericParam(
4955 gp.clone(),
4956 term.span,
4957 )));
4958 }
4959 if let Some((type_name, _)) = resolve_struct_type_path(
4960 &term.name.value,
4961 term.name.span,
4962 registry,
4963 owner,
4964 src,
4965 module_ctx,
4966 )? {
4967 return Ok(ResolvedTypeExpr::Struct(type_name, term.span));
4968 }
4969 if let Some(atom) = term.name.value.as_bare()
4970 && let Some(gp) = dim_params.iter().find(|p| p.as_str() == atom.as_str())
4971 {
4972 return Ok(ResolvedTypeExpr::GenericDimParam(gp.clone(), term.span));
4973 }
4974 }
4975
4976 let has_generic = dim_expr.terms.iter().any(|item| {
4977 item.term
4978 .name
4979 .value
4980 .as_bare()
4981 .is_some_and(|atom| dim_params.iter().any(|p| p.as_str() == atom.as_str()))
4982 });
4983
4984 if has_generic {
4985 let terms = dim_expr
4986 .terms
4987 .iter()
4988 .map(|item| {
4989 resolve_dim_term_in_generic_expr(item, registry, dim_params, src, module_ctx)
4990 })
4991 .collect::<Result<Vec<_>, _>>()?;
4992 Ok(ResolvedTypeExpr::GenericDimExpr {
4993 terms,
4994 span: dim_expr.span,
4995 })
4996 } else {
4997 let result = dim_expr.terms.iter().try_fold(
4998 Dimension::dimensionless(),
4999 |acc, item| -> Result<Dimension, GraphcalError> {
5000 let base = concrete_dimension_for_term(item, registry, src, module_ctx)?;
5001 let exp = item.term.power.unwrap_or(Rational::ONE);
5002 let overflow_err = || GraphcalError::DimensionOverflow {
5003 src: src.clone(),
5004 span: item.term.span.into(),
5005 };
5006 let powered = base.pow(exp).map_err(|_| overflow_err())?;
5007 match item.op {
5008 MulDivOp::Mul => (acc * powered).map_err(|_| overflow_err()),
5009 MulDivOp::Div => (acc / powered).map_err(|_| overflow_err()),
5010 }
5011 },
5012 )?;
5013 Ok(ResolvedTypeExpr::Scalar(result))
5014 }
5015}
5016
5017fn resolve_dim_term_in_generic_expr(
5018 item: &crate::desugar::desugared_ast::DimExprItem,
5019 registry: &Registry,
5020 dim_params: &[GenericParamName],
5021 src: &NamedSource<Arc<String>>,
5022 module_ctx: Option<ModuleTypeContext<'_>>,
5023) -> Result<ResolvedDimTerm, GraphcalError> {
5024 let power = item.term.power.unwrap_or(Rational::ONE);
5025 let op = item.op;
5026 if let Some(atom) = item.term.name.value.as_bare()
5027 && let Some(gp) = dim_params.iter().find(|p| p.as_str() == atom.as_str())
5028 {
5029 return Ok(ResolvedDimTerm::GenericParam {
5030 name: gp.clone(),
5031 power,
5032 op,
5033 span: item.term.span,
5034 });
5035 }
5036 concrete_dimension_for_term(item, registry, src, module_ctx)
5037 .map(|dim| ResolvedDimTerm::Concrete { dim, power, op })
5038}
5039
5040fn concrete_dimension_for_term(
5041 item: &crate::desugar::desugared_ast::DimExprItem,
5042 registry: &Registry,
5043 src: &NamedSource<Arc<String>>,
5044 module_ctx: Option<ModuleTypeContext<'_>>,
5045) -> Result<Dimension, GraphcalError> {
5046 resolve_dimension_path(
5047 &item.term.name.value,
5048 item.term.name.span,
5049 registry,
5050 src,
5051 module_ctx,
5052 )?
5053 .ok_or_else(|| {
5054 let name = item
5055 .term
5056 .name
5057 .value
5058 .as_bare()
5059 .map_or_else(|| item.term.name.value.display_path(), ToString::to_string);
5060 GraphcalError::UnknownDimension {
5061 name: DimName::new(name),
5062 src: src.clone(),
5063 span: item.term.span.into(),
5064 }
5065 })
5066}
5067
5068fn resolve_datetime_application(
5076 type_ann: &TypeExpr,
5077 type_args: &[TypeExpr],
5078 src: &NamedSource<Arc<String>>,
5079) -> Result<ResolvedTypeExpr, GraphcalError> {
5080 if type_args.len() != 1 {
5081 return Err(GraphcalError::EvalError {
5082 message: format!(
5083 "type `Datetime` expects 0 or 1 type argument(s), got {}",
5084 type_args.len()
5085 ),
5086 src: src.clone(),
5087 span: type_ann.span.into(),
5088 });
5089 }
5090 let arg = &type_args[0];
5091 match &arg.kind {
5092 TypeExprKind::DimExpr(dim_expr)
5093 if dim_expr.terms.len() == 1 && dim_expr.terms[0].term.power.is_none() =>
5094 {
5095 let term = &dim_expr.terms[0].term;
5096 let name = require_local_type_level_path(&term.name.value, term.name.span, src)?;
5097 name.parse::<TimeScale>().map_or_else(
5098 |_| {
5099 Err(GraphcalError::EvalError {
5100 message: format!(
5101 "unknown time scale `{name}`; \
5102 expected one of: UTC, TAI, TT, TDB, ET, GPST, GST, BDT"
5103 ),
5104 src: src.clone(),
5105 span: arg.span.into(),
5106 })
5107 },
5108 |scale| Ok(ResolvedTypeExpr::Datetime(scale)),
5109 )
5110 }
5111 _ => Err(GraphcalError::EvalError {
5112 message: "expected a time scale name (e.g., UTC, TAI, TT, TDB, GPST)".to_string(),
5113 src: src.clone(),
5114 span: arg.span.into(),
5115 }),
5116 }
5117}
5118
5119#[expect(
5126 clippy::too_many_arguments,
5127 reason = "passes full type resolution context from resolve_type_expr"
5128)]
5129fn resolve_type_application(
5130 type_ann: &TypeExpr,
5131 name: &crate::syntax::span::Spanned<NamePath>,
5132 type_args: &[TypeExpr],
5133 registry: &Registry,
5134 owner: &crate::dag_id::DagId,
5135 dim_params: &[GenericParamName],
5136 index_params: &[GenericParamName],
5137 nat_params: &[GenericParamName],
5138 src: &NamedSource<Arc<String>>,
5139 module_ctx: Option<ModuleTypeContext<'_>>,
5140) -> Result<ResolvedTypeExpr, GraphcalError> {
5141 let (type_name, type_def) =
5142 resolve_struct_type_path(&name.value, name.span, registry, owner, src, module_ctx)?
5143 .ok_or_else(|| GraphcalError::UnknownStructType {
5144 name: name.value.display_path(),
5145 src: src.clone(),
5146 span: name.span.into(),
5147 })?;
5148 check_type_application_arity(
5149 type_name.as_str(),
5150 type_def,
5151 type_args.len(),
5152 type_ann.span,
5153 src,
5154 )?;
5155 let mut resolved_args = Vec::with_capacity(type_def.generic_params.len());
5156 for (param, arg) in type_def.generic_params.iter().zip(type_args) {
5157 let resolved = resolve_type_arg_for_param(
5158 param,
5159 arg,
5160 registry,
5161 owner,
5162 dim_params,
5163 index_params,
5164 nat_params,
5165 src,
5166 module_ctx,
5167 )?;
5168 resolved_args.push(resolved);
5169 }
5170 for param in type_def.generic_params.iter().skip(type_args.len()) {
5172 let default_expr = param
5173 .default
5174 .as_ref()
5175 .ok_or_else(|| GraphcalError::EvalError {
5176 message: format!(
5177 "internal: generic parameter `{}` has no default",
5178 param.name
5179 ),
5180 src: src.clone(),
5181 span: type_ann.span.into(),
5182 })?;
5183 let default_ctx = module_ctx
5184 .map(|ctx| ModuleTypeContext::new(type_name.owner(), ctx.resolver, ctx.types));
5185 let resolved = resolve_type_arg_for_param(
5186 param,
5187 default_expr,
5188 registry,
5189 type_name.owner(),
5190 dim_params,
5191 index_params,
5192 nat_params,
5193 src,
5194 default_ctx,
5195 )?;
5196 resolved_args.push(resolved);
5197 }
5198 Ok(ResolvedTypeExpr::GenericStruct {
5199 name: type_name,
5200 type_args: resolved_args,
5201 span: type_ann.span,
5202 })
5203}
5204
5205#[expect(
5206 clippy::too_many_arguments,
5207 reason = "passes full type resolution context from resolve_type_application"
5208)]
5209fn resolve_type_arg_for_param(
5210 param: &crate::registry::types::TypeGenericParam,
5211 arg: &TypeExpr,
5212 registry: &Registry,
5213 owner: &crate::dag_id::DagId,
5214 dim_params: &[GenericParamName],
5215 index_params: &[GenericParamName],
5216 nat_params: &[GenericParamName],
5217 src: &NamedSource<Arc<String>>,
5218 module_ctx: Option<ModuleTypeContext<'_>>,
5219) -> Result<ResolvedTypeExpr, GraphcalError> {
5220 let resolved = resolve_type_expr_inner(
5221 arg,
5222 registry,
5223 owner,
5224 dim_params,
5225 index_params,
5226 nat_params,
5227 src,
5228 module_ctx,
5229 )?;
5230 match (param.constraint, &resolved) {
5231 (TypeGenericConstraint::Index, ResolvedTypeExpr::IndexArg(_)) => Ok(resolved),
5232 (TypeGenericConstraint::Index, _) => Err(GraphcalError::EvalError {
5233 message: format!(
5234 "generic parameter `{}` expects an Index argument",
5235 param.name
5236 ),
5237 src: src.clone(),
5238 span: arg.span.into(),
5239 }),
5240 (TypeGenericConstraint::Nat, _) => Err(GraphcalError::EvalError {
5241 message: format!(
5242 "generic parameter `{}` expects a Nat argument, got a type argument",
5243 param.name
5244 ),
5245 src: src.clone(),
5246 span: arg.span.into(),
5247 }),
5248 (TypeGenericConstraint::Dim, ResolvedTypeExpr::IndexArg(index)) => {
5249 Err(GraphcalError::EvalError {
5250 message: format!(
5251 "index `{}` cannot be used as a Dim argument",
5252 format_resolved_index(index)
5253 ),
5254 src: src.clone(),
5255 span: arg.span.into(),
5256 })
5257 }
5258 (TypeGenericConstraint::Unconstrained, ResolvedTypeExpr::IndexArg(index)) => {
5259 Err(GraphcalError::EvalError {
5260 message: format!(
5261 "index `{}` cannot be used as a Type argument",
5262 format_resolved_index(index)
5263 ),
5264 src: src.clone(),
5265 span: arg.span.into(),
5266 })
5267 }
5268 (TypeGenericConstraint::Dim | TypeGenericConstraint::Unconstrained, _) => Ok(resolved),
5269 }
5270}
5271
5272#[cfg(test)]
5273mod tests {
5274 use super::*;
5275 use crate::registry::prelude::load_prelude;
5276 use crate::registry::types::RegistryBuilder;
5277 use crate::syntax::dimension::BaseDimId;
5278 use crate::syntax::parser::Parser;
5279
5280 fn make_registry() -> Registry {
5281 let mut b = RegistryBuilder::new();
5282 load_prelude(&mut b).unwrap();
5283 b.build()
5284 }
5285
5286 fn make_dim_term_name(
5287 name: &str,
5288 ) -> crate::syntax::span::Spanned<crate::syntax::names::NamePath> {
5289 crate::syntax::span::Spanned::new(
5290 crate::syntax::names::NamePath::from(name),
5291 Span::new(0, 0),
5292 )
5293 }
5294
5295 fn make_dim_type_expr(name: &str) -> crate::desugar::desugared_ast::TypeExpr {
5297 crate::desugar::desugared_ast::TypeExpr {
5298 kind: crate::desugar::desugared_ast::TypeExprKind::DimExpr(
5299 crate::desugar::desugared_ast::DimExpr {
5300 terms: vec![crate::desugar::desugared_ast::DimExprItem {
5301 op: crate::desugar::desugared_ast::MulDivOp::Mul,
5302 term: crate::desugar::desugared_ast::DimTerm {
5303 name: make_dim_term_name(name),
5304 power: None,
5305 span: Span::new(0, 0),
5306 },
5307 }],
5308 span: Span::new(0, 0),
5309 },
5310 ),
5311 constraints: vec![],
5312 span: Span::new(0, 0),
5313 }
5314 }
5315
5316 fn make_registry_with_struct() -> Registry {
5317 let mut b = RegistryBuilder::new();
5318 load_prelude(&mut b).unwrap();
5319 b.register_type(crate::registry::types::TypeDef {
5320 name: StructTypeName::new("TransferResult"),
5321 generic_params: vec![],
5322 kind: crate::registry::types::TypeDefKind::Union {
5323 members: vec![crate::registry::types::UnionMemberDef {
5324 name: crate::syntax::names::ConstructorName::new("TransferResult"),
5325 fields: vec![
5326 crate::registry::types::StructField {
5327 name: crate::syntax::names::FieldName::new("dv1"),
5328 type_ann: make_dim_type_expr("Velocity"),
5329 },
5330 crate::registry::types::StructField {
5331 name: crate::syntax::names::FieldName::new("dv2"),
5332 type_ann: make_dim_type_expr("Velocity"),
5333 },
5334 ],
5335 }],
5336 },
5337 });
5338 b.build()
5339 }
5340
5341 fn make_registry_with_index() -> Registry {
5342 let mut b = RegistryBuilder::new();
5343 load_prelude(&mut b).unwrap();
5344 b.register_index(crate::registry::types::IndexDef {
5345 name: IndexName::new("Maneuver"),
5346 kind: crate::registry::types::IndexKind::Named {
5347 variants: vec![
5348 crate::syntax::names::IndexVariantName::new("Departure"),
5349 crate::syntax::names::IndexVariantName::new("Insertion"),
5350 ],
5351 },
5352 });
5353 b.build()
5354 }
5355
5356 fn make_src() -> NamedSource<Arc<String>> {
5357 NamedSource::new("test", Arc::new(String::new()))
5358 }
5359
5360 fn parse_type(source: &str) -> TypeExpr {
5362 let full = format!("param x: {source} = 0.0;");
5364 let raw_file = Parser::new(&full).parse_file().unwrap();
5365 let desugared = crate::syntax::desugar::desugar_multi_decls_in_file(raw_file);
5366 let file = desugared;
5367 match &file.declarations[0].kind {
5368 crate::desugar::desugared_ast::DeclKind::Param(p) => p.type_ann.clone(),
5369 _ => panic!("expected param"),
5370 }
5371 }
5372
5373 #[test]
5374 fn resolve_dimensionless() {
5375 let r = make_registry();
5376 let te = parse_type("Dimensionless");
5377 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5378 assert_eq!(resolved, ResolvedTypeExpr::Dimensionless);
5379 }
5380
5381 #[test]
5382 fn resolve_bool() {
5383 let r = make_registry();
5384 let te = parse_type("Bool");
5385 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5386 assert_eq!(resolved, ResolvedTypeExpr::Bool);
5387 }
5388
5389 #[test]
5390 fn resolve_int() {
5391 let r = make_registry();
5392 let te = parse_type("Int");
5393 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5394 assert_eq!(resolved, ResolvedTypeExpr::Int);
5395 }
5396
5397 #[test]
5398 fn resolve_concrete_dimension() {
5399 let r = make_registry();
5400 let te = parse_type("Length");
5401 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5402 assert_eq!(
5403 resolved,
5404 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude("Length".to_string())))
5405 );
5406 }
5407
5408 #[test]
5409 fn resolve_compound_dimension() {
5410 let r = make_registry();
5411 let te = parse_type("Length / Time^2");
5412 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5413 let expected = (Dimension::base(BaseDimId::Prelude("Length".to_string()))
5414 / Dimension::base(BaseDimId::Prelude("Time".to_string()))
5415 .pow_int(2)
5416 .unwrap())
5417 .unwrap();
5418 assert_eq!(resolved, ResolvedTypeExpr::Scalar(expected));
5419 }
5420
5421 #[test]
5422 fn resolve_struct_type() {
5423 let r = make_registry_with_struct();
5424 let te = parse_type("TransferResult");
5425 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5426 assert!(
5427 matches!(resolved, ResolvedTypeExpr::Struct(name, _) if name.as_str() == "TransferResult")
5428 );
5429 }
5430
5431 #[test]
5432 fn resolve_generic_dim_param() {
5433 let r = make_registry();
5434 let dim_params = vec![GenericParamName::new("D")];
5435 let te = parse_type("D");
5436 let resolved = resolve_type_expr(&te, &r, &dim_params, &[], &[], &make_src()).unwrap();
5437 assert!(
5438 matches!(resolved, ResolvedTypeExpr::GenericDimParam(name, _) if name.as_str() == "D")
5439 );
5440 }
5441
5442 #[test]
5443 fn resolve_generic_dim_expr_with_power() {
5444 let r = make_registry();
5445 let dim_params = vec![GenericParamName::new("D")];
5446 let te = parse_type("D^2");
5447 let resolved = resolve_type_expr(&te, &r, &dim_params, &[], &[], &make_src()).unwrap();
5448 match resolved {
5449 ResolvedTypeExpr::GenericDimExpr { terms, .. } => {
5450 assert_eq!(terms.len(), 1);
5451 match &terms[0] {
5452 ResolvedDimTerm::GenericParam { name, power, .. } => {
5453 assert_eq!(name.as_str(), "D");
5454 assert_eq!(*power, Rational::from_int(2));
5455 }
5456 ResolvedDimTerm::Concrete { .. } => panic!("expected GenericParam term"),
5457 }
5458 }
5459 _ => panic!("expected GenericDimExpr"),
5460 }
5461 }
5462
5463 #[test]
5464 fn resolve_mixed_generic_concrete() {
5465 let r = make_registry();
5466 let dim_params = vec![GenericParamName::new("D")];
5467 let te = parse_type("D * Length");
5469 let resolved = resolve_type_expr(&te, &r, &dim_params, &[], &[], &make_src()).unwrap();
5470 match resolved {
5471 ResolvedTypeExpr::GenericDimExpr { terms, .. } => {
5472 assert_eq!(terms.len(), 2);
5473 assert!(
5474 matches!(&terms[0], ResolvedDimTerm::GenericParam { name, .. } if name.as_str() == "D")
5475 );
5476 assert!(matches!(&terms[1], ResolvedDimTerm::Concrete { .. }));
5477 }
5478 _ => panic!("expected GenericDimExpr, got {resolved:?}"),
5479 }
5480 }
5481
5482 #[test]
5483 fn resolve_concrete_indexed() {
5484 let r = make_registry_with_index();
5485 let te = parse_type("Length[Maneuver]");
5486 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5487 match resolved {
5488 ResolvedTypeExpr::Indexed { base, indexes } => {
5489 assert_eq!(
5490 *base,
5491 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude(
5492 "Length".to_string()
5493 )))
5494 );
5495 assert_eq!(indexes.len(), 1);
5496 assert!(
5497 matches!(&indexes[0], ResolvedIndex::Concrete(name, _) if name.as_str() == "Maneuver")
5498 );
5499 }
5500 _ => panic!("expected Indexed"),
5501 }
5502 }
5503
5504 #[test]
5505 fn resolve_generic_indexed() {
5506 let r = make_registry();
5507 let dim_params = vec![GenericParamName::new("D")];
5508 let index_params = vec![GenericParamName::new("I")];
5509 let te = parse_type("D[I]");
5510 let resolved =
5511 resolve_type_expr(&te, &r, &dim_params, &index_params, &[], &make_src()).unwrap();
5512 match resolved {
5513 ResolvedTypeExpr::Indexed { base, indexes } => {
5514 assert!(
5515 matches!(*base, ResolvedTypeExpr::GenericDimParam(ref name, _) if name.as_str() == "D")
5516 );
5517 assert_eq!(indexes.len(), 1);
5518 assert!(
5519 matches!(&indexes[0], ResolvedIndex::GenericParam(name, _) if name.as_str() == "I")
5520 );
5521 }
5522 _ => panic!("expected Indexed"),
5523 }
5524 }
5525
5526 #[test]
5527 fn resolve_unknown_dimension_error() {
5528 let r = make_registry();
5529 let te = parse_type("UnknownDim");
5530 let err = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap_err();
5531 assert!(matches!(err, GraphcalError::UnknownDimension { .. }));
5532 }
5533
5534 #[test]
5535 fn resolve_unknown_index_error() {
5536 let r = make_registry();
5537 let te = parse_type("Length[UnknownIdx]");
5538 let err = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap_err();
5539 assert!(matches!(err, GraphcalError::UnknownIndex { .. }));
5540 }
5541
5542 #[test]
5543 fn resolve_struct_takes_priority_over_dim_param() {
5544 let r = make_registry_with_struct();
5551 let dim_params = vec![GenericParamName::new("TransferResult")];
5552 let te = parse_type("TransferResult");
5553 let resolved = resolve_type_expr(&te, &r, &dim_params, &[], &[], &make_src()).unwrap();
5554 assert!(matches!(resolved, ResolvedTypeExpr::Struct(..)));
5555 }
5556
5557 #[test]
5558 fn resolve_velocity_derived_dimension() {
5559 let r = make_registry();
5560 let te = parse_type("Velocity");
5561 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
5562 let expected = (Dimension::base(BaseDimId::Prelude("Length".to_string()))
5563 / Dimension::base(BaseDimId::Prelude("Time".to_string())))
5564 .unwrap();
5565 assert_eq!(resolved, ResolvedTypeExpr::Scalar(expected));
5566 }
5567
5568 fn parse_and_type_resolve(source: &str) -> Result<TIR, GraphcalError> {
5576 let raw_file = Parser::new(source).parse_file().unwrap();
5577 let desugared = crate::syntax::desugar::desugar_multi_decls_in_file(raw_file);
5578 let file = desugared;
5579 let src = NamedSource::new("test.gcl", Arc::new(source.to_string()));
5580 let ir = crate::ir::lower::lower(&file, &src)?;
5581 let parent_dag_id =
5582 crate::dag_id::DagId::from_relative_path(std::path::Path::new("test.gcl")).unwrap();
5583 let mut resolver = ModuleResolver::default();
5584 resolver
5585 .add_module(parent_dag_id.clone(), &file.declarations)
5586 .map_err(|err| {
5587 internal_error(
5588 format!("test module resolver failed for root module: {err}"),
5589 &src,
5590 Span::new(0, 0),
5591 )
5592 })?;
5593 for decl in &file.declarations {
5594 if let crate::desugar::desugared_ast::DeclKind::Dag(dag) = &decl.kind {
5595 resolver
5596 .add_module(parent_dag_id.child(dag.name.value.as_str()), &dag.body)
5597 .map_err(|err| {
5598 internal_error(
5599 format!(
5600 "test module resolver failed for inline dag `{}`: {err}",
5601 dag.name.value
5602 ),
5603 &src,
5604 Span::new(0, 0),
5605 )
5606 })?;
5607 }
5608 }
5609 let mut module_types = ModuleTypeRegistry::default();
5610 module_types.insert_graphcal_prelude().map_err(|err| {
5611 internal_error(
5612 format!("test module type prelude failed: {err}"),
5613 &src,
5614 Span::new(0, 0),
5615 )
5616 })?;
5617 module_types.insert_registry(&parent_dag_id, &ir.registry);
5618 let mut tir =
5619 type_resolve_with_modules(ir, parent_dag_id.clone(), &src, &resolver, &module_types)?;
5620 compile_inline_dag_bodies_test(&mut tir, &src, &parent_dag_id, &file.declarations)?;
5621 Ok(tir)
5622 }
5623
5624 fn compile_inline_dag_bodies_test(
5628 tir: &mut TIR,
5629 src: &NamedSource<Arc<String>>,
5630 parent_dag_id: &crate::dag_id::DagId,
5631 parent_declarations: &[crate::desugar::desugared_ast::Declaration],
5632 ) -> Result<(), GraphcalError> {
5633 let dag_bodies = tir
5634 .registry
5635 .dags
5636 .all_dags()
5637 .map(|(name, dag)| (name.clone(), dag.body.clone()))
5638 .collect::<Vec<_>>();
5639 let mut resolver = ModuleResolver::default();
5640 resolver
5641 .add_module(parent_dag_id.clone(), parent_declarations)
5642 .map_err(|err| {
5643 internal_error(
5644 format!("test module resolver failed for parent module: {err}"),
5645 src,
5646 Span::new(0, 0),
5647 )
5648 })?;
5649 for (name, body) in &dag_bodies {
5650 resolver
5651 .add_module(parent_dag_id.child(name.as_str()), body)
5652 .map_err(|err| {
5653 internal_error(
5654 format!("test module resolver failed for inline dag `{name}`: {err}"),
5655 src,
5656 Span::new(0, 0),
5657 )
5658 })?;
5659 }
5660 let mut module_types = ModuleTypeRegistry::default();
5661 module_types.insert_graphcal_prelude().map_err(|err| {
5662 internal_error(
5663 format!("test module type prelude failed: {err}"),
5664 src,
5665 Span::new(0, 0),
5666 )
5667 })?;
5668 module_types.insert_registry(parent_dag_id, &tir.registry);
5669
5670 for (name, body) in dag_bodies {
5671 let dag_body_ir = crate::ir::lower::lower_dag_body_to_ir(
5672 name.as_str(),
5673 &body,
5674 &tir.registry,
5675 &resolver,
5676 &crate::ir::resolve::ImportedValueNames::default(),
5677 HashMap::new(),
5678 HashMap::new(),
5679 src,
5680 parent_dag_id,
5681 )?;
5682 let dag_id = parent_dag_id.child(name.as_str());
5683 let mut compiled_dag = type_resolve_single_with_modules(
5684 dag_body_ir,
5685 &dag_id,
5686 src,
5687 &resolver,
5688 &module_types,
5689 )?;
5690 compiled_dag.populate_pub_nodes(&body);
5691 tir.dags.insert(dag_id, compiled_dag);
5692 }
5693 Ok(())
5694 }
5695
5696 #[test]
5697 fn module_aware_type_resolve_records_semantic_deps() {
5698 let source = "const node C: Dimensionless = 1.0;\n\
5699 const node D: Dimensionless = C;\n\
5700 param p: Dimensionless;\n\
5701 node x: Dimensionless = @p + D;";
5702 let raw_file = Parser::new(source).parse_file().unwrap();
5703 let desugared = crate::syntax::desugar::desugar_multi_decls_in_file(raw_file);
5704 let file = desugared;
5705 let src = NamedSource::new("test.gcl", Arc::new(source.to_string()));
5706 let dag_id =
5707 crate::dag_id::DagId::from_relative_path(std::path::Path::new("test.gcl")).unwrap();
5708 let ir = crate::ir::lower::lower(&file, &src).unwrap();
5709 let mut resolver = ModuleResolver::default();
5710 resolver
5711 .add_module(dag_id.clone(), &file.declarations)
5712 .unwrap();
5713 let mut module_types = ModuleTypeRegistry::default();
5714 module_types.insert_graphcal_prelude().unwrap();
5715 module_types.insert_registry(&dag_id, &ir.registry);
5716
5717 let tir =
5718 type_resolve_with_modules(ir, dag_id.clone(), &src, &resolver, &module_types).unwrap();
5719 let deps = &tir.root().semantic.dependencies;
5720 let c = ResolvedName::from_def(dag_id.clone(), DeclName::new("C"));
5721 let d = ResolvedName::from_def(dag_id.clone(), DeclName::new("D"));
5722 let p = ResolvedName::from_def(dag_id.clone(), DeclName::new("p"));
5723 let x = ResolvedName::from_def(dag_id, DeclName::new("x"));
5724
5725 assert!(deps.const_deps[&d].contains(&c));
5726 assert!(deps.const_deps[&c].is_empty());
5727 assert!(deps.runtime_deps[&x].contains(&p));
5728 assert!(deps.runtime_deps[&p].is_empty());
5729 }
5730
5731 #[test]
5732 fn type_resolve_rocket() {
5733 let source = include_str!("../../../../tests/fixtures/valid/rocket.gcl");
5734 let tir = parse_and_type_resolve(source).unwrap();
5735 assert!(
5737 tir.root()
5738 .resolved_decl_types
5739 .contains_key(&ScopedName::local("dry_mass"))
5740 );
5741 assert!(
5742 tir.root()
5743 .resolved_decl_types
5744 .contains_key(&ScopedName::local("delta_v"))
5745 );
5746 assert!(
5747 tir.root()
5748 .resolved_decl_types
5749 .contains_key(&ScopedName::local("g0"))
5750 );
5751 }
5752
5753 #[test]
5754 fn type_resolve_indexed() {
5755 let source = include_str!("../../../../tests/fixtures/valid/indexed.gcl");
5756 let tir = parse_and_type_resolve(source).unwrap();
5757 let dv_type = &tir.root().resolved_decl_types[&ScopedName::local("delta_v")];
5759 assert!(matches!(dv_type, ResolvedTypeExpr::Indexed { .. }));
5760 }
5761
5762 #[test]
5763 fn type_resolve_hohmann() {
5764 let source = include_str!("../../../../tests/fixtures/valid/hohmann.gcl");
5772 let err = parse_and_type_resolve(source).unwrap_err();
5773 assert!(
5774 err.to_string().contains("transfer"),
5775 "unexpected error: {err}"
5776 );
5777 }
5778
5779 #[test]
5780 fn type_resolve_generics() {
5781 let source = include_str!("../../../../tests/fixtures/valid/generics.gcl");
5782 let tir = parse_and_type_resolve(source).unwrap();
5783 let pos_type = &tir.root().resolved_decl_types[&ScopedName::local("pos_eci")];
5785 match pos_type {
5786 ResolvedTypeExpr::GenericStruct {
5787 name, type_args, ..
5788 } => {
5789 assert_eq!(name.as_str(), "Vec3");
5790 assert_eq!(type_args.len(), 2);
5791 assert_eq!(
5792 type_args[0],
5793 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude(
5794 "Length".to_string()
5795 )))
5796 );
5797 assert!(
5798 matches!(&type_args[1], ResolvedTypeExpr::Struct(n, _) if n.as_str() == "Eci")
5799 );
5800 }
5801 other => panic!("expected GenericStruct, got {other:?}"),
5802 }
5803 assert_eq!(
5805 tir.root().resolved_decl_types[&ScopedName::local("x_pos")],
5806 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude("Length".to_string())))
5807 );
5808 }
5809
5810 #[test]
5811 fn type_resolve_default_type_params() {
5812 let source = include_str!("../../../../tests/fixtures/valid/generics.gcl");
5813 let tir = parse_and_type_resolve(source).unwrap();
5814
5815 let pos3_eci = &tir.root().resolved_decl_types[&ScopedName::local("pos3_eci")];
5817 match pos3_eci {
5818 ResolvedTypeExpr::GenericStruct {
5819 name, type_args, ..
5820 } => {
5821 assert_eq!(name.as_str(), "Pos3");
5822 assert_eq!(type_args.len(), 2);
5823 assert_eq!(
5824 type_args[0],
5825 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude(
5826 "Length".to_string()
5827 )))
5828 );
5829 assert!(
5830 matches!(&type_args[1], ResolvedTypeExpr::Struct(n, _) if n.as_str() == "Eci")
5831 );
5832 }
5833 other => panic!("expected GenericStruct, got {other:?}"),
5834 }
5835
5836 let pos3_default = &tir.root().resolved_decl_types[&ScopedName::local("pos3_default")];
5838 match pos3_default {
5839 ResolvedTypeExpr::GenericStruct {
5840 name, type_args, ..
5841 } => {
5842 assert_eq!(name.as_str(), "Pos3");
5843 assert_eq!(type_args.len(), 2);
5844 assert_eq!(
5845 type_args[0],
5846 ResolvedTypeExpr::Scalar(Dimension::base(BaseDimId::Prelude(
5847 "Length".to_string()
5848 )))
5849 );
5850 assert!(
5851 matches!(&type_args[1], ResolvedTypeExpr::Struct(n, _) if n.as_str() == "Unframed"),
5852 "expected Struct(Unframed), got {:?}",
5853 type_args[1]
5854 );
5855 }
5856 other => panic!("expected GenericStruct, got {other:?}"),
5857 }
5858 }
5859
5860 use crate::registry::declared_type::{DeclaredType, IndexTypeRef, StructTypeRef};
5863
5864 #[test]
5865 fn generic_index_substitution_preserves_resolved_owner() {
5866 use crate::tir::dim_check::{InferredIndex, InferredType};
5867
5868 let src = make_src();
5869 let registry = make_registry();
5870 let owner = crate::dag_id::DagId::root("a");
5871 let resolved_index = ResolvedName::from_def(owner, IndexName::new("Phase"));
5872 let generic = GenericParamName::new("I");
5873 let resolved_type = ResolvedTypeExpr::Indexed {
5874 base: Box::new(ResolvedTypeExpr::Dimensionless),
5875 indexes: vec![ResolvedIndex::GenericParam(
5876 generic.clone(),
5877 Span::new(0, 0),
5878 )],
5879 };
5880 let actual = InferredType::Indexed {
5881 element: Box::new(InferredType::Scalar(Dimension::dimensionless())),
5882 index: InferredIndex::from_resolved(resolved_index.clone()),
5883 };
5884 let mut dim_sub = HashMap::new();
5885 let mut index_sub = HashMap::new();
5886 let mut nat_sub = HashMap::new();
5887
5888 unify_resolved_type(
5889 &resolved_type,
5890 &actual,
5891 &mut dim_sub,
5892 &mut index_sub,
5893 &mut nat_sub,
5894 ®istry,
5895 &src,
5896 Span::new(0, 0),
5897 )
5898 .unwrap();
5899 assert_eq!(
5900 index_sub[&generic].declared_resolved(),
5901 Some(&resolved_index)
5902 );
5903
5904 let substituted =
5905 substitute_resolved_type(&resolved_type, &dim_sub, &index_sub, &nat_sub, &src).unwrap();
5906 let InferredType::Indexed { index, .. } = substituted else {
5907 panic!("expected indexed type after substitution");
5908 };
5909 assert_eq!(index.declared_resolved(), Some(&resolved_index));
5910 }
5911
5912 #[test]
5913 fn convert_dimensionless() {
5914 let dt = resolved_to_declared_type(&ResolvedTypeExpr::Dimensionless, &make_src()).unwrap();
5915 assert_eq!(dt, DeclaredType::Scalar(Dimension::dimensionless()));
5916 }
5917
5918 #[test]
5919 fn convert_bool() {
5920 let dt = resolved_to_declared_type(&ResolvedTypeExpr::Bool, &make_src()).unwrap();
5921 assert_eq!(dt, DeclaredType::Bool);
5922 }
5923
5924 #[test]
5925 fn convert_int() {
5926 let dt = resolved_to_declared_type(&ResolvedTypeExpr::Int, &make_src()).unwrap();
5927 assert_eq!(dt, DeclaredType::Int);
5928 }
5929
5930 #[test]
5931 fn convert_scalar() {
5932 let dim = Dimension::base(BaseDimId::Prelude("Length".to_string()));
5933 let dt =
5934 resolved_to_declared_type(&ResolvedTypeExpr::Scalar(dim.clone()), &make_src()).unwrap();
5935 assert_eq!(dt, DeclaredType::Scalar(dim));
5936 }
5937
5938 #[test]
5939 fn convert_struct() {
5940 let owner = crate::dag_id::DagId::root("test");
5941 let resolved = ResolvedName::from_def(owner, StructTypeName::new("Foo"));
5942 let dt = resolved_to_declared_type(
5943 &ResolvedTypeExpr::Struct(resolved.clone(), Span::new(0, 0)),
5944 &make_src(),
5945 )
5946 .unwrap();
5947 assert_eq!(
5948 dt,
5949 DeclaredType::Struct(StructTypeRef::from_resolved(resolved), vec![])
5950 );
5951 }
5952
5953 #[test]
5954 fn convert_indexed() {
5955 let owner = crate::dag_id::DagId::root("test");
5956 let resolved_index = ResolvedName::from_def(owner, IndexName::new("M"));
5957 let dt = resolved_to_declared_type(
5958 &ResolvedTypeExpr::Indexed {
5959 base: Box::new(ResolvedTypeExpr::Scalar(Dimension::base(
5960 BaseDimId::Prelude("Length".to_string()),
5961 ))),
5962 indexes: vec![ResolvedIndex::Concrete(
5963 resolved_index.clone(),
5964 Span::new(0, 0),
5965 )],
5966 },
5967 &make_src(),
5968 )
5969 .unwrap();
5970 assert_eq!(
5971 dt,
5972 DeclaredType::Indexed {
5973 element: Box::new(DeclaredType::Scalar(Dimension::base(BaseDimId::Prelude(
5974 "Length".to_string()
5975 )))),
5976 index: IndexTypeRef::from_resolved(resolved_index),
5977 }
5978 );
5979 }
5980
5981 #[test]
5982 fn convert_generic_dim_param_fails() {
5983 let err = resolved_to_declared_type(
5984 &ResolvedTypeExpr::GenericDimParam(GenericParamName::new("D"), Span::new(0, 0)),
5985 &make_src(),
5986 )
5987 .unwrap_err();
5988 assert!(matches!(err, GraphcalError::EvalError { .. }));
5989 }
5990
5991 #[test]
5992 fn convert_generic_index_fails() {
5993 let err = resolved_to_declared_type(
5994 &ResolvedTypeExpr::Indexed {
5995 base: Box::new(ResolvedTypeExpr::Dimensionless),
5996 indexes: vec![ResolvedIndex::GenericParam(
5997 GenericParamName::new("I"),
5998 Span::new(0, 0),
5999 )],
6000 },
6001 &make_src(),
6002 )
6003 .unwrap_err();
6004 assert!(matches!(err, GraphcalError::EvalError { .. }));
6005 }
6006
6007 #[test]
6010 fn resolve_bare_datetime() {
6011 let r = make_registry();
6012 let te = parse_type("Datetime");
6013 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
6014 assert_eq!(resolved, ResolvedTypeExpr::Datetime(TimeScale::UTC));
6015 }
6016
6017 #[test]
6018 fn resolve_datetime_utc() {
6019 let r = make_registry();
6020 let te = parse_type("Datetime<UTC>");
6021 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
6022 assert_eq!(resolved, ResolvedTypeExpr::Datetime(TimeScale::UTC));
6023 }
6024
6025 #[test]
6026 fn resolve_datetime_tt() {
6027 let r = make_registry();
6028 let te = parse_type("Datetime<TT>");
6029 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
6030 assert_eq!(resolved, ResolvedTypeExpr::Datetime(TimeScale::TT));
6031 }
6032
6033 #[test]
6034 fn resolve_datetime_tai() {
6035 let r = make_registry();
6036 let te = parse_type("Datetime<TAI>");
6037 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
6038 assert_eq!(resolved, ResolvedTypeExpr::Datetime(TimeScale::TAI));
6039 }
6040
6041 #[test]
6042 fn resolve_datetime_gpst() {
6043 let r = make_registry();
6044 let te = parse_type("Datetime<GPST>");
6045 let resolved = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap();
6046 assert_eq!(resolved, ResolvedTypeExpr::Datetime(TimeScale::GPST));
6047 }
6048
6049 #[test]
6050 fn resolve_datetime_unknown_scale_error() {
6051 let r = make_registry();
6052 let te = parse_type("Datetime<XYZ>");
6053 let err = resolve_type_expr(&te, &r, &[], &[], &[], &make_src()).unwrap_err();
6054 assert!(matches!(err, GraphcalError::EvalError { .. }));
6055 }
6056
6057 #[test]
6058 fn convert_datetime_utc() {
6059 let dt =
6060 resolved_to_declared_type(&ResolvedTypeExpr::Datetime(TimeScale::UTC), &make_src())
6061 .unwrap();
6062 assert_eq!(dt, DeclaredType::Datetime(TimeScale::UTC));
6063 }
6064
6065 #[test]
6066 fn convert_datetime_tt() {
6067 let dt = resolved_to_declared_type(&ResolvedTypeExpr::Datetime(TimeScale::TT), &make_src())
6068 .unwrap();
6069 assert_eq!(dt, DeclaredType::Datetime(TimeScale::TT));
6070 }
6071
6072 #[test]
6077 fn nat_leq_constant_equal() {
6078 let a = NatPolyForm::from_constant(3);
6079 let b = NatPolyForm::from_constant(3);
6080 assert!(a.is_leq(&b));
6081 }
6082
6083 #[test]
6084 fn nat_leq_constant_less() {
6085 let a = NatPolyForm::from_constant(2);
6086 let b = NatPolyForm::from_constant(5);
6087 assert!(a.is_leq(&b));
6088 }
6089
6090 #[test]
6091 fn nat_leq_constant_greater() {
6092 let a = NatPolyForm::from_constant(5);
6093 let b = NatPolyForm::from_constant(3);
6094 assert!(!a.is_leq(&b));
6095 }
6096
6097 #[test]
6098 fn nat_leq_same_var() {
6099 let a = NatPolyForm::from_var(GenericParamName::new("N"));
6101 let b = NatPolyForm::from_var(GenericParamName::new("N"));
6102 assert!(a.is_leq(&b));
6103 }
6104
6105 #[test]
6106 fn nat_leq_var_plus_constant() {
6107 let a = NatPolyForm::from_var(GenericParamName::new("N"));
6109 let b = NatPolyForm::from_var(GenericParamName::new("N"))
6110 .add(&NatPolyForm::from_constant(1))
6111 .unwrap();
6112 assert!(a.is_leq(&b));
6113 }
6114
6115 #[test]
6116 fn nat_leq_var_plus_constant_reverse() {
6117 let a = NatPolyForm::from_var(GenericParamName::new("N"))
6119 .add(&NatPolyForm::from_constant(1))
6120 .unwrap();
6121 let b = NatPolyForm::from_var(GenericParamName::new("N"));
6122 assert!(!a.is_leq(&b));
6123 }
6124
6125 #[test]
6126 fn nat_leq_different_vars() {
6127 let a = NatPolyForm::from_var(GenericParamName::new("N"));
6129 let b = NatPolyForm::from_var(GenericParamName::new("M"));
6130 assert!(!a.is_leq(&b));
6131 }
6132
6133 #[test]
6134 fn nat_leq_zero_leq_anything() {
6135 let a = NatPolyForm::from_constant(0);
6137 let b = NatPolyForm::from_var(GenericParamName::new("N"));
6138 assert!(a.is_leq(&b));
6139 }
6140
6141 #[test]
6146 fn nat_range_identity_concrete_to_index_type_ref() -> Result<(), Box<dyn std::error::Error>> {
6147 let reference = NatPolyForm::from_constant(3)
6148 .to_nat_range_identity()?
6149 .to_index_type_ref()?;
6150 assert_eq!(
6151 reference
6152 .nat_range()
6153 .map(crate::registry::types::NatRangeIndex::size_u64),
6154 Some(3)
6155 );
6156 assert_eq!(reference.display_name().as_str(), "range(3)");
6157 Ok(())
6158 }
6159
6160 #[test]
6161 fn nat_range_identity_symbolic_to_display_only_index_type_ref()
6162 -> Result<(), Box<dyn std::error::Error>> {
6163 let reference = NatPolyForm::from_var(GenericParamName::new("N"))
6164 .add(&NatPolyForm::from_constant(1))
6165 .unwrap()
6166 .to_nat_range_identity()?
6167 .to_index_type_ref()?;
6168 assert_eq!(reference.nat_range(), None);
6169 assert_eq!(reference.display_name().as_str(), "range(N + 1)");
6170 Ok(())
6171 }
6172
6173 #[test]
6178 fn nat_mul_constants() {
6179 let a = NatPolyForm::from_constant(3);
6180 let b = NatPolyForm::from_constant(4);
6181 assert_eq!(a.mul(&b).unwrap(), NatPolyForm::from_constant(12));
6182 }
6183
6184 #[test]
6185 fn nat_mul_var_by_constant() {
6186 let n = NatPolyForm::from_var(GenericParamName::new("N"));
6188 let three = NatPolyForm::from_constant(3);
6189 let result = n.mul(&three).unwrap();
6190 assert_eq!(result.format(), "3 * N");
6192 let mut bindings = HashMap::new();
6194 bindings.insert(GenericParamName::new("N"), 5);
6195 assert_eq!(result.evaluate(&bindings), Some(15));
6196 }
6197
6198 #[test]
6199 fn nat_mul_two_vars() {
6200 let m = NatPolyForm::from_var(GenericParamName::new("M"));
6202 let n = NatPolyForm::from_var(GenericParamName::new("N"));
6203 let result = m.mul(&n).unwrap();
6204 assert_eq!(result.format(), "M * N");
6205 let mut bindings = HashMap::new();
6206 bindings.insert(GenericParamName::new("M"), 3);
6207 bindings.insert(GenericParamName::new("N"), 4);
6208 assert_eq!(result.evaluate(&bindings), Some(12));
6209 }
6210
6211 #[test]
6212 fn nat_mul_distributive() {
6213 let m = NatPolyForm::from_var(GenericParamName::new("M"));
6215 let n = NatPolyForm::from_var(GenericParamName::new("N"));
6216 let m_plus_1 = m.add(&NatPolyForm::from_constant(1)).unwrap();
6217 let result = m_plus_1.mul(&n).unwrap();
6218 let mut bindings = HashMap::new();
6220 bindings.insert(GenericParamName::new("M"), 2);
6221 bindings.insert(GenericParamName::new("N"), 3);
6222 assert_eq!(result.evaluate(&bindings), Some(9));
6223 }
6224
6225 #[test]
6226 fn nat_mul_mixed_add() {
6227 let m = NatPolyForm::from_var(GenericParamName::new("M"));
6229 let n = NatPolyForm::from_var(GenericParamName::new("N"));
6230 let result = m
6231 .mul(&n)
6232 .unwrap()
6233 .add(&NatPolyForm::from_constant(1))
6234 .unwrap();
6235 assert_eq!(result.format(), "M * N + 1");
6236 let mut bindings = HashMap::new();
6237 bindings.insert(GenericParamName::new("M"), 2);
6238 bindings.insert(GenericParamName::new("N"), 3);
6239 assert_eq!(result.evaluate(&bindings), Some(7));
6240 }
6241
6242 #[test]
6243 fn nat_poly_is_constant() {
6244 let c = NatPolyForm::from_constant(5);
6245 assert!(c.is_constant());
6246
6247 let n = NatPolyForm::from_var(GenericParamName::new("N"));
6248 assert!(!n.is_constant());
6249
6250 let mn = NatPolyForm::from_var(GenericParamName::new("M"))
6251 .mul(&NatPolyForm::from_var(GenericParamName::new("N")))
6252 .unwrap();
6253 assert!(!mn.is_constant());
6254 }
6255
6256 #[test]
6257 fn nat_poly_leq_with_mul() {
6258 let mn = NatPolyForm::from_var(GenericParamName::new("M"))
6260 .mul(&NatPolyForm::from_var(GenericParamName::new("N")))
6261 .unwrap();
6262 let mn_plus_1 = mn.add(&NatPolyForm::from_constant(1)).unwrap();
6263 assert!(mn.is_leq(&mn_plus_1));
6264 assert!(!mn_plus_1.is_leq(&mn));
6265 }
6266
6267 #[test]
6268 fn nat_add_overflow_errors() {
6269 let a = NatPolyForm::from_constant(u64::MAX);
6272 let b = NatPolyForm::from_constant(1);
6273 assert!(a.add(&b).is_err());
6274 }
6275
6276 #[test]
6277 fn nat_mul_overflow_errors() {
6278 let a = NatPolyForm::from_constant(u64::MAX);
6280 let b = NatPolyForm::from_constant(2);
6281 assert!(a.mul(&b).is_err());
6282 }
6283
6284 #[test]
6285 fn nat_unify_substituted_term_overflow_errors() {
6286 let form = NatPolyForm::from_constant(2)
6291 .mul(&NatPolyForm::from_var(GenericParamName::new("N")))
6292 .unwrap();
6293 let mut nat_sub = HashMap::new();
6294 nat_sub.insert(GenericParamName::new("N"), u64::MAX / 2 + 1);
6295 let src = NamedSource::new("<test>", Arc::new(String::new()));
6296 let result = unify_nat_poly_form(
6297 &form,
6298 4,
6299 &mut nat_sub,
6300 &IndexName::new("range(4)"),
6301 &src,
6302 Span::new(0, 0),
6303 );
6304 assert!(result.is_err());
6305 }
6306
6307 #[test]
6308 fn nat_poly_format_zero() {
6309 let z = NatPolyForm::from_constant(0);
6310 assert_eq!(z.format(), "0");
6311 }
6312}