#[cold]
#[track_caller]
#[inline(never)]
#[expect(
clippy::panic,
reason = "indicates a broken invariant — multi-decls must be desugared before this pass"
)]
pub fn unreachable_post_desugar() -> ! {
panic!(
"DeclKind::Sugar should have been removed by syntax::desugar::desugar_multi_decls_in_file"
)
}
use crate::syntax::ast::{
ConstNodeDecl, DeclKind, Declaration, Expr, ExprKind, File, MapEntry, MapEntryIndex,
MapEntryKey, MultiDecl, MultiHeaderCell, MultiSlotColumnSpan, MultiSlotKind, NodeDecl,
ParamDecl, TableIndexSpec,
};
use crate::syntax::names::{IndexName, IndexVariantName};
use crate::syntax::non_empty::NonEmpty;
use crate::syntax::phase::{Desugared, Raw};
use crate::syntax::span::Spanned;
fn multi_entry_keys(
mut prefix: Vec<MapEntryKey>,
row: MapEntryKey,
extra: Option<MapEntryKey>,
) -> NonEmpty<MapEntryKey> {
if prefix.is_empty() {
let rest = extra.into_iter().collect();
NonEmpty::new(row, rest)
} else {
let first = prefix.remove(0);
prefix.push(row);
prefix.extend(extra);
NonEmpty::new(first, prefix)
}
}
#[must_use]
pub fn desugar_multi_decls_in_file(file: File<Raw>) -> File<Desugared> {
file.into()
}
#[derive(Debug)]
pub enum ExpandedSlotDecl {
Param(ParamDecl, crate::syntax::span::Span),
Node(NodeDecl, crate::syntax::span::Span),
ConstNode(ConstNodeDecl, crate::syntax::span::Span),
}
impl ExpandedSlotDecl {
#[must_use]
pub fn into_declaration(self) -> Declaration {
let (kind, span) = match self {
Self::Param(p, span) => (DeclKind::Param(p), span),
Self::Node(n, span) => (DeclKind::Node(n), span),
Self::ConstNode(c, span) => (DeclKind::ConstNode(c), span),
};
Declaration {
attributes: vec![],
kind,
span,
}
}
}
#[must_use]
#[expect(
clippy::too_many_lines,
reason = "single cohesive routine for multi-decl expansion"
)]
pub fn expand_multi_decl(multi: &MultiDecl) -> Vec<ExpandedSlotDecl> {
let row_index_spec = multi.shared_axes.row_axis().clone();
let slice_axis_specs: &[TableIndexSpec] = multi.shared_axes.slice_axes();
let row_index_name = match &row_index_spec {
TableIndexSpec::Named(s) => Spanned::new(MapEntryIndex::Named(s.value.clone()), s.span),
TableIndexSpec::NatRange(n, sp) => Spanned::new(MapEntryIndex::NatRange(*n), *sp),
};
let mut out: Vec<ExpandedSlotDecl> = Vec::with_capacity(multi.slots.len());
for (slot_idx, slot) in multi.slots.iter().enumerate() {
let mut slot_entries: Vec<MapEntry> = Vec::new();
let mut slot_indexes: Vec<TableIndexSpec> = slice_axis_specs.to_vec();
slot_indexes.push(row_index_spec.clone());
let mut extra_axis_name: Option<Spanned<IndexName>> = None;
for slice in &multi.slices {
let col_span = &slice.column_layout[slot_idx];
match col_span {
MultiSlotColumnSpan::Single(col_idx) => {
for row in &slice.rows {
let row_key = MapEntryKey {
index: row_index_name.clone(),
variant: row.label.clone(),
};
slot_entries.push(MapEntry {
keys: multi_entry_keys(slice.prefix_keys.clone(), row_key, None),
value: row.values[*col_idx].clone(),
});
}
}
MultiSlotColumnSpan::Range {
start,
end,
extra_axis,
} => {
if extra_axis_name.is_none() {
extra_axis_name = Some(extra_axis.clone());
}
let extra_index_name = Spanned::new(
MapEntryIndex::Named(extra_axis.value.clone().into()),
extra_axis.span,
);
let col_variants: Vec<Spanned<IndexVariantName>> = slice.header_cells
[*start..*end]
.iter()
.filter_map(|c| match c {
MultiHeaderCell::Variant { variant, .. } => Some(variant.clone()),
MultiHeaderCell::Underscore { .. } => None,
})
.collect();
for row in &slice.rows {
for (local_col, col_variant) in col_variants.iter().enumerate() {
let global_col = start + local_col;
let row_key = MapEntryKey {
index: row_index_name.clone(),
variant: row.label.clone(),
};
let extra_key = MapEntryKey {
index: extra_index_name.clone(),
variant: col_variant.clone(),
};
slot_entries.push(MapEntry {
keys: multi_entry_keys(
slice.prefix_keys.clone(),
row_key,
Some(extra_key),
),
value: row.values[global_col].clone(),
});
}
}
}
}
}
if let Some(extra) = extra_axis_name {
slot_indexes.push(TableIndexSpec::Named(Spanned::new(
extra.value.into(),
extra.span,
)));
}
let table_expr = Expr::new(
ExprKind::Sugar(crate::syntax::ast::RawExprSugar::TableLiteral {
indexes: slot_indexes,
entries: slot_entries,
}),
multi.table_expr_span,
);
let decl_span = slot.header_span.merge(multi.span);
out.push(match slot.kind {
MultiSlotKind::Param => ExpandedSlotDecl::Param(
ParamDecl {
name: slot.name.clone(),
type_ann: slot.type_ann.clone(),
value: Some(table_expr),
},
decl_span,
),
MultiSlotKind::Node => ExpandedSlotDecl::Node(
NodeDecl {
visibility: slot.visibility,
name: slot.name.clone(),
type_ann: slot.type_ann.clone(),
value: table_expr,
},
decl_span,
),
MultiSlotKind::ConstNode => ExpandedSlotDecl::ConstNode(
ConstNodeDecl {
visibility: slot.visibility,
name: slot.name.clone(),
type_ann: slot.type_ann.clone(),
value: table_expr,
},
decl_span,
),
});
}
out
}