use crate::codegen::generators::type_paths::resolve_type_path;
use crate::core::ir::EnumDef;
use std::collections::HashMap;
pub(crate) fn emit_enum_wrapper(en: &EnumDef, source_crate: &str, type_paths: &HashMap<String, String>) -> String {
let mut out = String::new();
let source_path = resolve_type_path(&en.name, source_crate, type_paths);
out.push_str(&crate::backends::swift::template_env::render(
"enum_unit_header.jinja",
minijinja::context! {
name => &en.name,
},
));
for variant in &en.variants {
out.push_str(&crate::backends::swift::template_env::render(
"enum_unit_variant.jinja",
minijinja::context! {
variant_name => &variant.name,
},
));
}
out.push_str("}\n\n");
out.push_str(&crate::backends::swift::template_env::render(
"enum_from_impl_header.jinja",
minijinja::context! {
source_path => &source_path,
name => &en.name,
},
));
out.push_str(" match val {\n");
let has_cfg_variants = en.variants.iter().any(|v| v.cfg.is_some());
for variant in &en.variants {
let pattern = if variant.fields.is_empty() {
variant.name.clone()
} else if variant.is_tuple {
format!("{}(..)", variant.name)
} else {
format!("{} {{ .. }}", variant.name)
};
if let Some(condition) = variant.cfg.as_deref() {
out.push_str(" #[cfg(");
out.push_str(condition);
out.push_str(")]\n");
}
out.push_str(&crate::backends::swift::template_env::render(
"enum_from_variant.jinja",
minijinja::context! {
source_path => &source_path,
variant_name => &variant.name,
pattern => pattern,
},
));
}
if !en.excluded_variants.is_empty() || has_cfg_variants {
out.push_str(&format!(
" _ => unreachable!(\"bridge enum variant of {} not exposed in binding\"),\n",
en.name
));
}
out.push_str(" }\n");
out.push_str(" }\n");
out.push_str("}\n\n");
let mut variants = String::new();
for variant in &en.variants {
let serde_name = serde_variant_wire_name(variant, en.serde_rename_all.as_deref());
variants.push_str(&crate::backends::swift::template_env::render(
"rust_enum_to_string_variant.rs.jinja",
minijinja::context! {
variant_name => &variant.name,
serde_name => &serde_name,
},
));
}
out.push_str(&crate::backends::swift::template_env::render(
"rust_enum_to_string_impl.rs.jinja",
minijinja::context! {
enum_name => &en.name,
variants => variants,
},
));
out
}
fn serde_variant_wire_name(variant: &crate::core::ir::EnumVariant, rename_all: Option<&str>) -> String {
crate::codegen::naming::wire_variant_value(&variant.name, variant.serde_rename.as_deref(), rename_all)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::ir::{EnumDef, EnumVariant};
fn make_unit_variant(name: &str, cfg: Option<&str>) -> EnumVariant {
EnumVariant {
name: name.to_string(),
cfg: cfg.map(str::to_string),
..Default::default()
}
}
#[test]
fn cfg_gated_variant_emits_catch_all_in_from_impl() {
let en = EnumDef {
name: "ImageOutputFormat".to_string(),
variants: vec![
make_unit_variant("Jpeg", None),
make_unit_variant("Heif", Some(r#"feature = "heic""#)),
],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
out.contains("_ => unreachable!"),
"expected catch-all `_ => unreachable!` arm when cfg-gated variant present, got:\n{out}"
);
assert!(
out.contains("ImageOutputFormat"),
"catch-all message must include the enum name, got:\n{out}"
);
}
#[test]
fn no_cfg_or_excluded_variants_does_not_emit_catch_all() {
let en = EnumDef {
name: "SimpleEnum".to_string(),
variants: vec![make_unit_variant("A", None), make_unit_variant("B", None)],
methods: vec![],
excluded_variants: vec![],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
!out.contains("_ => unreachable!"),
"unexpected catch-all arm in From impl for fully-covered enum:\n{out}"
);
}
#[test]
fn excluded_variants_alone_emits_catch_all() {
let en = EnumDef {
name: "ImageOutputFormat".to_string(),
variants: vec![make_unit_variant("Jpeg", None)],
methods: vec![],
excluded_variants: vec![make_unit_variant("ExcludedVariant", None)],
..Default::default()
};
let type_paths = std::collections::HashMap::new();
let out = emit_enum_wrapper(&en, "mylib", &type_paths);
assert!(
out.contains("_ => unreachable!"),
"expected catch-all arm when excluded_variants is non-empty, got:\n{out}"
);
}
}