1use std::collections::{BTreeSet, HashMap};
19
20use thiserror::Error;
21
22use crate::dag_id::DagId;
23use crate::desugar::desugared_ast as ast;
24use crate::registry::resolve_types::{
25 AggregationFn, ConstructorFn, DatetimeExtractFn, DatetimeFromFn, DatetimeToFn, SpecialFnKind,
26 TypeConversionFn,
27};
28use crate::registry::time_scale::TimeScale;
29use crate::syntax::ast::{Ident, IdentPath, UnresolvedRef};
30use crate::syntax::module_resolve::{DeclSymbolKind, ModuleResolveError, ModuleResolver};
31use crate::syntax::names::{
32 DeclName, FieldName, GenericParamName, IndexName, IndexVariantName, LocalName, NameAtom,
33 NameAtomError, NameNamespace, NamePath, ResolvedIndexVariant, ResolvedName, ScopedName,
34 namespace,
35};
36use crate::syntax::non_empty::NonEmpty;
37use crate::syntax::phase::never;
38use crate::syntax::span::{Span, Spanned};
39
40use super::lower::{
41 GenericScope, HirLowerError, PreludeTypeScope, TypeLoweringContext, lower_nat_expr,
42 lower_type_expr,
43};
44use super::types::{NatExpr, TypeExpr};
45
46#[derive(Debug, Clone, PartialEq, Eq, Error)]
48pub enum ExprLowerError {
49 #[error(transparent)]
51 Type(#[from] HirLowerError),
52 #[error("{source}")]
54 ModuleResolve {
55 #[source]
56 source: ModuleResolveError,
57 span: Span,
58 },
59 #[error("invalid scoped-name segment `{segment}`: {source}")]
61 InvalidScopedNameSegment {
62 segment: String,
63 #[source]
64 source: NameAtomError,
65 span: Span,
66 },
67 #[error("unknown local variable `{name}`")]
69 UnknownLocalRef { name: LocalName, span: Span },
70 #[error("unknown graph reference `@{name}`")]
72 UnknownGraphRef { name: ScopedName, span: Span },
73 #[error("too many local bindings in one expression")]
75 TooManyLocals { span: Span },
76 #[error("map literal entry has no keys")]
78 EmptyMapEntry { span: Span },
79 #[error("extra variant `{variant_name}` in map literal for index `{index_name}`")]
81 ExtraMapVariant {
82 index_name: IndexName,
83 variant_name: IndexVariantName,
84 span: Span,
85 },
86 #[error("duplicate local binding `{name}`")]
88 DuplicateLocalBinding {
89 name: LocalName,
90 first: Span,
91 duplicate: Span,
92 },
93 #[error("unknown function `{path}`")]
95 UnknownFunction { path: String, span: Span },
96 #[error("function `{name}` expects {expected} argument(s), got {got}")]
98 WrongArity {
99 name: crate::syntax::names::FnName,
100 expected: usize,
101 got: usize,
102 span: Span,
103 },
104 #[error("unknown match pattern `{path}`")]
106 UnknownPattern { path: String, span: Span },
107}
108
109#[derive(Debug, Clone, Copy)]
111pub struct ExprLoweringContext<'a> {
112 pub owner: &'a DagId,
113 pub resolver: &'a ModuleResolver,
114 pub generic_scope: &'a GenericScope,
115 pub prelude: Option<&'a PreludeTypeScope>,
116 pub decl_bindings: Option<&'a HashMap<ScopedName, ResolvedName<namespace::Decl>>>,
117}
118
119impl<'a> ExprLoweringContext<'a> {
120 #[must_use]
122 pub const fn new(
123 owner: &'a DagId,
124 resolver: &'a ModuleResolver,
125 generic_scope: &'a GenericScope,
126 ) -> Self {
127 Self {
128 owner,
129 resolver,
130 generic_scope,
131 prelude: None,
132 decl_bindings: None,
133 }
134 }
135
136 #[must_use]
138 pub const fn with_prelude(self, prelude: &'a PreludeTypeScope) -> Self {
139 Self {
140 owner: self.owner,
141 resolver: self.resolver,
142 generic_scope: self.generic_scope,
143 prelude: Some(prelude),
144 decl_bindings: self.decl_bindings,
145 }
146 }
147
148 #[must_use]
151 pub const fn with_decl_bindings(
152 self,
153 decl_bindings: &'a HashMap<ScopedName, ResolvedName<namespace::Decl>>,
154 ) -> Self {
155 Self {
156 owner: self.owner,
157 resolver: self.resolver,
158 generic_scope: self.generic_scope,
159 prelude: self.prelude,
160 decl_bindings: Some(decl_bindings),
161 }
162 }
163
164 const fn type_context(self) -> TypeLoweringContext<'a> {
165 let ctx = TypeLoweringContext::new(self.owner, self.resolver, self.generic_scope);
166 match self.prelude {
167 Some(prelude) => ctx.with_prelude(prelude),
168 None => ctx,
169 }
170 }
171}
172
173#[must_use]
180pub fn lower_expr_tolerant(
181 expr: &ast::Expr,
182 ctx: ExprLoweringContext<'_>,
183) -> (Expr, Vec<ExprLowerError>) {
184 let mut lowerer = ExprLowerer::new(ctx);
185 let hir_expr = lowerer.lower_expr(expr);
186 (hir_expr, lowerer.diagnostics)
187}
188
189pub fn lower_expr(expr: &ast::Expr, ctx: ExprLoweringContext<'_>) -> Result<Expr, ExprLowerError> {
199 let (lowered, mut diagnostics) = lower_expr_tolerant(expr, ctx);
200 if diagnostics.is_empty() {
201 Ok(lowered)
202 } else {
203 Err(diagnostics.swap_remove(0))
204 }
205}
206
207#[must_use]
213pub fn lower_assert_body_tolerant(
214 body: &ast::AssertBody,
215 ctx: ExprLoweringContext<'_>,
216) -> (AssertBody, Vec<ExprLowerError>) {
217 match body {
218 ast::AssertBody::Expr(expr) => {
219 let (lowered, diagnostics) = lower_expr_tolerant(expr, ctx);
220 (AssertBody::Expr(lowered), diagnostics)
221 }
222 ast::AssertBody::Tolerance {
223 actual,
224 expected,
225 tolerance,
226 is_relative,
227 } => {
228 let (actual, mut diagnostics) = lower_expr_tolerant(actual, ctx);
229 let (expected, expected_diags) = lower_expr_tolerant(expected, ctx);
230 let (tolerance, tolerance_diags) = lower_expr_tolerant(tolerance, ctx);
231 diagnostics.extend(expected_diags);
232 diagnostics.extend(tolerance_diags);
233 (
234 AssertBody::Tolerance {
235 actual: Box::new(actual),
236 expected: Box::new(expected),
237 tolerance: Box::new(tolerance),
238 is_relative: *is_relative,
239 },
240 diagnostics,
241 )
242 }
243 }
244}
245
246pub fn lower_assert_body(
252 body: &ast::AssertBody,
253 ctx: ExprLoweringContext<'_>,
254) -> Result<AssertBody, ExprLowerError> {
255 let (lowered, mut diagnostics) = lower_assert_body_tolerant(body, ctx);
256 if diagnostics.is_empty() {
257 Ok(lowered)
258 } else {
259 Err(diagnostics.swap_remove(0))
260 }
261}
262
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
265pub struct LocalId(u32);
266
267impl LocalId {
268 #[must_use]
270 pub const fn index(self) -> u32 {
271 self.0
272 }
273}
274
275#[derive(Debug, Clone, PartialEq, Eq)]
277pub struct LocalDef {
278 pub id: LocalId,
279 pub name: LocalName,
280 pub span: Span,
281}
282
283#[derive(Debug)]
292pub struct LocalEnv<'a, V> {
293 parent: Option<&'a Self>,
294 bindings: Vec<(LocalId, V)>,
295}
296
297impl<'a, V> LocalEnv<'a, V> {
298 #[must_use]
300 pub const fn root() -> Self {
301 Self {
302 parent: None,
303 bindings: Vec::new(),
304 }
305 }
306
307 #[must_use]
309 pub const fn from_bindings(bindings: Vec<(LocalId, V)>) -> Self {
310 Self {
311 parent: None,
312 bindings,
313 }
314 }
315
316 #[must_use]
318 pub const fn child<'b>(&'b self, bindings: Vec<(LocalId, V)>) -> LocalEnv<'b, V>
319 where
320 'a: 'b,
321 {
322 LocalEnv {
323 parent: Some(self),
324 bindings,
325 }
326 }
327
328 #[must_use]
330 pub fn get(&self, id: LocalId) -> Option<&V> {
331 self.bindings
332 .iter()
333 .rev()
334 .find(|(bound, _)| *bound == id)
335 .map(|(_, value)| value)
336 .or_else(|| self.parent.and_then(|parent| parent.get(id)))
337 }
338
339 pub fn bind(&mut self, id: LocalId, value: V) {
344 match self.bindings.iter_mut().find(|(bound, _)| *bound == id) {
345 Some((_, slot)) => *slot = value,
346 None => self.bindings.push((id, value)),
347 }
348 }
349}
350
351impl<V> Default for LocalEnv<'_, V> {
352 fn default() -> Self {
353 Self::root()
354 }
355}
356
357macro_rules! define_builtin_names {
362 (
363 $(#[$meta:meta])*
364 $vis:vis enum $name:ident { $($variant:ident => $text:literal),+ $(,)? }
365 ) => {
366 $(#[$meta])*
367 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
368 $vis enum $name { $($variant),+ }
369
370 impl $name {
371 $vis const ALL: &'static [Self] = &[$(Self::$variant),+];
373
374 #[must_use]
377 $vis fn parse(name: &str) -> Option<Self> {
378 match name {
379 $($text => Some(Self::$variant),)+
380 _ => None,
381 }
382 }
383
384 #[must_use]
386 $vis const fn as_str(self) -> &'static str {
387 match self {
388 $(Self::$variant => $text),+
389 }
390 }
391 }
392 };
393}
394
395define_builtin_names! {
396 pub enum BuiltinConst {
398 Pi => "PI",
399 E => "E",
400 Tau => "TAU",
401 Sqrt2 => "SQRT2",
402 Ln2 => "LN2",
403 Ln10 => "LN10",
404 }
405}
406
407impl BuiltinConst {
408 #[must_use]
411 pub const fn value(self) -> f64 {
412 match self {
413 Self::Pi => std::f64::consts::PI,
414 Self::E => std::f64::consts::E,
415 Self::Tau => std::f64::consts::TAU,
416 Self::Sqrt2 => std::f64::consts::SQRT_2,
417 Self::Ln2 => std::f64::consts::LN_2,
418 Self::Ln10 => std::f64::consts::LN_10,
419 }
420 }
421}
422
423define_builtin_names! {
424 pub enum BuiltinFnName {
426 Sqrt => "sqrt",
427 Cbrt => "cbrt",
428 Exp => "exp",
429 Expm1 => "expm1",
430 Ln => "ln",
431 Log10 => "log10",
432 Log2 => "log2",
433 Log => "log",
434 Log1p => "log1p",
435 Sin => "sin",
436 Cos => "cos",
437 Tan => "tan",
438 Asin => "asin",
439 Acos => "acos",
440 Atan => "atan",
441 Atan2 => "atan2",
442 Sinh => "sinh",
443 Cosh => "cosh",
444 Tanh => "tanh",
445 Asinh => "asinh",
446 Acosh => "acosh",
447 Atanh => "atanh",
448 Abs => "abs",
449 Floor => "floor",
450 Ceil => "ceil",
451 Round => "round",
452 Trunc => "trunc",
453 Sign => "sign",
454 Min => "min",
455 Max => "max",
456 Hypot => "hypot",
457 Clamp => "clamp",
458 Sum => "sum",
459 Mean => "mean",
460 Count => "count",
461 ToFloat => "to_float",
462 ToInt => "to_int",
463 ToUtc => "to_utc",
464 ToTai => "to_tai",
465 ToTt => "to_tt",
466 ToTdb => "to_tdb",
467 ToEt => "to_et",
468 ToGpst => "to_gpst",
469 ToGst => "to_gst",
470 ToBdt => "to_bdt",
471 ToQzsst => "to_qzsst",
472 Datetime => "datetime",
473 Epoch => "epoch",
474 Year => "year",
475 Month => "month",
476 Day => "day",
477 Hour => "hour",
478 Minute => "minute",
479 Second => "second",
480 Weekday => "weekday",
481 DayOfYear => "day_of_year",
482 FromJd => "from_jd",
483 FromMjd => "from_mjd",
484 FromUnix => "from_unix",
485 ToJd => "to_jd",
486 ToMjd => "to_mjd",
487 ToUnix => "to_unix",
488 }
489}
490
491impl BuiltinFnName {
492 #[must_use]
495 pub const fn special_kind(self) -> Option<SpecialFnKind> {
496 match self {
497 Self::Sum => Some(SpecialFnKind::Aggregation(AggregationFn::Sum)),
498 Self::Min => Some(SpecialFnKind::Aggregation(AggregationFn::Min)),
499 Self::Max => Some(SpecialFnKind::Aggregation(AggregationFn::Max)),
500 Self::Mean => Some(SpecialFnKind::Aggregation(AggregationFn::Mean)),
501 Self::Count => Some(SpecialFnKind::Aggregation(AggregationFn::Count)),
502 Self::ToFloat => Some(SpecialFnKind::TypeConversion(TypeConversionFn::ToFloat)),
503 Self::ToInt => Some(SpecialFnKind::TypeConversion(TypeConversionFn::ToInt)),
504 Self::ToUtc => Some(SpecialFnKind::TimeScaleConversion(TimeScale::UTC)),
505 Self::ToTai => Some(SpecialFnKind::TimeScaleConversion(TimeScale::TAI)),
506 Self::ToTt => Some(SpecialFnKind::TimeScaleConversion(TimeScale::TT)),
507 Self::ToTdb => Some(SpecialFnKind::TimeScaleConversion(TimeScale::TDB)),
508 Self::ToEt => Some(SpecialFnKind::TimeScaleConversion(TimeScale::ET)),
509 Self::ToGpst => Some(SpecialFnKind::TimeScaleConversion(TimeScale::GPST)),
510 Self::ToGst => Some(SpecialFnKind::TimeScaleConversion(TimeScale::GST)),
511 Self::ToBdt => Some(SpecialFnKind::TimeScaleConversion(TimeScale::BDT)),
512 Self::ToQzsst => Some(SpecialFnKind::TimeScaleConversion(TimeScale::QZSST)),
513 Self::Datetime => Some(SpecialFnKind::Constructor(ConstructorFn::Datetime)),
514 Self::Epoch => Some(SpecialFnKind::Constructor(ConstructorFn::Epoch)),
515 Self::Year => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Year)),
516 Self::Month => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Month)),
517 Self::Day => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Day)),
518 Self::Hour => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Hour)),
519 Self::Minute => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Minute)),
520 Self::Second => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Second)),
521 Self::Weekday => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::Weekday)),
522 Self::DayOfYear => Some(SpecialFnKind::DatetimeExtract(DatetimeExtractFn::DayOfYear)),
523 Self::FromJd => Some(SpecialFnKind::DatetimeFrom(DatetimeFromFn::FromJd)),
524 Self::FromMjd => Some(SpecialFnKind::DatetimeFrom(DatetimeFromFn::FromMjd)),
525 Self::FromUnix => Some(SpecialFnKind::DatetimeFrom(DatetimeFromFn::FromUnix)),
526 Self::ToJd => Some(SpecialFnKind::DatetimeTo(DatetimeToFn::ToJd)),
527 Self::ToMjd => Some(SpecialFnKind::DatetimeTo(DatetimeToFn::ToMjd)),
528 Self::ToUnix => Some(SpecialFnKind::DatetimeTo(DatetimeToFn::ToUnix)),
529 _ => None,
530 }
531 }
532}
533
534#[derive(Debug)]
536pub struct Expr {
537 pub kind: ExprKind,
538 pub span: Span,
539}
540
541impl Clone for Expr {
547 fn clone(&self) -> Self {
548 crate::stack::with_stack_growth(|| Self {
549 kind: self.kind.clone(),
550 span: self.span,
551 })
552 }
553}
554
555impl Expr {
556 #[must_use]
557 pub const fn new(kind: ExprKind, span: Span) -> Self {
558 Self { kind, span }
559 }
560}
561
562#[derive(Debug, Clone, PartialEq, Eq)]
574pub struct IndexVariantRef {
575 pub variant: ResolvedIndexVariant,
577 pub index_span: Option<Span>,
582 pub variant_span: Span,
584}
585
586impl IndexVariantRef {
587 #[must_use]
591 pub fn path_span(&self) -> Span {
592 self.index_span
593 .map_or(self.variant_span, |index| index.merge(self.variant_span))
594 }
595}
596
597#[derive(Debug, Clone)]
599pub enum ExprKind {
600 Error,
606 Number(f64),
607 Integer(i64),
608 Bool(bool),
609 StringLiteral(String),
610 TypeSystemRef(Spanned<TypeSystemRef>),
611 GraphRef(Spanned<ResolvedName<namespace::Decl>>),
612 ConstRef(Spanned<ConstRef>),
613 LocalRef(Spanned<LocalId>),
614 BinOp {
615 op: ast::BinOp,
616 lhs: Box<Expr>,
617 rhs: Box<Expr>,
618 },
619 UnaryOp {
620 op: ast::UnaryOp,
621 operand: Box<Expr>,
622 },
623 FnCall {
624 callee: Spanned<FunctionRef>,
625 type_args: Vec<GenericArg>,
626 args: Vec<Expr>,
627 },
628 If {
629 condition: Box<Expr>,
630 then_branch: Box<Expr>,
631 else_branch: Box<Expr>,
632 },
633 UnitLiteral {
634 value: f64,
635 unit: ast::UnitExpr,
636 },
637 Convert {
638 expr: Box<Expr>,
639 target: ast::UnitExpr,
640 },
641 DisplayTimezone {
642 expr: Box<Expr>,
643 timezone: String,
644 },
645 FieldAccess {
646 expr: Box<Expr>,
647 field: Spanned<FieldName>,
648 },
649 ConstructorCall {
650 callee: Spanned<ResolvedName<namespace::Constructor>>,
651 generic_args: Vec<GenericArg>,
652 fields: Vec<FieldInit>,
653 },
654 MapLiteral {
655 entries: Vec<MapEntry>,
656 },
657 ForComp {
658 bindings: Vec<ForBinding>,
659 body: Box<Expr>,
660 },
661 IndexAccess {
662 expr: Box<Expr>,
663 args: Vec<IndexArg>,
664 },
665 Scan {
666 source: Box<Expr>,
667 init: Box<Expr>,
668 acc: LocalDef,
669 val: LocalDef,
670 body: Box<Expr>,
671 },
672 Unfold {
673 init: Box<Expr>,
674 prev: LocalDef,
675 curr: LocalDef,
676 body: Box<Expr>,
677 },
678 Match {
679 scrutinee: Box<Expr>,
680 arms: Vec<MatchArm>,
681 },
682 VariantLiteral(IndexVariantRef),
683 InlineDagRef {
684 target: Spanned<DagId>,
685 args: Vec<ParamBinding>,
686 output: Spanned<ResolvedName<namespace::Decl>>,
687 },
688}
689
690#[derive(Debug, Clone, Default, PartialEq, Eq)]
692pub struct ExprDependencies {
693 pub graph_refs: BTreeSet<ResolvedName<namespace::Decl>>,
695 pub const_refs: BTreeSet<ResolvedName<namespace::Decl>>,
697}
698
699#[must_use]
701pub fn collect_expr_dependencies(expr: &Expr) -> ExprDependencies {
702 let mut deps = ExprDependencies::default();
703 collect_expr_dependencies_into(expr, &mut deps);
704 deps
705}
706
707fn collect_expr_dependencies_into(expr: &Expr, deps: &mut ExprDependencies) {
708 crate::stack::with_stack_growth(|| collect_expr_dependencies_into_inner(expr, deps));
711}
712
713fn collect_expr_dependencies_into_inner(expr: &Expr, deps: &mut ExprDependencies) {
714 match &expr.kind {
715 ExprKind::Error
716 | ExprKind::Number(_)
717 | ExprKind::Integer(_)
718 | ExprKind::Bool(_)
719 | ExprKind::StringLiteral(_)
720 | ExprKind::TypeSystemRef(_)
721 | ExprKind::LocalRef(_)
722 | ExprKind::VariantLiteral(_)
723 | ExprKind::UnitLiteral { .. } => {}
724 ExprKind::GraphRef(target) => {
725 deps.graph_refs.insert(target.value.clone());
726 }
727 ExprKind::ConstRef(target) => {
728 if let ConstRef::Decl(resolved) = &target.value {
729 deps.const_refs.insert(resolved.clone());
730 }
731 }
732 ExprKind::BinOp { lhs, rhs, .. } => {
733 collect_expr_dependencies_into(lhs, deps);
734 collect_expr_dependencies_into(rhs, deps);
735 }
736 ExprKind::UnaryOp { operand, .. }
737 | ExprKind::Convert { expr: operand, .. }
738 | ExprKind::DisplayTimezone { expr: operand, .. }
739 | ExprKind::FieldAccess { expr: operand, .. } => {
740 collect_expr_dependencies_into(operand, deps);
741 }
742 ExprKind::FnCall { args, .. } => {
743 for arg in args {
744 collect_expr_dependencies_into(arg, deps);
745 }
746 }
747 ExprKind::If {
748 condition,
749 then_branch,
750 else_branch,
751 } => {
752 collect_expr_dependencies_into(condition, deps);
753 collect_expr_dependencies_into(then_branch, deps);
754 collect_expr_dependencies_into(else_branch, deps);
755 }
756 ExprKind::ConstructorCall { fields, .. } => {
757 for field in fields {
758 collect_expr_dependencies_into(&field.value, deps);
759 }
760 }
761 ExprKind::MapLiteral { entries } => {
762 for entry in entries {
763 collect_expr_dependencies_into(&entry.value, deps);
764 }
765 }
766 ExprKind::ForComp { body, .. } => collect_expr_dependencies_into(body, deps),
767 ExprKind::IndexAccess { expr, args } => {
768 collect_expr_dependencies_into(expr, deps);
769 for arg in args {
770 if let IndexArg::Expr(expr) = arg {
771 collect_expr_dependencies_into(expr, deps);
772 }
773 }
774 }
775 ExprKind::Scan {
776 source, init, body, ..
777 } => {
778 collect_expr_dependencies_into(source, deps);
779 collect_expr_dependencies_into(init, deps);
780 collect_expr_dependencies_into(body, deps);
781 }
782 ExprKind::Unfold { init, body, .. } => {
783 collect_expr_dependencies_into(init, deps);
784 collect_expr_dependencies_into(body, deps);
785 }
786 ExprKind::Match { scrutinee, arms } => {
787 collect_expr_dependencies_into(scrutinee, deps);
788 for arm in arms {
789 collect_expr_dependencies_into(&arm.body, deps);
790 }
791 }
792 ExprKind::InlineDagRef { args, .. } => {
793 for arg in args {
794 collect_expr_dependencies_into(&arg.value, deps);
795 }
796 }
797 }
798}
799
800#[must_use]
808pub fn has_ref_outside_unfold(expr: &Expr, name: &ResolvedName<namespace::Decl>) -> bool {
809 crate::stack::with_stack_growth(|| match &expr.kind {
812 ExprKind::GraphRef(target) => target.value == *name,
813 ExprKind::Unfold { .. }
816 | ExprKind::Error
817 | ExprKind::Number(_)
818 | ExprKind::Integer(_)
819 | ExprKind::Bool(_)
820 | ExprKind::StringLiteral(_)
821 | ExprKind::TypeSystemRef(_)
822 | ExprKind::LocalRef(_)
823 | ExprKind::VariantLiteral(_)
824 | ExprKind::UnitLiteral { .. }
825 | ExprKind::ConstRef(_) => false,
826 ExprKind::BinOp { lhs, rhs, .. } => {
827 has_ref_outside_unfold(lhs, name) || has_ref_outside_unfold(rhs, name)
828 }
829 ExprKind::UnaryOp { operand, .. }
830 | ExprKind::Convert { expr: operand, .. }
831 | ExprKind::DisplayTimezone { expr: operand, .. }
832 | ExprKind::FieldAccess { expr: operand, .. } => has_ref_outside_unfold(operand, name),
833 ExprKind::FnCall { args, .. } => args.iter().any(|arg| has_ref_outside_unfold(arg, name)),
834 ExprKind::If {
835 condition,
836 then_branch,
837 else_branch,
838 } => {
839 has_ref_outside_unfold(condition, name)
840 || has_ref_outside_unfold(then_branch, name)
841 || has_ref_outside_unfold(else_branch, name)
842 }
843 ExprKind::ConstructorCall { fields, .. } => fields
844 .iter()
845 .any(|field| has_ref_outside_unfold(&field.value, name)),
846 ExprKind::MapLiteral { entries } => entries
847 .iter()
848 .any(|entry| has_ref_outside_unfold(&entry.value, name)),
849 ExprKind::ForComp { body, .. } => has_ref_outside_unfold(body, name),
850 ExprKind::IndexAccess { expr, args } => {
851 has_ref_outside_unfold(expr, name)
852 || args.iter().any(|arg| match arg {
853 IndexArg::Expr(expr) => has_ref_outside_unfold(expr, name),
854 _ => false,
855 })
856 }
857 ExprKind::Scan {
858 source, init, body, ..
859 } => {
860 has_ref_outside_unfold(source, name)
861 || has_ref_outside_unfold(init, name)
862 || has_ref_outside_unfold(body, name)
863 }
864 ExprKind::Match { scrutinee, arms } => {
865 has_ref_outside_unfold(scrutinee, name)
866 || arms
867 .iter()
868 .any(|arm| has_ref_outside_unfold(&arm.body, name))
869 }
870 ExprKind::InlineDagRef { args, .. } => args
871 .iter()
872 .any(|arg| has_ref_outside_unfold(&arg.value, name)),
873 })
874}
875
876#[derive(Debug, Clone, PartialEq, Eq)]
878pub enum TypeSystemRef {
879 Type(ResolvedName<namespace::StructType>),
880 Dimension(ResolvedName<namespace::Dim>),
881 Index(ResolvedName<namespace::Index>),
882 IndexVariant(ResolvedIndexVariant),
883}
884
885impl TypeSystemRef {
886 #[must_use]
887 pub fn surface_description(&self) -> String {
888 match self {
889 Self::Type(name) => format!("type `{}`", name.as_str()),
890 Self::Dimension(name) => format!("dimension `{}`", name.as_str()),
891 Self::Index(name) => format!("index `{}`", name.as_str()),
892 Self::IndexVariant(variant) => format!(
893 "index label `{}.{}`",
894 variant.index().as_str(),
895 variant.variant()
896 ),
897 }
898 }
899
900 #[must_use]
901 pub fn value_position_error(&self) -> String {
902 format!("{} cannot be used as a value", self.surface_description())
903 }
904}
905
906#[derive(Debug, Clone, PartialEq, Eq)]
908pub enum ConstRef {
909 Decl(ResolvedName<namespace::Decl>),
910 Constructor(ResolvedName<namespace::Constructor>),
911 Builtin(BuiltinConst),
912 TimeScale(TimeScale),
913 GenericNatParam(super::types::GenericParamId),
914}
915
916#[derive(Debug, Clone, Copy, PartialEq, Eq)]
918pub enum FunctionRef {
919 Builtin(BuiltinFnName),
920}
921
922#[derive(Debug, Clone)]
924pub enum AssertBody {
925 Expr(Expr),
926 Tolerance {
927 actual: Box<Expr>,
928 expected: Box<Expr>,
929 tolerance: Box<Expr>,
930 is_relative: bool,
931 },
932}
933
934#[derive(Debug, Clone)]
936pub enum GenericArg {
937 Type(TypeExpr),
938 Nat(NatExpr),
939}
940
941#[derive(Debug, Clone)]
943pub struct FieldInit {
944 pub name: Spanned<FieldName>,
945 pub value: Expr,
946}
947
948#[derive(Debug, Clone)]
950pub struct ParamBinding {
951 pub target: Spanned<ResolvedName<namespace::Decl>>,
952 pub value: Expr,
953 pub span: Span,
954}
955
956#[derive(Debug, Clone)]
958pub struct MapEntry {
959 pub keys: NonEmpty<MapEntryKey>,
960 pub value: Expr,
961}
962
963#[derive(Debug, Clone, PartialEq, Eq)]
965pub enum MapEntryKey {
966 IndexVariant(IndexVariantRef),
967 NatRangeVariant {
968 size: u64,
969 variant: Spanned<IndexVariantName>,
970 },
971}
972
973#[derive(Debug, Clone)]
975pub struct ForBinding {
976 pub local: LocalDef,
977 pub index: ForBindingIndex,
978}
979
980#[derive(Debug, Clone, PartialEq, Eq)]
982pub enum ForBindingIndex {
983 Named(Spanned<ResolvedName<namespace::Index>>),
984 Range { arg: NatExpr, span: Span },
985}
986
987#[derive(Debug, Clone)]
989pub enum IndexArg {
990 Variant(IndexVariantRef),
991 Var(Spanned<LocalId>),
992 Expr(Box<Expr>),
993}
994
995#[derive(Debug, Clone)]
997pub struct MatchArm {
998 pub pattern: MatchPattern,
999 pub body: Expr,
1000 pub span: Span,
1001}
1002
1003#[derive(Debug, Clone)]
1005pub enum MatchPattern {
1006 Constructor {
1007 constructor: Spanned<ResolvedName<namespace::Constructor>>,
1008 bindings: Vec<PatternBinding>,
1009 span: Span,
1010 },
1011 IndexLabel {
1012 variant: IndexVariantRef,
1013 span: Span,
1014 },
1015}
1016
1017impl MatchPattern {
1018 fn bound_locals(&self) -> Vec<LocalDef> {
1019 match self {
1020 Self::Constructor { bindings, .. } => bindings
1021 .iter()
1022 .filter_map(|binding| match binding {
1023 PatternBinding::Bind { local, .. } => Some(local.clone()),
1024 PatternBinding::Wildcard { .. } => None,
1025 })
1026 .collect(),
1027 Self::IndexLabel { .. } => Vec::new(),
1028 }
1029 }
1030}
1031
1032#[derive(Debug, Clone)]
1034pub enum PatternBinding {
1035 Bind {
1036 field: Spanned<FieldName>,
1037 local: LocalDef,
1038 },
1039 Wildcard {
1040 field: Spanned<FieldName>,
1041 span: Span,
1042 },
1043}
1044
1045struct ExprLowerer<'a> {
1046 ctx: ExprLoweringContext<'a>,
1047 local_scopes: Vec<HashMap<LocalName, LocalDef>>,
1048 next_local: u32,
1049 diagnostics: Vec<ExprLowerError>,
1050}
1051
1052impl<'a> ExprLowerer<'a> {
1053 const fn new(ctx: ExprLoweringContext<'a>) -> Self {
1054 Self {
1055 ctx,
1056 local_scopes: Vec::new(),
1057 next_local: 0,
1058 diagnostics: Vec::new(),
1059 }
1060 }
1061
1062 fn lower_expr(&mut self, expr: &ast::Expr) -> Expr {
1068 crate::stack::with_stack_growth(|| match self.lower_expr_inner(expr) {
1071 Ok(lowered) => lowered,
1072 Err(err) => {
1073 self.diagnostics.push(err);
1074 Expr::new(ExprKind::Error, expr.span)
1075 }
1076 })
1077 }
1078
1079 #[expect(clippy::too_many_lines, reason = "exhaustive ExprKind lowering")]
1080 fn lower_expr_inner(&mut self, expr: &ast::Expr) -> Result<Expr, ExprLowerError> {
1081 let kind = match &expr.kind {
1082 ast::ExprKind::Number(value) => ExprKind::Number(*value),
1083 ast::ExprKind::Integer(value) => ExprKind::Integer(*value),
1084 ast::ExprKind::Bool(value) => ExprKind::Bool(*value),
1085 ast::ExprKind::StringLiteral(value) => ExprKind::StringLiteral(value.clone()),
1086 ast::ExprKind::UnresolvedRef(unresolved) => {
1087 let UnresolvedRef::Path(path) = unresolved;
1088 self.lower_unresolved_path(path)?
1089 }
1090 ast::ExprKind::GraphRef(name) => ExprKind::GraphRef(Spanned::new(
1091 self.resolve_decl_scoped_name(&name.value, name.span)?,
1092 name.span,
1093 )),
1094 ast::ExprKind::BinOp { op, lhs, rhs } => ExprKind::BinOp {
1095 op: *op,
1096 lhs: Box::new(self.lower_expr(lhs)),
1097 rhs: Box::new(self.lower_expr(rhs)),
1098 },
1099 ast::ExprKind::UnaryOp { op, operand } => ExprKind::UnaryOp {
1100 op: *op,
1101 operand: Box::new(self.lower_expr(operand)),
1102 },
1103 ast::ExprKind::FnCall {
1104 callee,
1105 type_args,
1106 args,
1107 } => ExprKind::FnCall {
1108 callee: {
1109 let function_ref = Self::lower_function_ref(callee)?;
1110 Self::check_function_arity(function_ref, args.len(), callee.span())?;
1111 Spanned::new(function_ref, callee.span())
1112 },
1113 type_args: type_args
1114 .iter()
1115 .map(|arg| self.lower_generic_arg(arg))
1116 .collect::<Result<Vec<_>, _>>()?,
1117 args: args.iter().map(|arg| self.lower_expr(arg)).collect(),
1118 },
1119 ast::ExprKind::If {
1120 condition,
1121 then_branch,
1122 else_branch,
1123 } => ExprKind::If {
1124 condition: Box::new(self.lower_expr(condition)),
1125 then_branch: Box::new(self.lower_expr(then_branch)),
1126 else_branch: Box::new(self.lower_expr(else_branch)),
1127 },
1128 ast::ExprKind::UnitLiteral { value, unit } => ExprKind::UnitLiteral {
1129 value: *value,
1130 unit: unit.clone(),
1131 },
1132 ast::ExprKind::Convert { expr, target } => ExprKind::Convert {
1133 expr: Box::new(self.lower_expr(expr)),
1134 target: target.clone(),
1135 },
1136 ast::ExprKind::DisplayTimezone { expr, timezone } => ExprKind::DisplayTimezone {
1137 expr: Box::new(self.lower_expr(expr)),
1138 timezone: timezone.clone(),
1139 },
1140 ast::ExprKind::FieldAccess { expr, field } => self
1147 .resolve_alias_field_access(expr, field)
1148 .unwrap_or_else(|| ExprKind::FieldAccess {
1149 expr: Box::new(self.lower_expr(expr)),
1150 field: field.clone(),
1151 }),
1152 ast::ExprKind::ConstructorCall {
1153 callee,
1154 generic_args,
1155 fields,
1156 } => ExprKind::ConstructorCall {
1157 callee: Spanned::new(
1158 self.ctx
1159 .resolver
1160 .resolve_constructor_ident_path(self.ctx.owner, callee)
1161 .map_err(|source| ExprLowerError::ModuleResolve {
1162 source,
1163 span: callee.span(),
1164 })?,
1165 callee.span(),
1166 ),
1167 generic_args: generic_args
1168 .iter()
1169 .map(|arg| self.lower_generic_arg(arg))
1170 .collect::<Result<Vec<_>, _>>()?,
1171 fields: fields
1172 .iter()
1173 .map(|field| self.lower_field_init(field))
1174 .collect(),
1175 },
1176 ast::ExprKind::MapLiteral { entries } => ExprKind::MapLiteral {
1177 entries: entries
1178 .iter()
1179 .map(|entry| self.lower_map_entry(entry, expr.span))
1180 .collect::<Result<Vec<_>, _>>()?,
1181 },
1182 ast::ExprKind::ForComp { bindings, body } => {
1183 let bindings = bindings
1184 .iter()
1185 .map(|binding| self.lower_for_binding(binding))
1186 .collect::<Result<Vec<_>, _>>()?;
1187 let locals = bindings
1188 .iter()
1189 .map(|binding| binding.local.clone())
1190 .collect::<Vec<_>>();
1191 self.push_scope(locals)?;
1192 let body = Box::new(self.lower_expr(body));
1193 self.pop_scope();
1194 ExprKind::ForComp { bindings, body }
1195 }
1196 ast::ExprKind::IndexAccess { expr, args } => ExprKind::IndexAccess {
1197 expr: Box::new(self.lower_expr(expr)),
1198 args: args
1199 .iter()
1200 .map(|arg| self.lower_index_arg(arg))
1201 .collect::<Result<Vec<_>, _>>()?,
1202 },
1203 ast::ExprKind::Scan {
1204 source,
1205 init,
1206 acc_name,
1207 val_name,
1208 body,
1209 } => {
1210 let source = Box::new(self.lower_expr(source));
1211 let init = Box::new(self.lower_expr(init));
1212 let acc = self.allocate_local(acc_name.value.clone(), acc_name.span)?;
1213 let val = self.allocate_local(val_name.value.clone(), val_name.span)?;
1214 self.push_scope(vec![acc.clone(), val.clone()])?;
1215 let body = Box::new(self.lower_expr(body));
1216 self.pop_scope();
1217 ExprKind::Scan {
1218 source,
1219 init,
1220 acc,
1221 val,
1222 body,
1223 }
1224 }
1225 ast::ExprKind::Unfold {
1226 init,
1227 prev_name,
1228 curr_name,
1229 body,
1230 } => {
1231 let init = Box::new(self.lower_expr(init));
1232 let prev = self.allocate_local(prev_name.value.clone(), prev_name.span)?;
1233 let curr = self.allocate_local(curr_name.value.clone(), curr_name.span)?;
1234 self.push_scope(vec![prev.clone(), curr.clone()])?;
1235 let body = Box::new(self.lower_expr(body));
1236 self.pop_scope();
1237 ExprKind::Unfold {
1238 init,
1239 prev,
1240 curr,
1241 body,
1242 }
1243 }
1244 ast::ExprKind::Match { scrutinee, arms } => ExprKind::Match {
1245 scrutinee: Box::new(self.lower_expr(scrutinee)),
1246 arms: arms
1247 .iter()
1248 .map(|arm| self.lower_match_arm(arm))
1249 .collect::<Result<Vec<_>, _>>()?,
1250 },
1251 ast::ExprKind::InlineDagRef { path, args, output } => {
1252 let target = self
1253 .ctx
1254 .resolver
1255 .resolve_module_path(self.ctx.owner, path)
1256 .map_err(|source| ExprLowerError::ModuleResolve {
1257 source,
1258 span: path.span(),
1259 })?;
1260 let lowered_args = args
1261 .iter()
1262 .map(|arg| self.lower_param_binding(&target, arg))
1263 .collect::<Result<Vec<_>, _>>()?;
1264 let output_path = NamePath::local(output.value.atom().clone());
1265 let lowered_output = self
1266 .ctx
1267 .resolver
1268 .resolve_decl_path(&target, &output_path)
1269 .map_err(|source| ExprLowerError::ModuleResolve {
1270 source,
1271 span: output.span,
1272 })?;
1273 ExprKind::InlineDagRef {
1274 target: Spanned::new(target, path.span()),
1275 args: lowered_args,
1276 output: Spanned::new(lowered_output, output.span),
1277 }
1278 }
1279 #[expect(
1281 clippy::uninhabited_references,
1282 reason = "Sugar(Infallible) proves this arm unreachable"
1283 )]
1284 ast::ExprKind::Sugar(s) => never(*s),
1285 };
1286 Ok(Expr::new(kind, expr.span))
1287 }
1288
1289 fn lower_unresolved_path(&self, path: &IdentPath) -> Result<ExprKind, ExprLowerError> {
1297 path.as_bare().map_or_else(
1298 || self.lower_dotted_path_ref(path),
1299 |ident| self.lower_bare_name_ref(ident),
1300 )
1301 }
1302
1303 fn lower_bare_name_ref(&self, ident: &Ident) -> Result<ExprKind, ExprLowerError> {
1312 let span = ident.span;
1313 if let Ok(local) = self.lookup_local(&LocalName::from_atom(ident.name.clone()), span) {
1314 return Ok(ExprKind::LocalRef(Spanned::new(local, span)));
1315 }
1316 if let Some(builtin) = BuiltinConst::parse(ident.name.as_str()) {
1317 return Ok(ExprKind::ConstRef(Spanned::new(
1318 ConstRef::Builtin(builtin),
1319 span,
1320 )));
1321 }
1322 if let Ok(scale) = ident.name.as_str().parse::<TimeScale>() {
1323 return Ok(ExprKind::ConstRef(Spanned::new(
1324 ConstRef::TimeScale(scale),
1325 span,
1326 )));
1327 }
1328 let path = NamePath::local(ident.name.clone());
1329 if let Ok(constructor) = self
1330 .ctx
1331 .resolver
1332 .resolve_constructor_path(self.ctx.owner, &path)
1333 {
1334 return Ok(ExprKind::ConstructorCall {
1335 callee: Spanned::new(constructor, span),
1336 generic_args: Vec::new(),
1337 fields: Vec::new(),
1338 });
1339 }
1340 if let Some(type_system_ref) =
1341 self.resolve_bare_type_system_name(&path, &ident.name, span)?
1342 {
1343 return Ok(ExprKind::TypeSystemRef(Spanned::new(type_system_ref, span)));
1344 }
1345 self.lower_const_ref(&ScopedName::local(ident.name.as_str()), span)
1346 .map(|const_ref| ExprKind::ConstRef(Spanned::new(const_ref, span)))
1347 }
1348
1349 fn resolve_bare_type_system_name(
1355 &self,
1356 path: &NamePath,
1357 name: &NameAtom,
1358 span: Span,
1359 ) -> Result<Option<TypeSystemRef>, ExprLowerError> {
1360 if let Ok(struct_type) = self
1361 .ctx
1362 .resolver
1363 .resolve_struct_type_path(self.ctx.owner, path)
1364 {
1365 return Ok(Some(TypeSystemRef::Type(struct_type)));
1366 }
1367 if let Ok(dimension) = self
1368 .ctx
1369 .resolver
1370 .resolve_dimension_path(self.ctx.owner, path)
1371 {
1372 return Ok(Some(TypeSystemRef::Dimension(dimension)));
1373 }
1374 if let Some(prelude) = self.ctx.prelude
1375 && let Some(dimension) = prelude.resolve_dimension_path(path)
1376 {
1377 return Ok(Some(TypeSystemRef::Dimension(dimension)));
1378 }
1379 if let Ok(index) = self.ctx.resolver.resolve_index_path(self.ctx.owner, path) {
1380 return Ok(Some(TypeSystemRef::Index(index)));
1381 }
1382 let variant_name = IndexVariantName::from_atom(name.clone());
1383 match self
1384 .ctx
1385 .resolver
1386 .resolve_bare_index_variant(self.ctx.owner, &variant_name)
1387 {
1388 Ok(variant) => Ok(Some(TypeSystemRef::IndexVariant(variant))),
1389 Err(ModuleResolveError::UnknownName { .. }) => Ok(None),
1390 Err(source) => Err(ExprLowerError::ModuleResolve { source, span }),
1391 }
1392 }
1393
1394 fn lower_dotted_path_ref(&self, path: &IdentPath) -> Result<ExprKind, ExprLowerError> {
1400 let span = path.span();
1401 if let [qualifier, member] = path.segments() {
1402 let index_path = NamePath::local(qualifier.name.clone());
1403 if self
1404 .ctx
1405 .resolver
1406 .resolve_index_path(self.ctx.owner, &index_path)
1407 .is_ok()
1408 {
1409 let variant = IndexVariantName::from_atom(member.name.clone());
1410 let resolved = self.resolve_index_variant_parts(
1411 &index_path,
1412 &variant,
1413 qualifier.span,
1414 member.span,
1415 )?;
1416 return Ok(ExprKind::VariantLiteral(IndexVariantRef {
1417 variant: resolved,
1418 index_span: Some(qualifier.span),
1419 variant_span: member.span,
1420 }));
1421 }
1422 }
1423
1424 let (qualifier, member) = path.split_last();
1425 let scoped = ScopedName::qualified_path(
1426 qualifier.iter().map(|segment| segment.name.to_string()),
1427 member.name.to_string(),
1428 );
1429 match self.lower_const_ref(&scoped, span) {
1430 Ok(const_ref) => Ok(ExprKind::ConstRef(Spanned::new(const_ref, span))),
1431 Err(const_err) => self
1435 .resolve_variant_literal_path(path)
1436 .ok_or(const_err)
1437 .map(ExprKind::VariantLiteral),
1438 }
1439 }
1440
1441 fn resolve_alias_field_access(
1451 &self,
1452 inner: &ast::Expr,
1453 field: &Spanned<FieldName>,
1454 ) -> Option<ExprKind> {
1455 let ast::ExprKind::GraphRef(name) = &inner.kind else {
1456 return None;
1457 };
1458 if name.value.is_qualified() {
1459 return None;
1460 }
1461 let scoped = ScopedName::qualified(name.value.member(), field.value.as_str());
1462 let span = name.span.merge(field.span);
1463 let path = scoped_name_to_path(&scoped, span).ok()?;
1464 let resolved = self
1465 .ctx
1466 .resolver
1467 .resolve_decl_path(self.ctx.owner, &path)
1468 .ok()?;
1469 Some(ExprKind::GraphRef(Spanned::new(resolved, span)))
1470 }
1471
1472 fn resolve_variant_literal_path(&self, path: &IdentPath) -> Option<IndexVariantRef> {
1476 let (qualifier, member) = path.split_last();
1477 let (first, rest) = qualifier.split_first()?;
1478 let index_span = rest
1479 .iter()
1480 .fold(first.span, |merged, segment| merged.merge(segment.span));
1481 let resolved = self
1482 .ctx
1483 .resolver
1484 .resolve_index_variant_path(self.ctx.owner, &path.to_name_path())
1485 .ok()?;
1486 Some(IndexVariantRef {
1487 variant: resolved,
1488 index_span: Some(index_span),
1489 variant_span: member.span,
1490 })
1491 }
1492
1493 fn lower_generic_arg(&self, arg: &ast::GenericArg) -> Result<GenericArg, ExprLowerError> {
1494 match arg {
1495 ast::GenericArg::Type(type_expr) => Ok(GenericArg::Type(lower_type_expr(
1496 type_expr,
1497 self.ctx.type_context(),
1498 )?)),
1499 ast::GenericArg::Nat(nat_expr) => Ok(GenericArg::Nat(lower_nat_expr(
1500 nat_expr,
1501 self.ctx.type_context(),
1502 )?)),
1503 }
1504 }
1505
1506 fn lower_field_init(&mut self, field: &ast::FieldInit) -> FieldInit {
1507 FieldInit {
1508 name: field.name.clone(),
1509 value: self.lower_expr(&field.value),
1510 }
1511 }
1512
1513 fn lower_param_binding(
1514 &mut self,
1515 target: &DagId,
1516 binding: &ast::ParamBinding,
1517 ) -> Result<ParamBinding, ExprLowerError> {
1518 let path = NamePath::local(binding.name.name.clone());
1519 let target_name = self
1520 .ctx
1521 .resolver
1522 .resolve_decl_path(target, &path)
1523 .map_err(|source| ExprLowerError::ModuleResolve {
1524 source,
1525 span: binding.name.span,
1526 })?;
1527 Ok(ParamBinding {
1528 target: Spanned::new(target_name, binding.name.span),
1529 value: self.lower_expr(&binding.value),
1530 span: binding.span,
1531 })
1532 }
1533
1534 fn lower_const_ref(&self, name: &ScopedName, span: Span) -> Result<ConstRef, ExprLowerError> {
1535 if !name.is_qualified() {
1536 if let Some(builtin) = BuiltinConst::parse(name.member()) {
1537 return Ok(ConstRef::Builtin(builtin));
1538 }
1539 if let Ok(scale) = name.member().parse::<TimeScale>() {
1540 return Ok(ConstRef::TimeScale(scale));
1541 }
1542 let generic_name = GenericParamName::new(name.member());
1543 if let Some(binding) = self.ctx.generic_scope.get(&generic_name)
1544 && binding.constraint == ast::GenericConstraint::Nat
1545 {
1546 return Ok(ConstRef::GenericNatParam(binding.id.clone()));
1547 }
1548 }
1549
1550 let path = scoped_name_to_path(name, span)?;
1551 let mut first_error = None;
1552
1553 if let Some(resolved) = self
1554 .ctx
1555 .decl_bindings
1556 .and_then(|bindings| bindings.get(name))
1557 .cloned()
1558 {
1559 return Ok(ConstRef::Decl(resolved));
1560 }
1561
1562 match self
1563 .ctx
1564 .resolver
1565 .resolve_const_decl_path(self.ctx.owner, &path)
1566 {
1567 Ok(resolved) => return Ok(ConstRef::Decl(resolved)),
1568 Err(err) => first_error.get_or_insert(err),
1569 };
1570 if let Some(resolved) = self.resolve_synthetic_child_decl_path(&path)
1571 && self
1572 .ctx
1573 .resolver
1574 .decl_symbol_kind(&resolved)
1575 .is_ok_and(DeclSymbolKind::is_const)
1576 {
1577 return Ok(ConstRef::Decl(resolved));
1578 }
1579 match self
1580 .ctx
1581 .resolver
1582 .resolve_constructor_path(self.ctx.owner, &path)
1583 {
1584 Ok(resolved) => return Ok(ConstRef::Constructor(resolved)),
1585 Err(err) => first_error.get_or_insert(err),
1586 };
1587
1588 first_error.map_or_else(
1589 || {
1590 Err(ExprLowerError::ModuleResolve {
1591 source: ModuleResolveError::UnknownName {
1592 owner: self.ctx.owner.clone(),
1593 namespace: namespace::Decl::DISPLAY_NAME,
1594 name: name.to_string(),
1595 },
1596 span,
1597 })
1598 },
1599 |source| Err(ExprLowerError::ModuleResolve { source, span }),
1600 )
1601 }
1602
1603 fn resolve_decl_scoped_name(
1604 &self,
1605 name: &ScopedName,
1606 span: Span,
1607 ) -> Result<ResolvedName<namespace::Decl>, ExprLowerError> {
1608 let path = scoped_name_to_path(name, span)?;
1609 if let Some(resolved) = self
1610 .ctx
1611 .decl_bindings
1612 .and_then(|bindings| bindings.get(name))
1613 .cloned()
1614 {
1615 return Ok(resolved);
1616 }
1617 self.ctx
1618 .resolver
1619 .resolve_decl_path(self.ctx.owner, &path)
1620 .or_else(|err| self.resolve_synthetic_child_decl_path(&path).ok_or(err))
1621 .map_err(|source| match source {
1622 ModuleResolveError::UnknownName { .. } => ExprLowerError::UnknownGraphRef {
1623 name: name.clone(),
1624 span,
1625 },
1626 source => ExprLowerError::ModuleResolve { source, span },
1627 })
1628 }
1629
1630 fn resolve_synthetic_child_decl_path(
1631 &self,
1632 path: &NamePath,
1633 ) -> Option<ResolvedName<namespace::Decl>> {
1634 let (qualifier, leaf) = path.qualifier_and_leaf()?;
1635 let owner = qualifier
1636 .iter()
1637 .fold(self.ctx.owner.clone(), |owner, segment| {
1638 owner.child(segment.as_str())
1639 });
1640 self.ctx
1641 .resolver
1642 .modules()
1643 .contains_key(&owner)
1644 .then(|| ResolvedName::from_def(owner, DeclName::from_atom(leaf.clone())))
1645 }
1646
1647 fn check_function_arity(
1651 function_ref: FunctionRef,
1652 got: usize,
1653 span: Span,
1654 ) -> Result<(), ExprLowerError> {
1655 let FunctionRef::Builtin(builtin) = function_ref;
1656 if builtin.special_kind().is_some_and(|kind| {
1657 matches!(
1658 kind,
1659 crate::registry::resolve_types::SpecialFnKind::Aggregation(_)
1660 )
1661 }) {
1662 return Ok(());
1663 }
1664 let Some(function) = crate::registry::builtins::builtin_functions().get(builtin.as_str())
1665 else {
1666 return Ok(());
1667 };
1668 if got != function.arity() {
1669 return Err(ExprLowerError::WrongArity {
1670 name: crate::syntax::names::FnName::new(builtin.as_str()),
1671 expected: function.arity(),
1672 got,
1673 span,
1674 });
1675 }
1676 Ok(())
1677 }
1678
1679 fn lower_function_ref(
1680 callee: &crate::syntax::ast::IdentPath,
1681 ) -> Result<FunctionRef, ExprLowerError> {
1682 let Some(ident) = callee.as_bare() else {
1683 return Err(ExprLowerError::UnknownFunction {
1684 path: callee.display_path(),
1685 span: callee.span(),
1686 });
1687 };
1688 BuiltinFnName::parse(ident.name.as_str())
1689 .map(FunctionRef::Builtin)
1690 .ok_or_else(|| ExprLowerError::UnknownFunction {
1691 path: callee.display_path(),
1692 span: callee.span(),
1693 })
1694 }
1695
1696 fn lower_map_entry(
1697 &mut self,
1698 entry: &ast::MapEntry,
1699 map_span: Span,
1700 ) -> Result<MapEntry, ExprLowerError> {
1701 let keys = entry
1702 .keys
1703 .iter()
1704 .map(|key| self.lower_map_entry_key(key, map_span))
1705 .collect::<Result<Vec<_>, _>>()?;
1706 let mut keys = keys.into_iter();
1707 let Some(first) = keys.next() else {
1708 return Err(ExprLowerError::EmptyMapEntry {
1709 span: entry.value.span,
1710 });
1711 };
1712 Ok(MapEntry {
1713 keys: NonEmpty::new(first, keys.collect()),
1714 value: self.lower_expr(&entry.value),
1715 })
1716 }
1717
1718 fn lower_map_entry_key(
1719 &self,
1720 key: &ast::MapEntryKey,
1721 map_span: Span,
1722 ) -> Result<MapEntryKey, ExprLowerError> {
1723 match &key.index.value {
1724 crate::syntax::ast::MapEntryIndex::Named(index_path) => {
1725 let variant = self
1726 .resolve_index_variant_parts(
1727 index_path,
1728 &key.variant.value,
1729 key.index.span,
1730 key.variant.span,
1731 )
1732 .map_err(|err| match err {
1733 ExprLowerError::ModuleResolve {
1734 source: ModuleResolveError::UnknownIndexVariant { index, variant },
1735 ..
1736 } => ExprLowerError::ExtraMapVariant {
1737 index_name: index.to_unowned_def_name(),
1738 variant_name: variant,
1739 span: map_span,
1740 },
1741 err => err,
1742 })?;
1743 Ok(MapEntryKey::IndexVariant(IndexVariantRef {
1744 variant,
1745 index_span: Some(key.index.span),
1746 variant_span: key.variant.span,
1747 }))
1748 }
1749 crate::syntax::ast::MapEntryIndex::NatRange(size) => Ok(MapEntryKey::NatRangeVariant {
1750 size: *size,
1751 variant: key.variant.clone(),
1752 }),
1753 }
1754 }
1755
1756 fn lower_for_binding(
1757 &mut self,
1758 binding: &ast::ForBinding,
1759 ) -> Result<ForBinding, ExprLowerError> {
1760 let local = self.allocate_local(binding.var.value.clone(), binding.var.span)?;
1761 let index = match &binding.index {
1762 ast::ForBindingIndex::Named(index) => {
1763 let resolved = self
1764 .ctx
1765 .resolver
1766 .resolve_index_path(self.ctx.owner, &index.value)
1767 .map_err(|source| ExprLowerError::ModuleResolve {
1768 source,
1769 span: index.span,
1770 })?;
1771 ForBindingIndex::Named(Spanned::new(resolved, index.span))
1772 }
1773 ast::ForBindingIndex::Range { arg, span } => ForBindingIndex::Range {
1774 arg: lower_nat_expr(arg, self.ctx.type_context())?,
1775 span: *span,
1776 },
1777 };
1778 Ok(ForBinding { local, index })
1779 }
1780
1781 fn lower_index_arg(&mut self, arg: &ast::IndexArg) -> Result<IndexArg, ExprLowerError> {
1782 match arg {
1783 ast::IndexArg::Variant { index, variant } => {
1784 let resolved = self.resolve_index_variant_parts(
1785 &index.value,
1786 &variant.value,
1787 index.span,
1788 variant.span,
1789 )?;
1790 Ok(IndexArg::Variant(IndexVariantRef {
1791 variant: resolved,
1792 index_span: Some(index.span),
1793 variant_span: variant.span,
1794 }))
1795 }
1796 ast::IndexArg::Var(ident) => Ok(IndexArg::Var(Spanned::new(
1797 self.lookup_local(&LocalName::from_atom(ident.name.clone()), ident.span)?,
1798 ident.span,
1799 ))),
1800 ast::IndexArg::Expr(expr) => Ok(IndexArg::Expr(Box::new(self.lower_expr(expr)))),
1801 }
1802 }
1803
1804 fn lower_match_arm(&mut self, arm: &ast::MatchArm) -> Result<MatchArm, ExprLowerError> {
1805 let pattern = self.lower_match_pattern(&arm.pattern)?;
1806 self.push_scope(pattern.bound_locals())?;
1807 let body = self.lower_expr(&arm.body);
1808 self.pop_scope();
1809 Ok(MatchArm {
1810 pattern,
1811 body,
1812 span: arm.span,
1813 })
1814 }
1815
1816 fn lower_match_pattern(
1817 &mut self,
1818 pattern: &ast::MatchPattern,
1819 ) -> Result<MatchPattern, ExprLowerError> {
1820 match pattern {
1821 ast::MatchPattern::Constructor {
1822 name,
1823 bindings,
1824 span,
1825 } => Ok(MatchPattern::Constructor {
1826 constructor: Spanned::new(
1827 self.ctx
1828 .resolver
1829 .resolve_constructor_path(
1830 self.ctx.owner,
1831 &NamePath::local(name.value.atom().clone()),
1832 )
1833 .map_err(|source| ExprLowerError::ModuleResolve {
1834 source,
1835 span: name.span,
1836 })?,
1837 name.span,
1838 ),
1839 bindings: bindings
1840 .iter()
1841 .map(|binding| self.lower_pattern_binding(binding))
1842 .collect::<Result<Vec<_>, _>>()?,
1843 span: *span,
1844 }),
1845 ast::MatchPattern::IndexLabel {
1846 index,
1847 variant,
1848 span,
1849 } => {
1850 let resolved = self.resolve_index_variant_parts(
1851 &index.value,
1852 &variant.value,
1853 index.span,
1854 variant.span,
1855 )?;
1856 Ok(MatchPattern::IndexLabel {
1857 variant: IndexVariantRef {
1858 variant: resolved,
1859 index_span: Some(index.span),
1860 variant_span: variant.span,
1861 },
1862 span: *span,
1863 })
1864 }
1865 ast::MatchPattern::Path {
1866 path,
1867 bindings,
1868 span,
1869 } => self.lower_path_pattern(path, bindings, *span),
1870 }
1871 }
1872
1873 fn lower_path_pattern(
1874 &mut self,
1875 path: &crate::syntax::ast::IdentPath,
1876 bindings: &[ast::PatternBinding],
1877 span: Span,
1878 ) -> Result<MatchPattern, ExprLowerError> {
1879 let name_path = path.to_name_path();
1880 if bindings.is_empty()
1881 && let Ok(variant) = self
1882 .ctx
1883 .resolver
1884 .resolve_index_variant_path(self.ctx.owner, &name_path)
1885 {
1886 let (qualifier, member) = path.split_last();
1887 let index_span = qualifier.split_first().map(|(first, rest)| {
1888 rest.iter()
1889 .fold(first.span, |merged, segment| merged.merge(segment.span))
1890 });
1891 return Ok(MatchPattern::IndexLabel {
1892 variant: IndexVariantRef {
1893 variant,
1894 index_span,
1895 variant_span: member.span,
1896 },
1897 span,
1898 });
1899 }
1900
1901 match self
1902 .ctx
1903 .resolver
1904 .resolve_constructor_path(self.ctx.owner, &name_path)
1905 {
1906 Ok(constructor) => Ok(MatchPattern::Constructor {
1907 constructor: Spanned::new(constructor, path.span()),
1908 bindings: bindings
1909 .iter()
1910 .map(|binding| self.lower_pattern_binding(binding))
1911 .collect::<Result<Vec<_>, _>>()?,
1912 span,
1913 }),
1914 Err(source) => match source {
1915 ModuleResolveError::UnknownName { .. }
1916 | ModuleResolveError::UnknownModuleAlias { .. }
1917 | ModuleResolveError::UnknownModule { .. } => Err(ExprLowerError::UnknownPattern {
1918 path: path.display_path(),
1919 span,
1920 }),
1921 source => Err(ExprLowerError::ModuleResolve { source, span }),
1922 },
1923 }
1924 }
1925
1926 fn lower_pattern_binding(
1927 &mut self,
1928 binding: &ast::PatternBinding,
1929 ) -> Result<PatternBinding, ExprLowerError> {
1930 match binding {
1931 ast::PatternBinding::Bind { field, var } => Ok(PatternBinding::Bind {
1932 field: field.clone(),
1933 local: self.allocate_local(LocalName::from_atom(var.name.clone()), var.span)?,
1934 }),
1935 ast::PatternBinding::Wildcard { field, span } => Ok(PatternBinding::Wildcard {
1936 field: field.clone(),
1937 span: *span,
1938 }),
1939 }
1940 }
1941
1942 fn resolve_index_variant_parts(
1943 &self,
1944 index_path: &NamePath,
1945 variant: &IndexVariantName,
1946 index_span: Span,
1947 variant_span: Span,
1948 ) -> Result<ResolvedIndexVariant, ExprLowerError> {
1949 self.ctx
1950 .resolver
1951 .resolve_index_variant_parts(self.ctx.owner, index_path, variant)
1952 .map_err(|source| {
1953 let span = match source {
1954 ModuleResolveError::UnknownIndexVariant { .. } => variant_span,
1955 _ => index_span,
1956 };
1957 ExprLowerError::ModuleResolve { source, span }
1958 })
1959 }
1960
1961 fn allocate_local(&mut self, name: LocalName, span: Span) -> Result<LocalDef, ExprLowerError> {
1962 let id = LocalId(self.next_local);
1963 let Some(next_local) = self.next_local.checked_add(1) else {
1964 return Err(ExprLowerError::TooManyLocals { span });
1965 };
1966 self.next_local = next_local;
1967 Ok(LocalDef { id, name, span })
1968 }
1969
1970 fn push_scope(&mut self, bindings: Vec<LocalDef>) -> Result<(), ExprLowerError> {
1971 let mut scope = HashMap::new();
1972 for binding in bindings {
1973 if let Some(first) = scope.insert(binding.name.clone(), binding.clone()) {
1974 return Err(ExprLowerError::DuplicateLocalBinding {
1975 name: binding.name,
1976 first: first.span,
1977 duplicate: binding.span,
1978 });
1979 }
1980 }
1981 self.local_scopes.push(scope);
1982 Ok(())
1983 }
1984
1985 fn pop_scope(&mut self) {
1986 self.local_scopes.pop();
1987 }
1988
1989 fn lookup_local(&self, name: &LocalName, span: Span) -> Result<LocalId, ExprLowerError> {
1990 self.local_scopes
1991 .iter()
1992 .rev()
1993 .find_map(|scope| scope.get(name.as_str()))
1994 .map(|def| def.id)
1995 .ok_or_else(|| ExprLowerError::UnknownLocalRef {
1996 name: name.clone(),
1997 span,
1998 })
1999 }
2000}
2001
2002fn scoped_name_to_path(name: &ScopedName, span: Span) -> Result<NamePath, ExprLowerError> {
2003 let qualifier = name
2004 .qualifier()
2005 .iter()
2006 .map(|segment| parse_atom(segment, span))
2007 .collect::<Result<Vec<_>, _>>()?;
2008 let leaf = parse_atom(name.member(), span)?;
2009 Ok(NamePath::qualified_path(qualifier, leaf))
2010}
2011
2012fn parse_atom(segment: &str, span: Span) -> Result<NameAtom, ExprLowerError> {
2013 NameAtom::parse(segment).map_err(|source| ExprLowerError::InvalidScopedNameSegment {
2014 segment: segment.to_string(),
2015 source,
2016 span,
2017 })
2018}
2019
2020#[cfg(test)]
2021mod tests {
2022 use super::*;
2023 use crate::syntax::parser::Parser;
2024
2025 fn desugared_source(source: &str) -> ast::File {
2026 let raw = Parser::new(source).parse_file().unwrap();
2027 crate::syntax::desugar::desugar_multi_decls_in_file(raw)
2028 }
2029
2030 #[test]
2031 fn local_env_layers_frames_without_cloning() {
2032 let a = LocalId(0);
2033 let b = LocalId(1);
2034 let c = LocalId(2);
2035
2036 let root: LocalEnv<'_, i32> = LocalEnv::root();
2037 assert_eq!(root.get(a), None);
2038
2039 let outer = root.child(vec![(a, 1)]);
2040 assert_eq!(outer.get(a), Some(&1));
2041 assert_eq!(outer.get(b), None);
2042
2043 let inner = outer.child(vec![(b, 2)]);
2044 assert_eq!(inner.get(a), Some(&1));
2045 assert_eq!(inner.get(b), Some(&2));
2046
2047 assert_eq!(outer.get(b), None);
2049
2050 let seeded = LocalEnv::from_bindings(vec![(c, 7)]);
2051 assert_eq!(seeded.get(c), Some(&7));
2052 }
2053
2054 #[test]
2055 fn local_env_bind_rebinds_in_place() {
2056 let a = LocalId(0);
2057 let b = LocalId(1);
2058 let root: LocalEnv<'_, i32> = LocalEnv::root();
2059 let mut frame = root.child(Vec::new());
2060
2061 for value in 0..3 {
2063 frame.bind(a, value);
2064 assert_eq!(frame.get(a), Some(&value));
2065 }
2066 frame.bind(b, 10);
2067 assert_eq!(frame.get(a), Some(&2));
2068 assert_eq!(frame.get(b), Some(&10));
2069 }
2070
2071 fn node_value<'a>(file: &'a ast::File, name: &str) -> &'a ast::Expr {
2072 file.declarations
2073 .iter()
2074 .find_map(|decl| match &decl.kind {
2075 ast::DeclKind::Node(node) if node.name.value.as_str() == name => Some(&node.value),
2076 _ => None,
2077 })
2078 .expect("source should contain requested node")
2079 }
2080
2081 fn resolver_with_import(
2082 lib_id: &DagId,
2083 main_id: &DagId,
2084 lib: &ast::File,
2085 main: &ast::File,
2086 ) -> ModuleResolver {
2087 let mut resolver = ModuleResolver::default();
2088 resolver
2089 .add_module(lib_id.clone(), &lib.declarations)
2090 .unwrap();
2091 resolver
2092 .add_module(main_id.clone(), &main.declarations)
2093 .unwrap();
2094 for decl in &main.declarations {
2095 let ast::DeclKind::Import(import) = &decl.kind else {
2096 continue;
2097 };
2098 resolver
2099 .register_import(main_id, &import.path, &import.kind, lib_id)
2100 .unwrap();
2101 }
2102 resolver
2103 }
2104
2105 #[test]
2106 fn lowers_qualified_index_variant_literal_to_canonical_owner() {
2107 let lib_id = DagId::root("lib");
2108 let main_id = DagId::root("main");
2109 let lib = desugared_source("pub index Phase = { Burn, Coast };");
2110 let main_source = "import lib as mission; node phase: Dimensionless = mission.Phase.Burn;";
2111 let main = desugared_source(main_source);
2112 let resolver = resolver_with_import(&lib_id, &main_id, &lib, &main);
2113 let scope = GenericScope::new();
2114
2115 let expr = lower_expr(
2116 node_value(&main, "phase"),
2117 ExprLoweringContext::new(&main_id, &resolver, &scope),
2118 )
2119 .unwrap();
2120
2121 let ExprKind::VariantLiteral(variant) = expr.kind else {
2122 panic!("expected variant literal, got {expr:?}");
2123 };
2124 assert_eq!(variant.variant.index().owner(), &lib_id);
2125 assert_eq!(variant.variant.index().as_str(), "Phase");
2126 assert_eq!(variant.variant.variant().as_str(), "Burn");
2127 let slice = |span: crate::syntax::span::Span| {
2129 &main_source[span.offset()..span.offset() + span.len()]
2130 };
2131 assert_eq!(slice(variant.variant_span), "Burn");
2132 assert_eq!(
2133 slice(variant.index_span.expect("written index path")),
2134 "mission.Phase"
2135 );
2136 }
2137
2138 #[test]
2139 fn lowers_qualified_nullary_constructor_const_ref_to_canonical_owner() {
2140 let lib_id = DagId::root("lib");
2141 let main_id = DagId::root("main");
2142 let lib = desugared_source("pub type BurnKind { Impulsive, Coast }");
2143 let main = desugared_source(
2144 "import lib as mission; node burn: Dimensionless = mission.Impulsive;",
2145 );
2146 let resolver = resolver_with_import(&lib_id, &main_id, &lib, &main);
2147 let scope = GenericScope::new();
2148
2149 let expr = lower_expr(
2150 node_value(&main, "burn"),
2151 ExprLoweringContext::new(&main_id, &resolver, &scope),
2152 )
2153 .unwrap();
2154
2155 let ExprKind::ConstRef(target) = expr.kind else {
2156 panic!("expected const-like ref, got {expr:?}");
2157 };
2158 let ConstRef::Constructor(constructor) = target.value else {
2159 panic!("expected constructor, got {target:?}");
2160 };
2161 assert_eq!(constructor.owner(), &lib_id);
2162 assert_eq!(constructor.as_str(), "Impulsive");
2163 }
2164
2165 #[test]
2166 fn lowers_for_locals_to_lexical_ids() {
2167 let owner = DagId::root("main");
2168 let file = desugared_source(
2169 "index Phase = { Burn }; node x: Dimensionless[Phase] = for p: Phase { p };",
2170 );
2171 let mut resolver = ModuleResolver::default();
2172 resolver
2173 .add_module(owner.clone(), &file.declarations)
2174 .unwrap();
2175 let scope = GenericScope::new();
2176
2177 let expr = lower_expr(
2178 node_value(&file, "x"),
2179 ExprLoweringContext::new(&owner, &resolver, &scope),
2180 )
2181 .unwrap();
2182
2183 let ExprKind::ForComp { bindings, body } = expr.kind else {
2184 panic!("expected for comp, got {expr:?}");
2185 };
2186 let [binding] = bindings.as_slice() else {
2187 panic!("expected one binding, got {bindings:?}");
2188 };
2189 let ExprKind::LocalRef(local) = body.kind else {
2190 panic!("expected local ref, got {body:?}");
2191 };
2192 assert_eq!(binding.local.id, local.value);
2193 }
2194
2195 #[test]
2196 fn lowers_qualified_constructor_match_pattern_and_binding() {
2197 let lib_id = DagId::root("lib");
2198 let main_id = DagId::root("main");
2199 let lib =
2200 desugared_source("pub type BurnKind { Impulsive(delta_v: Dimensionless), Coast }");
2201 let main = desugared_source(
2202 "import lib as mission; param burn: Dimensionless; \
2203 node dv: Dimensionless = match @burn { mission.Impulsive(delta_v: dv) => dv, mission.Coast => 0.0 };",
2204 );
2205 let resolver = resolver_with_import(&lib_id, &main_id, &lib, &main);
2206 let scope = GenericScope::new();
2207
2208 let expr = lower_expr(
2209 node_value(&main, "dv"),
2210 ExprLoweringContext::new(&main_id, &resolver, &scope),
2211 )
2212 .unwrap();
2213
2214 let ExprKind::Match { arms, .. } = expr.kind else {
2215 panic!("expected match, got {expr:?}");
2216 };
2217 let [first, _second] = arms.as_slice() else {
2218 panic!("expected two arms, got {arms:?}");
2219 };
2220 let MatchPattern::Constructor {
2221 constructor,
2222 bindings,
2223 ..
2224 } = &first.pattern
2225 else {
2226 panic!("expected constructor pattern, got {:?}", first.pattern);
2227 };
2228 assert_eq!(constructor.value.owner(), &lib_id);
2229 assert_eq!(constructor.value.as_str(), "Impulsive");
2230 let [PatternBinding::Bind { local, .. }] = bindings.as_slice() else {
2231 panic!("expected one field binding, got {bindings:?}");
2232 };
2233 let ExprKind::LocalRef(body_ref) = &first.body.kind else {
2234 panic!("expected local ref body, got {:?}", first.body);
2235 };
2236 assert_eq!(local.id, body_ref.value);
2237 }
2238
2239 #[test]
2240 fn collects_canonical_decl_dependencies_from_hir_expr() {
2241 let lib_id = DagId::root("lib");
2242 let main_id = DagId::root("main");
2243 let lib =
2244 desugared_source("pub const node C: Dimensionless = 1.0; param p: Dimensionless;");
2245 let main = desugared_source(
2246 "import lib as mission; import lib.{p}; node x: Dimensionless = @p + mission.C;",
2247 );
2248 let resolver = resolver_with_import(&lib_id, &main_id, &lib, &main);
2249 let scope = GenericScope::new();
2250
2251 let expr = lower_expr(
2252 node_value(&main, "x"),
2253 ExprLoweringContext::new(&main_id, &resolver, &scope),
2254 )
2255 .unwrap();
2256 let deps = collect_expr_dependencies(&expr);
2257
2258 let graph_refs = deps.graph_refs.into_iter().collect::<Vec<_>>();
2259 let const_refs = deps.const_refs.into_iter().collect::<Vec<_>>();
2260 let [graph_ref] = graph_refs.as_slice() else {
2261 panic!("expected one graph dep, got {graph_refs:?}");
2262 };
2263 let [const_ref] = const_refs.as_slice() else {
2264 panic!("expected one const dep, got {const_refs:?}");
2265 };
2266 assert_eq!(graph_ref.owner(), &lib_id);
2267 assert_eq!(graph_ref.as_str(), "p");
2268 assert_eq!(const_ref.owner(), &lib_id);
2269 assert_eq!(const_ref.as_str(), "C");
2270 }
2271
2272 #[test]
2273 fn const_ref_to_runtime_decl_is_rejected_by_decl_kind() {
2274 let owner = DagId::root("main");
2275 let file = desugared_source("param p: Dimensionless; node x: Dimensionless = p;");
2276 let mut resolver = ModuleResolver::default();
2277 resolver
2278 .add_module(owner.clone(), &file.declarations)
2279 .unwrap();
2280 let scope = GenericScope::new();
2281
2282 let err = lower_expr(
2283 node_value(&file, "x"),
2284 ExprLoweringContext::new(&owner, &resolver, &scope),
2285 )
2286 .unwrap_err();
2287
2288 assert!(
2289 err.to_string()
2290 .contains("expected const declaration `main.p`, found param"),
2291 "unexpected error: {err}"
2292 );
2293 }
2294}