use super::imports_helpers::emit_rustdoc;
use super::input_dto::{gen_input_dto_for_type_at_path, should_have_input_dto};
use super::params::{
borrow_opaque_param, format_param_unused, wasm_call_args, wasm_mapped_param_type, wasm_newtype_param_bindings,
wasm_serde_recovery_call_args, wasm_type_path_map,
};
use super::returns::{
gen_wasm_unimplemented_body, type_has_default, wasm_mapped_return_type, wasm_wrap_return_fn,
wrap_jsvalue_mapped_return,
};
use crate::backends::wasm::type_map::WasmMapper;
use crate::codegen::generators::binding_helpers::apply_return_newtype_unwrap;
use crate::codegen::type_mapper::TypeMapper;
use crate::codegen::{generators, naming::to_node_name};
use crate::core::ir::{FunctionDef, TypeRef};
use ahash::AHashSet;
use std::collections::HashMap;
pub(in crate::backends::wasm::gen_bindings) fn uses_input_dtos(
func: &FunctionDef,
opaque_types: &AHashSet<String>,
) -> bool {
!func.is_async
&& !generators::can_auto_delegate_function_with_named_let_bindings(func, opaque_types)
&& func.error_type.is_some()
&& (func.sanitized || generators::has_named_params(&func.params, opaque_types))
}
#[allow(clippy::too_many_arguments)]
#[cfg(test)]
pub(in crate::backends::wasm::gen_bindings) fn gen_function_with_emitted_dtos(
func: &FunctionDef,
mapper: &WasmMapper,
core_import: &str,
opaque_types: &AHashSet<String>,
prefix: &str,
mutex_types: &AHashSet<String>,
api: &crate::core::ir::ApiSurface,
emitted_input_dtos: &AHashSet<String>,
) -> String {
gen_function_with_emitted_dtos_and_remaps(
func,
mapper,
core_import,
opaque_types,
prefix,
mutex_types,
api,
emitted_input_dtos,
&[],
)
}
#[allow(clippy::too_many_arguments)]
pub(in crate::backends::wasm::gen_bindings) fn gen_function_with_emitted_dtos_and_remaps(
func: &FunctionDef,
mapper: &WasmMapper,
core_import: &str,
opaque_types: &AHashSet<String>,
prefix: &str,
mutex_types: &AHashSet<String>,
api: &crate::core::ir::ApiSurface,
emitted_input_dtos: &AHashSet<String>,
source_crate_remaps: &[(&str, &str)],
) -> String {
let type_paths = wasm_type_path_map(api, core_import, source_crate_remaps);
let mut input_dtos = String::new();
let mut input_dto_names: HashMap<String, String> = HashMap::new();
if uses_input_dtos(func, opaque_types) {
for p in &func.params {
if let TypeRef::Named(name) = &p.ty
&& !opaque_types.contains(name.as_str())
&& let Some(type_def) = api.types.iter().find(|t| t.name == *name)
&& should_have_input_dto(type_def)
{
if emitted_input_dtos.contains(name) {
input_dto_names.insert(name.clone(), format!("{}Input", name));
continue;
}
let core_path = type_paths
.get(name)
.cloned()
.unwrap_or_else(|| format!("{core_import}::{name}"));
let (dto_code, dto_name) =
gen_input_dto_for_type_at_path(name, &core_path, core_import, &type_paths, type_def);
if !dto_code.is_empty() {
input_dtos.push_str(&dto_code);
input_dtos.push_str("\n\n");
input_dto_names.insert(name.clone(), dto_name);
}
}
}
}
let can_delegate = generators::can_auto_delegate_function_with_named_let_bindings(func, opaque_types);
let params: Vec<String> = func
.params
.iter()
.map(|p| {
let ty = wasm_mapped_param_type(p, mapper);
let mapped_ty = if p.optional {
format!("Option<{}>", ty)
} else {
borrow_opaque_param(p, &ty, opaque_types)
};
format_param_unused(&p.name, &mapped_ty, !can_delegate && !func.is_async)
})
.collect();
let return_type = wasm_mapped_return_type(&func.return_type, &func.return_newtype_wrapper, mapper);
let return_annotation = mapper.wrap_return(&return_type, func.error_type.is_some());
let js_name = to_node_name(&func.name);
let js_name_attr = if js_name != func.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let mut attrs = emit_rustdoc(&func.doc);
if func.params.len() > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if func.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
let core_fn_path = {
let path = func.rust_path.replace('-', "_");
if path.starts_with(core_import) {
path
} else {
format!("{core_import}::{}", func.name)
}
};
if func.is_async {
super::async_wrappers::gen_async_free_function(
&input_dtos,
func,
mapper,
core_import,
opaque_types,
mutex_types,
api,
¶ms,
&attrs,
&js_name_attr,
&return_annotation,
&core_fn_path,
source_crate_remaps,
&type_paths,
)
} else if can_delegate {
let mut let_bindings = if crate::codegen::generators::has_named_params(&func.params, opaque_types) {
crate::codegen::generators::gen_named_let_bindings_no_promote(&func.params, opaque_types, core_import)
} else {
String::new()
};
let_bindings.push_str(&wasm_newtype_param_bindings(
&func.params,
mapper,
core_import,
source_crate_remaps,
&type_paths,
));
let has_named = crate::codegen::generators::has_named_params(&func.params, opaque_types);
let call_args = wasm_call_args(&func.params, opaque_types, has_named);
let core_call = format!("{core_fn_path}({call_args})");
let body = if func.error_type.is_some() {
let result = apply_return_newtype_unwrap("result", &func.return_newtype_wrapper);
let wrap = wrap_jsvalue_mapped_return(&result, &return_type).unwrap_or_else(|| {
wasm_wrap_return_fn(
&result,
&func.return_type,
opaque_types,
func.returns_ref,
func.returns_cow,
prefix,
mutex_types,
)
});
crate::backends::wasm::template_env::render(
"gen_result_body",
crate::alef_context! {
let_bindings => &let_bindings,
core_call => &core_call,
return_expr => &wrap,
is_async => false,
map_wasm_error => false,
map_js_error => true,
ok_return => true,
},
)
} else {
let result = apply_return_newtype_unwrap(&core_call, &func.return_newtype_wrapper);
let return_expr = wrap_jsvalue_mapped_return(&result, &return_type).unwrap_or_else(|| {
wasm_wrap_return_fn(
&result,
&func.return_type,
opaque_types,
func.returns_ref,
func.returns_cow,
prefix,
mutex_types,
)
});
crate::backends::wasm::template_env::render(
"gen_direct_body",
crate::alef_context! {
let_bindings => &let_bindings,
return_expr => &return_expr,
},
)
};
let fn_code = crate::backends::wasm::template_env::render(
"gen_free_function",
crate::alef_context! {
attrs => &attrs,
js_name_attr => &js_name_attr,
is_async => false,
function_name => &func.name,
params => params.join(", "),
return_annotation => &return_annotation,
body => body.trim_end(),
},
);
format!("{input_dtos}{fn_code}")
} else if func.error_type.is_some()
&& (func.sanitized || crate::codegen::generators::has_named_params(&func.params, opaque_types))
{
let serde_params: Vec<String> = func
.params
.iter()
.map(|p| match &p.ty {
TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
let mapped_ty = if p.optional {
"Option<JsValue>".to_string()
} else {
"JsValue".to_string()
};
format!("{}: {}", p.name, mapped_ty)
}
TypeRef::Vec(inner) => {
if matches!(inner.as_ref(), TypeRef::Named(_)) {
if p.optional {
format!("{}: Option<Vec<String>>", p.name)
} else {
format!("{}: Vec<String>", p.name)
}
} else {
let ty = wasm_mapped_param_type(p, mapper);
let mapped_ty = if p.optional { format!("Option<{}>", ty) } else { ty };
format!("{}: {}", p.name, mapped_ty)
}
}
_ => {
let ty = wasm_mapped_param_type(p, mapper);
let mapped_ty = if p.optional {
format!("Option<{}>", ty)
} else {
borrow_opaque_param(p, &ty, opaque_types)
};
format!("{}: {}", p.name, mapped_ty)
}
})
.collect();
let mut serde_bindings = String::new();
for p in &func.params {
match &p.ty {
TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
let core_path = type_paths
.get(name)
.cloned()
.unwrap_or_else(|| format!("{}::{}", core_import, name));
let err_conv = ".map_err(|e| JsValue::from_str(&e.to_string()))";
if api
.types
.iter()
.find(|t| t.name == *name)
.is_some_and(should_have_input_dto)
{
let input_dto_type = input_dto_names
.get(name)
.cloned()
.unwrap_or_else(|| format!("{}Input", name));
if p.optional {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_config_optional",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
input_dto_type => &input_dto_type,
},
));
serde_bindings.push_str(" ");
} else {
let has_default = type_has_default(name, api);
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_config_required",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
input_dto_type => &input_dto_type,
has_default => has_default,
},
));
serde_bindings.push_str(" ");
}
} else {
if p.optional {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_named_optional",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
},
));
serde_bindings.push_str(" ");
} else {
let has_default = type_has_default(name, api);
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_named_required",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
has_default => has_default,
is_mut => p.is_mut,
},
));
serde_bindings.push_str(" ");
}
}
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
let inner_name = match inner.as_ref() {
TypeRef::Named(n) => n,
_ => "UnknownTuple",
};
let core_path = type_paths
.get(inner_name)
.cloned()
.unwrap_or_else(|| format!("{}::{}", core_import, inner_name));
let err_conv = ".map_err(|e| JsValue::from_str(&e.to_string()))";
if p.optional {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_named_optional",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
},
));
serde_bindings.push_str(" ");
} else {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_named_required",
crate::alef_context! {
param_name => &p.name,
core_path => &core_path,
err_conv => &err_conv,
},
));
serde_bindings.push_str(" ");
}
}
TypeRef::Vec(inner)
if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char)
&& p.sanitized
&& p.original_type.is_some() =>
{
let err_conv = ".map_err(|e| JsValue::from_str(&e.to_string()))";
if p.optional {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_tuple_optional",
crate::alef_context! {
param_name => &p.name,
err_conv => &err_conv,
},
));
serde_bindings.push_str(" ");
} else {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_tuple_required",
crate::alef_context! {
param_name => &p.name,
err_conv => &err_conv,
},
));
serde_bindings.push_str(" ");
}
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref => {
if p.optional {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_string_refs_optional",
crate::alef_context! {
param_name => &p.name,
},
));
serde_bindings.push_str(" ");
} else {
serde_bindings.push_str(&crate::backends::wasm::template_env::render(
"serde_vec_string_refs_required",
crate::alef_context! {
param_name => &p.name,
},
));
serde_bindings.push_str(" ");
}
}
_ => {}
}
}
let generally_recovered_params: Vec<_> = func
.params
.iter()
.filter(|param| match ¶m.ty {
TypeRef::Named(_) => false,
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => false,
_ => true,
})
.cloned()
.collect();
serde_bindings.push_str(&wasm_newtype_param_bindings(
&generally_recovered_params,
mapper,
core_import,
source_crate_remaps,
&type_paths,
));
let call_args = wasm_serde_recovery_call_args(&func.params, opaque_types);
let core_call = format!("{core_fn_path}({call_args})");
let result = apply_return_newtype_unwrap("result", &func.return_newtype_wrapper);
let wrap = wrap_jsvalue_mapped_return(&result, &return_type).unwrap_or_else(|| {
wasm_wrap_return_fn(
&result,
&func.return_type,
opaque_types,
func.returns_ref,
func.returns_cow,
prefix,
mutex_types,
)
});
let body = if matches!(func.return_type, TypeRef::Unit) {
crate::backends::wasm::template_env::render(
"gen_unit_result_body",
crate::alef_context! {
let_bindings => &serde_bindings,
core_call => &core_call,
},
)
} else {
crate::backends::wasm::template_env::render(
"gen_result_body",
crate::alef_context! {
let_bindings => &serde_bindings,
core_call => &core_call,
return_expr => &wrap,
is_async => false,
map_wasm_error => false,
map_js_error => true,
ok_return => true,
},
)
};
let fn_code = crate::backends::wasm::template_env::render(
"gen_free_function",
crate::alef_context! {
attrs => &attrs,
js_name_attr => &js_name_attr,
is_async => false,
function_name => &func.name,
params => serde_params.join(", "),
return_annotation => &return_annotation,
body => body.trim_end(),
},
);
format!("{input_dtos}{fn_code}")
} else {
let body = gen_wasm_unimplemented_body(&func.return_type, &func.name, func.error_type.is_some());
let fn_code = crate::backends::wasm::template_env::render(
"gen_free_function",
crate::alef_context! {
attrs => &attrs,
js_name_attr => &js_name_attr,
is_async => false,
function_name => &func.name,
params => params.join(", "),
return_annotation => &return_annotation,
body => &body,
},
);
format!("{input_dtos}{fn_code}")
}
}