Skip to main content

graphcal_compiler/syntax/ast/
decl.rs

1use crate::syntax::ast::common::{
2    Attribute, BindableVisibility, ImportKind, ModulePath, Visibility,
3};
4use crate::syntax::ast::value::{
5    DimExpr, Expr, MapEntryKey, MultiDeclSharedAxes, ParamBinding, TypeExpr, UnitExpr,
6};
7use crate::syntax::names::{
8    ConstructorName, DeclName, DimName, FieldName, GenericParamName, IndexName, IndexVariantName,
9    PlotPropertyName, ScopedName, StructTypeName, UnitName,
10};
11use crate::syntax::phase::{Phase, Raw};
12use crate::syntax::span::{Span, Spanned};
13
14// ---------------------------------------------------------------------------
15// Raw-only sugar variants
16// ---------------------------------------------------------------------------
17
18/// Declaration-level sugar — only legal in [`Raw`].
19///
20/// Each variant corresponds to a surface declaration form that is rewritten
21/// into ordinary `DeclKind` variants by [`crate::desugar`]. After desugaring,
22/// `DeclKind::Sugar(_)` carries [`core::convert::Infallible`] and these variants vanish from
23/// the type system entirely.
24#[derive(Debug, Clone)]
25pub enum RawDeclSugar {
26    /// Multi-declaration (issue #481): N parallel slots sharing one
27    /// `table[…] {…}` initializer. Desugared into N separate
28    /// `DeclKind::{Param, Node, ConstNode}` declarations.
29    ///
30    /// Pinned to `MultiDecl<Raw>` because multi-decl is by definition a
31    /// raw-only construct — the desugar pass eliminates it.
32    Multi(MultiDecl<Raw>),
33}
34
35impl RawDeclSugar {
36    /// Returns the surface span of the sugar form.
37    #[must_use]
38    pub const fn span(&self) -> Span {
39        match self {
40            Self::Multi(m) => m.span,
41        }
42    }
43}
44/// A complete source file.
45///
46/// Generic over a [`Phase`] parameter that distinguishes the parser's raw
47/// AST (carrying surface sugar) from the desugared AST consumed by name
48/// resolution and below. Defaults to [`Raw`] so existing call sites — which
49/// always handle the parser output — keep compiling unchanged.
50#[derive(Debug, Clone)]
51pub struct File<P: Phase = Raw> {
52    pub declarations: Vec<Declaration<P>>,
53}
54/// A top-level declaration.
55#[derive(Debug, Clone)]
56pub struct Declaration<P: Phase = Raw> {
57    pub attributes: Vec<Attribute>,
58    pub kind: DeclKind<P>,
59    pub span: Span,
60}
61
62#[derive(Debug, Clone)]
63pub enum DeclKind<P: Phase = Raw> {
64    Param(ParamDecl<P>),
65    Node(NodeDecl<P>),
66    ConstNode(ConstNodeDecl<P>),
67    BaseDimension(BaseDimDecl),
68    Dimension(DimDecl),
69    Unit(UnitDecl<P>),
70    Type(TypeDecl<P>),
71    Index(IndexDecl<P>),
72    Import(ImportDecl),
73    Include(IncludeDecl<P>),
74    Dag(DagDecl<P>),
75    Assert(AssertDecl<P>),
76    Plot(PlotDecl<P>),
77    Figure(FigureDecl<P>),
78    Layer(LayerDecl<P>),
79    /// Phase-specific declaration sugar.
80    ///
81    /// In [`Raw`], this is [`crate::syntax::ast::RawDeclSugar`] and carries
82    /// surface forms like multi-decl (issue #481) that are eliminated by the
83    /// desugar pass. In [`Desugared`](Raw), the
84    /// payload is [`core::convert::Infallible`] — the variant is statically
85    /// unreachable, so post-desugar consumers handle it with
86    /// [`crate::syntax::phase::never`].
87    Sugar(P::DeclSugar),
88}
89
90impl<P: Phase> DeclKind<P> {
91    /// Returns the declaration name as a string slice and its span, if the
92    /// variant carries a name. `Import` and `Include` have no name and return
93    /// `None`.
94    #[must_use]
95    pub fn name_and_span(&self) -> Option<(&str, Span)> {
96        match self {
97            Self::Param(p) => Some((p.name.value.as_str(), p.name.span)),
98            Self::Node(n) => Some((n.name.value.as_str(), n.name.span)),
99            Self::ConstNode(c) => Some((c.name.value.as_str(), c.name.span)),
100            Self::BaseDimension(d) => Some((d.name.value.as_str(), d.name.span)),
101            Self::Dimension(d) => Some((d.name.value.as_str(), d.name.span)),
102            Self::Unit(u) => Some((u.name.value.as_str(), u.name.span)),
103            Self::Type(t) => Some((t.name.value.as_str(), t.name.span)),
104            Self::Index(i) => Some((i.name.value.as_str(), i.name.span)),
105            Self::Dag(d) => Some((d.name.value.as_str(), d.name.span)),
106            Self::Assert(a) => Some((a.name.value.as_str(), a.name.span)),
107            Self::Plot(p) => Some((p.name.value.as_str(), p.name.span)),
108            Self::Figure(f) => Some((f.name.value.as_str(), f.name.span)),
109            Self::Layer(l) => Some((l.name.value.as_str(), l.name.span)),
110            Self::Import(_) | Self::Include(_) | Self::Sugar(_) => None,
111        }
112    }
113}
114
115/// Assert declaration: `assert name = <expr>;`
116///
117/// The body must evaluate to `Bool`. No type annotation (it's always Bool).
118/// Assert declarations are leaf nodes — they are evaluated after the entire graph.
119#[derive(Debug, Clone)]
120pub struct AssertDecl<P: Phase = Raw> {
121    pub visibility: Visibility,
122    pub name: Spanned<DeclName>,
123    pub body: AssertBody<P>,
124}
125
126/// The body of an assert declaration.
127#[derive(Debug, Clone)]
128pub enum AssertBody<P: Phase = Raw> {
129    /// Plain boolean expression: `assert name = expr;`
130    Expr(Expr<P>),
131    /// Tolerance: `assert name = actual ~= expected +/- tolerance;`
132    Tolerance {
133        /// The actual value expression (left of `~=`).
134        actual: Box<Expr<P>>,
135        /// The expected value expression (right of `~=`).
136        expected: Box<Expr<P>>,
137        /// The tolerance expression (right of `+/-`).
138        tolerance: Box<Expr<P>>,
139        /// Whether the tolerance is relative (`%`).
140        is_relative: bool,
141    },
142}
143
144/// The mark type in a plot declaration (Vega-Lite grammar).
145#[derive(Debug, Clone, Copy, PartialEq, Eq)]
146pub enum MarkType {
147    Point,
148    Line,
149    Bar,
150    Area,
151    Rect,
152    Tick,
153}
154
155impl std::fmt::Display for MarkType {
156    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157        match self {
158            Self::Point => write!(f, "point"),
159            Self::Line => write!(f, "line"),
160            Self::Bar => write!(f, "bar"),
161            Self::Area => write!(f, "area"),
162            Self::Rect => write!(f, "rect"),
163            Self::Tick => write!(f, "tick"),
164        }
165    }
166}
167
168/// An encoding channel in a plot declaration (Vega-Lite grammar).
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum EncodingChannel {
171    X,
172    Y,
173    Color,
174    Size,
175    Shape,
176    Opacity,
177    Detail,
178    Text,
179    Tooltip,
180}
181
182impl std::fmt::Display for EncodingChannel {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        match self {
185            Self::X => write!(f, "x"),
186            Self::Y => write!(f, "y"),
187            Self::Color => write!(f, "color"),
188            Self::Size => write!(f, "size"),
189            Self::Shape => write!(f, "shape"),
190            Self::Opacity => write!(f, "opacity"),
191            Self::Detail => write!(f, "detail"),
192            Self::Text => write!(f, "text"),
193            Self::Tooltip => write!(f, "tooltip"),
194        }
195    }
196}
197
198/// The mark specification in a plot declaration: `mark: point` or `mark: line { stroke_width: 2.0 }`.
199#[derive(Debug, Clone)]
200pub struct MarkSpec<P: Phase = Raw> {
201    pub mark_type: MarkType,
202    pub mark_type_span: Span,
203    pub properties: Vec<PlotField<P>>,
204    pub span: Span,
205}
206
207/// An encoding channel mapping in a plot declaration.
208///
209/// Example: `x: for m: OpMode { @total_power[m] }`
210#[derive(Debug, Clone)]
211pub struct Encoding<P: Phase = Raw> {
212    pub channel: EncodingChannel,
213    pub channel_span: Span,
214    pub value: Expr<P>,
215    pub span: Span,
216}
217
218/// A named field in a plot or figure declaration body.
219///
220/// Example: `title: "My Chart"`
221#[derive(Debug, Clone)]
222pub struct PlotField<P: Phase = Raw> {
223    /// The field name (e.g., "title", "width", "height").
224    pub name: Spanned<PlotPropertyName>,
225    /// The field value expression.
226    pub value: Expr<P>,
227    pub span: Span,
228}
229
230/// Plot declaration: `plot name = { mark: point, encode: { x: ..., y: ... }, title: "..." };`
231///
232/// Plots are leaf declarations that depend on params/nodes via `@`-references.
233/// They produce a plot specification, not a runtime `Value`.
234#[derive(Debug, Clone)]
235pub struct PlotDecl<P: Phase = Raw> {
236    pub visibility: Visibility,
237    pub name: Spanned<DeclName>,
238    pub mark: MarkSpec<P>,
239    pub encodings: Vec<Encoding<P>>,
240    pub properties: Vec<PlotField<P>>,
241}
242
243/// Figure declaration: `figure name = { plots: [a, b], title: "..." };`
244///
245/// Figures group multiple plot declarations into a single combined chart
246/// with subplots. Like plots, they are leaf declarations.
247#[derive(Debug, Clone)]
248pub struct FigureDecl<P: Phase = Raw> {
249    pub visibility: Visibility,
250    pub name: Spanned<DeclName>,
251    /// The plot names referenced by this figure (from the `plots: [...]` field).
252    pub plot_names: Vec<Spanned<ScopedName>>,
253    /// Additional fields (e.g., `title`).
254    pub fields: Vec<PlotField<P>>,
255}
256
257/// A layer declaration: overlays multiple plots on shared axes.
258///
259/// Syntax: `layer name = { plots: [a, b], title: "..." };`
260///
261/// Unlike `figure` (which tiles plots side-by-side), `layer` overlays
262/// them on the same coordinate space. In Vega-Lite this maps to the
263/// `"layer"` composition operator.
264#[derive(Debug, Clone)]
265pub struct LayerDecl<P: Phase = Raw> {
266    pub visibility: Visibility,
267    pub name: Spanned<DeclName>,
268    /// The plot names to overlay (from the `plots: [...]` field).
269    pub plot_names: Vec<Spanned<ScopedName>>,
270    /// Additional fields (e.g., `title`).
271    pub fields: Vec<PlotField<P>>,
272}
273
274/// Import declaration (compile-time name import).
275///
276/// `import nasa.rocket;` — brings the leaf module into scope.
277/// `import nasa.rocket as nr;` — brings the leaf module under an alias.
278/// `import nasa.rocket.{Orbit, compute_thrust};` — brings only the listed names.
279///
280/// No param bindings — for DAG instantiation with param bindings, use `include`.
281#[derive(Debug, Clone)]
282pub struct ImportDecl {
283    pub visibility: Visibility,
284    pub path: ModulePath,
285    pub kind: ImportKind,
286}
287
288/// Include declaration (DAG embedding / instantiation).
289///
290/// `include nasa.rocket.compute_thrust(args);` — bare form; instance alias is
291/// the DAG's leaf name.
292/// `include nasa.rocket.compute_thrust(args) as ct;` — explicit instance alias.
293/// `include nasa.rocket.compute_thrust(args).{thrust};` — exposes selected
294/// outputs as nodes in the including DAG.
295#[derive(Debug, Clone)]
296pub struct IncludeDecl<P: Phase = Raw> {
297    pub visibility: Visibility,
298    pub path: ModulePath,
299    pub param_bindings: Vec<ParamBinding<P>>,
300    pub kind: ImportKind,
301}
302
303/// Inline DAG declaration: `dag name { ... }`
304///
305/// The body contains declarations (same as file-level). Semantics are not yet
306/// implemented — this phase only parses the syntax.
307#[derive(Debug, Clone)]
308pub struct DagDecl<P: Phase = Raw> {
309    pub visibility: Visibility,
310    /// The DAG name.
311    pub name: Spanned<DeclName>,
312    /// Declarations inside the DAG block.
313    pub body: Vec<Declaration<P>>,
314    /// Span covering the entire `dag name { ... }` block.
315    pub span: Span,
316}
317#[derive(Debug, Clone)]
318pub struct ParamDecl<P: Phase = Raw> {
319    pub name: Spanned<DeclName>,
320    pub type_ann: TypeExpr<P>,
321    /// The default value expression. `None` for required params (no default).
322    pub value: Option<Expr<P>>,
323}
324
325// ---------------------------------------------------------------------------
326// Multi-declaration surface info (issue #481)
327// ---------------------------------------------------------------------------
328//
329// A multi-decl is a single surface form — e.g.,
330//
331//     param a: T[I], const node b: U[I, J] = table[I, (_, J)] { : _, …; … };
332//
333// — represented in the AST as `DeclKind::Multi(MultiDecl)`. A dedicated
334// desugar pass (`syntax::desugar::desugar_multi_decls_in_file`) expands
335// each `Multi` into N parallel ordinary declarations before lowering;
336// consumers that want the surface form (formatter, surface-aware LSP
337// features) read the AST variant directly.
338
339/// The surface form of a multi-decl: parallel declaration slots sharing a
340/// single `table[…] {…}` initializer.
341#[derive(Debug, Clone)]
342pub struct MultiDecl<P: Phase = Raw> {
343    /// Slot headers in declaration order. Length = number of declarations
344    /// this multi-decl expanded into.
345    pub slots: Vec<MultiDeclSlot<P>>,
346    /// Shared axes from the bracket prefix `table[A, B, …, (…)]`.
347    pub shared_axes: MultiDeclSharedAxes,
348    /// Per-slot extra-axis annotation from the slot tuple. Same length
349    /// as `slots`.
350    pub slot_axes: Vec<MultiSlotAxis>,
351    /// Body slices. Exactly one slice for single-shared-axis multi-decls;
352    /// multiple slices for N-D shared-axis prefixes (v3).
353    pub slices: Vec<MultiDeclSlice<P>>,
354    /// Full surface span: from the first slot's kind keyword through the
355    /// closing `;`.
356    pub span: Span,
357    /// Span of the `table[…] {…}` sub-expression.
358    pub table_expr_span: Span,
359}
360
361/// One slot in a multi-decl: kind keyword, name, type annotation, visibility.
362#[derive(Debug, Clone)]
363pub struct MultiDeclSlot<P: Phase = Raw> {
364    /// Visibility for this slot. The first slot inherits the leading
365    /// `pub`/`pub(bind)` prefix consumed before the multi-decl was
366    /// recognized; subsequent slots accept their own optional prefix
367    /// before the kind keyword.
368    pub visibility: Visibility,
369    pub kind: MultiSlotKind,
370    /// Span covering the kind keyword(s) (`param`, `node`, or `const node`).
371    pub kind_span: Span,
372    pub name: Spanned<DeclName>,
373    pub type_ann: TypeExpr<P>,
374    /// Span from kind keyword through end of the type annotation.
375    pub header_span: Span,
376}
377
378/// Value-decl kinds that a multi-decl slot can have.
379#[derive(Debug, Clone, Copy, PartialEq, Eq)]
380pub enum MultiSlotKind {
381    Param,
382    Node,
383    ConstNode,
384}
385
386/// Per-slot entry in the slot tuple `(…)`.
387#[derive(Debug, Clone)]
388pub enum MultiSlotAxis {
389    /// `_` — 1-D slot, typed `T[SharedAxis]`.
390    Underscore,
391    /// Named axis — 2-D slot, typed `T[SharedAxis, ExtraAxis]`.
392    Axis(Spanned<IndexName>),
393}
394
395/// Where a slot's columns live within each slice's header row.
396#[derive(Debug, Clone)]
397pub enum MultiSlotColumnSpan {
398    /// 1-D slot: one column at `col_idx`.
399    Single(usize),
400    /// 2-D slot: columns `start..end`, one per variant of `extra_axis`.
401    Range {
402        start: usize,
403        end: usize,
404        extra_axis: Spanned<IndexName>,
405    },
406}
407
408/// One slice of a multi-decl body: optional slice-label prefix + header + rows.
409#[derive(Debug, Clone)]
410pub struct MultiDeclSlice<P: Phase = Raw> {
411    /// Slice labels covering the shared-axis prefix except the row axis.
412    /// Empty for single-shared-axis bodies.
413    pub prefix_keys: Vec<MapEntryKey>,
414    /// Header row cells, in left-to-right order.
415    pub header_cells: Vec<MultiHeaderCell>,
416    /// Span of the entire header row (`:` through `;`).
417    pub header_span: Span,
418    /// Per-slot column span into this slice's `header_cells` and `rows`
419    /// values. Same length as `MultiDecl::slots`. May differ between
420    /// slices if their header rows list variants in different orders.
421    pub column_layout: Vec<MultiSlotColumnSpan>,
422    /// Data rows for this slice.
423    pub rows: Vec<MultiDataRow<P>>,
424}
425
426/// One cell of a multi-decl header row.
427#[derive(Debug, Clone)]
428pub enum MultiHeaderCell {
429    Underscore {
430        span: Span,
431    },
432    Variant {
433        /// Axis qualifier, if the author wrote `Axis.Variant`.
434        axis: Option<Spanned<IndexName>>,
435        variant: Spanned<IndexVariantName>,
436        span: Span,
437    },
438}
439
440impl MultiHeaderCell {
441    /// Returns the span of this cell.
442    #[must_use]
443    pub const fn span(&self) -> Span {
444        match self {
445            Self::Underscore { span } | Self::Variant { span, .. } => *span,
446        }
447    }
448}
449
450/// One data row of a multi-decl body: label + value per column.
451#[derive(Debug, Clone)]
452pub struct MultiDataRow<P: Phase = Raw> {
453    pub label: Spanned<IndexVariantName>,
454    pub values: Vec<Expr<P>>,
455    pub span: Span,
456}
457
458/// Runtime node declaration: `node name: Type = expr;`
459#[derive(Debug, Clone)]
460pub struct NodeDecl<P: Phase = Raw> {
461    pub visibility: Visibility,
462    pub name: Spanned<DeclName>,
463    pub type_ann: TypeExpr<P>,
464    pub value: Expr<P>,
465}
466
467/// Const node declaration: `const node name: Type = expr;`
468#[derive(Debug, Clone)]
469pub struct ConstNodeDecl<P: Phase = Raw> {
470    pub visibility: Visibility,
471    pub name: Spanned<DeclName>,
472    pub type_ann: TypeExpr<P>,
473    pub value: Expr<P>,
474}
475
476/// Base dimension declaration: `base dim Length;`
477#[derive(Debug, Clone)]
478pub struct BaseDimDecl {
479    pub visibility: Visibility,
480    pub name: Spanned<DimName>,
481}
482
483/// Dimension declaration with a body or required.
484///
485/// Two forms:
486/// - Derived: `dim Velocity = Length / Time;` — `definition: Some(...)`
487/// - Required: `dim D;` — `definition: None`. The library requires a
488///   dimension to be bound here from outside (via an include with
489///   dim bindings). Treated like an opaque base dimension when the
490///   library is compiled standalone.
491#[derive(Debug, Clone)]
492pub struct DimDecl {
493    pub visibility: BindableVisibility,
494    pub name: Spanned<DimName>,
495    pub definition: Option<DimExpr>,
496}
497
498/// Whether a unit is allowed in compile-time (`const`) contexts.
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
500pub enum UnitConstness {
501    /// A compile-time unit: prelude units, `base unit`, or `const unit`.
502    Const,
503    /// A runtime unit declared with plain `unit`; its scale may depend on params or nodes.
504    Dynamic,
505}
506
507impl UnitConstness {
508    /// Returns `true` for units that may appear in `const node` bodies.
509    #[must_use]
510    pub const fn is_const(self) -> bool {
511        matches!(self, Self::Const)
512    }
513}
514
515/// Unit declaration: `const unit km: Length = 1000 m;`, `unit EUR: Money = (@rate) USD;`,
516/// or `base unit m: Length;`.
517#[derive(Debug, Clone)]
518pub struct UnitDecl<P: Phase = Raw> {
519    pub visibility: Visibility,
520    pub constness: UnitConstness,
521    pub name: Spanned<UnitName>,
522    /// The dimension this unit measures.
523    pub dim_type: DimExpr,
524    /// Scale definition: `(scale_value, base_unit_expr)`.
525    /// `None` iff this is a base unit (`base unit m: Length;`).
526    pub definition: Option<UnitDef<P>>,
527}
528
529/// The scale definition part of a unit declaration: `1000 m` or `1 kg * m / s^2`.
530#[derive(Debug, Clone)]
531pub struct UnitDef<P: Phase = Raw> {
532    pub scale_expr: Expr<P>,
533    pub unit_expr: UnitExpr,
534    pub span: Span,
535}
536
537/// Type declaration: required type stubs and tagged-union bodies.
538///
539/// Forms:
540/// - Required type: `type T;` — the library requires a type bound from
541///   outside; no body at declaration.
542/// - Tagged union: `type Maneuver { Impulsive(delta_v: Velocity), Coast }`
543/// - Record-shaped type: `type Position { Position(x: Length, y: Length) }`,
544///   a single-variant union whose constructor name matches the type name.
545#[derive(Debug, Clone)]
546pub struct TypeDecl<P: Phase = Raw> {
547    pub visibility: BindableVisibility,
548    pub name: Spanned<StructTypeName>,
549    pub generic_params: Vec<GenericParam<P>>,
550    pub body: TypeDeclBody<P>,
551}
552
553/// Body of a `type` declaration.
554#[derive(Debug, Clone)]
555pub enum TypeDeclBody<P: Phase = Raw> {
556    /// Required type with no body: `type T;`.
557    Required,
558    /// Tagged-union constructor list: `type T { Ctor, Other(x: U) }`.
559    Constructors(Vec<UnionMember<P>>),
560}
561
562/// A member of a type declaration body: a constructor with an optional payload.
563///
564/// Forms:
565/// - Unit: `Coast` — `payload` is `None`.
566/// - Record-payload (parens): `Impulsive(delta_v: Velocity)` —
567///   `payload` is `Some(vec![…])`.
568/// - Record-payload (braces): `LowThrust { thrust: Force, duration: Time }`
569///   — `payload` is `Some(vec![…])`. The brace/paren choice is purely
570///   surface syntax; both produce the same AST.
571#[derive(Debug, Clone)]
572pub struct UnionMember<P: Phase = Raw> {
573    /// The constructor's name. Lives in the constructor namespace —
574    /// distinct from the type namespace.
575    pub name: Spanned<ConstructorName>,
576    /// Inline payload fields, or `None` for unit constructors.
577    pub payload: Option<Vec<FieldDecl<P>>>,
578    pub span: Span,
579}
580
581/// A field in a variant or struct type declaration.
582#[derive(Debug, Clone)]
583pub struct FieldDecl<P: Phase = Raw> {
584    pub name: Spanned<FieldName>,
585    pub type_ann: TypeExpr<P>,
586}
587
588///// The kind of an index declaration.
589#[derive(Debug, Clone)]
590pub enum IndexDeclKind<P: Phase = Raw> {
591    /// Named variants: `{ Departure, Correction, Insertion }`
592    Named {
593        variants: Vec<Spanned<IndexVariantName>>,
594    },
595    /// Numeric range: `linspace(start, end, step: step)`
596    Range {
597        start: Box<Expr<P>>,
598        end: Box<Expr<P>>,
599        step: Box<Expr<P>>,
600    },
601    /// Required named index (no variants): `index Foo;`
602    ///
603    /// Must be bound via parameterized import.
604    RequiredNamed,
605    /// Required range index with dimension constraint: `index Foo: Time;`
606    ///
607    /// Must be bound via parameterized import.
608    RequiredRange { dimension: DimExpr },
609}
610
611impl<P: Phase> IndexDeclKind<P> {
612    /// Returns `true` for required index declarations that must be bound via import.
613    #[must_use]
614    pub const fn is_required(&self) -> bool {
615        matches!(self, Self::RequiredNamed | Self::RequiredRange { .. })
616    }
617}
618
619/// Index declaration: `index Maneuver = { Departure, Correction, Insertion };`
620/// or `index TimeStep = linspace(0.0 s, 100.0 s, step: 0.1 s);`
621#[derive(Debug, Clone)]
622pub struct IndexDecl<P: Phase = Raw> {
623    pub visibility: BindableVisibility,
624    pub name: Spanned<IndexName>,
625    pub kind: IndexDeclKind<P>,
626}
627
628/// A generic parameter: `D: Dim`
629#[derive(Debug, Clone)]
630pub struct GenericParam<P: Phase = Raw> {
631    pub name: Spanned<GenericParamName>,
632    pub constraint: GenericConstraint,
633    /// Optional default type, e.g. `F: Type = Unframed`.
634    pub default: Option<TypeExpr<P>>,
635}
636
637/// Constraint on a generic parameter.
638#[derive(Debug, Clone, Copy, PartialEq, Eq)]
639pub enum GenericConstraint {
640    /// `D: Dim` -- the generic stands for a dimension.
641    Dim,
642    /// `I: Index` -- the generic stands for an index.
643    Index,
644    /// `N: Nat` -- the generic stands for a natural number (type-level).
645    Nat,
646    /// `F: Type` -- the generic stands for any type (unconstrained phantom parameter).
647    Type,
648}