use super::*;
use crate::diagnostic_codes::Code;
#[test]
fn thrown_record_tags_select_the_error_union_arm() {
let declarations = r#"
type CallbackFailure = {
kind: "callback_failed",
category: "internal",
message: string,
cause: {terminal_error: unknown, callback_error: unknown},
}
type CancelledFailure = {category: "cancelled", message: string}
type Failure = string | CallbackFailure | CancelledFailure
"#;
for (record, rejected) in [
(
r#"{kind: "callback_failed", category: "internal", message: "failed", cause: {terminal_error: "first", callback_error: "second"}}"#,
false,
),
(r#"{category: "cancelled", message: "stopped"}"#, false),
(
r#"{knid: "callback_failed", category: "internal", message: "failed", cause: {terminal_error: "first", callback_error: "second"}}"#,
true,
),
(
r#"{kind: "callback_failed", category: "internal", message: "failed", cause: {terminal_error: "first"}}"#,
true,
),
(
r#"{...{category: "cancelled", message: "failed"}, category: "internal"}"#,
true,
),
] {
let source =
format!("{declarations}\nconst result: Result<_, Failure> = try {{ throw {record} }}");
let mismatches = check_source(&source)
.into_iter()
.filter(|diagnostic| diagnostic.code == Code::VariableTypeMismatch)
.count();
assert_eq!(mismatches, usize::from(rejected), "{record}");
}
let legacy = r#"const result: Result<_, string | dict> = try {
throw {knid: "callback_failed", category: "internal", message: "failed"}
}"#;
assert!(!check_source(legacy)
.iter()
.any(|diagnostic| diagnostic.severity == DiagnosticSeverity::Error));
}
fn throws_mismatches(source: &str) -> Vec<String> {
check_source(source)
.into_iter()
.filter(|d| d.code == Code::ThrowsTypeMismatch)
.map(|d| d.message)
.collect()
}
#[test]
fn throwing_the_declared_type_checks_clean() {
let diags = throws_mismatches(
r#"fn parse(s: string) throws string {
throw "bad input"
}"#,
);
assert!(
diags.is_empty(),
"throwing the declared type must not raise TYP-026: {diags:?}"
);
}
#[test]
fn throwing_an_undeclared_type_errors() {
let diags = throws_mismatches(
r"fn parse(s: string) throws string {
throw 42
}",
);
assert_eq!(
diags.len(),
1,
"throwing a type outside the declared set must raise exactly one TYP-026: {diags:?}"
);
}
#[test]
fn union_throws_set_covers_each_member() {
let diags = throws_mismatches(
r#"fn parse(s: string) throws string | int {
if s == "" {
throw 42
}
throw "bad"
}"#,
);
assert!(
diags.is_empty(),
"each member of a union throws set must be allowed: {diags:?}"
);
}
#[test]
fn missing_throws_clause_is_unconstrained() {
let diags = throws_mismatches(
r"fn parse(s: string) {
throw 42
}",
);
assert!(
diags.is_empty(),
"an unannotated callable must never be throws-checked: {diags:?}"
);
}
#[test]
fn pipeline_throws_clause_is_enforced() {
let diags = throws_mismatches(
r"pipeline run(task) throws string {
throw 42
}",
);
assert_eq!(
diags.len(),
1,
"a pipeline that throws outside its declared set must raise TYP-026: {diags:?}"
);
}
#[test]
fn caught_error_is_excluded_from_the_declared_channel() {
let diags = throws_mismatches(
r"enum Parse { Bad }
enum Net { Down }
fn f() throws Net {
try {
throw Parse.Bad
} catch (e: Parse) {
throw Net.Down
}
}",
);
assert!(
diags.is_empty(),
"an error handled by its catch clause must not count against `throws`: {diags:?}"
);
}
#[test]
fn uncaught_error_escapes_and_must_be_declared() {
let diags = throws_mismatches(
r"enum Parse { Bad }
enum Net { Down }
fn f() throws Parse {
try {
throw Net.Down
} catch (e: Parse) {
}
}",
);
assert_eq!(
diags.len(),
1,
"an error the catch does not cover must escape and raise TYP-026: {diags:?}"
);
}
#[test]
fn catch_all_absorbs_every_body_error() {
let diags = throws_mismatches(
r"enum Net { Down }
fn f() throws Net {
try {
throw Net.Down
} catch {
}
}",
);
assert!(
diags.is_empty(),
"a catch-all must absorb every body error: {diags:?}"
);
}
#[test]
fn finally_body_throw_escapes() {
let diags = throws_mismatches(
r"enum Parse { Bad }
enum Net { Down }
fn f() throws Parse {
try {
throw Parse.Bad
} catch (e: Parse) {
} finally {
throw Net.Down
}
}",
);
assert_eq!(
diags.len(),
1,
"a throw in `finally` must escape and be checked against `throws`: {diags:?}"
);
}