191 lines
6.7 KiB
Rust
191 lines
6.7 KiB
Rust
use dever::compiler::{check, model::Program};
|
|
use dever::syntax::{diagnostic::Diagnostic, source::Source};
|
|
|
|
mod async_stream;
|
|
mod channel;
|
|
mod group;
|
|
mod parallel;
|
|
mod select;
|
|
|
|
fn linked(text: &str) -> Result<Program, Diagnostic> {
|
|
let inputs = [
|
|
("main", text),
|
|
("dever.io", include_str!("../../../../lib/dever/io.dever")),
|
|
(
|
|
"dever.time",
|
|
include_str!("../../../../lib/dever/time.dever"),
|
|
),
|
|
(
|
|
"dever.task",
|
|
include_str!("../../../../lib/dever/task.dever"),
|
|
),
|
|
];
|
|
let modules = inputs
|
|
.iter()
|
|
.enumerate()
|
|
.map(|(index, (package, text))| {
|
|
dever::syntax::parse(&Source::new(
|
|
index,
|
|
if index == 0 {
|
|
"main.dever".into()
|
|
} else {
|
|
format!("{package}.dever")
|
|
},
|
|
(*text).into(),
|
|
))
|
|
})
|
|
.collect::<Result<Vec<_>, _>>()?;
|
|
check::check(
|
|
modules,
|
|
&inputs
|
|
.iter()
|
|
.map(|(package, _)| (*package).into())
|
|
.collect::<Vec<_>>(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn task_ownership_rejects_aliases_discard_overwrite_and_double_consumption() {
|
|
let prefix = "work() (value: Int) { value = 42 }\n";
|
|
for body in [
|
|
"task = run(work())",
|
|
"run(work())",
|
|
"task = run(work())\n alias = task\n value = wait(task)",
|
|
"task = run(work())\n tasks = [task]\n value = wait(task)",
|
|
"task = run(work())\n task = run(work())\n value = wait(task)",
|
|
"task = run(work())\n value = wait(task)\n again = wait(task)",
|
|
] {
|
|
let source = format!("{prefix}main() () {{ {body} }}");
|
|
let error = linked(&source).unwrap_err();
|
|
assert!(matches!(error.code, "C005" | "C013"), "{source}: {error:?}");
|
|
}
|
|
assert!(
|
|
linked(&format!(
|
|
"{prefix}main() (answer: Int) {{ task = run(work())\n answer = wait(task) }}"
|
|
))
|
|
.is_ok()
|
|
);
|
|
assert!(
|
|
linked(&format!(
|
|
"{prefix}main() (answer: Int) {{ answer = wait(run(work())) }}"
|
|
))
|
|
.is_ok()
|
|
);
|
|
assert_eq!(
|
|
linked(&format!("{prefix}main() () {{ stop(run(work())) }}"))
|
|
.unwrap_err()
|
|
.code,
|
|
"C005"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn short_circuit_cannot_conditionally_consume_tasks() {
|
|
let prefix = "work() (value: Bool) { value = true }\n";
|
|
assert_eq!(linked(&format!("{prefix}main(flag: Bool) (answer: Bool) {{ task = run(work())\n answer = flag and wait(task) }}")).unwrap_err().code, "C013");
|
|
assert!(linked(&format!("{prefix}main() (answer: Bool) {{ task = run(work())\n unused = false and wait(task)\n answer = wait(task) }}")).is_ok());
|
|
assert!(
|
|
linked(&format!(
|
|
"{prefix}main() (answer: Bool) {{ task = run(work())\n answer = true and wait(task) }}"
|
|
))
|
|
.is_ok()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn effects_distinguish_cpu_workers_blocking_calls_and_async_calls() {
|
|
for source in [
|
|
"main() () { dever.task.sleep(0)\n dever.io.println(\"bad\") }",
|
|
"main() () { dever.task.sleep(0)\n dever.time.sleep(0) }",
|
|
"work() () {}\nmain() () { parallel(work()) }",
|
|
"work() () pure {}\nmain() () { blocking(work()) }",
|
|
"main() () { blocking(dever.task.sleep(0)) }",
|
|
"main() () pure { stop(run(dever.task.sleep(0))) }",
|
|
"main() () pure { dever.time.sleep(0) }",
|
|
] {
|
|
assert_eq!(linked(source).unwrap_err().code, "C013", "{source}");
|
|
}
|
|
let program = linked("calc() (value: Int) pure { value = 42 }\nmain() (answer: Int) { answer = parallel(calc())\n blocking(dever.time.sleep(0))\n stop(run(dever.task.sleep(0))) }").unwrap();
|
|
let main = program
|
|
.functions
|
|
.iter()
|
|
.find(|function| function.signature.name == "main")
|
|
.unwrap();
|
|
assert!(main.signature.effects.suspend);
|
|
let calc = program
|
|
.functions
|
|
.iter()
|
|
.find(|function| function.signature.name == "calc")
|
|
.unwrap();
|
|
assert!(!calc.signature.effects.suspend);
|
|
}
|
|
|
|
#[test]
|
|
fn nontransferable_values_cannot_escape_into_tasks_or_cross_suspension() {
|
|
for source in [
|
|
"work(stream: Stream<Int>) () { close(stream) }\nmain(stream: Stream<Int>) () { stop(run(work(stream))) }",
|
|
"type Box { stream: Stream<Int> }\nwork(value: Box) () { close(value.stream) }\nmain(value: Box) () { stop(run(work(value))) }",
|
|
"main(stream: Stream<Int>) () { dever.task.sleep(0)\n close(stream) }",
|
|
"type Box { stream: Stream<Int> }\nmain(value: Box) () { dever.task.sleep(0)\n close(value.stream) }",
|
|
"work(stream: Stream<Int>) () { close(stream)\n dever.task.sleep(0) }\nmain(streams: List<Stream<Int>>) () { each(work, streams) }",
|
|
] {
|
|
assert_eq!(linked(source).unwrap_err().code, "C013", "{source}");
|
|
}
|
|
assert!(linked("main(stream: Stream<Int>) () { close(stream)\n dever.task.sleep(0) }").is_ok());
|
|
assert!(linked("main(file: dever.system.File) () { blocking(dever.io.close(file)) }").is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn task_failure_values_cannot_be_silently_discarded() {
|
|
let prefix = "type Result { Done(value: Int)\n error Failed(message: Text) }\nwork() (answer: Result) { answer = Result.Failed(\"reason\") }\n";
|
|
assert_eq!(
|
|
linked(&format!(
|
|
"{prefix}main() () {{ task = run(work())\n lost = wait(task) }}"
|
|
))
|
|
.unwrap_err()
|
|
.code,
|
|
"C012"
|
|
);
|
|
assert!(
|
|
linked(&format!(
|
|
"{prefix}main() (answer: Result) {{ task = run(work())\n answer = wait(task) }}"
|
|
))
|
|
.is_ok()
|
|
);
|
|
assert_eq!(
|
|
linked("main() () { lost = result(dever.task.sleep(-1)) }")
|
|
.unwrap_err()
|
|
.code,
|
|
"C012"
|
|
);
|
|
assert!(
|
|
linked("main() (answer: dever.time.SleepResult) { answer = result(dever.task.sleep(-1)) }")
|
|
.is_ok()
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn timer_intrinsics_cannot_be_called_by_user_packages() {
|
|
for name in ["timeSleep", "taskSleep"] {
|
|
assert_eq!(
|
|
linked(&format!("main() () {{ value = dever.system.{name}(0) }}"))
|
|
.unwrap_err()
|
|
.code,
|
|
"C006"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn cancelling_a_task_does_not_claim_its_input_errors_were_handled() {
|
|
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"worker handles an optional error\") {}\n";
|
|
let cancelled = format!(
|
|
"{prefix}main() () {{ pending = run(handle(Result.Failed(\"reason\")))\n stop(pending) }}"
|
|
);
|
|
assert_eq!(linked(&cancelled).unwrap_err().code, "C012");
|
|
let waited = format!(
|
|
"{prefix}main() () {{ pending = run(handle(Result.Failed(\"reason\")))\n wait(pending) }}"
|
|
);
|
|
assert!(linked(&waited).is_ok());
|
|
}
|