1
//! Result-type reconciliation shared by the multi-arm special forms (IF,
2
//! HANDLER-CASE). Each reachable arm is an already-compiled value, a
3
//! dimension-flexible numeric literal, or `nil`; the latter two are emitted only
4
//! once the unified block type is known, so a literal crosses Index↔Scalar
5
//! (ADR-0028) and a `nil` beside a list stays a null pair.
6

            
7
use crate::ast::{Expr, PairElement, WasmType};
8
use crate::compiler::context::CompileContext;
9
use crate::compiler::emit::FunctionEmitter;
10
use crate::compiler::expr::{
11
    classify_stack_type, compile_for_effect, compile_for_stack, compile_for_stack_as,
12
    emit_box_anyref, emit_relabel_chain, eval_value,
13
};
14
use crate::compiler::native::{chain_pair_element, compile_constant_list};
15
use crate::error::{Error, Result};
16
use crate::runtime::SymbolTable;
17

            
18
use super::block_exits::form_diverges;
19

            
20
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21
pub(super) enum Arm {
22
    /// Compiled: a value of this type is already on the arm's stack.
23
    Value(WasmType),
24
    /// A numeric literal of this default stratum, not yet emitted.
25
    NumberLiteral(WasmType),
26
    /// A form resolving to `nil`, not yet emitted.
27
    Nil,
28
    /// A form resolving to a constant list of this chain element, materialized
29
    /// at the reconciled element once the result is known.
30
    ConstantList(PairElement),
31
}
32

            
33
/// How genuinely disagreeing arm types unify.
34
#[derive(Clone, Copy)]
35
pub(super) enum OnConflict {
36
    Widen,
37
    Reject(&'static str),
38
}
39

            
40
/// Compiles one arm into `scratch`: a diverging arm emits its exit and yields
41
/// `None`; a literal / `nil` arm is deferred to [`finish_arm`]; any other arm
42
/// is compiled now as a [`Arm::Value`].
43
22078
pub(super) fn compile_arm(
44
22078
    ctx: &mut CompileContext,
45
22078
    scratch: &mut FunctionEmitter,
46
22078
    symbols: &mut SymbolTable,
47
22078
    expr: &Expr,
48
22078
) -> Result<Option<Arm>> {
49
22078
    if form_diverges(&mut symbols.clone(), expr)? {
50
2656
        compile_for_stack(ctx, scratch, symbols, expr)?;
51
2656
        return Ok(None);
52
19422
    }
53
19422
    if let Some(arm) = deferred_arm(symbols, expr) {
54
10043
        return Ok(Some(arm));
55
9379
    }
56
9379
    Ok(Some(Arm::Value(compile_for_stack(
57
9379
        ctx, scratch, symbols, expr,
58
    )?)))
59
22078
}
60

            
61
/// The deferred arm of a non-diverging `expr` that resolves to a numeric
62
/// literal or `nil`; `None` when the arm must be compiled up front. Probes on a
63
/// clone so no compile-time effect is applied.
64
19422
fn deferred_arm(symbols: &SymbolTable, expr: &Expr) -> Option<Arm> {
65
19422
    if ends_in_nil(expr) {
66
83
        return Some(Arm::Nil);
67
19339
    }
68
19339
    match eval_value(&mut symbols.clone(), expr).ok()? {
69
2490
        Expr::Nil => Some(Arm::Nil),
70
1909
        Expr::Quote(inner) => match *inner {
71
1909
            Expr::List(members) => chain_pair_element(&members).ok().map(Arm::ConstantList),
72
            _ => None,
73
        },
74
14940
        value => literal_arm(&value),
75
    }
76
19422
}
77

            
78
/// Whether `expr` is a BEGIN / PROGN whose value form is literally `nil`: its
79
/// value is nil whatever runtime effects precede it.
80
30544
fn ends_in_nil(expr: &Expr) -> bool {
81
30544
    let mut form = expr;
82
30959
    while let Expr::List(elems) = form
83
14442
        && let (Some(Expr::Symbol(head)), Some(last)) = (elems.first(), elems.last())
84
14442
        && elems.len() > 1
85
13944
        && matches!(head.as_str(), "BEGIN" | "PROGN")
86
    {
87
498
        if matches!(last, Expr::Nil) {
88
83
            return true;
89
415
        }
90
415
        form = last;
91
    }
92
30461
    false
93
30544
}
94

            
95
/// The arm a non-diverging `expr` contributes on the ctx-less eval surface,
96
/// peeked on a clone — the mirror of [`compile_arm`]. A constant list lowers to
97
/// a pair chain typed by the LIST rule; an unclassifiable value is `I32`.
98
10292
pub(super) fn eval_arm(symbols: &SymbolTable, expr: &Expr) -> Result<Option<Arm>> {
99
10292
    if form_diverges(&mut symbols.clone(), expr)? {
100
        return Ok(None);
101
10292
    }
102
10292
    let value = eval_value(&mut symbols.clone(), expr)?;
103
10292
    Ok(Some(arm_of_form(expr, &value)?))
104
10292
}
105

            
106
/// The arm of a form whose resolved eval value is `value`.
107
11122
pub(super) fn arm_of_form(expr: &Expr, value: &Expr) -> Result<Arm> {
108
11122
    if ends_in_nil(expr) {
109
        return Ok(Arm::Nil);
110
11122
    }
111
11122
    arm_of_value(value)
112
11122
}
113

            
114
/// The arm a resolved eval value contributes.
115
11122
fn arm_of_value(value: &Expr) -> Result<Arm> {
116
11122
    match value {
117
1743
        Expr::Nil => Ok(Arm::Nil),
118
2158
        Expr::Number(_) => Ok(literal_arm(value).unwrap_or(Arm::Value(WasmType::I32))),
119
1826
        Expr::Quote(inner) => match inner.as_ref() {
120
1826
            Expr::List(members) => Ok(Arm::Value(WasmType::PairRef(chain_pair_element(members)?))),
121
            _ => Ok(Arm::Value(WasmType::I32)),
122
        },
123
5395
        _ => Ok(Arm::Value(
124
5395
            classify_stack_type(value).unwrap_or(WasmType::I32),
125
5395
        )),
126
    }
127
11122
}
128

            
129
17098
fn literal_arm(value: &Expr) -> Option<Arm> {
130
17098
    match value {
131
7719
        Expr::Number(_) => classify_stack_type(value).map(Arm::NumberLiteral),
132
9379
        _ => None,
133
    }
134
17098
}
135

            
136
/// Unifies reachable arms: compiled values join (pair elements widen), a
137
/// numeric literal adopts an Index/Scalar value's stratum, and `nil` rides a
138
/// list / anyref / bool result. No reachable arm is the polymorphic `I32`.
139
17098
pub(super) fn reconcile_arms(arms: &[Arm], on_conflict: OnConflict) -> Result<WasmType> {
140
17098
    let mut value: Option<WasmType> = None;
141
17098
    let mut literal: Option<WasmType> = None;
142
17098
    let mut nil = false;
143
31291
    for arm in arms {
144
31291
        match *arm {
145
16600
            Arm::Value(ty) => value = Some(joined(value, ty, on_conflict)?),
146
1909
            Arm::ConstantList(elem) => {
147
1909
                value = Some(joined(value, WasmType::PairRef(elem), on_conflict)?);
148
            }
149
7719
            Arm::NumberLiteral(ty) => {
150
1411
                literal = Some(match literal {
151
1411
                    Some(prev) if prev != ty => WasmType::Ratio,
152
7719
                    _ => ty,
153
                });
154
            }
155
5063
            Arm::Nil => nil = true,
156
        }
157
    }
158
17015
    let typed = match (value, literal) {
159
10707
        (base, None) => base,
160
3071
        (None, lit) => lit,
161
83
        (Some(WasmType::AnyRef), Some(_)) => Some(WasmType::AnyRef),
162
3154
        (Some(base), Some(lit)) if literal_fits(base, lit) => Some(base),
163
249
        (Some(base), Some(lit)) => Some(join(base, lit, on_conflict)?),
164
    };
165
16932
    match (typed, nil) {
166
12118
        (ty, false) => Ok(ty.unwrap_or(WasmType::I32)),
167
332
        (None, true) => Ok(WasmType::Bool),
168
3237
        (Some(ty @ (WasmType::PairRef(_) | WasmType::AnyRef | WasmType::Bool)), true) => Ok(ty),
169
1245
        (Some(ty), true) => nil_beside(ty, on_conflict),
170
    }
171
17098
}
172

            
173
/// A nil arm beside a result that cannot be nil (an index, scalar, money,
174
/// string, …) widens the block to `AnyRef`, or is rejected by name.
175
1245
fn nil_beside(ty: WasmType, on_conflict: OnConflict) -> Result<WasmType> {
176
1245
    match on_conflict {
177
1162
        OnConflict::Widen => Ok(WasmType::AnyRef),
178
83
        OnConflict::Reject(form) => Err(Error::Compile(format!(
179
83
            "{form}: a nil arm cannot join a {ty} result"
180
83
        ))),
181
    }
182
1245
}
183

            
184
5312
fn join(a: WasmType, b: WasmType, on_conflict: OnConflict) -> Result<WasmType> {
185
5312
    match (a, b, on_conflict) {
186
5312
        _ if a == b => Ok(a),
187
1245
        (WasmType::PairRef(x), WasmType::PairRef(y), _) => Ok(WasmType::PairRef(x.widen(y))),
188
1660
        (_, _, OnConflict::Widen) => Ok(WasmType::AnyRef),
189
166
        (_, _, OnConflict::Reject(form)) => Err(Error::Compile(format!(
190
166
            "{form}: conflicting result types {a} and {b}"
191
166
        ))),
192
    }
193
5312
}
194

            
195
18509
fn joined(prev: Option<WasmType>, ty: WasmType, on_conflict: OnConflict) -> Result<WasmType> {
196
18509
    match prev {
197
5063
        Some(prev) => join(prev, ty, on_conflict),
198
13446
        None => Ok(ty),
199
    }
200
18509
}
201

            
202
/// Whether a literal of stratum `lit` can adopt `base`: an integer literal
203
/// becomes an index or a scalar, a fractional one only a scalar, and no literal
204
/// becomes Money (it has no currency).
205
8632
fn literal_fits(base: WasmType, lit: WasmType) -> bool {
206
664
    matches!(
207
8632
        (base, lit),
208
        (WasmType::I32 | WasmType::Ratio, WasmType::I32) | (WasmType::Ratio, WasmType::Ratio)
209
    )
210
8632
}
211

            
212
/// Completes an arm into its `scratch` at the unified `result`: a compiled
213
/// value is boxed when the block is `AnyRef`, a deferred literal is compiled at
214
/// the result stratum, a deferred `nil` emits its effects then the typed nil.
215
18924
pub(super) fn finish_arm(
216
18924
    ctx: &mut CompileContext,
217
18924
    scratch: &mut FunctionEmitter,
218
18924
    symbols: &mut SymbolTable,
219
18924
    expr: &Expr,
220
18924
    arm: Arm,
221
18924
    result: WasmType,
222
18924
) -> Result<()> {
223
5478
    match arm {
224
9047
        Arm::Value(ty) => {
225
9047
            box_arm_value(ctx, scratch, ty, result);
226
9047
            Ok(())
227
        }
228
5478
        Arm::NumberLiteral(lit) if literal_fits(result, lit) => {
229
5063
            compile_for_stack_as(ctx, scratch, symbols, expr, result)
230
        }
231
        Arm::NumberLiteral(_) => {
232
415
            let raw = compile_for_stack(ctx, scratch, symbols, expr)?;
233
415
            box_arm_value(ctx, scratch, raw, result);
234
415
            Ok(())
235
        }
236
        Arm::Nil => {
237
2490
            if !matches!(expr, Expr::Nil) {
238
415
                compile_for_effect(ctx, scratch, symbols, expr)?;
239
2075
            }
240
2490
            emit_typed_nil(ctx, scratch, result)
241
        }
242
1909
        Arm::ConstantList(own) => {
243
1909
            let elem = match result {
244
1909
                WasmType::PairRef(elem) => elem,
245
                _ => own,
246
            };
247
1909
            let members = constant_list_members(symbols, expr)?;
248
1909
            compile_constant_list(ctx, scratch, symbols, members, elem)?;
249
1909
            Ok(())
250
        }
251
    }
252
18924
}
253

            
254
/// The members of a constant-list arm, resolved on the live table so its
255
/// compile-time effects apply exactly once (a constant list has no runtime ones).
256
1909
fn constant_list_members(symbols: &mut SymbolTable, expr: &Expr) -> Result<Vec<Expr>> {
257
1909
    match eval_value(symbols, expr)? {
258
1909
        Expr::Quote(inner) => match *inner {
259
1909
            Expr::List(members) => Ok(members),
260
            other => Err(not_a_constant_list(&other)),
261
        },
262
        other => Err(not_a_constant_list(&other)),
263
    }
264
1909
}
265

            
266
fn not_a_constant_list(value: &Expr) -> Error {
267
    Error::Compile(format!(
268
        "constant-list arm resolved to {value:?}, not a list"
269
    ))
270
}
271

            
272
/// Retypes an already-emitted arm value of `ty` to the block `result`: an
273
/// i32-repr value boxes into an anyref block; a bool chain is rebuilt before it
274
/// rides a widened chain.
275
9462
fn box_arm_value(
276
9462
    ctx: &CompileContext,
277
9462
    scratch: &mut FunctionEmitter,
278
9462
    ty: WasmType,
279
9462
    result: WasmType,
280
9462
) {
281
9462
    match (ty, result) {
282
2905
        (_, WasmType::AnyRef) => emit_box_anyref(ctx, scratch, ty),
283
1328
        (WasmType::PairRef(from), WasmType::PairRef(to)) => {
284
1328
            emit_relabel_chain(ctx, scratch, from, to);
285
1328
        }
286
5229
        _ => {}
287
    }
288
9462
}
289

            
290
/// Pushes `nil` typed as the block `result`: a null pair, a null anyref, or a
291
/// falsy i32.
292
3071
pub(super) fn emit_typed_nil(
293
3071
    ctx: &CompileContext,
294
3071
    scratch: &mut FunctionEmitter,
295
3071
    result: WasmType,
296
3071
) -> Result<()> {
297
3071
    match result {
298
1079
        WasmType::PairRef(_) => scratch.ref_null(ctx.ids.ty_pair),
299
913
        WasmType::AnyRef => scratch.ref_null_any(),
300
1079
        WasmType::I32 | WasmType::Bool => scratch.i32_const(0),
301
        other => {
302
            return Err(Error::Compile(format!(
303
                "nil cannot be typed as a {other} result"
304
            )));
305
        }
306
    }
307
3071
    Ok(())
308
3071
}