1
//! Heterogeneous `PairElement::AnyRef` codegen — ADR-0025 escape hatch.
2
//!
3
//! `infer_pair_element` widens to `PairElement::AnyRef` when the CONS
4
//! arms disagree on element type. These tests exercise the resulting
5
//! emit paths end-to-end via wasm validation, so we know:
6
//!
7
//! - Heterogeneous CONS chains compile and validate.
8
//! - Each car-side type (I32, Ratio, StringRef) gets the correct
9
//!   `ref.i31` / no-cast treatment when widened.
10
//! - CAR / CDR / DOLIST / MAP over an AnyRef chain emit valid wasm —
11
//!   no per-element downcast needed since the payload is already anyref.
12
//! - Runtime arithmetic and ordered comparison still refuse AnyRef.
13

            
14
use super::common::{compile_and_validate, compile_expect_error};
15

            
16
#[test]
17
1
fn heterogeneous_runtime_ratio_and_string_widens() {
18
    // (transaction-post-date 0) is Ratio; (tag-name 0) is StringRef.
19
    // The mixed chain widens to PairElement::AnyRef. Both cars are GC
20
    // refs already so neither needs ref.i31 boxing — the test pins the
21
    // no-cast emit path.
22
1
    compile_and_validate(
23
1
        "(let* ((X (transaction-post-date 0)) (S (tag-name 0))) \
24
1
           (cons X (cons S nil)))",
25
    );
26
1
}
27

            
28
#[test]
29
1
fn heterogeneous_runtime_i32_and_ratio_widens() {
30
    // (entity-count) is I32; (transaction-post-date 0) is Ratio. The
31
    // I32 car must be ref.i31-boxed before riding the anyref slot.
32
1
    compile_and_validate(
33
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
34
1
           (cons IDX (cons X nil)))",
35
    );
36
1
}
37

            
38
#[test]
39
1
fn heterogeneous_runtime_i32_and_string_widens() {
40
    // I32 + StringRef — exercises the ref.i31-on-car path with a
41
    // ref-typed cdr-element.
42
1
    compile_and_validate(
43
1
        "(let* ((IDX (entity-count)) (S (tag-name 0))) \
44
1
           (cons IDX (cons S nil)))",
45
    );
46
1
}
47

            
48
#[test]
49
1
fn heterogeneous_runtime_three_distinct_types() {
50
    // Three runtime types in one chain — each successive CONS rides
51
    // the AnyRef variant once the second cell has widened.
52
1
    compile_and_validate(
53
1
        "(let* ((IDX (entity-count)) \
54
1
                (X (transaction-post-date 0)) \
55
1
                (S (tag-name 0))) \
56
1
           (cons IDX (cons X (cons S nil))))",
57
    );
58
1
}
59

            
60
#[test]
61
1
fn literal_string_with_typed_ratio_pair_tail_widens() {
62
    // The cdr is a typed `pair<ratio>`; the car is a literal string.
63
    // Pre-fix this errored with "expected ratio, got string"; the
64
    // widening contract now lifts the result to AnyRef.
65
1
    compile_and_validate(
66
1
        "(let* ((X (transaction-post-date 0))) \
67
1
           (cons \"label\" (cons X nil)))",
68
    );
69
1
}
70

            
71
#[test]
72
1
fn literal_integer_with_typed_string_pair_tail_widens() {
73
    // Symmetric counterpart: literal i32 car + typed string cdr.
74
    // Tests that the literal-shape fallback widens against any
75
    // disagreeing typed cdr, not just Ratio.
76
1
    compile_and_validate(
77
1
        "(let* ((S (tag-name 0))) \
78
1
           (cons 1 (cons S nil)))",
79
    );
80
1
}
81

            
82
#[test]
83
1
fn dolist_over_anyref_pair_chain() {
84
    // Once the chain is `pair<anyref>`, DOLIST must walk it without
85
    // emitting an i32 downcast on the loop variable. The body just
86
    // counts iterations so we don't need a per-element typed op.
87
1
    compile_and_validate(
88
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0)) (count 0)) \
89
1
           (dolist (e (cons IDX (cons X nil))) (setf count (+ count 1))) \
90
1
           count)",
91
    );
92
1
}
93

            
94
#[test]
95
1
fn car_of_anyref_pair_routes_through_debug() {
96
    // CAR on a `pair<anyref>` returns AnyRef. The debug native
97
    // accepts anyref via the universal-result convention, so this
98
    // pins the AnyRef serialization path (`emit_to_anyref` no-op).
99
1
    compile_and_validate(
100
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
101
1
           (debug (car (cons IDX (cons X nil)))))",
102
    );
103
1
}
104

            
105
#[test]
106
1
fn cdr_of_anyref_pair_returns_anyref_pair() {
107
    // CDR returns the tail. Storing back into a let still works since
108
    // the type bookkeeping carries the AnyRef element through.
109
1
    compile_and_validate(
110
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
111
1
           (cdr (cons IDX (cons X nil))))",
112
    );
113
1
}
114

            
115
#[test]
116
1
fn anyref_car_refuses_typed_arithmetic() {
117
    // Once we've widened to AnyRef the value can't pretend to be a
118
    // Ratio for `+`. `+` must surface a structured compile error.
119
1
    let err = compile_expect_error(
120
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
121
1
           (+ (car (cons IDX (cons X nil))) 1))",
122
    );
123
1
    assert!(
124
1
        err.contains("any") || err.contains("AnyRef") || err.contains("numeric"),
125
        "got: {err}"
126
    );
127
1
}
128

            
129
#[test]
130
1
fn anyref_car_refuses_ordered_comparison() {
131
    // `<` only accepts I32 / Ratio cars. AnyRef-bearing list values
132
    // surface a structured compile error rather than silently coercing.
133
1
    let err = compile_expect_error(
134
1
        "(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
135
1
           (< (car (cons IDX (cons X nil))) 5))",
136
    );
137
1
    assert!(
138
1
        err.contains("any") || err.contains("AnyRef") || err.contains("numeric"),
139
        "got: {err}"
140
    );
141
1
}
142

            
143
#[test]
144
1
fn map_static_fn_over_anyref_chain() {
145
    // A static identity fn over an AnyRef chain — exercises the MAP
146
    // runtime-list path with the widened element type. The lambda
147
    // doesn't constrain its argument, so this stays valid.
148
1
    compile_and_validate(
149
1
        "(defun id (x) x) \
150
1
         (let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
151
1
           (map (function id) (cons IDX (cons X nil))))",
152
    );
153
1
}