Skip to main content

graphcal_compiler/registry/
declared_type.rs

1//! Declared type of a const/param/node.
2
3use crate::dag_id::DagId;
4use crate::syntax::dimension::Dimension;
5use crate::syntax::names::{NameDef, NameNamespace, ResolvedName, namespace};
6
7use crate::registry::time_scale::TimeScale;
8use crate::registry::types::{DimensionRegistry, NatRangeIndex, NatRangeIndexError};
9use crate::syntax::nat::NatPolyForm;
10
11/// A type-level reference to a named compiler entity.
12///
13/// Every semantic type reference has a canonical owner. Leaf-only names belong
14/// at syntax/display boundaries; once a value crosses into the functional core,
15/// it must carry a [`ResolvedName`].
16#[derive(Debug, Clone, PartialEq, Eq, Hash)]
17pub struct TypeNameRef<Ns: NameNamespace> {
18    name: NameDef<Ns>,
19    resolved: ResolvedName<Ns>,
20}
21
22impl<Ns: NameNamespace> TypeNameRef<Ns> {
23    /// Create a module-aware reference from a canonical resolved name.
24    #[must_use]
25    pub fn from_resolved(resolved: ResolvedName<Ns>) -> Self {
26        Self {
27            name: resolved.to_unowned_def_name(),
28            resolved,
29        }
30    }
31
32    /// Create a reference with a display leaf that differs from the canonical
33    /// owner-qualified identity.
34    ///
35    /// This is used at value-display boundaries such as tagged-union
36    /// constructor values, where the semantic type is the owning union but the
37    /// rendered value should show the constructor leaf.
38    #[must_use]
39    pub const fn with_display_leaf(name: NameDef<Ns>, resolved: ResolvedName<Ns>) -> Self {
40        Self { name, resolved }
41    }
42
43    /// Resolve a definition-site leaf into the given owner.
44    #[must_use]
45    pub fn with_owner(owner: DagId, name: NameDef<Ns>) -> Self {
46        Self::from_resolved(ResolvedName::from_def(owner, name))
47    }
48
49    /// The leaf definition name used by registries and diagnostics.
50    #[must_use]
51    pub const fn name(&self) -> &NameDef<Ns> {
52        &self.name
53    }
54
55    /// The canonical owner/name identity.
56    #[must_use]
57    pub const fn resolved(&self) -> &ResolvedName<Ns> {
58        &self.resolved
59    }
60
61    /// Compare this reference against another type reference by owner-qualified identity.
62    #[must_use]
63    pub fn matches_ref(&self, other: &Self) -> bool {
64        self.resolved() == other.resolved()
65    }
66
67    /// Borrow the leaf string for diagnostic/display-only formatting.
68    #[must_use]
69    pub fn as_str(&self) -> &str {
70        self.name.as_str()
71    }
72
73    /// Clone the leaf definition name for diagnostic/display boundaries.
74    #[must_use]
75    pub fn to_unowned_name(&self) -> NameDef<Ns> {
76        self.name.clone()
77    }
78}
79
80impl<Ns: NameNamespace> From<ResolvedName<Ns>> for TypeNameRef<Ns> {
81    fn from(resolved: ResolvedName<Ns>) -> Self {
82        Self::from_resolved(resolved)
83    }
84}
85
86impl<Ns: NameNamespace> std::fmt::Display for TypeNameRef<Ns> {
87    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
88        self.name.fmt(f)
89    }
90}
91
92/// Type-level reference to a compiler-generated Nat range index.
93///
94/// Concrete Nat ranges carry a validated in-memory size. Symbolic Nat ranges
95/// carry the normalized type-level arithmetic form directly; no fake resolved
96/// name or parseable display string is used as semantic identity.
97#[derive(Debug, Clone, PartialEq, Eq, Hash)]
98pub enum NatRangeIndexRef {
99    Concrete(NatRangeIndex),
100    Symbolic(NatPolyForm),
101}
102
103impl NatRangeIndexRef {
104    /// Create a Nat range reference from a normalized Nat form.
105    ///
106    /// # Errors
107    ///
108    /// Returns an error when the form is a concrete invalid Nat range size.
109    pub fn from_form(form: NatPolyForm) -> Result<Self, NatRangeIndexError> {
110        if form.is_constant() {
111            NatRangeIndex::try_from_u64(form.constant()).map(Self::Concrete)
112        } else {
113            Ok(Self::Symbolic(form))
114        }
115    }
116
117    /// Return the concrete Nat range identity, if this reference is concrete.
118    #[must_use]
119    pub const fn concrete_index(&self) -> Option<NatRangeIndex> {
120        match self {
121            Self::Concrete(index) => Some(*index),
122            Self::Symbolic(_) => None,
123        }
124    }
125
126    /// Return the normalized Nat form for this reference.
127    #[must_use]
128    pub fn form(&self) -> NatPolyForm {
129        match self {
130            Self::Concrete(index) => NatPolyForm::from_constant(index.size_u64()),
131            Self::Symbolic(form) => form.clone(),
132        }
133    }
134
135    /// Render this Nat range as a source-like display name for diagnostics.
136    #[must_use]
137    pub fn display_name(&self) -> NameDef<namespace::Index> {
138        match self {
139            Self::Concrete(index) => index.display_name(),
140            Self::Symbolic(form) => NameDef::new(format!("range({})", form.format())),
141        }
142    }
143
144    /// Compare Nat range references by typed identity.
145    #[must_use]
146    pub fn matches_ref(&self, other: &Self) -> bool {
147        match (self, other) {
148            (Self::Concrete(lhs), Self::Concrete(rhs)) => lhs == rhs,
149            (Self::Symbolic(lhs), Self::Symbolic(rhs)) => lhs == rhs,
150            _ => false,
151        }
152    }
153}
154
155/// Type-level reference to an index definition.
156///
157/// Declared indexes are owner-qualified names. Compiler-generated `range(N)`
158/// axes are typed Nat-range identities and do not have declared resolved names.
159#[derive(Debug, Clone, PartialEq, Eq, Hash)]
160pub enum IndexTypeRef {
161    Declared(TypeNameRef<namespace::Index>),
162    NatRange(NatRangeIndexRef),
163}
164
165impl IndexTypeRef {
166    /// Create a module-aware reference from a canonical resolved declared-index name.
167    #[must_use]
168    pub fn from_resolved(resolved: ResolvedName<namespace::Index>) -> Self {
169        Self::Declared(TypeNameRef::from_resolved(resolved))
170    }
171
172    /// Resolve a declared-index definition-site leaf into the given owner.
173    #[must_use]
174    pub fn with_owner(owner: DagId, name: NameDef<namespace::Index>) -> Self {
175        Self::Declared(TypeNameRef::with_owner(owner, name))
176    }
177
178    /// Create a reference with a display leaf that differs from the canonical
179    /// owner-qualified identity.
180    #[must_use]
181    pub const fn with_display_leaf(
182        name: NameDef<namespace::Index>,
183        resolved: ResolvedName<namespace::Index>,
184    ) -> Self {
185        Self::Declared(TypeNameRef::with_display_leaf(name, resolved))
186    }
187
188    /// Create a concrete compiler-generated Nat range reference.
189    #[must_use]
190    pub const fn from_nat_range(index: NatRangeIndex) -> Self {
191        Self::NatRange(NatRangeIndexRef::Concrete(index))
192    }
193
194    /// Create a Nat range reference from a normalized Nat form.
195    ///
196    /// # Errors
197    ///
198    /// Returns an error when the form is a concrete invalid Nat range size.
199    pub fn from_nat_range_form(form: NatPolyForm) -> Result<Self, NatRangeIndexError> {
200        NatRangeIndexRef::from_form(form).map(Self::NatRange)
201    }
202
203    /// Wrap an already validated Nat range reference.
204    #[must_use]
205    pub const fn from_nat_range_ref(reference: NatRangeIndexRef) -> Self {
206        Self::NatRange(reference)
207    }
208
209    /// The declared leaf name, when this is a declared index.
210    #[must_use]
211    pub const fn declared_name(&self) -> Option<&NameDef<namespace::Index>> {
212        match self {
213            Self::Declared(reference) => Some(reference.name()),
214            Self::NatRange(_) => None,
215        }
216    }
217
218    /// The canonical declared owner/name identity, when this is a declared index.
219    #[must_use]
220    pub const fn declared_resolved(&self) -> Option<&ResolvedName<namespace::Index>> {
221        match self {
222            Self::Declared(reference) => Some(reference.resolved()),
223            Self::NatRange(_) => None,
224        }
225    }
226
227    /// Return the Nat range reference, when this is a compiler-generated Nat range.
228    #[must_use]
229    pub const fn nat_range_ref(&self) -> Option<&NatRangeIndexRef> {
230        match self {
231            Self::Declared(_) => None,
232            Self::NatRange(reference) => Some(reference),
233        }
234    }
235
236    /// Return the typed concrete Nat range identity, if this reference has one.
237    #[must_use]
238    pub const fn nat_range(&self) -> Option<NatRangeIndex> {
239        match self {
240            Self::NatRange(reference) => reference.concrete_index(),
241            Self::Declared(_) => None,
242        }
243    }
244
245    /// Return the normalized Nat form, when this is a Nat range reference.
246    #[must_use]
247    pub fn nat_range_form(&self) -> Option<NatPolyForm> {
248        self.nat_range_ref().map(NatRangeIndexRef::form)
249    }
250
251    /// Render a display-only leaf name for diagnostics and formatting.
252    #[must_use]
253    pub fn display_name(&self) -> NameDef<namespace::Index> {
254        match self {
255            Self::Declared(reference) => reference.to_unowned_name(),
256            Self::NatRange(reference) => reference.display_name(),
257        }
258    }
259
260    /// Compare this reference against another type reference.
261    #[must_use]
262    pub fn matches_ref(&self, other: &Self) -> bool {
263        match (self, other) {
264            (Self::Declared(lhs), Self::Declared(rhs)) => lhs.matches_ref(rhs),
265            (Self::NatRange(lhs), Self::NatRange(rhs)) => lhs.matches_ref(rhs),
266            _ => false,
267        }
268    }
269
270    /// Clone the leaf definition/display name for diagnostic/display boundaries.
271    #[must_use]
272    pub fn to_unowned_name(&self) -> NameDef<namespace::Index> {
273        self.display_name()
274    }
275}
276
277impl From<ResolvedName<namespace::Index>> for IndexTypeRef {
278    fn from(resolved: ResolvedName<namespace::Index>) -> Self {
279        Self::from_resolved(resolved)
280    }
281}
282
283impl std::fmt::Display for IndexTypeRef {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        self.display_name().fmt(f)
286    }
287}
288
289/// Type-level reference to a struct/tagged-union definition.
290pub type StructTypeRef = TypeNameRef<namespace::StructType>;
291
292/// The declared type of a const/param/node: either a scalar with a dimension, a bool, or a struct.
293#[derive(Debug, Clone, PartialEq, Eq)]
294pub enum DeclaredType {
295    Scalar(Dimension),
296    Bool,
297    Int,
298    /// A datetime instant in a specific time scale. `Datetime(UTC)` is the default for civil use.
299    Datetime(TimeScale),
300    /// An index argument captured inside a generic struct instantiation.
301    ///
302    /// This is not a standalone value type; it is carried only as metadata for
303    /// generic type parameters constrained as `Index`.
304    IndexArg(IndexTypeRef),
305    /// A struct type, optionally with concrete type arguments for generic structs.
306    Struct(StructTypeRef, Vec<Self>),
307    Indexed {
308        element: Box<Self>,
309        index: IndexTypeRef,
310    },
311}
312
313impl DeclaredType {
314    /// Format as a human-readable string for diagnostics (e.g. `"Length / Time"`, `"Bool"`).
315    #[must_use]
316    pub fn format(&self, dims: &DimensionRegistry) -> String {
317        match self {
318            Self::Scalar(d) => dims.format_dimension(d),
319            Self::Bool => "Bool".to_string(),
320            Self::Int => "Int".to_string(),
321            Self::Datetime(scale) => {
322                if scale.is_utc() {
323                    "Datetime".to_string()
324                } else {
325                    format!("Datetime<{scale}>")
326                }
327            }
328            Self::IndexArg(index) => format!("index {index}"),
329            Self::Struct(name, args) => {
330                if args.is_empty() {
331                    name.to_string()
332                } else {
333                    let args_str: Vec<String> = args.iter().map(|a| a.format(dims)).collect();
334                    format!("{name}<{}>", args_str.join(", "))
335                }
336            }
337            Self::Indexed { element, index } => {
338                format!("{}[{index}]", element.format(dims))
339            }
340        }
341    }
342}