Skip to main content

graphcal_compiler/desugar/
convert.rs

1//! `From<X<Raw>> for X<Desugared>` impls.
2//!
3//! Every phase-parameterized AST type gets a [`From`] impl converting its
4//! `Raw` form to its `Desugared` form. Most are mechanical structural
5//! pass-throughs; the only interesting case is [`DeclKind`], which expands
6//! the `Sugar(RawDeclSugar::Multi(_))` variant via
7//! [`crate::syntax::desugar::expand_multi_decl`] instead of pass-through.
8//!
9//! These impls let consumers say `vec_of_raw.into_iter().map(Into::into)` or
10//! `option_of_raw.map(Into::into)` to lift any AST tree from `Raw` to
11//! `Desugared`. The desugar pass uses them to produce `File<Desugared>`.
12//!
13//! # Why so many impls?
14//!
15//! Rust has no quantification over generic type *constructors*, so we
16//! cannot write a single blanket `impl<T<P>> From<T<Raw>> for T<Desugared>`.
17//! Each phase-parameterized type needs its own `From` impl.
18//!
19//! # Phase-invariant types
20//!
21//! Types without a `<P>` parameter (e.g., `Attribute`, `Ident`, `ModulePath`,
22//! `DimExpr`, `UnitExpr`, `IndexExpr`, `NatExpr`, `MapEntryKey`,
23//! `MatchPattern`, etc.) are used as-is in both phases — no conversion
24//! needed.
25//!
26//! # `MultiDecl` family
27//!
28//! `MultiDecl`, `MultiDeclSlot`, `MultiDeclSlice`, `MultiDataRow` are
29//! parameterized over `<P>` for symmetry but only ever instantiated with
30//! `<Raw>` (they live exclusively inside `RawDeclSugar::Multi`). They have
31//! no `From<…<Raw>> for …<Desugared>` impl because the desugar pass
32//! eliminates them entirely via `expand_multi_decl`.
33
34use crate::syntax::ast::{
35    AssertBody, AssertDecl, ConstNodeDecl, DagDecl, DeclKind, Declaration, DomainBound, Encoding,
36    Expr, ExprKind, FieldDecl, FieldInit, FigureDecl, File, GenericArg, GenericParam, IncludeDecl,
37    IndexArg, IndexDecl, IndexDeclKind, LayerDecl, MapEntry, MarkSpec, MatchArm, NodeDecl,
38    ParamBinding, ParamDecl, PlotDecl, PlotField, TypeDecl, TypeDeclBody, TypeExpr, TypeExprKind,
39    UnionMember, UnitDecl, UnitDef,
40};
41use crate::syntax::ast::{RawDeclSugar, RawExprSugar};
42use crate::syntax::phase::{Desugared, Raw};
43
44// ---------------------------------------------------------------------------
45// File / Declaration / DeclKind
46// ---------------------------------------------------------------------------
47
48impl From<File<Raw>> for File<Desugared> {
49    fn from(f: File<Raw>) -> Self {
50        Self {
51            declarations: f.declarations.into_iter().flat_map(convert_decl).collect(),
52        }
53    }
54}
55
56/// Convert one `Declaration<Raw>` into N `Declaration<Desugared>`.
57///
58/// Returns a `Vec` because multi-decl sugar expands one declaration into
59/// many. All other variants produce exactly one output declaration.
60fn convert_decl(d: Declaration<Raw>) -> Vec<Declaration<Desugared>> {
61    let Declaration {
62        attributes,
63        kind,
64        span,
65    } = d;
66    match kind {
67        DeclKind::Sugar(RawDeclSugar::Multi(multi)) => {
68            // `expand_multi_decl` produces `Declaration<Raw>` values (one per
69            // slot, all Param/Node/ConstNode — never `Sugar`). Lift each to
70            // `Declaration<Desugared>` so the rest of the pass sees a uniform
71            // post-desugar type.
72            crate::syntax::desugar::expand_multi_decl(&multi)
73                .into_iter()
74                .map(lift_slot_decl)
75                .collect()
76        }
77        other => vec![Declaration {
78            attributes,
79            kind: convert_decl_kind_non_sugar(other),
80            span,
81        }],
82    }
83}
84
85/// Lift one multi-decl expansion slot to a `Declaration<Desugared>`.
86///
87/// [`ExpandedSlotDecl`] can only hold `Param` / `Node` / `ConstNode`, so no
88/// unreachable `Sugar` arm (and no panic) is needed here.
89fn lift_slot_decl(d: crate::syntax::desugar::ExpandedSlotDecl) -> Declaration<Desugared> {
90    use crate::syntax::desugar::ExpandedSlotDecl;
91    let (kind, span) = match d {
92        ExpandedSlotDecl::Param(p, span) => (DeclKind::Param(p.into()), span),
93        ExpandedSlotDecl::Node(n, span) => (DeclKind::Node(n.into()), span),
94        ExpandedSlotDecl::ConstNode(c, span) => (DeclKind::ConstNode(c.into()), span),
95    };
96    Declaration {
97        attributes: vec![],
98        kind,
99        span,
100    }
101}
102
103/// Convert a non-sugar `DeclKind<Raw>` variant to `DeclKind<Desugared>`.
104///
105/// Panics if called with `DeclKind::Sugar(_)` — `convert_decl` handles that
106/// case directly so it never reaches here.
107#[expect(
108    clippy::panic,
109    reason = "invariant: convert_decl handles Sugar separately and never calls this with Sugar"
110)]
111fn convert_decl_kind_non_sugar(k: DeclKind<Raw>) -> DeclKind<Desugared> {
112    match k {
113        DeclKind::Param(p) => DeclKind::Param(p.into()),
114        DeclKind::Node(n) => DeclKind::Node(n.into()),
115        DeclKind::ConstNode(c) => DeclKind::ConstNode(c.into()),
116        DeclKind::BaseDimension(d) => DeclKind::BaseDimension(d),
117        DeclKind::Dimension(d) => DeclKind::Dimension(d),
118        DeclKind::Unit(u) => DeclKind::Unit(u.into()),
119        DeclKind::Type(t) => DeclKind::Type(t.into()),
120        DeclKind::Index(i) => DeclKind::Index(i.into()),
121        DeclKind::Import(i) => DeclKind::Import(i),
122        DeclKind::Include(i) => DeclKind::Include(i.into()),
123        DeclKind::Dag(d) => DeclKind::Dag(d.into()),
124        DeclKind::Assert(a) => DeclKind::Assert(a.into()),
125        DeclKind::Plot(p) => DeclKind::Plot(p.into()),
126        DeclKind::Figure(f) => DeclKind::Figure(f.into()),
127        DeclKind::Layer(l) => DeclKind::Layer(l.into()),
128        // The only caller is the `other` arm of `convert_decl`'s match,
129        // which handles `Sugar` two lines above — visibly unreachable.
130        DeclKind::Sugar(_) => {
131            panic!("convert_decl dispatches Sugar before calling convert_decl_kind_non_sugar")
132        }
133    }
134}
135
136// ---------------------------------------------------------------------------
137// Decl-specific structs
138// ---------------------------------------------------------------------------
139
140impl From<ParamDecl<Raw>> for ParamDecl<Desugared> {
141    fn from(p: ParamDecl<Raw>) -> Self {
142        Self {
143            name: p.name,
144            type_ann: p.type_ann.into(),
145            value: p.value.map(Into::into),
146        }
147    }
148}
149
150impl From<NodeDecl<Raw>> for NodeDecl<Desugared> {
151    fn from(n: NodeDecl<Raw>) -> Self {
152        Self {
153            visibility: n.visibility,
154            name: n.name,
155            type_ann: n.type_ann.into(),
156            value: n.value.into(),
157        }
158    }
159}
160
161impl From<ConstNodeDecl<Raw>> for ConstNodeDecl<Desugared> {
162    fn from(c: ConstNodeDecl<Raw>) -> Self {
163        Self {
164            visibility: c.visibility,
165            name: c.name,
166            type_ann: c.type_ann.into(),
167            value: c.value.into(),
168        }
169    }
170}
171
172impl From<UnitDecl<Raw>> for UnitDecl<Desugared> {
173    fn from(u: UnitDecl<Raw>) -> Self {
174        Self {
175            visibility: u.visibility,
176            constness: u.constness,
177            name: u.name,
178            dim_type: u.dim_type,
179            definition: u.definition.map(Into::into),
180        }
181    }
182}
183
184impl From<UnitDef<Raw>> for UnitDef<Desugared> {
185    fn from(u: UnitDef<Raw>) -> Self {
186        Self {
187            scale_expr: u.scale_expr.into(),
188            unit_expr: u.unit_expr,
189            span: u.span,
190        }
191    }
192}
193
194impl From<TypeDecl<Raw>> for TypeDecl<Desugared> {
195    fn from(t: TypeDecl<Raw>) -> Self {
196        Self {
197            visibility: t.visibility,
198            name: t.name,
199            generic_params: t.generic_params.into_iter().map(Into::into).collect(),
200            body: match t.body {
201                TypeDeclBody::Required => TypeDeclBody::Required,
202                TypeDeclBody::Constructors(members) => {
203                    TypeDeclBody::Constructors(members.into_iter().map(Into::into).collect())
204                }
205            },
206        }
207    }
208}
209
210impl From<UnionMember<Raw>> for UnionMember<Desugared> {
211    fn from(u: UnionMember<Raw>) -> Self {
212        Self {
213            name: u.name,
214            payload: u.payload.map(|fs| fs.into_iter().map(Into::into).collect()),
215            span: u.span,
216        }
217    }
218}
219
220impl From<FieldDecl<Raw>> for FieldDecl<Desugared> {
221    fn from(f: FieldDecl<Raw>) -> Self {
222        Self {
223            name: f.name,
224            type_ann: f.type_ann.into(),
225        }
226    }
227}
228
229impl From<GenericParam<Raw>> for GenericParam<Desugared> {
230    fn from(g: GenericParam<Raw>) -> Self {
231        Self {
232            name: g.name,
233            constraint: g.constraint,
234            default: g.default.map(Into::into),
235        }
236    }
237}
238
239impl From<IndexDecl<Raw>> for IndexDecl<Desugared> {
240    fn from(i: IndexDecl<Raw>) -> Self {
241        Self {
242            visibility: i.visibility,
243            name: i.name,
244            kind: i.kind.into(),
245        }
246    }
247}
248
249impl From<IndexDeclKind<Raw>> for IndexDeclKind<Desugared> {
250    fn from(k: IndexDeclKind<Raw>) -> Self {
251        match k {
252            IndexDeclKind::Named { variants } => Self::Named { variants },
253            IndexDeclKind::Range { start, end, step } => Self::Range {
254                start: Box::new((*start).into()),
255                end: Box::new((*end).into()),
256                step: Box::new((*step).into()),
257            },
258            IndexDeclKind::RequiredNamed => Self::RequiredNamed,
259            IndexDeclKind::RequiredRange { dimension } => Self::RequiredRange { dimension },
260        }
261    }
262}
263
264impl From<IncludeDecl<Raw>> for IncludeDecl<Desugared> {
265    fn from(i: IncludeDecl<Raw>) -> Self {
266        Self {
267            visibility: i.visibility,
268            path: i.path,
269            param_bindings: i.param_bindings.into_iter().map(Into::into).collect(),
270            kind: i.kind,
271        }
272    }
273}
274
275impl From<ParamBinding<Raw>> for ParamBinding<Desugared> {
276    fn from(p: ParamBinding<Raw>) -> Self {
277        Self {
278            name: p.name,
279            value: p.value.into(),
280            span: p.span,
281        }
282    }
283}
284
285impl From<DagDecl<Raw>> for DagDecl<Desugared> {
286    fn from(d: DagDecl<Raw>) -> Self {
287        Self {
288            visibility: d.visibility,
289            name: d.name,
290            body: d.body.into_iter().flat_map(convert_decl).collect(),
291            span: d.span,
292        }
293    }
294}
295
296impl From<AssertDecl<Raw>> for AssertDecl<Desugared> {
297    fn from(a: AssertDecl<Raw>) -> Self {
298        Self {
299            visibility: a.visibility,
300            name: a.name,
301            body: a.body.into(),
302        }
303    }
304}
305
306impl From<AssertBody<Raw>> for AssertBody<Desugared> {
307    fn from(b: AssertBody<Raw>) -> Self {
308        match b {
309            AssertBody::Expr(e) => Self::Expr(e.into()),
310            AssertBody::Tolerance {
311                actual,
312                expected,
313                tolerance,
314                is_relative,
315            } => Self::Tolerance {
316                actual: Box::new((*actual).into()),
317                expected: Box::new((*expected).into()),
318                tolerance: Box::new((*tolerance).into()),
319                is_relative,
320            },
321        }
322    }
323}
324
325// ---------------------------------------------------------------------------
326// Plot family
327// ---------------------------------------------------------------------------
328
329impl From<PlotDecl<Raw>> for PlotDecl<Desugared> {
330    fn from(p: PlotDecl<Raw>) -> Self {
331        Self {
332            visibility: p.visibility,
333            name: p.name,
334            mark: p.mark.into(),
335            encodings: p.encodings.into_iter().map(Into::into).collect(),
336            properties: p.properties.into_iter().map(Into::into).collect(),
337        }
338    }
339}
340
341impl From<MarkSpec<Raw>> for MarkSpec<Desugared> {
342    fn from(m: MarkSpec<Raw>) -> Self {
343        Self {
344            mark_type: m.mark_type,
345            mark_type_span: m.mark_type_span,
346            properties: m.properties.into_iter().map(Into::into).collect(),
347            span: m.span,
348        }
349    }
350}
351
352impl From<Encoding<Raw>> for Encoding<Desugared> {
353    fn from(e: Encoding<Raw>) -> Self {
354        Self {
355            channel: e.channel,
356            channel_span: e.channel_span,
357            value: e.value.into(),
358            span: e.span,
359        }
360    }
361}
362
363impl From<PlotField<Raw>> for PlotField<Desugared> {
364    fn from(p: PlotField<Raw>) -> Self {
365        Self {
366            name: p.name,
367            value: p.value.into(),
368            span: p.span,
369        }
370    }
371}
372
373impl From<FigureDecl<Raw>> for FigureDecl<Desugared> {
374    fn from(f: FigureDecl<Raw>) -> Self {
375        Self {
376            visibility: f.visibility,
377            name: f.name,
378            plot_names: f.plot_names,
379            fields: f.fields.into_iter().map(Into::into).collect(),
380        }
381    }
382}
383
384impl From<LayerDecl<Raw>> for LayerDecl<Desugared> {
385    fn from(l: LayerDecl<Raw>) -> Self {
386        Self {
387            visibility: l.visibility,
388            name: l.name,
389            plot_names: l.plot_names,
390            fields: l.fields.into_iter().map(Into::into).collect(),
391        }
392    }
393}
394
395// ---------------------------------------------------------------------------
396// Type expressions
397// ---------------------------------------------------------------------------
398
399impl From<TypeExpr<Raw>> for TypeExpr<Desugared> {
400    fn from(t: TypeExpr<Raw>) -> Self {
401        Self {
402            kind: t.kind.into(),
403            constraints: t.constraints.into_iter().map(Into::into).collect(),
404            span: t.span,
405        }
406    }
407}
408
409impl From<TypeExprKind<Raw>> for TypeExprKind<Desugared> {
410    fn from(k: TypeExprKind<Raw>) -> Self {
411        match k {
412            TypeExprKind::Dimensionless => Self::Dimensionless,
413            TypeExprKind::Bool => Self::Bool,
414            TypeExprKind::Int => Self::Int,
415            TypeExprKind::Datetime => Self::Datetime,
416            TypeExprKind::DimExpr(d) => Self::DimExpr(d),
417            TypeExprKind::Indexed { base, indexes } => Self::Indexed {
418                base: Box::new((*base).into()),
419                indexes,
420            },
421            TypeExprKind::TypeApplication { name, type_args } => Self::TypeApplication {
422                name,
423                type_args: type_args.into_iter().map(Into::into).collect(),
424            },
425            TypeExprKind::DatetimeApplication { type_args } => Self::DatetimeApplication {
426                type_args: type_args.into_iter().map(Into::into).collect(),
427            },
428        }
429    }
430}
431
432impl From<DomainBound<Raw>> for DomainBound<Desugared> {
433    fn from(d: DomainBound<Raw>) -> Self {
434        Self {
435            kind: d.kind,
436            kind_span: d.kind_span,
437            value: d.value.into(),
438            span: d.span,
439        }
440    }
441}
442
443impl From<GenericArg<Raw>> for GenericArg<Desugared> {
444    fn from(g: GenericArg<Raw>) -> Self {
445        match g {
446            GenericArg::Type(t) => Self::Type(t.into()),
447            GenericArg::Nat(n) => Self::Nat(n),
448        }
449    }
450}
451
452// ---------------------------------------------------------------------------
453// Expressions
454// ---------------------------------------------------------------------------
455
456impl From<Expr<Raw>> for Expr<Desugared> {
457    fn from(e: Expr<Raw>) -> Self {
458        // Recursion choke point: conversion recurses once per tree level
459        // (unbounded for left-nested operator chains).
460        crate::stack::with_stack_growth(|| Self::new(e.kind.into(), e.span))
461    }
462}
463
464impl From<ExprKind<Raw>> for ExprKind<Desugared> {
465    #[expect(
466        clippy::too_many_lines,
467        reason = "exhaustive variant pass-through over a wide enum is inherently long"
468    )]
469    fn from(k: ExprKind<Raw>) -> Self {
470        match k {
471            // Phase-invariant payload — direct rebind.
472            ExprKind::Number(n) => Self::Number(n),
473            ExprKind::Integer(n) => Self::Integer(n),
474            ExprKind::Bool(b) => Self::Bool(b),
475            ExprKind::StringLiteral(s) => Self::StringLiteral(s),
476            ExprKind::GraphRef(r) => Self::GraphRef(r),
477            ExprKind::UnitLiteral { value, unit } => Self::UnitLiteral { value, unit },
478            ExprKind::UnresolvedRef(r) => Self::UnresolvedRef(r),
479            // Recursive — convert children.
480            ExprKind::BinOp { op, lhs, rhs } => Self::BinOp {
481                op,
482                lhs: Box::new((*lhs).into()),
483                rhs: Box::new((*rhs).into()),
484            },
485            ExprKind::UnaryOp { op, operand } => Self::UnaryOp {
486                op,
487                operand: Box::new((*operand).into()),
488            },
489            ExprKind::FnCall {
490                callee,
491                type_args,
492                args,
493            } => Self::FnCall {
494                callee,
495                type_args: type_args.into_iter().map(Into::into).collect(),
496                args: args.into_iter().map(Into::into).collect(),
497            },
498            ExprKind::If {
499                condition,
500                then_branch,
501                else_branch,
502            } => Self::If {
503                condition: Box::new((*condition).into()),
504                then_branch: Box::new((*then_branch).into()),
505                else_branch: Box::new((*else_branch).into()),
506            },
507            ExprKind::Convert { expr, target } => Self::Convert {
508                expr: Box::new((*expr).into()),
509                target,
510            },
511            ExprKind::DisplayTimezone { expr, timezone } => Self::DisplayTimezone {
512                expr: Box::new((*expr).into()),
513                timezone,
514            },
515            ExprKind::FieldAccess { expr, field } => Self::FieldAccess {
516                expr: Box::new((*expr).into()),
517                field,
518            },
519            ExprKind::ConstructorCall {
520                callee,
521                generic_args,
522                fields,
523            } => Self::ConstructorCall {
524                callee,
525                generic_args: generic_args.into_iter().map(Into::into).collect(),
526                fields: fields.into_iter().map(Into::into).collect(),
527            },
528            ExprKind::MapLiteral { entries } => Self::MapLiteral {
529                entries: entries.into_iter().map(Into::into).collect(),
530            },
531            ExprKind::ForComp { bindings, body } => Self::ForComp {
532                bindings,
533                body: Box::new((*body).into()),
534            },
535            ExprKind::IndexAccess { expr, args } => Self::IndexAccess {
536                expr: Box::new((*expr).into()),
537                args: args.into_iter().map(Into::into).collect(),
538            },
539            ExprKind::Scan {
540                source,
541                init,
542                acc_name,
543                val_name,
544                body,
545            } => Self::Scan {
546                source: Box::new((*source).into()),
547                init: Box::new((*init).into()),
548                acc_name,
549                val_name,
550                body: Box::new((*body).into()),
551            },
552            ExprKind::Unfold {
553                init,
554                prev_name,
555                curr_name,
556                body,
557            } => Self::Unfold {
558                init: Box::new((*init).into()),
559                prev_name,
560                curr_name,
561                body: Box::new((*body).into()),
562            },
563            ExprKind::Match { scrutinee, arms } => Self::Match {
564                scrutinee: Box::new((*scrutinee).into()),
565                arms: arms.into_iter().map(Into::into).collect(),
566            },
567            ExprKind::InlineDagRef { path, args, output } => Self::InlineDagRef {
568                path,
569                args: args.into_iter().map(Into::into).collect(),
570                output,
571            },
572            ExprKind::Sugar(RawExprSugar::TableLiteral {
573                indexes: _,
574                entries,
575            }) => {
576                // Drop the `indexes` metadata — the entries already carry
577                // full `Index.Variant` keys (the parser materializes synthetic
578                // names for `NatRange` axes during `parse_table_*`). The
579                // `table` keyword is purely surface syntax preserved by the
580                // formatter via the raw AST; downstream stages see the
581                // canonical map form.
582                Self::MapLiteral {
583                    entries: entries.into_iter().map(Into::into).collect(),
584                }
585            }
586        }
587    }
588}
589
590impl From<MapEntry<Raw>> for MapEntry<Desugared> {
591    fn from(m: MapEntry<Raw>) -> Self {
592        Self {
593            keys: m.keys,
594            value: m.value.into(),
595        }
596    }
597}
598
599impl From<IndexArg<Raw>> for IndexArg<Desugared> {
600    fn from(a: IndexArg<Raw>) -> Self {
601        match a {
602            IndexArg::Variant { index, variant } => Self::Variant { index, variant },
603            IndexArg::Var(i) => Self::Var(i),
604            IndexArg::Expr(e) => Self::Expr(Box::new((*e).into())),
605        }
606    }
607}
608
609impl From<FieldInit<Raw>> for FieldInit<Desugared> {
610    fn from(f: FieldInit<Raw>) -> Self {
611        Self {
612            name: f.name,
613            value: f.value.into(),
614        }
615    }
616}
617
618impl From<MatchArm<Raw>> for MatchArm<Desugared> {
619    fn from(a: MatchArm<Raw>) -> Self {
620        Self {
621            pattern: a.pattern,
622            body: a.body.into(),
623            span: a.span,
624        }
625    }
626}