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");
}
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();
}
declared.first().cloned().unwrap_or_else(|| "anyerror".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_falls_back_to_the_first_declared_set_for_anyhow() {
let declared = vec!["FirstError".to_string(), "SampleCrateError".to_string()];
assert_eq!(resolve_zig_error_type("anyhow::Error", &declared), "FirstError");
assert_eq!(
resolve_zig_error_type("crate::error::SampleCrateError", &declared),
"SampleCrateError"
);
}
#[test]
fn resolve_zig_error_type_yields_anyerror_without_any_declared_set() {
assert_eq!(resolve_zig_error_type("SampleCrateError", &[]), "anyerror");
}
}