Initial Dever 0.0.1

This commit is contained in:
Codex committed 2026-11-03 03:32:07 +00:00
commit 81233d098c
1479 files changed
+163782

No files matched your search

+125
View File
@@ -0,0 +1,125 @@
use super::linked;
#[test]
fn async_streams_support_typed_sequential_consumers_and_infer_suspension() {
let source = "add(value: Int, state: Int) (next: Int) pure { next = value + state }\n main() (answer: Int) { answer = reduce(add, stream([1, 2]), 0) }";
let program = linked(source).unwrap();
let main = program
.functions
.iter()
.find(|function| function.signature.name == "main")
.unwrap();
assert!(main.signature.effects.suspend);
assert!(main.signature.effects.async_stream);
for source in [
"main() (values: AsyncStream<Int>) { values = stream([]) }",
"main() (values: AsyncStream<Int>) { values = dever.task.ticks(1) }",
"main(values: AsyncStream<Int>) () { close(values) }",
] {
let program = linked(source).unwrap();
let main = program
.functions
.iter()
.find(|function| function.signature.name == "main")
.unwrap();
assert!(!main.signature.effects.suspend);
}
}
#[test]
fn async_streams_reject_wrong_handlers_nontransferable_values_and_private_intrinsics() {
for (source, code) in [
(
"main(values: List<Stream<Int>>) () { close(stream(values)) }",
"C013",
),
(
"main(values: AsyncStream<Stream<Int>>) () { close(values) }",
"C013",
),
("main() () pure { close(stream([1])) }", "C013"),
("main() () { close(dever.system.taskTicks(1)) }", "C006"),
(
"copy(value: Int) (next: Int) { next = value }\nmain() () { each(copy, stream([1])) }",
"C005",
),
(
"consume(value: Int, state: Stream<Int>) () { close(state) }\nmain(state: Stream<Int>) () { each(consume, stream([1]), state) }",
"C013",
),
("main() () { close(stream(1)) }", "C005"),
] {
assert_eq!(linked(source).unwrap_err().code, code, "{source}");
}
}
#[test]
fn streams_preserve_existing_error_obligations() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"handles stream errors\") {}\n";
assert!(
linked(&format!(
"{prefix}main() () {{ each(handle, stream([Result.Failed(\"reason\")])) }}"
))
.is_ok()
);
for body in [
"values = stream([Result.Failed(\"reason\")])\n close(values)",
"values = stream([Result.Failed(\"reason\")])",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ {body} }}"))
.unwrap_err()
.code,
"C012"
);
}
assert!(linked(&format!("{prefix}main() (values: AsyncStream<Result>) {{ values = stream([Result.Failed(\"reason\")]) }}")).is_ok());
}
#[test]
fn closed_stream_aliases_cannot_discharge_unread_error_values() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"handles errors\") {}\nforward(values: AsyncStream<Result>) (output: AsyncStream<Result>) { output = values }\ntype Box { values: AsyncStream<Result> }\n";
for body in [
"values = stream([Result.Failed(\"reason\")])\n alias = values\n close(alias)\n each(handle, values)",
"values = stream([Result.Failed(\"reason\")])\n alias = forward(values)\n close(alias)\n each(handle, values)",
"values = stream([Result.Failed(\"reason\")])\n box = Box { values = values }\n close(box.values)\n each(handle, values)",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ {body} }}"))
.unwrap_err()
.code,
"C012",
"{body}"
);
}
assert_eq!(linked(&format!("{prefix}main() (output: AsyncStream<Result>) {{ values = stream([Result.Failed(\"reason\")])\n close(values)\n output = values }}")).unwrap_err().code, "C012");
assert!(linked(&format!("{prefix}main() () {{ original = [Result.Failed(\"reason\")]\n close(stream(original))\n each(handle, original) }}")).is_ok());
assert!(linked(&format!("{prefix}main() () {{ original = [Result.Failed(\"reason\")]\n first = stream(original)\n second = stream(original)\n close(first)\n each(handle, second) }}")).is_ok());
}
#[test]
fn forwarding_a_lazy_stream_keeps_future_item_error_obligations() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nforward(values: AsyncStream<Result>) (output: AsyncStream<Result>) { output = values }\ncollect(value: Result, state: List<Result>) (next: List<Result>) { next = append(state, value) }\n";
for input in [
"stream(values)",
"forward(stream(values))",
"forward(forward(stream(values)))",
] {
assert_eq!(
linked(&format!(
"{prefix}main(values: Channel<Result>) () {{ lost = reduce(collect, {input}, []) }}"
))
.unwrap_err()
.code,
"C012",
"{input}"
);
assert!(linked(&format!("{prefix}main(values: Channel<Result>) (output: List<Result>) {{ output = reduce(collect, {input}, []) }}")).is_ok());
}
assert!(
linked(&format!(
"{prefix}main(values: Channel<Result>) () {{ close(forward(stream(values))) }}"
))
.is_ok()
);
}
+84
View File
@@ -0,0 +1,84 @@
use super::linked;
use dever::compiler::build;
#[test]
fn channel_types_require_transferable_elements_and_typed_values() {
assert!(linked("main() () { values = channel(List<Int?>, 2)\n send(values, [null, 1])\n close(values) }").is_ok());
assert!(linked("main() () { values = channel(Channel<Int>, 1)\n send(values, channel(Int, 1))\n close(values) }").is_ok());
assert!(linked("type Message { Value(value: Int?) }\nmain() () { values = channel(Message, 1)\n send(values, Message.Value(null))\n close(values) }").is_ok());
for (source, code) in [
("main() () { values = channel(Int, true) }", "C005"),
("main() () { values = channel(Int?, 1) }", "C013"),
("main(values: Channel<Int?>) () { close(values) }", "C013"),
(
"type Unused { values: Channel<Int?> }\nmain() () {}",
"C013",
),
(
"main() () { values = channel(Int, 1)\n send(values, true) }",
"C005",
),
("main() () { values = channel(Stream<Int>, 1) }", "C013"),
(
"main() () { values = channel(List<Stream<Int>>, 1) }",
"C013",
),
(
"main() () { values = channel(Map<Channel<Int>, Int>, 1) }",
"C005",
),
("type Loop { next: Channel<Loop> }\nmain() () {}", "C007"),
("main() () pure { values = channel(Int, 1) }", "C013"),
(
"main(values: Stream<Int>, channel: Channel<Int>) () { close(channel)\n close(values) }",
"C013",
),
] {
assert_eq!(linked(source).unwrap_err().code, code, "{source}");
}
}
#[test]
fn send_transfers_errors_and_receive_requires_handling_them() {
let prefix = "type Result { Done\n error Failed(message: Text) }\n";
assert!(linked(&format!("{prefix}main() () {{ values = channel(Result, 1)\n send(values, Result.Failed(\"reason\"))\n close(values) }}")).is_ok());
assert_eq!(
linked(&format!(
"{prefix}main(values: Channel<Result>) () {{ lost = receive(values)\n close(values) }}"
))
.unwrap_err()
.code,
"C012"
);
assert!(
linked(&format!(
"{prefix}main(values: Channel<Result>) (answer: Result?) {{ answer = receive(values) }}"
))
.is_ok()
);
}
#[test]
fn channel_creation_and_type_references_enable_dependency_without_suspending() {
for (source, required) in [
(
"main() (value: Channel<Int>) { value = channel(Int, 1) }",
true,
),
("main() (value: Channel<Int>?) { value = null }", true),
("type Unused { value: Channel<Int> }\nmain() () {}", false),
] {
let program = linked(source).unwrap();
let main = program
.functions
.iter()
.position(|function| function.signature.name == "main")
.unwrap();
assert!(!program.functions[main].signature.effects.suspend);
assert!(!program.functions[main].signature.effects.schedule);
assert_eq!(
build::runtime_features(&program, &program.reachable([main])).contains(&"task"),
required
);
}
}
+77
View File
@@ -0,0 +1,77 @@
use super::linked;
#[test]
fn groups_require_local_affine_handles_and_zero_output_tasks() {
let prefix = "work() () {}\nvalue() (answer: Int) { answer = 1 }\n";
for body in [
"pool = group(2)\n run(pool, work())\n run(pool, work())\n wait(pool)",
"wait(group(1))\n stop(group(2))",
"pool = group(1)\n wait(pool)\n pool = group(2)\n stop(pool)",
] {
assert!(
linked(&format!("{prefix}main() () {{ {body} }}")).is_ok(),
"{body}"
);
}
for body in [
"pool = group(2)",
"pool = group(2)\n alias = pool\n wait(pool)",
"pool = group(2)\n values = [pool]\n wait(pool)",
"pool = group(2)\n wait(pool)\n wait(pool)",
"pool = group(2)\n wait(pool)\n run(pool, work())",
"pool = group(2)\n pool = group(1)\n wait(pool)",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ {body} }}"))
.unwrap_err()
.code,
"C013",
"{body}"
);
}
for body in [
"run(group(1), work())",
"pool = group(1)\n run(pool, value())\n wait(pool)",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ {body} }}"))
.unwrap_err()
.code,
"C005"
);
}
}
#[test]
fn group_wait_discharges_inputs_but_stop_does_not() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"handles input\") {}\n";
let body = "pool = group(2)\n run(pool, handle(Result.Failed(\"first\")))\n run(pool, handle(Result.Failed(\"second\")))";
assert!(linked(&format!("{prefix}main() () {{ {body}\n wait(pool) }}")).is_ok());
assert_eq!(
linked(&format!("{prefix}main() () {{ {body}\n stop(pool) }}"))
.unwrap_err()
.code,
"C012"
);
}
#[test]
fn group_arguments_keep_ownership_and_transferability_rules() {
let prefix =
"work(value: Int) () {}\nduration() (value: Int) { dever.task.sleep(0)\n value = 1 }\n";
assert!(
linked(&format!(
"{prefix}main() () {{ pool = group(1)\n run(pool, work(duration()))\n wait(pool) }}"
))
.is_ok()
);
assert_eq!(
linked(&format!(
"{prefix}main() () {{ pool = group(1)\n run(pool, work(length([wait(pool)]))) }}"
))
.unwrap_err()
.code,
"C013"
);
assert_eq!(linked("work(values: List<Stream<Int>>) () {}\nmain() () { pool = group(1)\n run(pool, work([]))\n wait(pool) }").unwrap_err().code, "C013");
}
+190
View File
@@ -0,0 +1,190 @@
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());
}
+86
View File
@@ -0,0 +1,86 @@
use super::linked;
#[test]
fn parallel_consumers_select_threads_or_runtime_from_their_body() {
let prefix = "work(value: Int) () pure {}\n";
let program = linked(&format!(
"{prefix}main() () {{ parallelEach(work, [1, 2], 2) }}"
))
.unwrap();
let main = program
.functions
.iter()
.find(|function| function.signature.name == "main")
.unwrap();
assert!(!main.signature.effects.suspend);
assert!(main.signature.effects.blocking);
for body in [
"dever.task.sleep(0)\n parallelEach(work, [1, 2], 2)",
"parallelEach(work, stream([1, 2]), 2)",
] {
let program = linked(&format!("{prefix}main() () {{ {body} }}")).unwrap();
let main = program
.functions
.iter()
.find(|function| function.signature.name == "main")
.unwrap();
assert!(main.signature.effects.suspend);
assert!(main.signature.effects.schedule);
assert!(!main.signature.effects.blocking);
}
assert!(linked("work(value: Int) () { dever.task.sleep(value) }\nmain() () { parallelEach(work, [0, 1], 2) }").is_ok());
}
#[test]
fn parallel_handlers_keep_typed_outputs_context_and_transfer_boundaries() {
for (source, code) in [
(
"work(value: Int) (answer: Int) { answer = value }\nmain() () { parallelEach(work, [1], 2) }",
"C005",
),
(
"work(value: Int) () {}\nmain() () { parallelEach(work, [1], true) }",
"C005",
),
(
"work(value: Int, context: Bool) () {}\nmain() () { parallelEach(work, [1], 2, 3) }",
"C005",
),
(
"work(value: Stream<Int>) () { close(value) }\nmain(values: List<Stream<Int>>) () { parallelEach(work, values, 2) }",
"C013",
),
(
"work(value: Int) () pure {}\nmain() () pure { parallelEach(work, [1], 2) }",
"C013",
),
(
"work(value: Int) () {}\nmain(values: Stream<Int>) () { dever.task.sleep(0)\n parallelEach(work, values, 2) }",
"C013",
),
(
"leaf(value: Int) () {}\nwork(value: Int) () { parallelEach(leaf, [value], 2) }\nmain() () { dever.task.sleep(0)\n parallelEach(work, [1], 2) }",
"C013",
),
] {
assert_eq!(linked(source).unwrap_err().code, code, "{source}");
}
}
#[test]
fn parallel_each_transfers_errors_only_through_valid_handlers() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"handles each error\") {}\n";
assert!(
linked(&format!(
"{prefix}main() () {{ parallelEach(handle, [Result.Failed(\"reason\")], 2) }}"
))
.is_ok()
);
assert!(
linked(&format!(
"{prefix}main() () {{ parallelEach(handle, stream([Result.Failed(\"reason\")]), 2) }}"
))
.is_ok()
);
assert_eq!(linked("type Result { error Failed(values: List<Stream<Int>>) }\nwork(value: Int) () { fail(Result.Failed([])) }\nmain() () { parallelEach(work, [1], 2) }").unwrap_err().code, "C013");
}
+116
View File
@@ -0,0 +1,116 @@
use super::linked;
#[test]
fn selection_checks_named_outputs_static_fallback_and_affine_ownership() {
let prefix = "work() (value: Int) { value = 1 }\nfallback() (value: Int) { value = 2 }\n";
for body in [
"value = timeout(run(work()), 1, fallback)",
"first = run(work())\n second = run(work())\n value = race(first, second)",
] {
assert!(linked(&format!("{prefix}main() (value: Int) {{ {body} }}")).is_ok());
}
for (extra, body, code) in [
(
"wrong() (answer: Int) { answer = 0 }",
"value = timeout(run(work()), 1, wrong)",
"C005",
),
(
"wrong(input: Int) (value: Int) { value = input }",
"value = timeout(run(work()), 1, wrong)",
"C005",
),
(
"wrong() (answer: Int) { answer = 0 }",
"value = race(run(work()), run(wrong()))",
"C005",
),
("", "value = race(run(work()))", "C005"),
("", "value = race(run(work()), 1)", "C005"),
(
"",
"pending = run(work())\n value = race(pending, pending)",
"C013",
),
(
"",
"pending = run(work())\n value = timeout(pending, 1, fallback)\n again = wait(pending)",
"C013",
),
("", "value = timeout(run(work()), 1.0e0, fallback)", "C005"),
] {
let source = format!("{prefix}{extra}\nmain() (value: Int) {{ {body} }}");
assert_eq!(linked(&source).unwrap_err().code, code, "{source}");
}
}
#[test]
fn timeout_retains_task_ownership_while_duration_is_awaited() {
let source = "work() (value: Int) { value = 1 }\nfallback() (value: Int) { value = 2 }\nduration() (value: Int) { dever.task.sleep(0)\n value = 1 }\nmain() (value: Int) { value = timeout(run(work()), duration(), fallback) }";
assert!(linked(source).is_ok());
assert_eq!(linked("work() () {}\nfallback() () { dever.io.println(\"blocked\") }\nmain() () { timeout(run(work()), 1, fallback) }").unwrap_err().code, "C013");
}
#[test]
fn cancellation_does_not_discharge_input_error_obligations() {
let prefix = "type Result { Done\n error Failed(message: Text) }\nhandle(value: Result) () recover(\"handles input\") {}\nempty() () {}\n";
for body in [
"timeout(run(handle(Result.Failed(\"first\"))), 1, empty)",
"race(run(handle(Result.Failed(\"first\"))), run(handle(Result.Failed(\"second\"))))",
"problem = Result.Failed(\"shared\")\n race(run(handle(problem)), run(empty()))",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ {body} }}"))
.unwrap_err()
.code,
"C012"
);
}
assert!(linked(&format!("{prefix}main() () {{ problem = Result.Failed(\"shared\")\n race(run(handle(problem)), run(handle(problem))) }}")).is_ok());
}
#[test]
fn selected_error_values_must_be_returned_or_handled() {
let prefix = "type Result { Done(value: Int)\n error Failed(message: Text) }\nwork() (value: Result) { value = Result.Failed(\"reason\") }\nfallback() (value: Result) { value = Result.Done(0) }\n";
for operation in [
"timeout(run(work()), 1, fallback)",
"race(run(work()), run(work()))",
] {
assert_eq!(
linked(&format!("{prefix}main() () {{ lost = {operation} }}"))
.unwrap_err()
.code,
"C012"
);
assert!(
linked(&format!(
"{prefix}main() (value: Result) {{ value = {operation} }}"
))
.is_ok()
);
}
}
#[test]
fn selected_ranges_include_fallback_and_each_possible_winner() {
let prefix = "one() (value: Int) { value = 1 }\ntwo() (value: Int) { value = 2 }\n";
for operation in [
"timeout(run(one()), 1, two)",
"race(run(one()), run(two()))",
] {
assert_eq!(
linked(&format!(
"{prefix}main() (value: Int <= 1) {{ value = {operation} }}"
))
.unwrap_err()
.code,
"C011"
);
assert!(
linked(&format!(
"{prefix}main() (value: Int <= 2) {{ value = {operation} }}"
))
.is_ok()
);
}
}