use super::checked; const BASE: &str = "increment(value: Int) (answer: Int) pure { answer = value + 1 }\ndecrement(value: Int) (answer: Int) pure { answer = value - 1 }\napply(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) }\nforward(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = apply(route, value) }\n"; #[test] fn specializes_handlers_and_reuses_identical_bindings() { let source = BASE.to_owned() + "main() (answer: Int) pure { answer = forward(increment, 1) + forward(increment, 2) + forward(decrement, 3) }"; let program = checked(&source).unwrap(); assert_eq!(program.functions.len(), 7); for name in ["apply", "forward"] { let instances = program .functions .iter() .filter(|function| function.signature.name == name) .collect::>(); assert_eq!(instances.len(), 2); assert!( instances .iter() .all(|function| function.signature.inputs.len() == 1 && function.signature.static_parameters == 1) ); } let source = BASE.to_owned() + "main() (answer: List) { answer = each(increment, [1, 2]) }"; let program = checked(&source).unwrap(); assert_eq!(program.functions.len(), 3); } #[test] fn checks_unused_templates_and_handler_signatures() { for body in [ "bad(route: handler(value: Int) (answer: Int)) (answer: Text) { answer = route(1) }", "bad(route: handler(value: Int) (answer: Int)) (answer: Int) {}", "bad(route: handler(value: List) ()) () {}", "bad(route: handler() (answer: List)) () {}", "bad(route: handler(private value: Int) ()) () {}", "bad(route: handler(value: Int) (value: Int)) () {}", "bad(route: handler(value: Int) (answer: Int)) (route: Int) { route = 1 }", "wrong(value: Int) (other: Int) { other = value }\nmain() (answer: Int) { answer = apply(wrong, 1) }", "wrong(value: Text) (answer: Int) { answer = length(value) }\nmain() (answer: Int) { answer = apply(wrong, 1) }", "main() (answer: Int) { answer = apply(increment(1), 1) }", "main() (answer: Int) { increment = 1\n answer = apply(increment, 1) }", "bad(route: handler(value: Text) (answer: Int), value: Int) (answer: Int) { answer = forward(route, value) }", ] { assert!(checked(&(BASE.to_owned() + body)).is_err(), "{body}"); } } #[test] fn handlers_are_not_values_or_clause_axes() { for body in [ "bad(route: handler(value: Int) (answer: Int)) () { value = route }", "bad(route: handler(value: Int) (answer: Int)) () { route = 1 }", "bad(route: handler(value: Int) (answer: Int)) () { values = [route] }", "bad(route: handler(value: Int) (answer: Int)) (answer: Bool) { answer = route == route }", "bad(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) }\nbad(route: other, value: other) (answer: Int) { answer = 0 }", "bad(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) }\nbad(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = 0 }", ] { assert!(checked(&(BASE.to_owned() + body)).is_err(), "{body}"); } let source = BASE.to_owned() + "choose(route: handler(value: Int) (answer: Int), value: Int < 0) (answer: Int) { answer = 0 }\nchoose(route: handler(value: Int) (answer: Int), value: other) (answer: Int) { answer = route(value) }\nmain() (answer: Int) { answer = choose(increment, 1) }"; assert!(checked(&source).is_ok()); } #[test] fn handler_cycles_and_unrestricted_pure_effects_are_rejected() { for source in [ BASE.to_owned() + "cycle(value: Int) (answer: Int) { answer = forward(cycle, value) }", BASE.to_owned() + "unused(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = unused(route, value) }", ] { assert_eq!(checked(&source).unwrap_err().code, "C007"); } for body in [ "bad(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) pure { answer = route(value) }", "bad(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) pure { answer = forward(route, value) }", "bad(route: handler(value: Int) (answer: Int), values: List) (answers: List) pure { answers = each(route, values) }", ] { assert_eq!( checked(&(BASE.to_owned() + body)).unwrap_err().code, "C013", "{body}" ); } let source = BASE.to_owned() + "ignore(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) pure { answer = value }\npass(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) pure { answer = ignore(route, value) }"; assert!(checked(&source).is_ok()); } #[test] fn generic_error_outputs_keep_handling_obligations() { let prefix = "type Result { Ready(value: Int)\n error Failed(message: Text) }\n"; for body in [ "bad(route: handler(value: Int) (answer: Result)) () { lost = route(1) }", "bad(route: handler(value: Int) (answer: Result), values: List) (answer: Int) { lost = each(route, values)\n answer = length(lost) }", "bad(route: handler(value: Int) (answer: Result)) (answer: Result) { answer = route(1)\n answer = Result.Ready(0) }", ] { assert_eq!( checked(&(prefix.to_owned() + body)).unwrap_err().code, "C012", "{body}" ); } let source = prefix.to_owned() + "valid(route: handler(value: Int) (answer: Result)) (answer: Result) { answer = route(1) }"; assert!(checked(&source).is_ok()); } #[test] fn each_binding_keeps_its_own_nominal_failure_set() { let source = BASE.to_owned() + "type First { error Failed(code: Int) }\ntype Second { error Failed(code: Int) }\ntype FirstResult { Ready(value: Int)\n error Failed(problem: First) }\ntype SecondResult { Ready(value: Int)\n error Failed(problem: Second) }\nfirst(value: Int) (answer: Int) { fail(First.Failed(value)) }\nsecond(value: Int) (answer: Int) { fail(Second.Failed(value)) }\nfirstEntry() (answer: FirstResult) { answer = result(forward(first, 1)) }\nsecondEntry() (answer: SecondResult) { answer = result(forward(second, 2)) }"; let program = checked(&source).unwrap(); let instances = program .functions .iter() .filter(|function| function.signature.name == "apply") .collect::>(); assert_eq!(instances.len(), 2); assert!( instances .iter() .all(|function| function.signature.failures.len() == 1) ); assert_ne!( instances[0].signature.failures, instances[1].signature.failures ); let wrong = source.replace( "firstEntry() (answer: FirstResult)", "firstEntry() (answer: SecondResult)", ); assert_eq!(checked(&wrong).unwrap_err().code, "C005"); }