use super::checked; const TYPES: &str = "type Result { Ready(value: Int)\n error Failed(message: Text) }\ntype Box { result: Result\n label: Text }\nfailValue(message: Text) (value: Int) { fail(Result.Failed(message)) }\nfailure(message: Text) (value: Result) { value = result(failValue(message)) }\n"; #[test] fn rejects_discard_overwrite_and_nested_failure_loss() { for body in [ "main() () { value = failure(\"bad\") }", "main() (answer: Result) { answer = failure(\"bad\")\n answer = Result.Ready(1) }", "main() (answer: Text) { box = Box { result = failure(\"bad\")\n label = \"x\" }\n answer = box.label }", "main() (answer: Int) { values = [failure(\"bad\")]\n answer = length(values) }", "ignore(value: Result) () {}", "read(value: Result.Ready(number)) (answer: Int) { answer = number }\nread(value: Result.Failed(message)) (answer: Int) { answer = length(message) }", "main() (answer: Result?) { old = failure(\"bad\")\n answer = null }", "main(old: Result, next: Result?) (answer: Result?) { answer = next }", "main(old: Result, next: List) (answer: List) { answer = next }", ] { let error = checked(&(TYPES.to_owned() + body)).unwrap_err(); assert_eq!(error.code, "C012", "{body}: {error:?}"); } } #[test] fn capture_arguments_keep_default_propagation() { let source = TYPES.to_owned() + "identity(value: Int) (answer: Int) { answer = value }\nmain() (answer: Result) { answer = result(identity(failValue(\"bad\"))) }"; let program = checked(&source).unwrap(); let main = program .functions .iter() .find(|function| function.signature.name == "main") .unwrap(); assert_eq!(main.signature.failures.len(), 1); assert!(main.signature.returns.is_none()); } #[test] fn accepts_return_propagation_translation_and_explicit_recovery() { for body in [ "main() (answer: Result) { answer = failure(\"bad\") }", "main() (answer: Int) { answer = failValue(\"bad\") }", "main() (answer: Result) { old = failure(\"bad\")\n answer = Result.Failed(\"replacement\") }", "identity(value: Result) (answer: Result) { answer = value }\nmain() (answer: Result) { first = failure(\"bad\")\n second = identity(first)\n answer = first }", "read(value: Result.Ready(number)) (answer: Int) { answer = number }\nread(value: Result.Failed(message)) (answer: Int) { fail(Result.Failed(message)) }", "read(value: Result.Ready(number)) (answer: Int) recover(\"use zero when unavailable\") pure { answer = number }\nread(value: Result.Failed(message)) (answer: Int) pure recover(\"use zero when unavailable\") { answer = 0 }", ] { let result = checked(&(TYPES.to_owned() + body)); assert!(result.is_ok(), "{body}: {result:?}"); } } #[test] fn recovery_and_fail_are_not_generic_error_bypasses() { for body in [ "main() (answer: Int) { fail(Result.Ready(1)) }", "main() (answer: Int) { fail(1) }", "main() (answer: Int) recover(\" \") { answer = 0 }", "read(value: Result.Ready(number)) (answer: Int) recover(\"fallback\") { answer = number }\nread(value: Result.Failed(message)) (answer: Int) { answer = 0 }", "main() (answer: Int) { fail(Result.Failed(\"bad\"))\n answer = 1 }", ] { assert!(checked(&(TYPES.to_owned() + body)).is_err(), "{body}"); } let recovery = "handled(value: Result) (answer: Bool) recover(\"discard unavailable optional probe\") { answer = true }\n"; for expression in [ "false and handled(value)", "true or handled(value)", "flag and handled(value)", ] { let body = format!( "main(flag: Bool) (answer: Bool) {{ value = failure(\"bad\")\n answer = {expression} }}" ); assert_eq!( checked(&(TYPES.to_owned() + recovery + &body)) .unwrap_err() .code, "C012", "{body}" ); } } #[test] fn capture_requires_exact_nominal_error_identity() { let prefix = "type Problem { error Denied(code: Int)\n error Missing(message: Text) }\ntype Result { Ready(value: Int)\n error Failed(problem: Problem) }\nraise() (value: Int) { fail(Problem.Denied(7)) }\nmain() (answer: Result) { answer = result(raise()) }"; assert!(checked(prefix).is_ok()); let ambiguous = prefix.replace( "error Failed(problem: Problem)", "error Failed(problem: Problem)\n error Again(problem: Problem)", ); assert!(checked(&ambiguous).is_err()); let wrong = prefix.replace("Failed(problem: Problem)", "Failed(problem: Int)"); assert!(checked(&wrong).is_err()); let normal = prefix.replace("error Missing", "Missing"); assert!(checked(&normal).is_ok()); let unrelated = format!( "type Other {{ error Denied(code: Int) }}\n{}", prefix.replace("Failed(problem: Problem)", "Failed(problem: Other)") ); assert!(checked(&unrelated).is_err()); } #[test] fn capture_preserves_zero_and_multiple_success_outputs() { for source in [ "type Done { Complete }\nwork() () {}\nmain() (value: Done) { value = result(work()) }", "type Pair { Complete(number: Int, text: Text) }\nwork() (number: Int, text: Text) { number = 1\n text = \"ok\" }\nmain() (value: Pair) { value = result(work()) }", ] { checked(source).unwrap(); } }