Skip to main content

graphcal_compiler/registry/
resolve_types.rs

1//! Data types and function classification constants used by the declaration-collection layer.
2//!
3//! These types have no dependency on the resolution logic itself, making them
4//! suitable for use across all compilation phases.
5
6use std::collections::{HashMap, HashSet};
7
8use crate::dag_id::DagId;
9use crate::desugar::desugared_ast::{AssertBody, Expr, FigureDecl, LayerDecl, PlotDecl};
10use crate::registry::declared_type::IndexTypeRef;
11use crate::syntax::names::{
12    DeclName, IndexName, IndexVariantName, NamePath, ResolvedIndexVariant, ScopedName,
13};
14use crate::syntax::span::Span;
15
16// ---------------------------------------------------------------------------
17// Function classification enums
18// ---------------------------------------------------------------------------
19//
20// Each special-function category has its own enum whose variants are the
21// canonical source of truth for the function names in that category.
22// The `classify_special_fn` function maps a string to one of these.
23
24/// Aggregation functions: operate on indexed collections.
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
26pub enum AggregationFn {
27    Sum,
28    Min,
29    Max,
30    Mean,
31    Count,
32}
33
34impl AggregationFn {
35    #[must_use]
36    pub fn parse(name: &str) -> Option<Self> {
37        match name {
38            "sum" => Some(Self::Sum),
39            "min" => Some(Self::Min),
40            "max" => Some(Self::Max),
41            "mean" => Some(Self::Mean),
42            "count" => Some(Self::Count),
43            _ => None,
44        }
45    }
46
47    #[must_use]
48    pub const fn as_str(self) -> &'static str {
49        match self {
50            Self::Sum => "sum",
51            Self::Min => "min",
52            Self::Max => "max",
53            Self::Mean => "mean",
54            Self::Count => "count",
55        }
56    }
57}
58
59/// Type conversion functions: `to_float`, `to_int`.
60#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum TypeConversionFn {
62    ToFloat,
63    ToInt,
64}
65
66impl TypeConversionFn {
67    #[must_use]
68    pub fn parse(name: &str) -> Option<Self> {
69        match name {
70            "to_float" => Some(Self::ToFloat),
71            "to_int" => Some(Self::ToInt),
72            _ => None,
73        }
74    }
75
76    #[must_use]
77    pub const fn as_str(self) -> &'static str {
78        match self {
79            Self::ToFloat => "to_float",
80            Self::ToInt => "to_int",
81        }
82    }
83}
84
85/// Datetime constructor functions.
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum ConstructorFn {
88    Datetime,
89    Epoch,
90}
91
92impl ConstructorFn {
93    #[must_use]
94    pub fn parse(name: &str) -> Option<Self> {
95        match name {
96            "datetime" => Some(Self::Datetime),
97            "epoch" => Some(Self::Epoch),
98            _ => None,
99        }
100    }
101
102    #[must_use]
103    pub const fn as_str(self) -> &'static str {
104        match self {
105            Self::Datetime => "datetime",
106            Self::Epoch => "epoch",
107        }
108    }
109}
110
111/// Datetime extraction functions: extract a component from a `Datetime`.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum DatetimeExtractFn {
114    Year,
115    Month,
116    Day,
117    Hour,
118    Minute,
119    Second,
120    Weekday,
121    DayOfYear,
122}
123
124impl DatetimeExtractFn {
125    #[must_use]
126    pub fn parse(name: &str) -> Option<Self> {
127        match name {
128            "year" => Some(Self::Year),
129            "month" => Some(Self::Month),
130            "day" => Some(Self::Day),
131            "hour" => Some(Self::Hour),
132            "minute" => Some(Self::Minute),
133            "second" => Some(Self::Second),
134            "weekday" => Some(Self::Weekday),
135            "day_of_year" => Some(Self::DayOfYear),
136            _ => None,
137        }
138    }
139
140    #[must_use]
141    pub const fn as_str(self) -> &'static str {
142        match self {
143            Self::Year => "year",
144            Self::Month => "month",
145            Self::Day => "day",
146            Self::Hour => "hour",
147            Self::Minute => "minute",
148            Self::Second => "second",
149            Self::Weekday => "weekday",
150            Self::DayOfYear => "day_of_year",
151        }
152    }
153}
154
155/// Datetime-from-numeric constructors.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum DatetimeFromFn {
158    FromJd,
159    FromMjd,
160    FromUnix,
161}
162
163impl DatetimeFromFn {
164    #[must_use]
165    pub fn parse(name: &str) -> Option<Self> {
166        match name {
167            "from_jd" => Some(Self::FromJd),
168            "from_mjd" => Some(Self::FromMjd),
169            "from_unix" => Some(Self::FromUnix),
170            _ => None,
171        }
172    }
173
174    #[must_use]
175    pub const fn as_str(self) -> &'static str {
176        match self {
177            Self::FromJd => "from_jd",
178            Self::FromMjd => "from_mjd",
179            Self::FromUnix => "from_unix",
180        }
181    }
182}
183
184/// Datetime-to-numeric functions.
185#[derive(Debug, Clone, Copy, PartialEq, Eq)]
186pub enum DatetimeToFn {
187    ToJd,
188    ToMjd,
189    ToUnix,
190}
191
192impl DatetimeToFn {
193    #[must_use]
194    pub fn parse(name: &str) -> Option<Self> {
195        match name {
196            "to_jd" => Some(Self::ToJd),
197            "to_mjd" => Some(Self::ToMjd),
198            "to_unix" => Some(Self::ToUnix),
199            _ => None,
200        }
201    }
202
203    #[must_use]
204    pub const fn as_str(self) -> &'static str {
205        match self {
206            Self::ToJd => "to_jd",
207            Self::ToMjd => "to_mjd",
208            Self::ToUnix => "to_unix",
209        }
210    }
211}
212
213/// Classification of special built-in functions.
214///
215/// Each variant carries a sub-enum identifying the specific function, so
216/// downstream handlers can match on typed variants instead of raw strings.
217#[derive(Debug, Clone, Copy, PartialEq, Eq)]
218pub enum SpecialFnKind {
219    /// Aggregation functions: `sum`, `min`, `max`, `mean`, `count`.
220    Aggregation(AggregationFn),
221    /// Type conversion functions: `to_float`, `to_int`.
222    TypeConversion(TypeConversionFn),
223    /// Time-scale conversion functions: `to_utc`, `to_tai`, etc.
224    TimeScaleConversion(crate::registry::time_scale::TimeScale),
225    /// Constructor functions: `datetime`, `epoch`.
226    Constructor(ConstructorFn),
227    /// Datetime extraction functions: `year`, `month`, `day`, etc.
228    DatetimeExtract(DatetimeExtractFn),
229    /// Datetime-from-numeric functions: `from_jd`, `from_mjd`, `from_unix`.
230    DatetimeFrom(DatetimeFromFn),
231    /// Datetime-to-numeric functions: `to_jd`, `to_mjd`, `to_unix`.
232    DatetimeTo(DatetimeToFn),
233}
234
235/// Classify a function name as a special built-in function.
236///
237/// Returns `None` if the name is not a recognized special function.
238#[must_use]
239pub fn classify_special_fn(name: &str) -> Option<SpecialFnKind> {
240    if let Some(f) = AggregationFn::parse(name) {
241        return Some(SpecialFnKind::Aggregation(f));
242    }
243    if let Some(f) = TypeConversionFn::parse(name) {
244        return Some(SpecialFnKind::TypeConversion(f));
245    }
246    if let Some(scale) = crate::registry::time_scale::time_scale_from_conversion_fn(name) {
247        return Some(SpecialFnKind::TimeScaleConversion(scale));
248    }
249    if let Some(f) = ConstructorFn::parse(name) {
250        return Some(SpecialFnKind::Constructor(f));
251    }
252    if let Some(f) = DatetimeExtractFn::parse(name) {
253        return Some(SpecialFnKind::DatetimeExtract(f));
254    }
255    if let Some(f) = DatetimeFromFn::parse(name) {
256        return Some(SpecialFnKind::DatetimeFrom(f));
257    }
258    if let Some(f) = DatetimeToFn::parse(name) {
259        return Some(SpecialFnKind::DatetimeTo(f));
260    }
261    None
262}
263
264/// Returns `true` if `name` is a built-in aggregation function (`sum`, `min`, etc.).
265#[must_use]
266pub fn is_aggregation_fn(name: &str) -> bool {
267    AggregationFn::parse(name).is_some()
268}
269
270/// Returns `true` if `name` is a time scale identifier (`UTC`, `TT`, `TAI`, etc.).
271#[must_use]
272pub fn is_time_scale_name(name: &str) -> bool {
273    crate::registry::time_scale::TimeScale::ALL_NAMES.contains(&name)
274}
275
276// ---------------------------------------------------------------------------
277// Types
278// ---------------------------------------------------------------------------
279
280/// Pre-evaluated value bindings imported from already-evaluated dependency files.
281///
282/// Unlike `ImportedNames` which carries AST expressions, this carries
283/// evaluated values. Used in per-file evaluation where each file is
284/// compiled and evaluated independently.
285#[derive(Debug, Default, Clone)]
286pub struct ImportedValueNames {
287    /// Imported const names (for scope checking only — actual values are in the exec plan).
288    pub const_names: Vec<(ScopedName, Span)>,
289    /// Imported param names.
290    pub param_names: Vec<(ScopedName, Span)>,
291    /// Imported node names.
292    pub node_names: Vec<(ScopedName, Span)>,
293    /// Imported assert names (for `#[assumes]` validation).
294    pub assert_names: Vec<(DeclName, Span)>,
295    /// Plot aliases requested by include brace lists (#847). Registered in
296    /// the value namespace for collision checking and recorded on the DAG so
297    /// figures/layers can reference them.
298    pub plot_names: Vec<(ScopedName, Span)>,
299}
300
301/// The kind of a declaration (used for source-order tracking).
302#[derive(Debug, Clone, Copy)]
303pub enum DeclCategory {
304    Const,
305    Param,
306    Node,
307    Assert,
308    Plot,
309    Figure,
310    Layer,
311}
312
313impl std::fmt::Display for DeclCategory {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        match self {
316            Self::Const => write!(f, "const"),
317            Self::Param => write!(f, "param"),
318            Self::Node => write!(f, "node"),
319            Self::Assert => write!(f, "assert"),
320            Self::Plot => write!(f, "plot"),
321            Self::Figure => write!(f, "figure"),
322            Self::Layer => write!(f, "layer"),
323        }
324    }
325}
326
327// ---------------------------------------------------------------------------
328// Entry types for resolved declarations
329// ---------------------------------------------------------------------------
330
331/// A resolved const declaration (before type annotation is added).
332#[derive(Debug)]
333pub struct ResolvedConstEntry {
334    pub name: String,
335    pub expr: Expr,
336    pub span: Span,
337}
338
339/// A resolved param declaration (before type annotation is added).
340#[derive(Debug)]
341pub struct ResolvedParamEntry {
342    pub name: String,
343    pub default_expr: Option<Expr>,
344    pub span: Span,
345}
346
347/// A resolved node declaration (before type annotation is added).
348#[derive(Debug)]
349pub struct ResolvedNodeEntry {
350    pub name: String,
351    pub expr: Expr,
352    pub span: Span,
353}
354
355/// A resolved assert declaration.
356#[derive(Debug)]
357pub struct ResolvedAssertEntry {
358    pub name: String,
359    pub body: AssertBody,
360    pub span: Span,
361}
362
363/// A resolved plot declaration.
364#[derive(Debug)]
365pub struct ResolvedPlotEntry {
366    pub name: String,
367    pub decl: PlotDecl,
368    pub span: Span,
369}
370
371/// A resolved figure declaration.
372#[derive(Debug)]
373pub struct ResolvedFigureEntry {
374    pub name: String,
375    pub decl: FigureDecl,
376    pub span: Span,
377}
378
379/// A resolved layer declaration.
380#[derive(Debug)]
381pub struct ResolvedLayerEntry {
382    pub name: String,
383    pub decl: LayerDecl,
384    pub span: Span,
385}
386
387/// One axis segment in a per-variant `#[expected_fail(...)]` key.
388#[derive(Debug, Clone, PartialEq, Eq)]
389pub enum ExpectedFailKeyPart {
390    /// An `Index.Variant` / `module.Index.Variant` segment for a named axis.
391    ///
392    /// The `index` carrier is the semantic key used by runtime assertion
393    /// checks. Before module-aware TIR resolution, `source_index_path`
394    /// preserves the structured syntax path. After resolution, `index`
395    /// carries the canonical owner used by runtime checks.
396    Named {
397        index: IndexTypeRef,
398        variant: IndexVariantName,
399        source_index_path: Option<NamePath>,
400        span: Span,
401    },
402    /// A `#N` segment for a Nat range axis (#816).
403    ///
404    /// Range axes have no source-level index name, so the axis identity is
405    /// positional: the segment binds to the assertion's axis at the same
406    /// tuple position, validated at dim-check time.
407    RangeStep { step: u64, span: Span },
408}
409
410impl ExpectedFailKeyPart {
411    fn unresolved_owner() -> DagId {
412        DagId::root("<expected-fail-unresolved>")
413    }
414
415    #[must_use]
416    pub fn unresolved(index_path: NamePath, variant: IndexVariantName, span: Span) -> Self {
417        Self::Named {
418            index: IndexTypeRef::with_owner(
419                Self::unresolved_owner(),
420                IndexName::from_atom(index_path.leaf().clone()),
421            ),
422            variant,
423            source_index_path: Some(index_path),
424            span,
425        }
426    }
427
428    #[must_use]
429    pub fn with_owner(
430        owner: DagId,
431        index: IndexName,
432        variant: IndexVariantName,
433        span: Span,
434    ) -> Self {
435        Self::Named {
436            index: IndexTypeRef::with_owner(owner, index),
437            variant,
438            source_index_path: None,
439            span,
440        }
441    }
442
443    #[must_use]
444    pub fn resolved(resolved: ResolvedIndexVariant, span: Span) -> Self {
445        let (index, variant) = resolved.into_parts();
446        Self::Named {
447            index: IndexTypeRef::from_resolved(index),
448            variant,
449            source_index_path: None,
450            span,
451        }
452    }
453
454    #[must_use]
455    pub fn with_resolved_variant(&self, resolved: ResolvedIndexVariant) -> Self {
456        Self::resolved(resolved, self.span())
457    }
458
459    /// The source span of this key segment.
460    #[must_use]
461    pub const fn span(&self) -> Span {
462        match self {
463            Self::Named { span, .. } | Self::RangeStep { span, .. } => *span,
464        }
465    }
466
467    /// The named index reference, when this segment targets a named axis.
468    #[must_use]
469    pub const fn named_index(&self) -> Option<&IndexTypeRef> {
470        match self {
471            Self::Named { index, .. } => Some(index),
472            Self::RangeStep { .. } => None,
473        }
474    }
475
476    /// The structured syntax path awaiting resolution, for named segments.
477    #[must_use]
478    pub const fn source_index_path(&self) -> Option<&NamePath> {
479        match self {
480            Self::Named {
481                source_index_path, ..
482            } => source_index_path.as_ref(),
483            Self::RangeStep { .. } => None,
484        }
485    }
486
487    /// The variant key this segment selects within its axis.
488    #[must_use]
489    pub fn variant(&self) -> IndexVariantName {
490        match self {
491            Self::Named { variant, .. } => variant.clone(),
492            Self::RangeStep { step, .. } => IndexVariantName::range_step(*step),
493        }
494    }
495
496    /// Whether this segment selects the given entry of an indexed value.
497    ///
498    /// Named segments require the entry's index identity to match; `#N`
499    /// segments match the `#N` entry of any Nat range axis (the axis itself
500    /// was bound positionally at dim-check time).
501    #[must_use]
502    pub fn matches_entry(&self, index: &IndexTypeRef, variant: &IndexVariantName) -> bool {
503        match self {
504            Self::Named {
505                index: expected,
506                variant: expected_variant,
507                ..
508            } => expected.matches_ref(index) && variant == expected_variant,
509            Self::RangeStep { step, .. } => {
510                matches!(index, IndexTypeRef::NatRange(_))
511                    && *variant == IndexVariantName::range_step(*step)
512            }
513        }
514    }
515
516    /// Render this segment for diagnostics: `Index.Variant` or `#N`.
517    #[must_use]
518    pub fn display(&self) -> String {
519        match self {
520            Self::Named { index, variant, .. } => format!("{}.{variant}", index.display_name()),
521            Self::RangeStep { step, .. } => format!("#{step}"),
522        }
523    }
524}
525
526/// A single expected-fail key: a list of index/variant pairs.
527///
528/// - Length 1 for single-index assertions: `[Mode.Boost]`
529/// - Length >1 for multi-index assertions: `[(Mode.Boost, Phase.Launch)]`
530pub type ExpectedFailKey = Vec<ExpectedFailKeyPart>;
531
532/// Describes how an assertion is expected to fail.
533#[derive(Debug, Clone)]
534pub enum ExpectedFail {
535    /// The entire assertion is expected to fail: `#[expected_fail]`.
536    All,
537    /// Specific index keys are expected to fail: `#[expected_fail(Index.Variant, ...)]`.
538    Variants(Vec<ExpectedFailKey>),
539}
540
541/// The result of declaration collection: declarations separated by category.
542#[derive(Debug)]
543pub struct ResolvedFile {
544    /// Const declarations in source order.
545    pub consts: Vec<ResolvedConstEntry>,
546    /// Param declarations in source order.
547    pub params: Vec<ResolvedParamEntry>,
548    /// Node declarations in source order.
549    pub nodes: Vec<ResolvedNodeEntry>,
550    /// Assert declarations in source order.
551    pub asserts: Vec<ResolvedAssertEntry>,
552    /// Plot declarations in source order.
553    pub plots: Vec<ResolvedPlotEntry>,
554    /// Figure declarations in source order.
555    pub figures: Vec<ResolvedFigureEntry>,
556    /// Layer declarations in source order.
557    pub layers: Vec<ResolvedLayerEntry>,
558    /// All declaration names in source order with their category.
559    pub source_order: Vec<(DeclName, DeclCategory)>,
560    /// Set of all assert names (for checking `@assert_name` errors).
561    pub assert_names: HashSet<DeclName>,
562    /// Mapping from assert name to the list of declarations that assume it.
563    /// Built from `#[assumes(...)]` attributes.
564    pub assumes_map: HashMap<DeclName, Vec<DeclName>>,
565    /// Mapping from assert name to its expected-fail configuration.
566    /// Built from `#[expected_fail]` / `#[expected_fail(...)]` attributes.
567    pub expected_fail: HashMap<DeclName, ExpectedFail>,
568    /// Plot names carrying `#[hidden]`: evaluated and referenceable from
569    /// figures/layers, but excluded from standalone output (#847).
570    pub hidden_plots: HashSet<DeclName>,
571    /// Names of all declarations marked `pub` in this file (values + type-system).
572    pub pub_names: HashSet<DeclName>,
573}