use std::collections::HashSet;
use crate::codegen::cfg::is_host_owned_rust_path;
use crate::core::ir::{EnumDef, EnumVariant};
use super::ConversionConfig;
use super::helpers::{binding_to_core_match_arm_ext_cfg, core_enum_path_remapped, core_to_binding_match_arm_ext_cfg};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum VariantDeclaration {
Keep { cfg: Option<String> },
Drop,
}
#[must_use]
pub fn enum_variant_declaration(
variant: &EnumVariant,
is_host_enum: bool,
configured_features: Option<&HashSet<&str>>,
) -> VariantDeclaration {
let Some(cfg) = variant.cfg.as_deref() else {
return VariantDeclaration::Keep { cfg: None };
};
if is_host_enum {
return VariantDeclaration::Keep {
cfg: Some(cfg.to_string()),
};
}
match configured_features {
Some(features) if foreign_variant_proven_unreachable(cfg, features) => VariantDeclaration::Drop,
_ => VariantDeclaration::Keep { cfg: None },
}
}
#[must_use]
pub fn enum_variant_declaration_without_cfg_attribute(
variant: &EnumVariant,
is_host_enum: bool,
configured_features: &HashSet<&str>,
) -> VariantDeclaration {
if is_host_enum {
return match variant.cfg.as_deref() {
None => VariantDeclaration::Keep { cfg: None },
Some(cfg) if crate::core::ir::cfg_feature_satisfied(Some(cfg), configured_features) => {
VariantDeclaration::Keep { cfg: None }
}
Some(_) => VariantDeclaration::Drop,
};
}
enum_variant_declaration(variant, is_host_enum, None)
}
fn foreign_variant_proven_unreachable(cfg: &str, configured_features: &HashSet<&str>) -> bool {
!crate::core::ir::cfg_feature_satisfied(Some(cfg), configured_features)
}
fn has_unresolved_foreign_cfg_variants(
enum_def: &EnumDef,
is_host_enum: bool,
configured_features: Option<&HashSet<&str>>,
) -> bool {
if is_host_enum {
return false;
}
enum_def.variants.iter().any(|v| match v.cfg.as_deref() {
None => false,
Some(cfg) => match configured_features {
Some(features) => !foreign_variant_proven_unreachable(cfg, features),
None => true,
},
})
}
fn configured_features_set<'a>(config: &ConversionConfig<'a>) -> Option<HashSet<&'a str>> {
config
.configured_features
.map(|features| features.iter().map(String::as_str).collect())
}
fn emit_cfg_gated_arm(
enum_def: &EnumDef,
variant: &crate::core::ir::EnumVariant,
is_host_enum: bool,
arm: String,
direction: &str,
) -> Option<minijinja::value::Value> {
let Some(cfg) = variant.cfg.as_deref() else {
return Some(minijinja::context! { arm => arm, cfg => Option::<&str>::None });
};
if !is_host_enum {
tracing::warn!(
enum_name = %enum_def.name,
enum_rust_path = %enum_def.rust_path,
variant_name = %variant.name,
cfg = cfg,
direction = direction,
"dropping enum conversion match arm for a foreign-crate variant behind a #[cfg(...)] \
this binding crate cannot declare as a Cargo feature; the variant is unreachable \
from this conversion"
);
return None;
}
Some(minijinja::context! { arm => arm, cfg => cfg })
}
#[must_use]
pub fn enum_conversion_needs_catch_all(
has_cfg_variants: bool,
is_host_enum: bool,
has_excluded_variants: bool,
) -> bool {
has_excluded_variants || (has_cfg_variants && !is_host_enum)
}
#[must_use]
pub fn enum_conversion_needs_catch_all_for_features(
enum_def: &EnumDef,
is_host_enum: bool,
has_excluded_variants: bool,
configured_features: Option<&[String]>,
) -> bool {
let features_set: Option<HashSet<&str>> =
configured_features.map(|features| features.iter().map(String::as_str).collect());
let has_unresolved_cfg_variants =
has_unresolved_foreign_cfg_variants(enum_def, is_host_enum, features_set.as_ref());
enum_conversion_needs_catch_all(has_unresolved_cfg_variants, is_host_enum, has_excluded_variants)
}
pub fn gen_enum_from_binding_to_core(enum_def: &EnumDef, core_import: &str) -> String {
gen_enum_from_binding_to_core_cfg(enum_def, core_import, &ConversionConfig::default())
}
pub fn gen_enum_from_binding_to_core_cfg(enum_def: &EnumDef, core_import: &str, config: &ConversionConfig) -> String {
let core_path = core_enum_path_remapped(enum_def, core_import, config.source_crate_remaps);
let binding_name = format!("{}{}", config.type_name_prefix, enum_def.name);
let is_host_enum = is_host_owned_rust_path(core_import, &enum_def.rust_path);
let arms: Vec<minijinja::value::Value> = enum_def
.variants
.iter()
.filter_map(|variant| {
let arm = binding_to_core_match_arm_ext_cfg(
&binding_name,
&variant.name,
&variant.fields,
config.binding_enums_have_data,
config,
crate::codegen::conversions::helpers::variant_emits_tuple_form(enum_def, variant)
&& config.binding_tuple_form_for_variants,
);
emit_cfg_gated_arm(enum_def, variant, is_host_enum, arm, "binding_to_core")
})
.collect();
let configured_features = configured_features_set(config);
let has_unresolved_cfg_variants =
has_unresolved_foreign_cfg_variants(enum_def, is_host_enum, configured_features.as_ref());
let needs_catch_all = enum_conversion_needs_catch_all(has_unresolved_cfg_variants, is_host_enum, false);
crate::codegen::template_env::render(
"conversions/enum_from_binding_to_core",
minijinja::context! {
binding_name => binding_name,
core_path => core_path,
arms => arms,
has_excluded_variants => needs_catch_all,
},
)
}
pub fn gen_enum_from_core_to_binding(enum_def: &EnumDef, core_import: &str) -> String {
gen_enum_from_core_to_binding_cfg(enum_def, core_import, &ConversionConfig::default())
}
pub fn gen_enum_from_core_to_binding_cfg(enum_def: &EnumDef, core_import: &str, config: &ConversionConfig) -> String {
let core_path = core_enum_path_remapped(enum_def, core_import, config.source_crate_remaps);
let binding_name = format!("{}{}", config.type_name_prefix, enum_def.name);
let is_host_enum = is_host_owned_rust_path(core_import, &enum_def.rust_path);
let arms: Vec<minijinja::value::Value> = enum_def
.variants
.iter()
.filter_map(|variant| {
let arm = core_to_binding_match_arm_ext_cfg(
&core_path,
&variant.name,
&variant.fields,
config.binding_enums_have_data,
config,
crate::codegen::conversions::helpers::variant_emits_tuple_form(enum_def, variant)
&& config.binding_tuple_form_for_variants,
);
emit_cfg_gated_arm(enum_def, variant, is_host_enum, arm, "core_to_binding")
})
.collect();
let configured_features = configured_features_set(config);
let has_unresolved_cfg_variants =
has_unresolved_foreign_cfg_variants(enum_def, is_host_enum, configured_features.as_ref());
let needs_catch_all = enum_conversion_needs_catch_all(
has_unresolved_cfg_variants,
is_host_enum,
!enum_def.excluded_variants.is_empty(),
);
crate::codegen::template_env::render(
"conversions/enum_from_core_to_binding",
minijinja::context! {
binding_name => binding_name,
core_path => core_path,
arms => arms,
has_excluded_variants => needs_catch_all,
},
)
}