Lines
98.81 %
Functions
60 %
Branches
100 %
//! Static inference of the `PairElement` a `$pair` cell rides. Single source
//! of truth for CONS, LIST / APPEND chains and the literal-slot mirrors: the
//! most specific homogeneous element when every cell agrees, widened to
//! `PairElement::AnyRef` (ADR-0025 escape hatch) when they disagree.
use crate::ast::{Expr, Fraction, PairElement, WasmType};
use crate::compiler::expr::format_expr;
use crate::error::{Error, Result};
/// The values an `I32` cell holds exactly: `ref.i31` keeps 31 bits.
const INDEX_CELL_RANGE: std::ops::RangeInclusive<i64> = -(1 << 30)..=(1 << 30) - 1;
/// What one cell contributes to its chain's element.
#[derive(Clone, Copy)]
enum CellSlot {
Fixed(PairElement),
/// An integer literal an index cell holds exactly: Index by default
/// (ADR-0028), crossing to a `Ratio` neighbor.
IndexLiteral,
}
/// Infers the `PairElement` for a runtime CONS. The car's runtime type or
/// literal slot unifies with the cdr's cells — a typed `PairRef` cdr's element,
/// or each member of a constant quoted-list cdr. A constant car with no literal
/// slot adopts a typed cdr's element.
pub(super) fn infer_pair_element(car: &Expr, cdr: &Expr) -> Result<PairElement> {
let cdr_slots = cdr_slots(cdr)?;
let mut slots = vec![car_slot(car, &cdr_slots)?];
slots.extend(cdr_slots);
Ok(unify_slots(&slots))
fn car_slot(car: &Expr, cdr_slots: &[CellSlot]) -> Result<CellSlot> {
if let Some(ty) = car.wasm_type() {
return PairElement::from_wasm_type(ty)
.map(CellSlot::Fixed)
.ok_or_else(|| {
Error::Compile(format!(
"CONS: car type {ty} cannot ride a $pair cell (nested pairs land in a follow-up slice)"
))
});
match (literal_cell_slot(car), cdr_slots.first()) {
(Some(slot), _) => Ok(slot),
(None, Some(slot)) => Ok(*slot),
(None, None) => Err(Error::Compile(format!(
"CONS: cannot infer element type from constant car {} — \
supply a typed cdr (a pair) or a typed runtime car",
format_expr(car)
))),
fn cdr_slots(cdr: &Expr) -> Result<Vec<CellSlot>> {
match (cdr.wasm_type(), cdr) {
(Some(WasmType::PairRef(elem)), _) => Ok(vec![CellSlot::Fixed(elem)]),
(None, Expr::Quote(inner)) => match inner.as_ref() {
Expr::List(members) => members.iter().map(element_slot).collect(),
_ => Ok(Vec::new()),
},
/// The natural `PairElement` of a literal car: an integer an index cell holds
/// exactly is `I32` (ADR-0028), any other number `Ratio`, `nil` / a bool the
/// `Bool` truth slot, a string `StringRef`.
pub(in crate::compiler) fn literal_pair_element(car: &Expr) -> Option<PairElement> {
literal_cell_slot(car).map(|slot| match slot {
CellSlot::Fixed(elem) => elem,
CellSlot::IndexLiteral => PairElement::I32,
})
/// Whether a resolved value is a numeric literal that must ride a `Ratio` car:
/// fractional, or an integer outside the range an index cell holds.
pub(super) fn is_scalar_literal(value: &Expr) -> bool {
matches!(
literal_cell_slot(value),
Some(CellSlot::Fixed(PairElement::Ratio))
)
fn literal_cell_slot(expr: &Expr) -> Option<CellSlot> {
match expr {
Expr::Number(n) if fits_index_cell(n) => Some(CellSlot::IndexLiteral),
Expr::Number(_) => Some(CellSlot::Fixed(PairElement::Ratio)),
Expr::T | Expr::Nil => Some(CellSlot::Fixed(PairElement::Bool)),
Expr::String(_) => Some(CellSlot::Fixed(PairElement::StringRef)),
_ => None,
fn fits_index_cell(n: &Fraction) -> bool {
*n.denom() == 1 && INDEX_CELL_RANGE.contains(n.numer())
/// The unified `PairElement` of a chain built from resolved `elems` (LIST
/// cells, APPEND members, a FOLD seed). A non-literal constant rides `AnyRef`;
/// an empty chain is `AnyRef`.
pub(in crate::compiler) fn chain_pair_element(elems: &[Expr]) -> Result<PairElement> {
let slots = elems.iter().map(element_slot).collect::<Result<Vec<_>>>()?;
fn element_slot(elem: &Expr) -> Result<CellSlot> {
match elem.wasm_type() {
Some(ty) => PairElement::from_wasm_type(ty)
Error::Compile(format!("LIST: element type {ty} cannot ride a $pair cell"))
}),
None => Ok(literal_cell_slot(elem).unwrap_or(CellSlot::Fixed(PairElement::AnyRef))),
/// Fixed slots widen pairwise; index literals take an `I32` / `Ratio` result
/// (and make a literal-only chain `I32`), widening anything else to `AnyRef`.
fn unify_slots(slots: &[CellSlot]) -> PairElement {
let fixed = slots
.iter()
.filter_map(|slot| match slot {
CellSlot::Fixed(elem) => Some(*elem),
CellSlot::IndexLiteral => None,
.reduce(PairElement::widen);
let index_literal = slots
.any(|slot| matches!(slot, CellSlot::IndexLiteral));
match (fixed, index_literal) {
(None, true) => PairElement::I32,
(None, false) => PairElement::AnyRef,
(Some(elem @ (PairElement::I32 | PairElement::Ratio)), _) | (Some(elem), false) => elem,
(Some(_), true) => PairElement::AnyRef,