use lex_ast::canonicalize_program;
use lex_syntax::parse_source;
use lex_types::{check_program, TypeError};
fn check(src: &str) -> Result<(), Vec<TypeError>> {
let p = parse_source(src).expect("parse");
let stages = canonicalize_program(&p);
check_program(&stages).map(|_| ())
}
#[test]
fn bare_caller_satisfies_specific_callee() {
let src = r#"
fn callee() -> [mcp("ocpp")] Int { 1 }
fn caller() -> [mcp] Int { callee() }
"#;
check(src).unwrap_or_else(|errs| panic!("expected ok, got: {errs:#?}"));
}
#[test]
fn specific_caller_satisfies_same_specific_callee() {
let src = r#"
fn callee() -> [mcp("ocpp")] Int { 1 }
fn caller() -> [mcp("ocpp")] Int { callee() }
"#;
check(src).unwrap_or_else(|errs| panic!("expected ok, got: {errs:#?}"));
}
#[test]
fn specific_caller_does_not_grant_other_specific() {
let src = r#"
fn callee() -> [mcp("ocpp")] Int { 1 }
fn caller() -> [mcp("optimizer")] Int { callee() }
"#;
let errs = check(src).unwrap_err();
assert!(errs.iter().any(|e| matches!(e, TypeError::EffectNotDeclared { .. })),
"expected EffectNotDeclared, got: {errs:#?}");
}
#[test]
fn specific_caller_does_not_grant_bare_callee() {
let src = r#"
fn callee() -> [net] Int { 1 }
fn caller() -> [net("wttr.in")] Int { callee() }
"#;
let errs = check(src).unwrap_err();
assert!(errs.iter().any(|e| matches!(e, TypeError::EffectNotDeclared { .. })),
"expected EffectNotDeclared, got: {errs:#?}");
}
#[test]
fn empty_caller_does_not_grant_specific() {
let src = r#"
fn callee() -> [fs_read("/etc")] Int { 1 }
fn caller() -> Int { callee() }
"#;
let errs = check(src).unwrap_err();
assert!(errs.iter().any(|e| matches!(e, TypeError::EffectNotDeclared { .. })),
"expected EffectNotDeclared, got: {errs:#?}");
}
#[test]
fn parameterized_effect_appears_in_error_message() {
let src = r#"
fn callee() -> [fs_read("/etc/passwd")] Int { 1 }
fn caller() -> Int { callee() }
"#;
let errs = check(src).unwrap_err();
let msg = match errs.first() {
Some(TypeError::EffectNotDeclared { effect, .. }) => effect.clone(),
other => panic!("expected EffectNotDeclared, got: {other:#?}"),
};
assert!(msg.contains("fs_read") && msg.contains("/etc/passwd"),
"error should name the parameterized effect; got: {msg}");
}