use crate::codegen::cfg::enabled_features_for_language;
use crate::codegen::shared;
use crate::core::config::{Language, ResolvedCrateConfig, resolve_output_layout};
use crate::core::ir::{ApiSurface, ReceiverKind};
use ahash::AHashSet;
use regex::Regex;
use super::cfg::is_gated_behind_disabled_feature;
use super::trait_bridge_docs;
pub(super) fn wasm_output_layout(config: &ResolvedCrateConfig) -> crate::core::config::OutputLayout {
resolve_output_layout(config.output_paths.get("wasm"), &config.name, "crates/{name}-wasm/src/")
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum WasmCallability {
Callable,
NotExported,
UnknownSymbol,
}
pub(crate) fn wasm_callability(
function_name: &str,
functions: &[crate::core::ir::FunctionDef],
config: &ResolvedCrateConfig,
) -> WasmCallability {
if function_is_callable(function_name, functions, config) {
return WasmCallability::Callable;
}
match rust_identity_for_wasm_symbol(function_name, functions, config) {
Some(_) => WasmCallability::NotExported,
None => WasmCallability::UnknownSymbol,
}
}
pub(super) fn function_is_callable(
function_name: &str,
functions: &[crate::core::ir::FunctionDef],
config: &ResolvedCrateConfig,
) -> bool {
let Some(identity) = rust_identity_for_wasm_symbol(function_name, functions, config) else {
return false;
};
if config
.wasm
.as_ref()
.is_some_and(|wasm| wasm.exclude_functions.iter().any(|name| name == identity))
{
return false;
}
if crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(identity, &config.trait_bridges) {
return true;
}
function_is_exported(identity, functions, config)
}
pub(super) fn rust_identity_for_wasm_symbol<'a>(
symbol: &str,
functions: &'a [crate::core::ir::FunctionDef],
config: &'a ResolvedCrateConfig,
) -> Option<&'a str> {
wasm_export_candidates(functions, config)
.find(|candidate| *candidate == symbol)
.or_else(|| {
wasm_export_candidates(functions, config)
.find(|candidate| crate::codegen::generators::trait_bridge::to_camel_case(candidate) == symbol)
})
}
pub(super) fn wasm_export_candidates<'a>(
functions: &'a [crate::core::ir::FunctionDef],
config: &'a ResolvedCrateConfig,
) -> impl Iterator<Item = &'a str> {
let bridge_registry_fns = trait_bridge_docs::active_bridges(config)
.flat_map(|bridge| {
[
bridge.register_fn.as_deref(),
bridge.unregister_fn.as_deref(),
bridge.clear_fn.as_deref(),
]
})
.flatten();
functions
.iter()
.map(|function| function.name.as_str())
.chain(bridge_registry_fns)
}
pub(super) fn function_is_exported(
function_name: &str,
functions: &[crate::core::ir::FunctionDef],
config: &ResolvedCrateConfig,
) -> bool {
if config
.wasm
.as_ref()
.is_some_and(|wasm| wasm.exclude_functions.iter().any(|name| name == function_name))
{
return false;
}
if crate::codegen::generators::trait_bridge::is_trait_bridge_managed_fn(function_name, &config.trait_bridges) {
return false;
}
let enabled_features = &enabled_features_for_language(config, Language::Wasm);
let core_import = config.core_import_for_language(Language::Wasm);
let source_remaps = config
.wasm
.as_ref()
.map(|wasm| &wasm.source_crate_remaps)
.into_iter()
.flatten()
.map(|name| name.replace('-', "_"))
.collect::<AHashSet<_>>();
let dropped_crates = config
.wasm
.as_ref()
.map(|wasm| &wasm.exclude_extra_dependencies)
.into_iter()
.flatten()
.map(|name| name.replace('-', "_"))
.filter(|name| name != &core_import && !source_remaps.contains(name))
.collect::<AHashSet<_>>();
functions.iter().any(|function| {
function.name == function_name
&& !is_gated_behind_disabled_feature(&function.cfg, enabled_features)
&& !dropped_crates.contains(&function.rust_path.split("::").next().unwrap_or("").replace('-', "_"))
})
}
pub(super) fn prepend_cfg(cfg: Option<&str>, item: String) -> String {
match cfg {
Some(pred) if !pred.is_empty() => format!("#[cfg({pred})]\n{item}"),
_ => item,
}
}
pub(super) fn prepend_unknown_type_omission_marker(omissions: Option<&Vec<(String, String)>>, item: String) -> String {
let Some(omissions) = omissions else {
return item;
};
let mut marker = String::from(
"// ALEF-OMITTED: the field(s) below were dropped from this WASM binding\n\
// because their Rust type has no generated wasm-bindgen representation.\n",
);
for (field_name, type_name) in omissions {
marker.push_str(&format!(
"// - field `{field_name}`: type `{type_name}` is not part of the bound wasm API surface\n"
));
}
format!("{marker}{item}")
}
pub(super) fn types_needing_self_delegation_reverse_impl(
api: &ApiSurface,
opaque_types: &AHashSet<String>,
) -> AHashSet<String> {
let mut needed = AHashSet::default();
for typ in api.types.iter().filter(|t| !t.is_trait) {
let has_mut_methods = typ
.methods
.iter()
.any(|m| matches!(m.receiver.as_ref(), Some(ReceiverKind::RefMut)));
let is_opaque_type = opaque_types.contains(&typ.name);
for method in &typ.methods {
if method.is_static {
continue;
}
let is_ref_mut_receiver = matches!(method.receiver.as_ref(), Some(ReceiverKind::RefMut));
if is_opaque_type && has_mut_methods && !is_ref_mut_receiver {
continue;
}
let delegates_via_self_conversion = if method.is_async {
true
} else if is_ref_mut_receiver && has_mut_methods {
!method.sanitized
&& method
.params
.iter()
.all(|p| !p.sanitized && shared::is_delegatable_param(&p.ty, opaque_types))
&& shared::is_opaque_delegatable_type(&method.return_type)
} else {
shared::can_auto_delegate(method, opaque_types)
};
if delegates_via_self_conversion {
needed.insert(typ.name.clone());
break;
}
}
}
needed
}
pub(super) fn fix_dropped_payload_enum_option_fields(content: String) -> String {
let Ok(dropped_boxed_option_field) =
Regex::new(r"(?m)^(?P<indent>[ \t]*)(?P<field>\w+): Default::default\(\)\.map\(Box::new\),$")
else {
return content;
};
dropped_boxed_option_field
.replace_all(&content, |caps: ®ex::Captures<'_>| {
format!(
"{indent}// ALEF-OMITTED: `{field}` is always None on wasm -- its Rust type is a \
payload-carrying enum, which wasm_bindgen cannot represent.\n\
{indent}{field}: None,",
indent = &caps["indent"],
field = &caps["field"],
)
})
.into_owned()
}