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

@@ -0,0 +1,61 @@
use super::super::checked;
#[test]
fn handler_ranges_check_unused_templates_inference_and_forwarding() {
let apply = "apply(route: handler(value: Int >= 1) (answer: Int >= 2)) (answer: Int >= 2) { answer = route(1) }\n";
checked(apply).unwrap();
checked(&(apply.to_owned() + "twice(value: Int) (answer: Int) { answer = 2 }\nmake() (answer: Int >= 2) { answer = apply(twice) }")).unwrap();
assert_eq!(checked(&(apply.to_owned() + "identity(value: Int) (answer: Int) { answer = value }\nmake() (answer: Int) { answer = apply(identity) }")).unwrap_err().code, "C011");
assert_eq!(
checked(
"bad(route: handler(value: Int >= 1) (answer: Int)) (answer: Int) { answer = route(0) }"
)
.unwrap_err()
.code,
"C011"
);
checked(&(apply.to_owned() + "forward(route: handler(value: Int) (answer: Int >= 3)) (answer: Int >= 2) { answer = apply(route) }")).unwrap();
for contract in [
"handler(value: Int >= 2) (answer: Int >= 3)",
"handler(value: Int) (answer: Int >= 1)",
] {
assert_eq!(
checked(&format!(
"{apply}forward(route: {contract}) (answer: Int) {{ answer = apply(route) }}"
))
.unwrap_err()
.code,
"C011"
);
}
}
#[test]
fn prototype_cache_keeps_distinct_range_contracts() {
let prefix = "first(route: handler(value: Int) (answer: Int >= 2)) (answer: Int >= 2) { answer = route(1) }\n";
assert_eq!(checked(&(prefix.to_owned() + "second(route: handler(value: Int) (answer: Int >= 1)) (answer: Int >= 2) { answer = route(1) }")).unwrap_err().code, "C011");
assert_eq!(checked("first(route: handler(value: Int >= 1) (answer: Int)) (answer: Int) { answer = route(1) }\nsecond(route: handler(value: Int >= 2) (answer: Int)) (answer: Int) { answer = route(1) }").unwrap_err().code, "C011");
}
#[test]
fn collection_handlers_check_items_context_and_repeated_state() {
checked("valid(route: handler(value: Int >= 1) (answer: Int >= 2)) (answer: List<Int>) { answer = each(route, [1, 2]) }").unwrap();
for body in [
"bad(route: handler(value: Int >= 1) (answer: Int), values: List<Int>) (answer: List<Int>) { answer = each(route, values) }",
"bad(route: handler(value: Int >= 1) (answer: Int)) (answer: List<Int>) { answer = each(route, [0, 1]) }",
"bad(route: handler(value: Int, state: Int >= 1) (answer: Int)) (answer: Int) { answer = reduce(route, [1, 2], 1) }",
"bad(route: handler(value: Int, context: Int >= 1) (answer: Int)) (answer: List<Int>) { answer = each(route, [1], 0) }",
] {
assert_eq!(checked(body).unwrap_err().code, "C011", "{body}");
}
checked("valid(route: handler(value: Int, state: Int >= 1) (answer: Int >= 1)) (answer: Int >= 1) { answer = reduce(route, [1, 2], 1) }").unwrap();
checked("valid(route: handler(value: Int >= 0 and <= 255) (answer: Int), values: Bytes) (answer: List<Int>) { answer = each(route, values) }").unwrap();
}
#[test]
fn failing_collection_inputs_do_not_produce_invalid_values() {
let prefix =
"type Problem { error Failed }\nabortOperation() (answer: Int) { fail(Problem.Failed) }\n";
checked(&(prefix.to_owned() + "apply(route: handler(value: Int, context: Int >= 1) (answer: Int)) (answer: List<Int>) { answer = each(route, [1], abortOperation()) }")).unwrap();
checked("type Problem { error Failed }\nabortOperation(value: Int, state: Int) (answer: Int) { fail(Problem.Failed) }\napply(route: handler(value: Int, state: Int >= 1) (answer: Int >= 1)) (answer: Int >= 1) { answer = reduce(route, [1], 1) }\nentry() (answer: Int >= 1) { answer = apply(abortOperation) }").unwrap();
}
+76
View File
@@ -0,0 +1,76 @@
use super::checked;
mod handler_range;
mod range;
#[test]
fn decimal_and_float_clauses_cover_their_actual_value_domains() {
for text in [
"pick(value: Decimal < 0.0) (answer: Int) { answer = -1 }\npick(value: Decimal >= 0.0) (answer: Int) { answer = 1 }",
"pick(value: other) (answer: Int) { answer = 1 }\npick(value: -0.0) (answer: Int) { answer = 0 }",
"pick(value: null) (answer: Int) { answer = 0 }\npick(value: Decimal < 0.0) (answer: Int) { answer = -1 }\npick(value: other) (answer: Int) { answer = 1 }",
"pick(value: 1.0e0) (answer: Int) { answer = 1 }\npick(value: other) (answer: Int) { answer = 0 }",
"pick(value: null) (answer: Int) { answer = 0 }\npick(value: 0.0e0) (answer: Int) { answer = 1 }\npick(value: other) (answer: Int) { answer = 2 }",
"pick(value: Int <= 9223372036854775807) () {}",
"pick(value: Int >= -9223372036854775808) () {}",
] {
checked(text).unwrap_or_else(|error| panic!("{text}\n{error:?}"));
}
for text in [
"pick(value: Decimal < 0.0) () {}",
"pick(value: Decimal <= 0.0) () {}\npick(value: Decimal >= 0.0) () {}",
"pick(value: 1.0) () {}\npick(value: 1.00) () {}\npick(value: other) () {}",
"pick(value: 0.0e0) () {}\npick(value: -0.0e0) () {}\npick(value: other) () {}",
"pick(value: 1.0e0) () {}",
"pick(value: Float) () {}\npick(value: other) () {}",
"pick(value: Int > 9223372036854775807) () {}\npick(value: other) () {}",
] {
let error = checked(text).unwrap_err();
assert_eq!(error.code, "C009", "{text}: {error:?}");
}
}
#[test]
fn decimal_partitions_use_exact_successors_at_precision_and_exponent_limits() {
for (left, right) in [
("1.0", "1.000000000000000000000000000000001"),
("0.0", "1e-6176"),
("-1e-6176", "0.0"),
(
"9.999999999999999999999999999999998e6144",
"9.999999999999999999999999999999999e6144",
),
] {
let text = format!(
"pick(value: Decimal <= {left}) () {{}}\npick(value: Decimal >= {right}) () {{}}"
);
checked(&text).unwrap_or_else(|error| panic!("{text}: {error:?}"));
}
for text in [
"pick(value: Decimal <= 1.0) () {}\npick(value: Decimal >= 1.000000000000000000000000000000002) () {}",
"pick(value: Decimal > 9.999999999999999999999999999999999e6144) () {}\npick(value: other) () {}",
"pick(value: Decimal < -9.999999999999999999999999999999999e6144) () {}\npick(value: other) () {}",
] {
assert_eq!(checked(text).unwrap_err().code, "C009", "{text}");
}
}
#[test]
fn numeric_pattern_validation_rejects_invalid_bounds_and_mixed_types() {
for (text, code) in [
("pick(value: 9223372036854775808) () {}", "C008"),
("pick(value: 1e9999) () {}", "C008"),
(
"pick(value: Decimal < 0.12345678901234567890123456789012345) () {}",
"C008",
),
("pick(value: Int < 1.5) () {}", "C008"),
("pick(value: Float < 1.0e0) () {}", "C005"),
("pick(value: Decimal? < 1.0) () {}", "C005"),
("pick(value: 1.0) () {}\npick(value: Float) () {}", "C005"),
] {
assert_eq!(checked(text).unwrap_err().code, code, "{text}");
}
assert!(checked("pick(value: Int >= 1 and) () {}").is_err());
assert!(checked("pick(value: Decimal < 1.0 and) () {}").is_err());
}
+206
View File
@@ -0,0 +1,206 @@
use super::super::checked;
const PORT: &str = "type Port { value: Int >= 1 and <= 65535 }\n";
#[test]
fn nonreturning_values_propagate_failure_before_range_boundaries() {
let source = PORT.to_owned()
+ "type Problem { error Failed }\nabortOperation() (answer: Int) { fail(Problem.Failed) }\nmake() (answer: Port) { answer = Port { value = abortOperation() } }\n";
checked(&source).unwrap();
checked(&(source + "apply(route: handler() (answer: Int >= 1)) (answer: Int >= 1) { answer = route() }\nentry() (answer: Int >= 1) { answer = apply(abortOperation) }")).unwrap();
}
#[test]
fn ranges_check_construction_outputs_and_every_write() {
for body in [
"make() (answer: Port) { answer = Port { value = 8080 } }",
"make(value: Int >= 1 and <= 65535) (answer: Port) { answer = Port { value = value } }\nmake(value: other) (answer: Port) { answer = Port { value = 8080 } }",
"make(value: Port) (answer: Int >= 1 and <= 65535) { answer = value.value }",
"make(value: Int < 1) (answer: Int >= 1) { answer = 1 }\nmake(value: other) (answer: Int >= 1) { answer = value }",
"make(value: Int < 1) (answer: Int >= 1 and <= 65535) { answer = 1 }\nmake(value: Int > 65535) (answer: Int >= 1 and <= 65535) { answer = 65535 }\nmake(value: other) (answer: Int >= 1 and <= 65535) { answer = value }",
] {
checked(&(PORT.to_owned() + body)).unwrap_or_else(|error| panic!("{body}: {error:?}"));
}
for body in [
"make() (answer: Port) { answer = Port { value = 0 } }",
"make(value: Int) (answer: Port) { answer = Port { value = value } }",
"make(value: Port) (answer: Port) { value.value = 65536\nanswer = value }",
"make(value: Int) (answer: Int >= 1) { answer = value }",
"make() (answer: Int >= 1) { answer = 0\nanswer = 1 }",
"type Outer { port: Port }\nmake(value: Outer) (answer: Outer) { value.port.value = 0\nanswer = value }",
] {
assert_eq!(
checked(&(PORT.to_owned() + body)).unwrap_err().code,
"C011",
"{body}"
);
}
}
#[test]
fn snapshots_survive_mutation_without_retaining_stale_facts() {
let prefix = "type Row { value: Int }\ntype Pair { left: Row\nright: Row }\n";
let body = "row = Row { value = 3 }\npair = Pair { left = row, right = row }\nsaved = pair\npair.left.value = 0\nrow.value = -1\n";
for value in ["saved.left.value", "saved.right.value", "pair.right.value"] {
checked(&format!(
"{prefix}make() (answer: Int >= 1) {{ {body}answer = {value} }}"
))
.unwrap();
}
for value in ["pair.left.value", "row.value"] {
assert_eq!(
checked(&format!(
"{prefix}make() (answer: Int >= 1) {{ {body}answer = {value} }}"
))
.unwrap_err()
.code,
"C011"
);
}
assert_eq!(checked("make(value: Int >= 1) (answer: Int >= 1) { value = 0\nanswer = value }\nmake(value: other) (answer: Int >= 1) { answer = 1 }").unwrap_err().code, "C011");
}
#[test]
fn choice_payloads_and_captured_outputs_preserve_contracts() {
let prefix = "type Result { Ready(value: Int >= 1)\nerror Failed(message: Text) }\n";
let consumer = "read(value: Result.Ready(number)) (answer: Int >= 1) recover(\"fallback\") { answer = number }\nread(value: Result.Failed(message)) (answer: Int >= 1) recover(\"fallback\") { answer = 1 }\n";
checked(
&(prefix.to_owned() + consumer + "make() (answer: Result) { answer = Result.Ready(1) }"),
)
.unwrap();
for value in ["0", "unknown"] {
assert_eq!(
checked(&format!(
"{prefix}make(unknown: Int) (answer: Result) {{ answer = Result.Ready({value}) }}"
))
.unwrap_err()
.code,
"C011"
);
}
checked(&format!("{prefix}produce() (answer: Int) {{ answer = 3 }}\nmake() (answer: Result) {{ answer = result(produce()) }}")).unwrap();
assert_eq!(checked(&format!("{prefix}produce(value: Int) (answer: Int) {{ answer = value }}\nmake(value: Int) (answer: Result) {{ answer = result(produce(value)) }}")).unwrap_err().code, "C011");
checked("pair() (small: Int >= 1 and <= 3, large: Int >= 5) { small = 2\nlarge = 8 }\nread() (answer: Int >= 5) { saved = pair()\nanswer = saved.large }").unwrap();
}
#[test]
fn arithmetic_proof_uses_checked_int_and_decimal_semantics() {
checked(
&(PORT.to_owned()
+ "step(value: Port) (answer: Int >= 2 and <= 65536) { answer = value.value + 1 }"),
)
.unwrap();
checked("step(value: Int >= -8 and <= 8) (answer: Int >= -16 and <= 16) { answer = value * -2 }\nstep(value: other) (answer: Int >= -16 and <= 16) { answer = 0 }").unwrap();
checked("type Rate { value: Decimal >= 0.0 and <= 1.0 }\nscale(value: Rate) (answer: Decimal >= 0.0 and <= 100.0) { answer = value.value * 100.0 }").unwrap();
checked("type Tiny { value: Decimal > 0.0 and <= 1e-6176 }\ncopy(value: Tiny) (answer: Decimal > 0.0) { answer = value.value }").unwrap();
checked("type Edge { value: Decimal > 1.0 and <= 1.000000000000000000000000000000001 }\ncopy(value: Edge) (answer: Decimal > 1.0) { answer = value.value }").unwrap();
checked("convert(value: Int >= 1 and <= 8) (answer: Decimal >= 1.0 and <= 8.0) { answer = value }\nconvert(value: other) (answer: Decimal >= 1.0 and <= 8.0) { answer = 1.0 }").unwrap();
for body in [
"read(value: Int) (answer: Int >= 0) { answer = value + 1 }",
"read(value: Int >= 1) (answer: Int >= 0) { answer = -value }\nread(value: other) (answer: Int >= 0) { answer = 0 }",
"read(value: Int >= 0) (answer: Int >= 0) { answer = value + 1 }\nread(value: other) (answer: Int >= 0) { answer = 0 }",
] {
assert_eq!(checked(body).unwrap_err().code, "C011", "{body}");
}
}
#[test]
fn invalid_empty_and_inconsistent_ranges_are_rejected() {
for fields in [
"value: Int > 9223372036854775807",
"value: Int < -9223372036854775808",
"value: Int > 1 and < 2",
"value: Decimal > 0.0 and < 1e-6176",
] {
assert_eq!(
checked(&format!("type Invalid {{ {fields} }}"))
.unwrap_err()
.code,
"C011"
);
}
for fields in [
"value: Float >= 1.0e0",
"value: Int? >= 1",
"value: Text >= 1",
] {
assert_eq!(
checked(&format!("type Invalid {{ {fields} }}"))
.unwrap_err()
.code,
"C005"
);
}
assert_eq!(
checked("type Invalid { value: Decimal >= 0.12345678901234567890123456789012345 }")
.unwrap_err()
.code,
"C008"
);
assert_eq!(checked("read(value: Int < 0) (answer: Int >= 1) { answer = 1 }\nread(value: other) (answer: Int >= 2) { answer = 2 }").unwrap_err().code, "C013");
checked("read(value: Int < 0) (answer: Int > 0) { answer = 1 }\nread(value: other) (answer: Int >= 1) { answer = 2 }").unwrap();
}
#[test]
fn range_proofs_remove_only_redundant_arithmetic_checks() {
let text = "type Port { value: Int >= 1 and <= 65535 }\nstep(value: Port) (answer: Int >= 2 and <= 65536) { answer = value.value + 1 }";
let mut plain = text.to_owned();
for bound in [">= 1 and <= 65535", ">= 2 and <= 65536"] {
plain = plain.replace(bound, &" ".repeat(bound.len()));
}
let source = |text: &str| dever::syntax::source::Source::new(0, "case.dever", text.into());
let emit = |text: &str| {
dever::compiler::emit::generate(
&checked(text).unwrap(),
&[source(text)],
&std::collections::BTreeSet::from([0]),
)
};
let bounded = emit(text);
let unbounded = emit(&plain);
assert!(bounded.contains("wrapping_add"));
assert!(!bounded.contains("checked_add"));
assert!(unbounded.contains("checked_add"));
assert!(!unbounded.contains("wrapping_add"));
}
#[test]
fn arithmetic_proofs_follow_field_updates_and_each_specialized_handler() {
let text = "type Row { value: Int }\ncopy(value: Int) (answer: Int) { answer = value }\nread(value: Int) (answer: Int) { row = Row { value = 1 }\nsaved = row\nrow.value = value\nknown = saved.value + 1\nunknown = row.value + 1\nanswer = known + unknown }\napply(route: handler(value: Int) (answer: Int), value: Int) (answer: Int) { answer = route(value) + 1 }\nfixed(value: Int) (answer: Int) { answer = 1 }\nfixedCall(value: Int) (answer: Int) { answer = apply(fixed, value) }\nunknownCall(value: Int) (answer: Int) { answer = apply(copy, value) }";
let program = checked(text).unwrap();
let source = dever::syntax::source::Source::new(0, "case.dever", text.into());
let emit = |name: &str| {
let index = program
.functions
.iter()
.position(|function| function.signature.name == name)
.unwrap();
dever::compiler::emit::generate(&program, std::slice::from_ref(&source), &[index].into())
};
let changed = emit("read");
assert!(changed.contains("wrapping_add"));
assert!(changed.contains("checked_add"));
assert!(emit("fixedCall").contains("wrapping_add"));
assert!(!emit("unknownCall").contains("wrapping_add"));
assert!(emit("unknownCall").contains("checked_add"));
}
#[test]
fn range_complement_proof_stops_at_its_work_budget() {
let mut source = String::new();
for value in 0..100 {
source.push_str(&format!(
"classify(value: {value}) (answer: Int) {{ answer = value }}\n"
));
}
source.push_str("classify(value: other) (answer: Int) { answer = value }");
checked(&source).unwrap();
for value in 100..200 {
source.push_str(&format!(
"\nclassify(value: {value}) (answer: Int) {{ answer = value }}"
));
}
let error = checked(&source).unwrap_err();
assert_eq!(error.code, "C015");
assert!(error.message.contains("range proof"), "{error:?}");
}