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