213 lines
10 KiB
Rust
213 lines
10 KiB
Rust
use super::checked;
|
|
|
|
#[test]
|
|
fn literal_collections_select_values_without_building_containers() {
|
|
for text in [
|
|
"main(value: Text) (answer: Text?) { answer = get({ \"left\" = value }, \"left\") }",
|
|
"main(value: Text?) (answer: Text?) { answer = get({ \"雪\" = value }, \"雪\") }",
|
|
"main(value: Text) (answer: Text?) { answer = get({ \"left\" = value }, \"missing\") }",
|
|
"main(value: Text) (answer: Text?) { answer = first([value]) }",
|
|
"main(value: Text?) (answer: Text?) { answer = first([value]) }",
|
|
] {
|
|
let source = dever::syntax::source::Source::new(0, "case.dever", text.into());
|
|
let mut program = checked(text).unwrap();
|
|
program.functions[0].signature.name = "renamed".into();
|
|
let generated = dever::compiler::emit::generate(&program, &[source], &[0].into());
|
|
assert!(!generated.contains("Map::with_capacity"), "{generated}");
|
|
assert!(!generated.contains("vec!["), "{generated}");
|
|
assert!(generated.contains(".to_owned()"), "{generated}");
|
|
}
|
|
for (text, constructor) in [
|
|
(
|
|
"main(value: Text, key: Text) (answer: Text?) { answer = get({ \"left\" = value }, key) }",
|
|
"Map::with_capacity",
|
|
),
|
|
(
|
|
"main(value: Text, key: Text) (answer: Text?) { answer = get({ key = value }, \"left\") }",
|
|
"Map::with_capacity",
|
|
),
|
|
(
|
|
"main(value: dever.system.File) (answer: dever.system.File?) { answer = get({ \"left\" = value }, \"left\") }",
|
|
"Map::with_capacity",
|
|
),
|
|
(
|
|
"main(value: dever.system.File) (answer: dever.system.File?) { answer = first([value]) }",
|
|
"vec![",
|
|
),
|
|
] {
|
|
let source = dever::syntax::source::Source::new(0, "case.dever", text.into());
|
|
let program = checked(text).unwrap();
|
|
let generated = dever::compiler::emit::generate(&program, &[source], &[0].into());
|
|
assert!(generated.contains(constructor), "{generated}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn contains_checks_comparable_items_without_consuming_error_obligations() {
|
|
for source in [
|
|
"main(values: List<Text>, target: Text) (found: Bool) { found = contains(values, target) }",
|
|
"main() (found: Bool) { found = contains([], 1) }",
|
|
"type Row { id: Int }\nmain(rows: List<Row>, row: Row) (found: Bool) { found = contains(rows, row) }",
|
|
] {
|
|
checked(source).unwrap();
|
|
}
|
|
for source in [
|
|
"main() (found: Bool) { found = contains([1], true) }",
|
|
"main() (found: Bool) { found = contains([], null) }",
|
|
"type Outcome { Done\nerror Failed }\nmain() (found: Bool) { found = contains([Outcome.Failed], Outcome.Done) }",
|
|
] {
|
|
assert!(checked(source).is_err(), "{source}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn record_construction_borrows_before_moving_owned_values() {
|
|
for fields in [
|
|
"first = owned\n second = length(owned)",
|
|
"second = length(owned)\n first = owned",
|
|
] {
|
|
let text = format!(
|
|
"type Row {{ first: Text\n second: Int }}\nmain(input: Text) (answer: Row) {{ owned = input + \"!\"\n answer = Row {{ {fields} }} }}"
|
|
);
|
|
let source = dever::syntax::source::Source::new(0, "case.dever", text);
|
|
let program = checked(&source.text).unwrap();
|
|
let generated = dever::compiler::emit::generate(&program, &[source], &[0].into());
|
|
assert_eq!(generated.matches(".clone()").count(), 0, "{generated}");
|
|
assert!(!generated.contains(".to_owned()"), "{generated}");
|
|
}
|
|
let text = "type Row { first: Text\n second: Text }\nmain(input: Text) (answer: Row) { owned = input + \"!\"\n answer = Row { first = owned\n second = owned } }";
|
|
let source = dever::syntax::source::Source::new(0, "case.dever", text.into());
|
|
let program = checked(text).unwrap();
|
|
let generated = dever::compiler::emit::generate(&program, &[source], &[0].into());
|
|
assert_eq!(generated.matches(".clone()").count(), 1, "{generated}");
|
|
}
|
|
|
|
#[test]
|
|
fn map_keys_and_literal_identity_are_checked() {
|
|
for kind in [
|
|
"Float",
|
|
"Decimal",
|
|
"Int?",
|
|
"List<Int>",
|
|
"Map<Int, Int>",
|
|
"Row",
|
|
"Payload",
|
|
] {
|
|
let source = format!(
|
|
"type Row {{ count: Int }}\ntype Payload {{ Value(value: Int) }}\nunused(values: Map<{kind}, Int>) () {{}}"
|
|
);
|
|
assert_eq!(checked(&source).unwrap_err().code, "C005", "{kind}");
|
|
}
|
|
for body in [
|
|
"value = { \"a\" = 1\n \"a\" = 2 }",
|
|
"value = { 1 + 1 = 1\n 2 = 2 }",
|
|
"value = { Key.First = 1\n Key.First = 2 }",
|
|
"key = \"a\"\n alias = key\n value = { alias = 1\n \"a\" = 2 }",
|
|
"value = { input = 1\n input = 2 }",
|
|
] {
|
|
let source = format!("type Key {{ First\n Second }}\ninvalid(input: Text) () {{ {body} }}");
|
|
assert_eq!(checked(&source).unwrap_err().code, "C002", "{body}");
|
|
}
|
|
assert!(checked("valid(input: Text, other: Text) (values: Map<Text, Int>) { alias = input\n input = other\n values = { alias = 1\n input = 2 } }").is_ok());
|
|
assert_eq!(
|
|
checked("type Node { children: Map<Int, Node> }")
|
|
.unwrap_err()
|
|
.code,
|
|
"C007"
|
|
);
|
|
assert_eq!(
|
|
checked("type Bad { values: Map<Float, Int> }")
|
|
.unwrap_err()
|
|
.code,
|
|
"C005"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn access_types_empty_contexts_and_nullable_results_are_checked() {
|
|
for source in [
|
|
"main() (values: List<Int>) { values = append([], 1) }",
|
|
"main() (values: Map<Text, Int>) { values = put({}, \"a\", 1) }",
|
|
"main() (value: Int?) { value = first([]) }",
|
|
"main(values: Map<Text, Int?>) (value: Int?) { value = get(values, \"a\") }",
|
|
"main(values: List<Int?>) (value: Int?) { value = first(values) }",
|
|
"main() (values: List<MapEntry<Text, Int>>) { values = entries({}) }",
|
|
"main(values: List<MapEntry<Text, Int>>) (result: List<MapEntry<Text, Int>>) { result = values }",
|
|
"main(value: MapEntry<Text, Int>) (result: Text) { result = value.key }",
|
|
] {
|
|
assert!(checked(source).is_ok(), "{source}");
|
|
}
|
|
for source in [
|
|
"main() () { values = {} }",
|
|
"main() () { values = { 1 = 2\n \"a\" = 3 } }",
|
|
"main() () { values = { 1 = 2\n 2 = true } }",
|
|
"main() () { values = put({}, 1.0, 1) }",
|
|
"main(values: Map<Text, Int>) () { value = get(values, 1) }",
|
|
"main(values: List<Int>) () { value = append(values, true) }",
|
|
"main(values: Bytes) () { value = append(values, 1) }",
|
|
"main(values: Map<Text, Int>) () { value = first(values) }",
|
|
"main(value: MapEntry<Text, Int>) () { field = value.missing }",
|
|
] {
|
|
assert!(checked(source).is_err(), "{source}");
|
|
}
|
|
}
|
|
|
|
const ERRORS: &str = "type Result { Ready(value: Int)\n error Failed(message: Text) }\nconsume(input: Result.Ready(value)) () recover(\"discard explicitly in test\") {}\nconsume(input: Result.Failed(message)) () recover(\"discard explicitly in test\") {}\n";
|
|
|
|
#[test]
|
|
fn nested_collection_shapes_survive_error_free_records_and_calls() {
|
|
let types = "type Row { values: List<Int> }\ntype Box { row: Row }\ntype Choice { Row(value: Row)\n Empty }\n";
|
|
for body in [
|
|
"update(row: Row) (answer: List<Int>) { answer = append(row.values, 1) }",
|
|
"update(box: Box) (answer: List<Int>) { answer = append(box.row.values, 1) }",
|
|
"update(entry: MapEntry<Int, Row>) (answer: List<Int>) { answer = append(entry.value.values, 1) }",
|
|
"update(row: Row) (answer: List<Int>) { answer = append(row.values, 1) }\nupdate(row: null) (answer: List<Int>) { answer = [] }",
|
|
"update(choice: Choice.Row(row)) (answer: List<Int>) { answer = append(row.values, 1) }\nupdate(choice: Choice.Empty) (answer: List<Int>) { answer = [] }",
|
|
"choose(flag: true, row: Row) (answer: Row) { answer = row }\nchoose(flag: false, row: Row) (answer: Row) { answer = Row { values = [] } }\nupdate(flag: Bool, row: Row) (answer: List<Int>) { selected = choose(flag, row)\n answer = append(selected.values, 1) }",
|
|
] {
|
|
let result = checked(&(types.to_owned() + body));
|
|
assert!(result.is_ok(), "{body}: {result:?}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn map_errors_cannot_be_discarded_by_lookup_or_mutation() {
|
|
let types = "type Result { Ready(value: Int)\n error Failed(message: Text) }\n";
|
|
for body in [
|
|
"bad(values: Map<Text, Result>) (answer: Int) { answer = length(values) }",
|
|
"bad(values: Map<Text, Result>) (answer: Result?) { answer = get(values, \"one\") }",
|
|
"bad(values: Map<Text, Result>) (answer: Map<Text, Result>) { answer = remove(values, \"one\") }",
|
|
"bad(values: Map<Text, Result>) (answer: Map<Text, Result>) { answer = put(values, \"one\", Result.Ready(1)) }",
|
|
"bad(values: List<Result>) (answer: Result?) { answer = first(values) }",
|
|
"bad() (answer: Result?) { answer = first([Result.Ready(1), Result.Failed(\"lost\")]) }",
|
|
"bad(value: MapEntry<Text, Result>) (answer: Text) { answer = value.key }",
|
|
] {
|
|
assert_eq!(
|
|
checked(&(types.to_owned() + body)).unwrap_err().code,
|
|
"C012",
|
|
"{body}"
|
|
);
|
|
}
|
|
for body in [
|
|
"valid(values: Map<Text, Result>) (answer: Map<Text, Result>) { answer = values }",
|
|
"valid(values: Map<Text, Result>) (answer: List<MapEntry<Text, Result>>) { answer = entries(values) }",
|
|
"valid(values: List<Result>) (answer: List<Result>) { answer = append(values, Result.Ready(1)) }",
|
|
"valid() (answer: Result?) { answer = first([Result.Failed(\"returned\")]) }",
|
|
"valid() (answer: Map<Text, Result>) { answer = put({}, \"one\", Result.Failed(\"returned\")) }",
|
|
] {
|
|
assert!(checked(&(types.to_owned() + body)).is_ok(), "{body}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn complete_entries_traversal_handles_keys_values_and_nested_errors() {
|
|
let source = ERRORS.to_owned();
|
|
let source = source
|
|
+ "consumeEntry(entry: MapEntry<Text, Result>) () { consume(entry.value) }\nvalid(values: Map<Text, Result>) () { each(consumeEntry, entries(values)) }";
|
|
let result = checked(&source);
|
|
assert!(result.is_ok(), "{result:?}");
|
|
let source = "type Key { Fine\n error Failed }\nconsume(entry: MapEntry<Key, Int>) () recover(\"key error policy\") {}\nvalid(values: Map<Key, Int>) () { each(consume, entries(values)) }";
|
|
assert!(checked(source).is_ok());
|
|
assert_eq!(checked("type Key { Fine\n error Failed }\nbad(values: Map<Key, Int>) (answer: Int) { answer = length(values) }").unwrap_err().code, "C012");
|
|
}
|