//! 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 { 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")); }