1
use crate::ast::{Expr, PairElement, WasmType};
2

            
3
use super::infer::infer_pair_element;
4

            
5
/// `infer_pair_element` decides the static PairElement from a
6
/// `(cons car cdr)` form, balancing the car's compile-time type
7
/// against the cdr's. The legacy integration test reached this
8
/// via `Symbol::new("BAL").with_value(Expr::WasmRuntime(Commodity))`
9
/// in the symbol table; the unit form exercises the function
10
/// directly so the contract is locked in without needing a
11
/// script-mode Commodity producer.
12
#[test]
13
1
fn commodity_car_with_nil_cdr_is_commodity_pair() {
14
1
    let car = Expr::WasmRuntime(WasmType::Commodity);
15
1
    let elem = infer_pair_element(&car, &Expr::Nil).unwrap();
16
1
    assert_eq!(elem, PairElement::Commodity);
17
1
}
18

            
19
#[test]
20
1
fn commodity_car_with_pair_of_i32_cdr_widens_to_anyref() {
21
    // ADR-0025 escape hatch: when the car and cdr disagree on element
22
    // type, CONS widens to `PairElement::AnyRef` (canonical Common Lisp
23
    // heterogeneous `cons`) rather than rejecting. The strict typed path
24
    // still applies when both arms agree.
25
1
    let car = Expr::WasmRuntime(WasmType::Commodity);
26
1
    let cdr = Expr::WasmRuntime(WasmType::PairRef(PairElement::I32));
27
1
    let elem = infer_pair_element(&car, &cdr).expect("heterogeneous CONS widens to AnyRef");
28
1
    assert_eq!(elem, PairElement::AnyRef);
29
1
}
30

            
31
#[test]
32
1
fn ratio_car_with_pair_of_ratio_cdr_resolves_to_ratio_pair() {
33
1
    let car = Expr::WasmRuntime(WasmType::Ratio);
34
1
    let cdr = Expr::WasmRuntime(WasmType::PairRef(PairElement::Ratio));
35
1
    let elem = infer_pair_element(&car, &cdr).unwrap();
36
1
    assert_eq!(elem, PairElement::Ratio);
37
1
}
38

            
39
#[test]
40
1
fn integer_number_car_with_nil_cdr_uses_ratio() {
41
    // A numeric literal is a dimensionless Ratio in EVERY context, including a
42
    // pair cell — never an I32 count. Reclassifying an integer literal as I32
43
    // here would be an illegal implicit Ratio→count conversion: the `1` would
44
    // come back from the list as a count and arithmetic would refuse it.
45
1
    let car = Expr::Number(crate::ast::Fraction::from_integer(1));
46
1
    let elem = infer_pair_element(&car, &Expr::Nil).unwrap();
47
1
    assert_eq!(elem, PairElement::Ratio);
48
1
}
49

            
50
#[test]
51
1
fn fractional_number_car_with_nil_cdr_uses_ratio_default() {
52
1
    let car = Expr::Number(crate::ast::Fraction::new(1, 2));
53
1
    let elem = infer_pair_element(&car, &Expr::Nil).unwrap();
54
1
    assert_eq!(elem, PairElement::Ratio);
55
1
}
56

            
57
#[test]
58
1
fn bool_car_with_nil_cdr_uses_bool_slot() {
59
    // A bool literal car rides PairElement::Bool (not I32), so CAR recovers a
60
    // truth value that serializes as Nil/Bool rather than Number.
61
1
    let car = Expr::Bool(true);
62
1
    let elem = infer_pair_element(&car, &Expr::Nil).unwrap();
63
1
    assert_eq!(elem, PairElement::Bool);
64
1
}
65

            
66
#[test]
67
1
fn string_car_with_nil_cdr_uses_stringref() {
68
1
    let car = Expr::String("hi".to_string());
69
1
    let elem = infer_pair_element(&car, &Expr::Nil).unwrap();
70
1
    assert_eq!(elem, PairElement::StringRef);
71
1
}
72

            
73
#[test]
74
1
fn anyref_cdr_propagates_anyref_pair_element() {
75
1
    let car = Expr::WasmRuntime(WasmType::Ratio);
76
1
    let cdr = Expr::WasmRuntime(WasmType::PairRef(PairElement::AnyRef));
77
1
    let elem = infer_pair_element(&car, &cdr).expect("AnyRef cdr accepts any car");
78
1
    assert_eq!(elem, PairElement::AnyRef);
79
1
}
80

            
81
#[test]
82
1
fn ratio_car_with_pair_of_string_cdr_widens_to_anyref() {
83
1
    let car = Expr::WasmRuntime(WasmType::Ratio);
84
1
    let cdr = Expr::WasmRuntime(WasmType::PairRef(PairElement::StringRef));
85
1
    let elem = infer_pair_element(&car, &cdr).expect("heterogeneous CONS widens to AnyRef");
86
1
    assert_eq!(elem, PairElement::AnyRef);
87
1
}
88

            
89
#[test]
90
1
fn anyref_widen_is_symmetric_and_idempotent() {
91
1
    assert_eq!(
92
1
        PairElement::I32.widen(PairElement::Ratio),
93
        PairElement::AnyRef
94
    );
95
1
    assert_eq!(
96
1
        PairElement::Ratio.widen(PairElement::I32),
97
        PairElement::AnyRef
98
    );
99
1
    assert_eq!(
100
1
        PairElement::AnyRef.widen(PairElement::I32),
101
        PairElement::AnyRef
102
    );
103
1
    assert_eq!(PairElement::I32.widen(PairElement::I32), PairElement::I32);
104
1
}
105

            
106
#[test]
107
1
fn literal_string_car_with_pair_of_ratio_cdr_widens_to_anyref() {
108
    // Regression: before the literal-shape branch ran ahead of the
109
    // cdr-element fallback, a literal String car with a typed Ratio
110
    // cdr was silently classified as Ratio and erroried at codegen
111
    // ("expected ratio, got string"). The widening contract says a
112
    // disagreeing arm widens to AnyRef instead.
113
1
    let car = Expr::String("label".to_string());
114
1
    let cdr = Expr::WasmRuntime(WasmType::PairRef(PairElement::Ratio));
115
1
    let elem = infer_pair_element(&car, &cdr).expect("literal car widens against typed cdr");
116
1
    assert_eq!(elem, PairElement::AnyRef);
117
1
}
118

            
119
#[test]
120
1
fn pair_element_anyref_round_trips_through_wasm_type() {
121
1
    assert_eq!(PairElement::AnyRef.as_wasm_type(), WasmType::AnyRef);
122
1
    assert_eq!(
123
1
        PairElement::from_wasm_type(WasmType::AnyRef),
124
        Some(PairElement::AnyRef)
125
    );
126
1
}