use super::MagnusBackend;
use crate::core::backend::Backend;
use crate::core::config::{NewAlefConfig, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, EnumDef, EnumVariant, FunctionDef, ParamDef, TypeRef};
fn magnus_config_with_feature(configured_feature: Option<&str>) -> ResolvedCrateConfig {
let features_line = configured_feature
.map(|f| format!("features = [\"{f}\"]\n"))
.unwrap_or_default();
let toml_src = format!(
"[workspace]\nlanguages = [\"ruby\"]\n[[crates]]\nname = \"test-lib\"\nsources = [\"src/lib.rs\"]\n\
[crates.ruby]\ngem_name = \"test_lib\"\n{features_line}"
);
let cfg: NewAlefConfig = toml::from_str(&toml_src).unwrap();
cfg.resolve().unwrap().remove(0)
}
fn foreign_cfg_enum_api() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![EnumDef {
name: "RoutingStrategy".to_string(),
rust_path: "dep_crate::RoutingStrategy".to_string(),
variants: vec![
EnumVariant {
name: "Primary".to_string(),
..Default::default()
},
EnumVariant {
name: "Extra".to_string(),
cfg: Some(r#"feature = "extra-tier""#.to_string()),
..Default::default()
},
],
..Default::default()
}],
..Default::default()
}
}
fn foreign_cfg_enum_api_with_param_function() -> ApiSurface {
let mut api = foreign_cfg_enum_api();
api.functions.push(FunctionDef {
name: "set_routing_strategy".to_string(),
rust_path: "test_lib::set_routing_strategy".to_string(),
params: vec![ParamDef {
name: "strategy".to_string(),
ty: TypeRef::Named("RoutingStrategy".to_string()),
..Default::default()
}],
return_type: TypeRef::Unit,
..Default::default()
});
api
}
fn lib_rs_content(files: &[crate::core::backend::GeneratedFile]) -> &str {
&files
.iter()
.find(|f| f.path.to_string_lossy().ends_with("lib.rs"))
.expect("generate_bindings must emit lib.rs")
.content
}
fn core_to_binding_conversion(lib_rs: &str) -> &str {
let start = lib_rs
.find("impl From<dep_crate::RoutingStrategy> for RoutingStrategy {")
.expect("generated crate must convert the foreign enum from core to the binding type");
let end = lib_rs[start..]
.find("\n}")
.map(|i| start + i + 2)
.expect("conversion impl must close");
&lib_rs[start..end]
}
fn binding_to_core_conversion(lib_rs: &str) -> &str {
let start = lib_rs
.find("impl From<RoutingStrategy> for dep_crate::RoutingStrategy {")
.expect("generated crate must convert the binding enum back to the foreign core type");
let end = lib_rs[start..]
.find("\n}")
.map(|i| start + i + 2)
.expect("conversion impl must close");
&lib_rs[start..end]
}
#[test]
fn generate_bindings_omits_unreachable_catch_all_for_foreign_variant_proven_unreachable_end_to_end() {
let api = foreign_cfg_enum_api();
let config = magnus_config_with_feature(None);
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs);
assert!(
!conversion.contains("_ => Default::default(),"),
"a foreign cfg-gated variant proven unreachable by this binding's own configured feature \
set must not leave behind an unreachable catch-all (a cargo clippy -D warnings failure), \
got:\n{conversion}"
);
}
#[test]
fn generate_bindings_keeps_catch_all_for_foreign_variant_not_proven_unreachable_end_to_end() {
let api = foreign_cfg_enum_api();
let config = magnus_config_with_feature(Some("extra-tier"));
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs);
assert!(
conversion.contains("_ => Default::default(),"),
"a foreign cfg-gated variant that is NOT proven unreachable must keep the catch-all so the \
match stays exhaustive, got:\n{conversion}"
);
}
#[test]
fn generate_bindings_omits_binding_to_core_catch_all_for_foreign_variant_proven_unreachable_end_to_end() {
let api = foreign_cfg_enum_api_with_param_function();
let config = magnus_config_with_feature(None);
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = binding_to_core_conversion(lib_rs);
assert!(
!conversion.contains("_ => Default::default(),"),
"Magnus's enum declaration now drops a foreign variant proven unreachable, so the \
binding->core match is exhaustive without a catch-all -- keeping one is an unreachable \
pattern (a cargo clippy -D warnings failure), got:\n{conversion}"
);
assert!(
!conversion.contains("Extra"),
"the dropped foreign variant must not be named anywhere in the binding->core conversion, \
got:\n{conversion}"
);
}
#[test]
fn generate_bindings_keeps_binding_to_core_catch_all_for_foreign_variant_not_proven_unreachable_end_to_end() {
let api = foreign_cfg_enum_api_with_param_function();
let config = magnus_config_with_feature(Some("extra-tier"));
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = binding_to_core_conversion(lib_rs);
assert!(
conversion.contains("_ => Default::default(),"),
"a foreign cfg-gated variant that is NOT proven unreachable is still declared \
unconditionally, so the binding->core match must keep its catch-all, got:\n{conversion}"
);
}
fn returning_function(name: &str, enum_name: &str) -> FunctionDef {
FunctionDef {
name: name.to_string(),
rust_path: format!("test_lib::{name}"),
return_type: TypeRef::Named(enum_name.to_string()),
..Default::default()
}
}
fn foreign_cfg_enum_api_three_variants() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![EnumDef {
name: "RoutingStrategy".to_string(),
rust_path: "dep_crate::RoutingStrategy".to_string(),
variants: vec![
EnumVariant {
name: "Primary".to_string(),
..Default::default()
},
EnumVariant {
name: "Secondary".to_string(),
..Default::default()
},
EnumVariant {
name: "Extra".to_string(),
cfg: Some(r#"feature = "extra-tier""#.to_string()),
..Default::default()
},
],
..Default::default()
}],
..Default::default()
}
}
fn wrapper_enum_declaration(lib_rs: &str) -> &str {
let start = lib_rs
.find("pub enum RoutingStrategy {")
.expect("generated crate must declare the RoutingStrategy wrapper enum");
let end = lib_rs[start..]
.find("\n}")
.map(|i| start + i + 2)
.expect("enum declaration must close");
&lib_rs[start..end]
}
fn declared_variant_names(rendered: &str) -> std::collections::BTreeSet<String> {
rendered
.lines()
.filter_map(|line| {
let trimmed = line.trim();
let name = trimmed.strip_suffix(',')?;
let is_variant_ident =
name.starts_with(|c: char| c.is_ascii_uppercase()) && name.chars().all(|c| c.is_alphanumeric());
is_variant_ident.then(|| name.to_string())
})
.collect()
}
fn names(values: &[&str]) -> std::collections::BTreeSet<String> {
values.iter().map(|s| s.to_string()).collect()
}
#[test]
fn generate_bindings_declares_exact_retained_variant_set_for_foreign_variant_proven_unreachable() {
let api = foreign_cfg_enum_api_three_variants();
let excluded_config = magnus_config_with_feature(None);
let excluded_files = MagnusBackend.generate_bindings(&api, &excluded_config).unwrap();
let excluded_decl = wrapper_enum_declaration(lib_rs_content(&excluded_files));
assert_eq!(
declared_variant_names(excluded_decl),
names(&["Primary", "Secondary"]),
"the declared set must be exactly the two retained variants, got:\n{excluded_decl}"
);
let active_config = magnus_config_with_feature(Some("extra-tier"));
let active_files = MagnusBackend.generate_bindings(&api, &active_config).unwrap();
let active_decl = wrapper_enum_declaration(lib_rs_content(&active_files));
assert_eq!(
declared_variant_names(active_decl),
names(&["Primary", "Secondary", "Extra"]),
"with \"extra-tier\" configured, the declared set must include the retained foreign \
variant, got:\n{active_decl}"
);
}
#[test]
fn generate_bindings_never_drops_host_owned_cfg_variant_from_declaration() {
let mut api = foreign_cfg_enum_api_three_variants();
api.enums[0].rust_path = "test_lib::RoutingStrategy".to_string();
let config = magnus_config_with_feature(None);
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let decl = wrapper_enum_declaration(lib_rs_content(&files));
assert_eq!(
declared_variant_names(decl),
names(&["Primary", "Secondary", "Extra"]),
"a host-owned cfg-gated variant must stay declared even with no features configured, \
got:\n{decl}"
);
}
#[test]
fn generate_bindings_declares_exact_retained_variant_set_for_data_enum_foreign_variant() {
let api = ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![EnumDef {
name: "PageAction".to_string(),
rust_path: "dep_crate::PageAction".to_string(),
variants: vec![
EnumVariant {
name: "Scrape".to_string(),
..Default::default()
},
EnumVariant {
name: "Click".to_string(),
fields: vec![crate::core::ir::FieldDef {
name: "selector".to_string(),
ty: TypeRef::String,
..Default::default()
}],
..Default::default()
},
EnumVariant {
name: "Testkit".to_string(),
fields: vec![crate::core::ir::FieldDef {
name: "note".to_string(),
ty: TypeRef::String,
..Default::default()
}],
cfg: Some(r#"feature = "testkit""#.to_string()),
..Default::default()
},
],
..Default::default()
}],
..Default::default()
};
let config = magnus_config_with_feature(None);
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let decl_start = lib_rs
.find("pub enum PageAction {")
.expect("generated crate must declare the PageAction data enum");
let decl_end = lib_rs[decl_start..]
.find("\n}")
.map(|i| decl_start + i + 2)
.expect("enum declaration must close");
let decl = &lib_rs[decl_start..decl_end];
assert!(
decl.contains("Scrape,") && decl.contains("Click { selector: String },"),
"the two always-present variants must still be declared, got:\n{decl}"
);
assert!(
!decl.contains("Testkit"),
"the excluded data variant must not appear anywhere in the declaration, got:\n{decl}"
);
}
#[test]
fn generate_bindings_default_variant_selection_skips_a_variant_dropped_from_the_declaration() {
let api = ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![EnumDef {
name: "RoutingStrategy".to_string(),
rust_path: "dep_crate::RoutingStrategy".to_string(),
variants: vec![
EnumVariant {
name: "Extra".to_string(),
is_default: true,
cfg: Some(r#"feature = "extra-tier""#.to_string()),
..Default::default()
},
EnumVariant {
name: "Primary".to_string(),
..Default::default()
},
],
..Default::default()
}],
functions: vec![returning_function("get_strategy", "RoutingStrategy")],
..Default::default()
};
let config = magnus_config_with_feature(None);
let files = MagnusBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let decl = wrapper_enum_declaration(lib_rs);
assert!(
!decl.contains("Extra"),
"the dropped default variant must not appear in the declared set, got:\n{decl}"
);
assert!(
lib_rs.contains("fn default() -> Self { Self::Primary }"),
"impl Default must fall back to a variant the declaration actually keeps, got:\n{lib_rs}"
);
}