Lines
100 %
Functions
Branches
//! Heterogeneous `PairElement::AnyRef` codegen — ADR-0025 escape hatch.
//!
//! `infer_pair_element` widens to `PairElement::AnyRef` when the CONS
//! arms disagree on element type. These tests exercise the resulting
//! emit paths end-to-end via wasm validation, so we know:
//! - Heterogeneous CONS chains compile and validate.
//! - Each car-side type (I32, Ratio, StringRef) gets the correct
//! `ref.i31` / no-cast treatment when widened.
//! - CAR / CDR / DOLIST / MAP over an AnyRef chain emit valid wasm —
//! no per-element downcast needed since the payload is already anyref.
//! - Runtime arithmetic and ordered comparison still refuse AnyRef.
use super::common::{compile_and_validate, compile_expect_error};
#[test]
fn heterogeneous_runtime_ratio_and_string_widens() {
// (transaction-post-date 0) is Ratio; (tag-name 0) is StringRef.
// The mixed chain widens to PairElement::AnyRef. Both cars are GC
// refs already so neither needs ref.i31 boxing — the test pins the
// no-cast emit path.
compile_and_validate(
"(let* ((X (transaction-post-date 0)) (S (tag-name 0))) \
(cons X (cons S nil)))",
);
}
fn heterogeneous_runtime_i32_and_ratio_widens() {
// (entity-count) is I32; (transaction-post-date 0) is Ratio. The
// I32 car must be ref.i31-boxed before riding the anyref slot.
"(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
(cons IDX (cons X nil)))",
fn heterogeneous_runtime_i32_and_string_widens() {
// I32 + StringRef — exercises the ref.i31-on-car path with a
// ref-typed cdr-element.
"(let* ((IDX (entity-count)) (S (tag-name 0))) \
(cons IDX (cons S nil)))",
fn heterogeneous_runtime_three_distinct_types() {
// Three runtime types in one chain — each successive CONS rides
// the AnyRef variant once the second cell has widened.
"(let* ((IDX (entity-count)) \
(X (transaction-post-date 0)) \
(S (tag-name 0))) \
(cons IDX (cons X (cons S nil))))",
fn literal_string_with_typed_ratio_pair_tail_widens() {
// The cdr is a typed `pair<ratio>`; the car is a literal string.
// Pre-fix this errored with "expected ratio, got string"; the
// widening contract now lifts the result to AnyRef.
"(let* ((X (transaction-post-date 0))) \
(cons \"label\" (cons X nil)))",
fn literal_integer_with_typed_string_pair_tail_widens() {
// Symmetric counterpart: literal i32 car + typed string cdr.
// Tests that the literal-shape fallback widens against any
// disagreeing typed cdr, not just Ratio.
"(let* ((S (tag-name 0))) \
(cons 1 (cons S nil)))",
fn dolist_over_anyref_pair_chain() {
// Once the chain is `pair<anyref>`, DOLIST must walk it without
// emitting an i32 downcast on the loop variable. The body just
// counts iterations so we don't need a per-element typed op.
"(let* ((IDX (entity-count)) (X (transaction-post-date 0)) (count 0)) \
(dolist (e (cons IDX (cons X nil))) (setf count (+ count 1))) \
count)",
fn car_of_anyref_pair_routes_through_debug() {
// CAR on a `pair<anyref>` returns AnyRef. The debug native
// accepts anyref via the universal-result convention, so this
// pins the AnyRef serialization path (`emit_to_anyref` no-op).
(debug (car (cons IDX (cons X nil)))))",
fn cdr_of_anyref_pair_returns_anyref_pair() {
// CDR returns the tail. Storing back into a let still works since
// the type bookkeeping carries the AnyRef element through.
(cdr (cons IDX (cons X nil))))",
fn anyref_car_refuses_typed_arithmetic() {
// Once we've widened to AnyRef the value can't pretend to be a
// Ratio for `+`. `+` must surface a structured compile error.
let err = compile_expect_error(
(+ (car (cons IDX (cons X nil))) 1))",
assert!(
err.contains("any") || err.contains("AnyRef") || err.contains("numeric"),
"got: {err}"
fn anyref_car_refuses_ordered_comparison() {
// `<` only accepts I32 / Ratio cars. AnyRef-bearing list values
// surface a structured compile error rather than silently coercing.
(< (car (cons IDX (cons X nil))) 5))",
fn map_static_fn_over_anyref_chain() {
// A static identity fn over an AnyRef chain — exercises the MAP
// runtime-list path with the widened element type. The lambda
// doesn't constrain its argument, so this stays valid.
"(defun id (x) x) \
(let* ((IDX (entity-count)) (X (transaction-post-date 0))) \
(map (function id) (cons IDX (cons X nil))))",