138 lines
7.0 KiB
Rust
138 lines
7.0 KiB
Rust
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::<Vec<_>>();
|
|
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<Int>) { 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<Int, Text>)) () {}",
|
|
"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<Int>) (answers: List<Int>) 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<Int>) (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::<Vec<_>>();
|
|
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");
|
|
}
|