1
//! Static inference of the `PairElement` a `$pair` cell rides. Single source
2
//! of truth for CONS, LIST / APPEND chains and the literal-slot mirrors: the
3
//! most specific homogeneous element when every cell agrees, widened to
4
//! `PairElement::AnyRef` (ADR-0025 escape hatch) when they disagree.
5

            
6
use crate::ast::{Expr, Fraction, PairElement, WasmType};
7
use crate::compiler::expr::format_expr;
8
use crate::error::{Error, Result};
9

            
10
/// The values an `I32` cell holds exactly: `ref.i31` keeps 31 bits.
11
const INDEX_CELL_RANGE: std::ops::RangeInclusive<i64> = -(1 << 30)..=(1 << 30) - 1;
12

            
13
/// What one cell contributes to its chain's element.
14
#[derive(Clone, Copy)]
15
enum CellSlot {
16
    Fixed(PairElement),
17
    /// An integer literal an index cell holds exactly: Index by default
18
    /// (ADR-0028), crossing to a `Ratio` neighbor.
19
    IndexLiteral,
20
}
21

            
22
/// Infers the `PairElement` for a runtime CONS. The car's runtime type or
23
/// literal slot unifies with the cdr's cells — a typed `PairRef` cdr's element,
24
/// or each member of a constant quoted-list cdr. A constant car with no literal
25
/// slot adopts a typed cdr's element.
26
46498
pub(super) fn infer_pair_element(car: &Expr, cdr: &Expr) -> Result<PairElement> {
27
46498
    let cdr_slots = cdr_slots(cdr)?;
28
46498
    let mut slots = vec![car_slot(car, &cdr_slots)?];
29
46496
    slots.extend(cdr_slots);
30
46496
    Ok(unify_slots(&slots))
31
46498
}
32

            
33
46498
fn car_slot(car: &Expr, cdr_slots: &[CellSlot]) -> Result<CellSlot> {
34
46498
    if let Some(ty) = car.wasm_type() {
35
43500
        return PairElement::from_wasm_type(ty)
36
43500
            .map(CellSlot::Fixed)
37
43500
            .ok_or_else(|| {
38
1
                Error::Compile(format!(
39
1
                    "CONS: car type {ty} cannot ride a $pair cell (nested pairs land in a follow-up slice)"
40
1
                ))
41
1
            });
42
2998
    }
43
2998
    match (literal_cell_slot(car), cdr_slots.first()) {
44
2830
        (Some(slot), _) => Ok(slot),
45
167
        (None, Some(slot)) => Ok(*slot),
46
1
        (None, None) => Err(Error::Compile(format!(
47
1
            "CONS: cannot infer element type from constant car {} — \
48
1
             supply a typed cdr (a pair) or a typed runtime car",
49
1
            format_expr(car)
50
1
        ))),
51
    }
52
46498
}
53

            
54
46498
fn cdr_slots(cdr: &Expr) -> Result<Vec<CellSlot>> {
55
46498
    match (cdr.wasm_type(), cdr) {
56
22917
        (Some(WasmType::PairRef(elem)), _) => Ok(vec![CellSlot::Fixed(elem)]),
57
2326
        (None, Expr::Quote(inner)) => match inner.as_ref() {
58
2326
            Expr::List(members) => members.iter().map(element_slot).collect(),
59
            _ => Ok(Vec::new()),
60
        },
61
21255
        _ => Ok(Vec::new()),
62
    }
63
46498
}
64

            
65
/// The natural `PairElement` of a literal car: an integer an index cell holds
66
/// exactly is `I32` (ADR-0028), any other number `Ratio`, `nil` / a bool the
67
/// `Bool` truth slot, a string `StringRef`.
68
2833
pub(in crate::compiler) fn literal_pair_element(car: &Expr) -> Option<PairElement> {
69
2833
    literal_cell_slot(car).map(|slot| match slot {
70
171
        CellSlot::Fixed(elem) => elem,
71
668
        CellSlot::IndexLiteral => PairElement::I32,
72
839
    })
73
2833
}
74

            
75
/// Whether a resolved value is a numeric literal that must ride a `Ratio` car:
76
/// fractional, or an integer outside the range an index cell holds.
77
4482
pub(super) fn is_scalar_literal(value: &Expr) -> bool {
78
4316
    matches!(
79
4482
        literal_cell_slot(value),
80
        Some(CellSlot::Fixed(PairElement::Ratio))
81
    )
82
4482
}
83

            
84
35741
fn literal_cell_slot(expr: &Expr) -> Option<CellSlot> {
85
19869
    match expr {
86
19869
        Expr::Number(n) if fits_index_cell(n) => Some(CellSlot::IndexLiteral),
87
4576
        Expr::Number(_) => Some(CellSlot::Fixed(PairElement::Ratio)),
88
5819
        Expr::T | Expr::Nil => Some(CellSlot::Fixed(PairElement::Bool)),
89
5234
        Expr::String(_) => Some(CellSlot::Fixed(PairElement::StringRef)),
90
4819
        _ => None,
91
    }
92
35741
}
93

            
94
19869
fn fits_index_cell(n: &Fraction) -> bool {
95
19869
    *n.denom() == 1 && INDEX_CELL_RANGE.contains(n.numer())
96
19869
}
97

            
98
/// The unified `PairElement` of a chain built from resolved `elems` (LIST
99
/// cells, APPEND members, a FOLD seed). A non-literal constant rides `AnyRef`;
100
/// an empty chain is `AnyRef`.
101
26912
pub(in crate::compiler) fn chain_pair_element(elems: &[Expr]) -> Result<PairElement> {
102
26912
    let slots = elems.iter().map(element_slot).collect::<Result<Vec<_>>>()?;
103
26911
    Ok(unify_slots(&slots))
104
26912
}
105

            
106
47598
fn element_slot(elem: &Expr) -> Result<CellSlot> {
107
47598
    match elem.wasm_type() {
108
22170
        Some(ty) => PairElement::from_wasm_type(ty)
109
22170
            .map(CellSlot::Fixed)
110
22170
            .ok_or_else(|| {
111
1
                Error::Compile(format!("LIST: element type {ty} cannot ride a $pair cell"))
112
1
            }),
113
25428
        None => Ok(literal_cell_slot(elem).unwrap_or(CellSlot::Fixed(PairElement::AnyRef))),
114
    }
115
47598
}
116

            
117
/// Fixed slots widen pairwise; index literals take an `I32` / `Ratio` result
118
/// (and make a literal-only chain `I32`), widening anything else to `AnyRef`.
119
73407
fn unify_slots(slots: &[CellSlot]) -> PairElement {
120
73407
    let fixed = slots
121
73407
        .iter()
122
117010
        .filter_map(|slot| match slot {
123
103381
            CellSlot::Fixed(elem) => Some(*elem),
124
13629
            CellSlot::IndexLiteral => None,
125
117010
        })
126
73407
        .reduce(PairElement::widen);
127
73407
    let index_literal = slots
128
73407
        .iter()
129
108704
        .any(|slot| matches!(slot, CellSlot::IndexLiteral));
130
73407
    match (fixed, index_literal) {
131
3406
        (None, true) => PairElement::I32,
132
167
        (None, false) => PairElement::AnyRef,
133
68919
        (Some(elem @ (PairElement::I32 | PairElement::Ratio)), _) | (Some(elem), false) => elem,
134
915
        (Some(_), true) => PairElement::AnyRef,
135
    }
136
73407
}