use super::gen_enum;
use crate::core::ir::{EnumDef, EnumVariant};
fn host_enum_with_gated_variants(name: &str, variant_cfgs: &[(&str, Option<&str>)]) -> EnumDef {
EnumDef {
name: name.to_string(),
rust_path: format!("test::{name}"),
variants: variant_cfgs
.iter()
.map(|(variant_name, cfg)| EnumVariant {
name: (*variant_name).to_string(),
cfg: cfg.map(|c| c.to_string()),
..Default::default()
})
.collect(),
..Default::default()
}
}
#[test]
fn gen_enum_default_impl_reachable_when_only_later_variant_feature_enabled() {
let enum_def = host_enum_with_gated_variants(
"SearchAlgorithm",
&[
("Alpha", Some(r#"feature = "algo-alpha""#)),
("Beta", Some(r#"feature = "algo-beta""#)),
],
);
let output = gen_enum(&enum_def, "Js", true, "test", None);
assert_eq!(
output.matches("impl Default for JsSearchAlgorithm {").count(),
2,
"every declared variant needs its own candidate Default impl so the impl survives \
whichever single variant's feature ends up enabled, got:\n{output}"
);
assert!(
output.contains(
"#[cfg(feature = \"algo-alpha\")]\n#[allow(clippy::derivable_impls)]\nimpl Default for JsSearchAlgorithm {\n fn default() -> Self { Self::Alpha }\n}"
),
"the first-declared variant's candidate must stay gated on exactly its own feature, got:\n{output}"
);
assert!(
output.contains(
"#[cfg(all(feature = \"algo-beta\", not(any(feature = \"algo-alpha\"))))]\n#[allow(clippy::derivable_impls)]\nimpl Default for JsSearchAlgorithm {\n fn default() -> Self { Self::Beta }\n}"
),
"the second-declared variant's candidate must be reachable whenever the first variant's \
feature is off, so a build enabling only the second variant's feature still gets a \
compilable Default impl, got:\n{output}"
);
}
#[test]
fn gen_enum_default_impl_cascade_accumulates_negation_across_many_gated_variants() {
let cases: &[&[&str]] = &[
&["feature = \"a\"", "feature = \"b\""],
&["feature = \"a\"", "feature = \"b\"", "feature = \"c\""],
&[
"feature = \"a\"",
"feature = \"b\"",
"feature = \"c\"",
"feature = \"d\"",
],
];
for cfgs in cases {
let variant_names: Vec<String> = (0..cfgs.len()).map(|i| format!("V{i}")).collect();
let variant_cfgs: Vec<(&str, Option<&str>)> = variant_names
.iter()
.zip(cfgs.iter())
.map(|(name, cfg)| (name.as_str(), Some(*cfg)))
.collect();
let enum_def = host_enum_with_gated_variants("SearchAlgorithm", &variant_cfgs);
let output = gen_enum(&enum_def, "Js", true, "test", None);
assert_eq!(
output.matches("impl Default for JsSearchAlgorithm {").count(),
cfgs.len(),
"every gated variant needs its own candidate ({} variants), got:\n{output}",
cfgs.len()
);
let mut prior: Vec<&str> = Vec::new();
for (i, cfg) in cfgs.iter().enumerate() {
let expected_cfg = if prior.is_empty() {
(*cfg).to_string()
} else {
format!("all({cfg}, not(any({})))", prior.join(", "))
};
let expected_block = format!(
"#[cfg({expected_cfg})]\n#[allow(clippy::derivable_impls)]\nimpl Default for JsSearchAlgorithm {{\n fn default() -> Self {{ Self::V{i} }}\n}}"
);
assert!(
output.contains(&expected_block),
"variant V{i} (of {} total) must carry the guard `{expected_cfg}`, got:\n{output}",
cfgs.len()
);
prior.push(cfg);
}
}
}
#[test]
fn gen_enum_default_impl_cascade_terminates_at_unconditional_variant() {
let enum_def = host_enum_with_gated_variants(
"SearchAlgorithm",
&[
("Alpha", Some(r#"feature = "algo-alpha""#)),
("Beta", None),
("Gamma", Some(r#"feature = "algo-gamma""#)),
],
);
let output = gen_enum(&enum_def, "Js", true, "test", None);
assert_eq!(
output.matches("impl Default for JsSearchAlgorithm {").count(),
2,
"the cascade must stop at the first unconditionally-present variant (Beta); Gamma can \
never be needed as a fallback and must not get its own candidate, got:\n{output}"
);
assert!(
!output.contains("Self::Gamma }"),
"a variant declared after an unconditional one must never appear in a Default body, got:\n{output}"
);
assert!(
output.contains(
"#[cfg(not(any(feature = \"algo-alpha\")))]\n#[allow(clippy::derivable_impls)]\nimpl Default for JsSearchAlgorithm {\n fn default() -> Self { Self::Beta }\n}"
),
"the unconditional variant's candidate must be reachable whenever every earlier candidate \
is off, got:\n{output}"
);
}
#[test]
fn gen_enum_default_impl_stays_unconditional_when_ungated() {
let enum_def = host_enum_with_gated_variants("SearchAlgorithm", &[("Alpha", None), ("Beta", None)]);
let output = gen_enum(&enum_def, "Js", true, "test", None);
assert_eq!(
output.matches("impl Default for JsSearchAlgorithm {").count(),
1,
"an ungated enum must keep a single Default impl, got:\n{output}"
);
assert!(
output.contains(
"#[allow(clippy::derivable_impls)]\nimpl Default for JsSearchAlgorithm {\n fn default() -> Self { Self::Alpha }\n}"
),
"an ungated enum's Default impl must stay unconditional (no #[cfg(...)] guard) and keep \
defaulting to the first declared variant, got:\n{output}"
);
assert!(
!output.contains("#[cfg("),
"an ungated enum must not gain a #[cfg(...)] guard on its Default impl, got:\n{output}"
);
}