use crate::core::ir::ErrorDef;
fn zig_error_variant_component(name: &str) -> String {
crate::codegen::naming::public_host_identifier(
crate::core::config::Language::Zig,
crate::codegen::naming::PublicIdentifierKind::Type,
name,
)
}
pub(crate) fn emit_error_set(error: &ErrorDef, out: &mut String) {
if !error.doc.is_empty() {
out.push_str(&crate::backends::zig::template_env::render(
"error_doc_block.jinja",
minijinja::context! {
error_doc_lines => error.doc.lines().collect::<Vec<_>>(),
},
));
}
out.push_str(&crate::backends::zig::template_env::render(
"error_set_header.jinja",
minijinja::context! {
error_name => &error.name,
},
));
for variant in &error.variants {
out.push_str(&crate::backends::zig::template_env::render(
"error_set_variant.jinja",
minijinja::context! {
variant_name => zig_error_variant_component(&variant.name),
},
));
}
if !error
.variants
.iter()
.any(|v| zig_error_variant_component(&v.name) == "OutOfMemory")
{
out.push_str(&crate::backends::zig::template_env::render(
"error_set_variant.jinja",
minijinja::context! {
variant_name => "OutOfMemory",
},
));
}
if !error
.variants
.iter()
.any(|v| zig_error_variant_component(&v.name) == "UnknownFfiError")
{
out.push_str(&crate::backends::zig::template_env::render(
"error_set_variant.jinja",
minijinja::context! {
variant_name => "UnknownFfiError",
},
));
}
out.push_str("};\n");
}
const UNMATCHED_ERROR_SET: &str = "error{OutOfMemory,UnknownFfiError}";
pub(crate) fn resolve_zig_error_type(error_type: &str, declared: &[String]) -> String {
let last = error_type.rsplit("::").next().unwrap_or(error_type);
if declared.iter().any(|d| d == last) {
return last.to_string();
}
if declared.is_empty() {
return "anyerror".to_string();
}
UNMATCHED_ERROR_SET.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::ErrorVariant;
fn variant(name: &str, template: Option<&str>) -> ErrorVariant {
ErrorVariant {
error_code: None,
name: name.to_string(),
message_template: template.map(str::to_string),
..ErrorVariant::default()
}
}
#[test]
fn emit_error_set_emits_only_the_error_set() {
let error = ErrorDef {
name: "MyError".into(),
rust_path: "x::MyError".into(),
original_rust_path: String::new(),
variants: vec![variant("Boom", Some("Boom happened: {0}"))],
doc: String::new(),
methods: vec![],
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
};
let mut out = String::new();
emit_error_set(&error, &mut out);
assert!(
out.contains("pub const MyError = error{"),
"expected error set header:\n{out}"
);
assert!(out.contains("Boom,"), "expected Boom variant:\n{out}");
assert!(
out.contains("OutOfMemory,"),
"expected implicit OutOfMemory variant:\n{out}"
);
assert!(
out.trim_end().ends_with("};"),
"expected closing brace of error set:\n{out}"
);
assert!(
!out.contains("_from_ffi_msg_"),
"message-prefix dispatch was replaced by numeric taxonomy-code dispatch \
in helpers::_error_with_message and must not be emitted here:\n{out}"
);
assert!(
out.contains("UnknownFfiError,"),
"expected implicit UnknownFfiError variant:\n{out}"
);
}
#[test]
fn emit_error_set_never_duplicates_an_explicit_unknown_ffi_error_variant() {
let error = ErrorDef {
name: "MyError".into(),
rust_path: "x::MyError".into(),
original_rust_path: String::new(),
variants: vec![variant("UnknownFfiError", None), variant("Boom", None)],
doc: String::new(),
methods: vec![],
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
};
let mut out = String::new();
emit_error_set(&error, &mut out);
assert_eq!(
out.matches("UnknownFfiError,").count(),
1,
"the unknown-error member must be declared exactly once:\n{out}"
);
}
#[test]
fn resolve_zig_error_type_matches_a_qualified_path_against_the_second_declared_set() {
let declared = vec!["FirstError".to_string(), "SampleCrateError".to_string()];
assert_eq!(
resolve_zig_error_type("crate::error::SampleCrateError", &declared),
"SampleCrateError"
);
}
#[test]
fn resolve_zig_error_type_never_falls_back_to_an_unrelated_declared_set() {
let declared = vec!["FirstError".to_string(), "SampleCrateError".to_string()];
let resolved = resolve_zig_error_type("anyhow::Error", &declared);
assert_ne!(
resolved, "FirstError",
"must not masquerade as the unrelated first-declared set"
);
assert_ne!(
resolved, "SampleCrateError",
"must not masquerade as the unrelated second-declared set"
);
assert_eq!(resolved, UNMATCHED_ERROR_SET);
}
#[test]
fn resolve_zig_error_type_yields_anyerror_without_any_declared_set() {
assert_eq!(resolve_zig_error_type("SampleCrateError", &[]), "anyerror");
}
mod wrapper_return_type_integration {
use crate::backends::zig::gen_bindings::functions::wrapper_return_type;
use crate::core::ir::{FunctionDef, TypeRef};
fn declared_two_errors() -> Vec<String> {
vec!["FirstError".to_string(), "SecondError".to_string()]
}
fn fallible_function(name: &str, error_type: &str) -> FunctionDef {
FunctionDef {
name: name.to_string(),
return_type: TypeRef::Unit,
error_type: Some(error_type.to_string()),
..FunctionDef::default()
}
}
#[test]
fn a_function_returning_the_second_declared_error_resolves_to_it() {
let f = fallible_function("uses_second_error", "SecondError");
let return_ty = wrapper_return_type(&f, &declared_two_errors(), &Default::default(), &Default::default());
assert!(
return_ty.starts_with("SecondError!"),
"a function returning `Result<_, SecondError>` must be typed `SecondError!...`, got: {return_ty}"
);
assert!(
!return_ty.contains("FirstError"),
"must never carry the unrelated first-declared error: {return_ty}"
);
}
#[test]
fn a_function_returning_the_first_declared_error_still_resolves_to_it() {
let f = fallible_function("uses_first_error", "FirstError");
let return_ty = wrapper_return_type(&f, &declared_two_errors(), &Default::default(), &Default::default());
assert!(
return_ty.starts_with("FirstError!"),
"a function returning `Result<_, FirstError>` must be typed `FirstError!...`, got: {return_ty}"
);
}
#[test]
fn a_function_with_an_unmatched_error_type_is_never_mislabeled_as_a_declared_set() {
let f = fallible_function("uses_foreign_error", "anyhow::Error");
let return_ty = wrapper_return_type(&f, &declared_two_errors(), &Default::default(), &Default::default());
assert!(
!return_ty.starts_with("FirstError!") && !return_ty.starts_with("SecondError!"),
"an unmatched error type must never be attributed to an unrelated declared error, got: {return_ty}"
);
assert!(
return_ty.contains("UnknownFfiError"),
"the safe fallback must still admit UnknownFfiError so `_error_with_message` compiles: {return_ty}"
);
}
}
}