d5c565d48d
Tasks 1-5 GREEN. T1 baseline pins (re-point annotation + @ailang_rc_alloc heap-box byte-pin: size=8+n*8, tag@0, fields@8/16). T2 export gate widened (is_c_scalar -> two-level is_c_abi_type: single-ctor all-Int/Float record; multi-ctor/Str/List/nested still RED; gate suite 10/10; M1 adt-ret must-fail re-pointed to multi-ctor+Str Reading). T3 codegen forwarder widened (llvm_scalar record Type::Con -> ptr; M2 forwarder body byte-unchanged; 3/3 staticlib pins). T4/T5 E2E record round-trip own+borrow, global leak-freedom. Boss spec-consistency repair (M2.1-precedent class): orchestrator correctly BLOCKED Task 5 on a genuine spec defect -- the single-ctx-readback allocs==frees proof model is unsatisfiable for borrow (and only coincidentally passes for own) because M2's TLS-ctx is bound only during the synchronous forwarder call, so host-side decs land on g_rc_*, not ctx. Boss-verified globally leak-free + value-correct both modes. Spec + plan + harness amended to the stronger global model (sum all ailang_rc_stats: lines; the M2-TLS cross-attribution documented as correct behaviour). No fresh grounding-check (removes an over-strong measurement assumption). Tasks 6 (DESIGN.md frozen-layout SSOT + lockstep pointers + freeze wording + enforceability demo) and 7 (workspace-green gate) re-dispatched on the amended plan. Bench/architect milestone-close is audit-owned. iter embedding-abi-m3.1 (PARTIAL); INDEX.md line deferred to the DONE commit
99 lines
3.4 KiB
Rust
99 lines
3.4 KiB
Rust
//! Embedding-ABI M1 export-signature gate (spec §3, clause-3
|
|
//! discriminator): an `(export …)` fn whose params/ret are not all
|
|
//! `Int`/`Float`, or whose effect set is non-empty, MUST fail
|
|
//! `ail check` with the new diagnostic. A scalar+pure export passes.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
fn examples_dir() -> PathBuf {
|
|
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
|
.parent().unwrap().parent().unwrap()
|
|
.join("examples")
|
|
}
|
|
|
|
fn check_codes(file: &str) -> Vec<String> {
|
|
let path = examples_dir().join(file);
|
|
let ws = ailang_surface::load_workspace(&path)
|
|
.unwrap_or_else(|e| panic!("load {file}: {e}"));
|
|
ailang_check::check_workspace(&ws)
|
|
.into_iter()
|
|
.map(|d| d.code)
|
|
.collect()
|
|
}
|
|
|
|
#[test]
|
|
fn str_param_export_rejected() {
|
|
assert!(check_codes("embed_export_str_param_rejected.ail")
|
|
.iter().any(|c| c == "export-non-scalar-signature"));
|
|
}
|
|
|
|
// M3 RE-POINT (deliberate, reviewed — spec 2026-05-18-embedding-abi-m3
|
|
// §"must-fail axis"): the fixture this asserts on
|
|
// (embed_export_adt_ret_rejected.ail) is single-ctor two-Int = M3-VALID.
|
|
// Task 2 re-points the fixture to a genuinely-still-rejected ADT
|
|
// (multi-ctor + Str field). This pin's *meaning* changes from
|
|
// "any ADT rejected" to "non-M3-shaped ADT rejected"; it is NOT
|
|
// silently inverted — the rejection (export-non-scalar-signature)
|
|
// stays asserted, on a fixture that still genuinely fails it.
|
|
#[test]
|
|
fn adt_ret_export_rejected() {
|
|
assert!(check_codes("embed_export_adt_ret_rejected.ail")
|
|
.iter().any(|c| c == "export-non-scalar-signature"));
|
|
}
|
|
|
|
#[test]
|
|
fn io_export_rejected() {
|
|
assert!(check_codes("embed_export_io_rejected.ail")
|
|
.iter().any(|c| c == "export-has-effects"));
|
|
}
|
|
|
|
#[test]
|
|
fn combined_effectful_export_rejected_on_signature_first() {
|
|
let codes = check_codes("embed_export_effectful_rejected.ail");
|
|
assert!(codes.iter().any(|c| c == "export-non-scalar-signature"),
|
|
"combined fixture must fail signature-first; got {codes:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn scalar_pure_export_passes() {
|
|
let codes = check_codes("embed_export_float_ok.ail");
|
|
assert!(!codes.iter().any(|c|
|
|
c == "export-non-scalar-signature" || c == "export-has-effects"),
|
|
"scalar+pure export must pass the gate; got {codes:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn headline_int_export_passes() {
|
|
let codes = check_codes("embed_backtest_step.ail");
|
|
assert!(!codes.iter().any(|c|
|
|
c == "export-non-scalar-signature" || c == "export-has-effects"),
|
|
"headline export must pass the gate; got {codes:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn record_export_passes() {
|
|
// single-ctor (Float,Int) record export — M3-valid
|
|
let codes = check_codes("embed_export_record_ok.ail");
|
|
assert!(!codes.iter().any(|c|
|
|
c == "export-non-scalar-signature" || c == "export-has-effects"),
|
|
"single-ctor scalar record export must pass the gate; got {codes:?}");
|
|
}
|
|
|
|
#[test]
|
|
fn str_field_record_export_rejected() {
|
|
assert!(check_codes("embed_export_str_field_record_rejected.ail")
|
|
.iter().any(|c| c == "export-non-scalar-signature"));
|
|
}
|
|
|
|
#[test]
|
|
fn list_field_record_export_rejected() {
|
|
assert!(check_codes("embed_export_list_field_record_rejected.ail")
|
|
.iter().any(|c| c == "export-non-scalar-signature"));
|
|
}
|
|
|
|
#[test]
|
|
fn multictor_export_rejected() {
|
|
assert!(check_codes("embed_export_multictor_rejected.ail")
|
|
.iter().any(|c| c == "export-non-scalar-signature"));
|
|
}
|