use super::RustlerBackend;
use crate::core::backend::Backend;
use crate::core::config::{NewAlefConfig, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, EnumDef, EnumVariant, FieldDef, FunctionDef, ParamDef, TypeRef};
fn rustler_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 = [\"elixir\"]\n[[crates]]\nname = \"test-lib\"\nsources = [\"src/lib.rs\"]\n\
[crates.elixir]\napp_name = \"test_lib\"\n{features_line}"
);
let cfg: NewAlefConfig = toml::from_str(&toml_src).unwrap();
cfg.resolve().unwrap().remove(0)
}
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<'a>(lib_rs: &'a str, marker: &str) -> &'a str {
let start = lib_rs
.find(marker)
.unwrap_or_else(|| panic!("generated crate must contain conversion impl starting with:\n{marker}"));
let end = lib_rs[start..]
.find("\n}")
.map(|i| start + i + 2)
.expect("conversion impl must close");
&lib_rs[start..end]
}
fn foreign_cfg_tagged_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_tagged_enum_api_with_param_function() -> ApiSurface {
let mut api = foreign_cfg_tagged_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 foreign_cfg_flat_data_enum_api() -> ApiSurface {
ApiSurface {
crate_name: "test-lib".to_string(),
version: "0.1.0".to_string(),
enums: vec![EnumDef {
name: "PayloadKind".to_string(),
rust_path: "dep_crate::PayloadKind".to_string(),
variants: vec![
EnumVariant {
name: "Primary".to_string(),
fields: vec![FieldDef {
name: "_0".to_string(),
ty: TypeRef::String,
..Default::default()
}],
is_tuple: true,
..Default::default()
},
EnumVariant {
name: "Extra".to_string(),
cfg: Some(r#"feature = "extra-tier""#.to_string()),
..Default::default()
},
],
..Default::default()
}],
..Default::default()
}
}
#[test]
fn generate_bindings_omits_unreachable_catch_all_for_tagged_enum_foreign_variant_proven_unreachable_end_to_end() {
let api = foreign_cfg_tagged_enum_api();
let config = rustler_config_with_feature(None);
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<dep_crate::RoutingStrategy> for RoutingStrategy {");
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_tagged_enum_foreign_variant_not_proven_unreachable_end_to_end() {
let api = foreign_cfg_tagged_enum_api();
let config = rustler_config_with_feature(Some("extra-tier"));
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<dep_crate::RoutingStrategy> for RoutingStrategy {");
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_tagged_enum_foreign_variant_proven_unreachable_end_to_end() {
let api = foreign_cfg_tagged_enum_api_with_param_function();
let config = rustler_config_with_feature(None);
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<RoutingStrategy> for dep_crate::RoutingStrategy {");
assert!(
!conversion.contains("_ => Default::default(),"),
"Rustler's own declaration drops a foreign cfg-gated variant proven unreachable by this \
binding's own configured feature set, so the binding->core match it declares is already \
exhaustive without a catch-all -- keeping one is unreachable_patterns under \
-D warnings, got:\n{conversion}"
);
}
#[test]
fn generate_bindings_keeps_binding_to_core_catch_all_for_tagged_enum_foreign_variant_not_proven_unreachable_end_to_end()
{
let api = foreign_cfg_tagged_enum_api_with_param_function();
let config = rustler_config_with_feature(Some("extra-tier"));
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<RoutingStrategy> for dep_crate::RoutingStrategy {");
assert!(
conversion.contains("_ => Default::default(),"),
"a foreign cfg-gated variant that is NOT proven unreachable is still declared \
unconditionally while its conversion arm is still dropped, so the catch-all must stay \
to cover it -- omitting it is error[E0004]: non-exhaustive patterns, got:\n{conversion}"
);
}
#[test]
fn generate_bindings_omits_unreachable_catch_all_for_flat_data_enum_foreign_variant_proven_unreachable_end_to_end() {
let api = foreign_cfg_flat_data_enum_api();
let config = rustler_config_with_feature(None);
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<dep_crate::PayloadKind> for PayloadKind {");
assert!(
!conversion.contains("_ => Self::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_flat_data_enum_foreign_variant_not_proven_unreachable_end_to_end() {
let api = foreign_cfg_flat_data_enum_api();
let config = rustler_config_with_feature(Some("extra-tier"));
let files = RustlerBackend.generate_bindings(&api, &config).unwrap();
let lib_rs = lib_rs_content(&files);
let conversion = core_to_binding_conversion(lib_rs, "impl From<dep_crate::PayloadKind> for PayloadKind {");
assert!(
conversion.contains("_ => Self::default(),"),
"a foreign cfg-gated variant that is NOT proven unreachable must keep the catch-all so the \
match stays exhaustive, got:\n{conversion}"
);
}