use std::collections::{BTreeSet, HashSet};
use crate::codegen::cfg::{enabled_features_for_language, is_host_owned_rust_path};
use crate::core::config::{Language, ResolvedCrateConfig};
use crate::core::ir::{ApiSurface, cfg_feature_satisfied};
fn universally_dropped_variant_references(
api: &ApiSurface,
config: &ResolvedCrateConfig,
languages: &[Language],
) -> BTreeSet<String> {
let host_crates = host_crate_spellings(api, config, languages);
if host_crates.is_empty() {
return BTreeSet::new();
}
api.enums
.iter()
.filter(|enum_def| {
host_crates
.iter()
.all(|host_crate| !is_host_owned_rust_path(host_crate, &enum_def.rust_path))
})
.flat_map(|enum_def| {
enum_def
.variants
.iter()
.filter(|variant| variant.cfg.is_some())
.flat_map(|variant| {
[
format!("{}::{}", enum_def.name, variant.name),
format!("{}.{}", enum_def.name, variant.name),
]
})
})
.collect()
}
fn strip_unreachable_variant_doc_lines(value: &mut serde_json::Value, references: &BTreeSet<String>) {
match value {
serde_json::Value::Array(values) => {
for value in values {
strip_unreachable_variant_doc_lines(value, references);
}
}
serde_json::Value::Object(fields) => {
for (name, value) in fields {
if name == "doc"
&& let serde_json::Value::String(doc) = value
{
*doc = doc
.split('\n')
.filter(|line| {
!references
.iter()
.any(|reference| contains_complete_variant_reference(line, reference))
})
.collect::<Vec<_>>()
.join("\n");
} else {
strip_unreachable_variant_doc_lines(value, references);
}
}
}
_ => {}
}
}
fn contains_complete_variant_reference(line: &str, reference: &str) -> bool {
line.match_indices(reference).any(|(start, _)| {
let end = start + reference.len();
let left_is_identifier = line[..start]
.chars()
.next_back()
.is_some_and(|character| character.is_alphanumeric() || character == '_');
let right_is_identifier = line[end..]
.chars()
.next()
.is_some_and(|character| character.is_alphanumeric() || character == '_');
!left_is_identifier && !right_is_identifier
})
}
pub fn project_docs_without_unreachable_foreign_variants(
api: &ApiSurface,
config: &ResolvedCrateConfig,
languages: &[Language],
) -> anyhow::Result<ApiSurface> {
let references = universally_dropped_variant_references(api, config, languages);
if references.is_empty() {
return Ok(api.clone());
}
let mut value = serde_json::to_value(api)?;
strip_unreachable_variant_doc_lines(&mut value, &references);
Ok(serde_json::from_value(value)?)
}
fn host_crate_spellings(api: &ApiSurface, config: &ResolvedCrateConfig, languages: &[Language]) -> BTreeSet<String> {
let mut spellings = BTreeSet::new();
for &language in languages {
match language {
Language::Dart | Language::Swift => {
spellings.insert(config.name.replace('-', "_"));
spellings.insert(api.crate_name.replace('-', "_"));
}
other => {
spellings.insert(config.core_import_for_language(other));
}
}
}
spellings
}
pub fn warn_foreign_cfg_gated_variants(api: &ApiSurface, config: &ResolvedCrateConfig, languages: &[Language]) {
let host_crates = host_crate_spellings(api, config, languages);
if host_crates.is_empty() {
return;
}
let enabled_features = languages
.iter()
.map(|&language| enabled_features_for_language(config, language))
.collect::<Vec<_>>();
let enabled_feature_refs = enabled_features
.iter()
.map(|features| features.iter().map(String::as_str).collect::<HashSet<_>>())
.collect::<Vec<_>>();
for enum_def in &api.enums {
if host_crates
.iter()
.any(|host_crate| is_host_owned_rust_path(host_crate, &enum_def.rust_path))
{
continue;
}
let owning_crate = enum_def.rust_path.split("::").next().unwrap_or_default();
for variant in &enum_def.variants {
let Some(cfg) = variant.cfg.as_deref() else {
continue;
};
if enabled_feature_refs
.iter()
.all(|features| !cfg_feature_satisfied(Some(cfg), features))
{
continue;
}
tracing::warn!(
enum_name = %enum_def.name,
enum_rust_path = %enum_def.rust_path,
variant_name = %variant.name,
cfg = cfg,
owning_crate = owning_crate,
"dropping a reachable or indeterminate foreign-crate enum variant from every \
generated binding: its #[cfg(...)] gate cannot be re-emitted in generated binding \
crates without an `unexpected cfg condition value` error. \
Either source-root the owning crate so alef controls its features, or exclude \
the enum. Run with RUST_LOG=alef=debug for the per-backend detail"
);
}
}
}
#[cfg(test)]
mod tests;