Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,137 @@
|
||||
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");
|
||||
}
|
||||
Reference in new issue
Block a user