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, target: Text) (found: Bool) { found = contains(values, target) }", "main() (found: Bool) { found = contains([], 1) }", "type Row { id: Int }\nmain(rows: List, 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", "Map", "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) { alias = input\n input = other\n values = { alias = 1\n input = 2 } }").is_ok()); assert_eq!( checked("type Node { children: Map }") .unwrap_err() .code, "C007" ); assert_eq!( checked("type Bad { values: Map }") .unwrap_err() .code, "C005" ); } #[test] fn access_types_empty_contexts_and_nullable_results_are_checked() { for source in [ "main() (values: List) { values = append([], 1) }", "main() (values: Map) { values = put({}, \"a\", 1) }", "main() (value: Int?) { value = first([]) }", "main(values: Map) (value: Int?) { value = get(values, \"a\") }", "main(values: List) (value: Int?) { value = first(values) }", "main() (values: List>) { values = entries({}) }", "main(values: List>) (result: List>) { result = values }", "main(value: MapEntry) (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) () { value = get(values, 1) }", "main(values: List) () { value = append(values, true) }", "main(values: Bytes) () { value = append(values, 1) }", "main(values: Map) () { value = first(values) }", "main(value: MapEntry) () { 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 }\ntype Box { row: Row }\ntype Choice { Row(value: Row)\n Empty }\n"; for body in [ "update(row: Row) (answer: List) { answer = append(row.values, 1) }", "update(box: Box) (answer: List) { answer = append(box.row.values, 1) }", "update(entry: MapEntry) (answer: List) { answer = append(entry.value.values, 1) }", "update(row: Row) (answer: List) { answer = append(row.values, 1) }\nupdate(row: null) (answer: List) { answer = [] }", "update(choice: Choice.Row(row)) (answer: List) { answer = append(row.values, 1) }\nupdate(choice: Choice.Empty) (answer: List) { 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) { 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) (answer: Int) { answer = length(values) }", "bad(values: Map) (answer: Result?) { answer = get(values, \"one\") }", "bad(values: Map) (answer: Map) { answer = remove(values, \"one\") }", "bad(values: Map) (answer: Map) { answer = put(values, \"one\", Result.Ready(1)) }", "bad(values: List) (answer: Result?) { answer = first(values) }", "bad() (answer: Result?) { answer = first([Result.Ready(1), Result.Failed(\"lost\")]) }", "bad(value: MapEntry) (answer: Text) { answer = value.key }", ] { assert_eq!( checked(&(types.to_owned() + body)).unwrap_err().code, "C012", "{body}" ); } for body in [ "valid(values: Map) (answer: Map) { answer = values }", "valid(values: Map) (answer: List>) { answer = entries(values) }", "valid(values: List) (answer: List) { answer = append(values, Result.Ready(1)) }", "valid() (answer: Result?) { answer = first([Result.Failed(\"returned\")]) }", "valid() (answer: Map) { 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) () { consume(entry.value) }\nvalid(values: Map) () { each(consumeEntry, entries(values)) }"; let result = checked(&source); assert!(result.is_ok(), "{result:?}"); let source = "type Key { Fine\n error Failed }\nconsume(entry: MapEntry) () recover(\"key error policy\") {}\nvalid(values: Map) () { each(consume, entries(values)) }"; assert!(checked(source).is_ok()); assert_eq!(checked("type Key { Fine\n error Failed }\nbad(values: Map) (answer: Int) { answer = length(values) }").unwrap_err().code, "C012"); }