use super::checked; const HANDLERS: &str = "double(value: Int) (answer: Int) { answer = value * 2 }\npositive(value: Int) (answer: Bool) { answer = value > 0 }\nadd(value: Int, state: Int) (next: Int) { next = value + state }\nstop(value: Int, state: Int) (next: Int, stop: Bool) { next = value + state\n stop = next >= 3 }\n"; const ERRORS: &str = "type Result { Ready(value: Int)\n error Failed(message: Text) }\nidentity(value: Result) (answer: Result) { answer = value }\nhandled(value: Result) (answer: Bool) recover(\"optional probe failure is explicitly discarded\") { answer = true }\ncontext(value: Int, state: Result) () recover(\"optional context probe is discarded\") {}\nkeep(value: Int, state: Result) (next: Result) { next = state }\nconsume(value: Result, state: Int) (next: Int) recover(\"count failed probes\") { next = state + 1 }\npartial(value: Result, state: Int) (next: Int, stop: Bool) recover(\"count failed probes\") { next = state + 1\n stop = true }\n"; #[test] fn checks_collection_handler_types_and_cycles() { for body in [ "main() (answer: List) { answer = each(double, []) }", "main() (answer: List) { answer = filter(positive, [1, -1]) }", "main() (answer: Int?) { answer = find(positive, []) }", "main() (answer: Int) { answer = sum(double, []) }", "main() (answer: Float) { answer = sum([]) }", "main() (answer: Decimal?) { answer = sum([]) }", "main() (answer: Int) { answer = reduce(add, [], 10) }", "main() (answer: Int) { answer = reduceUntil(stop, [1, 2, 3], 0) }", ] { let result = checked(&(HANDLERS.to_owned() + body)); assert!(result.is_ok(), "{body}: {result:?}"); } for body in [ "main() (answer: List) { answer = each(positive, [1]) }", "main() (answer: List) { answer = filter(double, [1]) }", "main() (answer: Int?) { answer = find(positive, [\"one\"]) }", "main() (answer: Int) { answer = sum(positive, [1]) }", "main() (answer: Int) { answer = reduce(add, [1], true) }", "main() (answer: Int) { answer = reduceUntil(add, [1], 0) }", "main() (answer: Int) { answer = reduce(add, [1], 0, 1) }", "main() (answer: List) { double = 2\n answer = each(double, [1]) }", "main() () { handler = double }", ] { assert!(checked(&(HANDLERS.to_owned() + body)).is_err(), "{body}"); } assert_eq!( checked("cycle(value: Int) (answer: Int) { answer = sum(cycle, [value]) }") .unwrap_err() .code, "C007" ); assert_eq!(checked("first(value: Int) (answer: Int) { answer = sum(second, [value]) }\nsecond(value: Int) (answer: Int) { answer = first(value) }").unwrap_err().code, "C007"); } #[test] fn collections_preserve_error_obligations() { for body in [ "main(values: List) (answer: Int) { copies = each(identity, values)\n answer = length(copies) }", "main(values: List) (answer: Result?) { answer = find(handled, values) }", "main(values: List) (answer: Int) { answer = reduceUntil(partial, values, 0) }", "main() (answer: Int) { answer = reduceUntil(partial, [Result.Failed(\"one\"), Result.Failed(\"two\")], 0) }", "main(state: Result) () { each(context, [], state) }", "main(values: List, state: Result) () { each(context, values, state) }", "main(state: Result) (answer: Int) { kept = reduce(keep, [], state)\n answer = 1 }", "main() (answer: List) { lost = Result.Failed(\"lost\")\n answer = each(identity, []) }", ] { let result = checked(&(ERRORS.to_owned() + body)); assert_eq!(result.unwrap_err().code, "C012", "{body}"); } for body in [ "main(values: List) (answer: List) { answer = each(identity, values) }", "main(values: List) (answer: Int) { answer = reduce(consume, values, 0) }", "main(state: Result) () { each(context, [1], state) }", "main() (answer: Result?) { answer = find(handled, [Result.Failed(\"one\")]) }", "main() (answer: Int) { answer = reduceUntil(partial, [Result.Failed(\"one\")], 0) }", "main(state: Result) (answer: Result) { answer = reduce(keep, [], state) }", ] { let result = checked(&(ERRORS.to_owned() + body)); assert!(result.is_ok(), "{body}: {result:?}"); } } #[test] fn collection_error_summaries_survive_calls_and_repeated_state() { let prefix = ERRORS.to_owned() + "copy(values: List) (answer: List) { answer = each(identity, values) }\nreplace(value: Result, state: Result) (next: Result) recover(\"last probe replaces previous probe\") { next = value }\n"; for body in [ "main(values: List) (answer: Int) { copies = copy(values)\n answer = length(copies) }", "main(values: List, state: Result) (answer: Int) { last = reduce(replace, values, state)\n answer = 1 }", "main(state: Result) () { copies = copy([])\n count = length(copies) }", ] { assert_eq!( checked(&(prefix.clone() + body)).unwrap_err().code, "C012", "{body}" ); } let source = prefix + "main(values: List, state: Result) (answer: Result) { answer = reduce(replace, values, state) }"; assert!(checked(&source).is_ok()); } #[test] fn optional_traversal_cannot_transfer_unvisited_obligations() { let prefix = ERRORS.to_owned() + "takeContext(value: Int, context: Result) (answer: Result) { answer = context }\nfirst(value: Result, state: Result) (next: Result, stop: Bool) recover(\"replace previous state\") { next = value\n stop = true }\n"; for body in [ "main(values: List, state: Result) (answer: List) { answer = each(takeContext, values, state) }", "main(values: List) (answer: Result) { answer = reduceUntil(first, values, Result.Ready(0)) }", ] { assert_eq!( checked(&(prefix.clone() + body)).unwrap_err().code, "C012", "{body}" ); } assert!(checked(&(prefix + "main(state: Result) (answer: List) { answer = each(takeContext, [1], state) }")).is_ok()); } #[test] fn replacing_record_fields_still_checks_discarded_failures() { let source = ERRORS.to_owned() + "type Row { result: Result\n count: Int }\nreplace(value: Row) (answer: Row) { answer = value\n answer.result = Result.Ready(1)\n answer.count = 0 }"; assert_eq!(checked(&source).unwrap_err().code, "C012"); } #[test] fn known_length_map_builds_reserve_once_and_update_in_place() { for (kind, item, value, sequence) in [ ("Bytes", "Int", "value", "values"), ("List", "Int", "value", "values"), ( "Map", "MapEntry", "value.value", "entries(values)", ), ] { let text = format!( "insert(value: {item}, state: Map) (next: Map) {{ next = put(state, length(state), {value}) }}\nmain(values: {kind}) (answer: Map) {{ answer = reduce(insert, {sequence}, {{}}) }}" ); 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], &[1].into()); let main = generated.split("pub fn function1(").nth(1).unwrap(); let main = main.split("pub fn ").next().unwrap(); assert!( main.contains("Map::with_capacity(sequence.len())"), "{main}" ); assert!(main.contains("state.insert("), "{main}"); assert!(!main.contains("state.clone()"), "{main}"); assert!(!main.contains(".to_owned()"), "{main}"); } } #[test] fn record_reductions_keep_a_single_growing_buffer() { let text = "type State { values: List\n count: Int }\nstep(value: Int, state: State) (next: State) { values = append(state.values, value)\n next = State { values = values\n count = state.count + 1 } }\nmain(input: Bytes) (answer: State) { answer = reduce(step, input, State { values = []\n count = 0 }) }"; 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], &[1].into()); let main = generated.split("pub fn function1(").nth(1).unwrap(); let main = main.split("\npub ").next().unwrap(); assert!(!main.contains(".clone()"), "{main}"); let iteration = main.split("for byte in sequence").nth(1).unwrap(); let iteration = iteration.split("Type0 {").next().unwrap(); assert!(!iteration.contains(".reserve("), "{main}"); assert!(iteration.contains(".push(element)"), "{main}"); } #[test] fn map_selection_copies_only_matching_entries() { for (operation, result) in [ ("filter", "List>"), ("find", "MapEntry?"), ] { let text = format!( "matches(entry: MapEntry, target: Text) (answer: Bool) {{ answer = entry.value == target }}\nmain(values: Map, target: Text) (answer: {result}) {{ answer = {operation}(matches, entries(values), target) }}" ); let source = dever::syntax::source::Source::new(0, "case.dever", text.clone()); let program = checked(&text).unwrap(); let generated = dever::compiler::emit::generate(&program, &[source], &[1].into()); let main = generated.split("pub fn function1(").nth(1).unwrap(); let main = main.split("\npub ").next().unwrap(); let iteration = main.split("for (key, value) in sequence").nth(1).unwrap(); let (inspection, selected) = iteration.split_once("if ").unwrap(); assert!(!inspection.contains(".clone()"), "{main}"); assert!(selected.contains("(key.clone(), value.clone())"), "{main}"); } }