use crate::type_map::WasmMapper;
use ahash::AHashSet;
use alef_codegen::builder::{ImplBuilder, RustFileBuilder};
use alef_codegen::generators::{self};
use alef_codegen::naming::to_node_name;
use alef_codegen::shared::{self, constructor_parts};
use alef_codegen::type_mapper::TypeMapper;
use alef_core::backend::{Backend, BuildConfig, Capabilities, GeneratedFile};
use alef_core::config::{AlefConfig, Language, resolve_output_dir};
use alef_core::ir::{ApiSurface, EnumDef, FieldDef, FunctionDef, MethodDef, TypeDef, TypeRef};
use std::fmt::Write;
use std::path::PathBuf;
fn is_copy_type(ty: &TypeRef, enum_names: &AHashSet<String>) -> bool {
match ty {
TypeRef::Primitive(_) => true, TypeRef::Duration => true, TypeRef::String | TypeRef::Char | TypeRef::Bytes | TypeRef::Path | TypeRef::Json => false,
TypeRef::Optional(inner) => is_copy_type(inner, enum_names), TypeRef::Vec(_) | TypeRef::Map(_, _) => false,
TypeRef::Named(n) => enum_names.contains(n), TypeRef::Unit => true,
}
}
pub struct WasmBackend;
impl Backend for WasmBackend {
fn name(&self) -> &str {
"wasm"
}
fn language(&self) -> Language {
Language::Wasm
}
fn capabilities(&self) -> Capabilities {
Capabilities {
supports_async: true,
supports_classes: true,
supports_enums: true,
supports_option: true,
supports_result: true,
..Capabilities::default()
}
}
fn generate_bindings(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let wasm_config = config.wasm.as_ref();
let exclude_functions = wasm_config.map(|c| c.exclude_functions.clone()).unwrap_or_default();
let exclude_types = wasm_config.map(|c| c.exclude_types.clone()).unwrap_or_default();
let type_overrides = wasm_config.map(|c| c.type_overrides.clone()).unwrap_or_default();
let mapper = WasmMapper::new(type_overrides);
let core_import = config.core_import();
let mut builder = RustFileBuilder::new().with_generated_header();
builder.add_import("wasm_bindgen::prelude::*");
for trait_path in generators::collect_trait_imports(api) {
builder.add_import(&trait_path);
}
let has_maps = api
.types
.iter()
.any(|t| t.fields.iter().any(|f| matches!(&f.ty, TypeRef::Map(_, _))))
|| api
.functions
.iter()
.any(|f| matches!(&f.return_type, TypeRef::Map(_, _)));
if has_maps {
builder.add_import("std::collections::HashMap");
}
let custom_mods = config.custom_modules.for_language(Language::Wasm);
for module in custom_mods {
builder.add_item(&format!("pub mod {module};"));
}
let opaque_types: AHashSet<String> = api
.types
.iter()
.filter(|t| t.is_opaque && !exclude_types.contains(&t.name))
.map(|t| t.name.clone())
.collect();
if !opaque_types.is_empty() {
builder.add_import("std::sync::Arc");
}
for typ in &api.types {
if exclude_types.contains(&typ.name) {
continue;
}
if typ.is_opaque {
builder.add_item(&gen_opaque_struct(typ, &core_import));
builder.add_item(&gen_opaque_struct_methods(typ, &mapper, &opaque_types, &core_import));
} else {
builder.add_item(&gen_struct(typ, &mapper));
builder.add_item(&gen_struct_methods(
typ,
&mapper,
&exclude_types,
&core_import,
&opaque_types,
&api.enums,
));
}
}
for enum_def in &api.enums {
if !exclude_types.contains(&enum_def.name) {
builder.add_item(&gen_enum(enum_def));
}
}
for func in &api.functions {
if !exclude_functions.contains(&func.name) {
builder.add_item(&gen_function(func, &mapper, &core_import, &opaque_types));
}
}
let wasm_conv_config = alef_codegen::conversions::ConversionConfig {
type_name_prefix: "Js",
map_uses_jsvalue: true,
option_duration_on_defaults: true,
..Default::default()
};
let convertible = alef_codegen::conversions::convertible_types(api);
let core_to_binding_convertible = alef_codegen::conversions::core_to_binding_convertible_types(api);
for typ in &api.types {
if exclude_types.contains(&typ.name) {
continue;
}
let is_strict = alef_codegen::conversions::can_generate_conversion(typ, &convertible);
let is_relaxed = alef_codegen::conversions::can_generate_conversion(typ, &core_to_binding_convertible);
if is_strict {
builder.add_item(&alef_codegen::conversions::gen_from_binding_to_core_cfg(
typ,
&core_import,
&wasm_conv_config,
));
builder.add_item(&alef_codegen::conversions::gen_from_core_to_binding_cfg(
typ,
&core_import,
&opaque_types,
&wasm_conv_config,
));
} else if is_relaxed {
builder.add_item(&alef_codegen::conversions::gen_from_core_to_binding_cfg(
typ,
&core_import,
&opaque_types,
&wasm_conv_config,
));
}
}
for e in &api.enums {
if !exclude_types.contains(&e.name) {
if alef_codegen::conversions::can_generate_enum_conversion(e) {
builder.add_item(&alef_codegen::conversions::gen_enum_from_binding_to_core_cfg(
e,
&core_import,
&wasm_conv_config,
));
}
if alef_codegen::conversions::can_generate_enum_conversion_from_core(e) {
builder.add_item(&alef_codegen::conversions::gen_enum_from_core_to_binding_cfg(
e,
&core_import,
&wasm_conv_config,
));
}
}
}
for error in &api.errors {
builder.add_item(&alef_codegen::error_gen::gen_wasm_error_converter(error, &core_import));
}
let _adapter_bodies = alef_adapters::build_adapter_bodies(config, Language::Wasm)?;
let content = builder.build();
let output_dir = resolve_output_dir(
config.output.wasm.as_ref(),
&config.crate_config.name,
"crates/{name}-wasm/src/",
);
Ok(vec![GeneratedFile {
path: PathBuf::from(&output_dir).join("lib.rs"),
content,
generated_header: false,
}])
}
fn generate_public_api(&self, api: &ApiSurface, config: &AlefConfig) -> anyhow::Result<Vec<GeneratedFile>> {
let wasm_config = config.wasm.as_ref();
let exclude_functions = wasm_config.map(|c| c.exclude_functions.clone()).unwrap_or_default();
let exclude_types = wasm_config.map(|c| c.exclude_types.clone()).unwrap_or_default();
let mut exports = vec![];
for typ in &api.types {
if !exclude_types.contains(&typ.name) {
exports.push(format!("Js{}", typ.name));
}
}
for enum_def in &api.enums {
if !exclude_types.contains(&enum_def.name) {
exports.push(format!("Js{}", enum_def.name));
}
}
for func in &api.functions {
if !exclude_functions.contains(&func.name) {
let js_name = to_node_name(&func.name);
exports.push(js_name);
}
}
for error in &api.errors {
exports.push(error.name.clone());
}
exports.sort();
let mut lines = vec![
"// This file is auto-generated by alef. DO NOT EDIT.".to_string(),
"".to_string(),
];
if !exports.is_empty() {
lines.push("export {".to_string());
for (i, name) in exports.iter().enumerate() {
let comma = if i < exports.len() - 1 { "," } else { "" };
lines.push(format!(" {}{}", name, comma));
}
lines.push("} from './wasm';".to_string());
}
let content = lines.join("\n");
let output_path = PathBuf::from("packages/wasm/src/index.ts");
Ok(vec![GeneratedFile {
path: output_path,
content,
generated_header: false,
}])
}
fn build_config(&self) -> Option<BuildConfig> {
Some(BuildConfig {
tool: "wasm-pack",
crate_suffix: "-wasm",
depends_on_ffi: false,
post_build: vec![],
})
}
}
fn gen_opaque_struct(typ: &TypeDef, core_import: &str) -> String {
let js_name = format!("Js{}", typ.name);
let mut out = String::with_capacity(256);
writeln!(out, "#[derive(Clone)]").ok();
writeln!(out, "#[wasm_bindgen]").ok();
writeln!(out, "pub struct {} {{", js_name).ok();
let core_path = alef_codegen::conversions::core_type_path(typ, core_import);
writeln!(out, " inner: Arc<{}>,", core_path).ok();
write!(out, "}}").ok();
out
}
fn gen_opaque_struct_methods(
typ: &TypeDef,
mapper: &WasmMapper,
opaque_types: &AHashSet<String>,
core_import: &str,
) -> String {
let js_name = format!("Js{}", typ.name);
let mut impl_builder = ImplBuilder::new(&js_name);
impl_builder.add_attr("wasm_bindgen");
for method in &typ.methods {
if method.is_static {
impl_builder.add_method(&gen_opaque_static_method(
method,
mapper,
&typ.name,
opaque_types,
core_import,
));
} else {
impl_builder.add_method(&gen_opaque_method(method, mapper, &typ.name, opaque_types));
}
}
impl_builder.build()
}
fn gen_opaque_method(
method: &MethodDef,
mapper: &WasmMapper,
type_name: &str,
opaque_types: &AHashSet<String>,
) -> String {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let ty = mapper.map_type(&p.ty);
if p.optional {
format!("{}: Option<{}>", p.name, ty)
} else {
format!("{}: {}", p.name, ty)
}
})
.collect();
let return_type = mapper.map_type(&method.return_type);
let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
let js_name = to_node_name(&method.name);
let js_name_attr = if js_name != method.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let can_delegate = shared::can_auto_delegate(method, opaque_types);
let async_kw = if method.is_async { "async " } else { "" };
let needs_clone = matches!(method.receiver, Some(alef_core::ir::ReceiverKind::Owned));
let body = if can_delegate {
let call_args = generators::gen_call_args(&method.params, opaque_types);
let core_call = if needs_clone {
format!("(*self.inner).clone().{}({})", method.name, call_args)
} else {
format!("self.inner.{}({})", method.name, call_args)
};
if method.is_async {
let result_wrap = wasm_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
if method.error_type.is_some() {
format!(
"let result = {core_call}.await\n \
.map_err(|e| JsValue::from_str(&e.to_string()))?;\n \
Ok({result_wrap})"
)
} else {
format!("let result = {core_call}.await;\n Ok({result_wrap})")
}
} else if method.error_type.is_some() {
if matches!(method.return_type, TypeRef::Unit) {
format!("{core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok(())")
} else {
let wrap = wasm_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
format!("let result = {core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok({wrap})")
}
} else {
wasm_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
)
}
} else {
gen_wasm_unimplemented_body(&method.return_type, &method.name, method.error_type.is_some())
};
let mut attrs = String::new();
if method.params.len() + 1 > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if method.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
if generators::is_trait_method_name(&method.name) {
attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
}
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub {async_kw}fn {}(&self, {}) -> {} {{\n \
{body}\n}}",
method.name,
params.join(", "),
return_annotation
)
}
fn gen_opaque_static_method(
method: &MethodDef,
mapper: &WasmMapper,
type_name: &str,
opaque_types: &AHashSet<String>,
core_import: &str,
) -> String {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let ty = mapper.map_type(&p.ty);
if p.optional {
format!("{}: Option<{}>", p.name, ty)
} else {
format!("{}: {}", p.name, ty)
}
})
.collect();
let return_type = mapper.map_type(&method.return_type);
let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
let js_name = to_node_name(&method.name);
let js_name_attr = if js_name != method.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let can_delegate = shared::can_auto_delegate(method, opaque_types);
let body = if can_delegate {
let call_args = generators::gen_call_args(&method.params, opaque_types);
let core_call = format!("{core_import}::{type_name}::{}({call_args})", method.name);
if method.error_type.is_some() {
let wrap = wasm_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
);
format!("let result = {core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok({wrap})")
} else {
wasm_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
true,
method.returns_ref,
)
}
} else {
gen_wasm_unimplemented_body(&method.return_type, &method.name, method.error_type.is_some())
};
let mut attrs = String::new();
if method.params.len() > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if method.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
if generators::is_trait_method_name(&method.name) {
attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
}
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub fn {}({}) -> {} {{\n \
{body}\n}}",
method.name,
params.join(", "),
return_annotation
)
}
fn gen_struct(typ: &TypeDef, mapper: &WasmMapper) -> String {
let js_name = format!("Js{}", typ.name);
let mut out = String::with_capacity(512);
if typ.has_default {
writeln!(out, "#[derive(Clone, Default)]").ok();
} else {
writeln!(out, "#[derive(Clone)]").ok();
}
writeln!(out, "#[wasm_bindgen]").ok();
writeln!(out, "pub struct {} {{", js_name).ok();
for field in &typ.fields {
if field.cfg.is_some() {
continue;
}
let field_type = if field.optional {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
writeln!(out, " {}: {},", field.name, field_type).ok();
}
writeln!(out, "}}").ok();
out
}
fn gen_struct_methods(
typ: &TypeDef,
mapper: &WasmMapper,
exclude_types: &[String],
core_import: &str,
opaque_types: &AHashSet<String>,
api_enums: &[EnumDef],
) -> String {
let js_name = format!("Js{}", typ.name);
let mut impl_builder = ImplBuilder::new(&js_name);
impl_builder.add_attr("wasm_bindgen");
if !typ.fields.is_empty() {
impl_builder.add_method(&gen_new_method(typ, mapper));
}
let enum_names: AHashSet<String> = api_enums.iter().map(|e| e.name.clone()).collect();
for field in &typ.fields {
impl_builder.add_method(&gen_getter(field, mapper, &enum_names));
impl_builder.add_method(&gen_setter(field, mapper));
}
if !exclude_types.contains(&typ.name) {
for method in &typ.methods {
impl_builder.add_method(&gen_method(method, mapper, &typ.name, core_import, opaque_types));
}
}
impl_builder.build()
}
fn gen_new_method(typ: &TypeDef, mapper: &WasmMapper) -> String {
let map_fn = |ty: &alef_core::ir::TypeRef| mapper.map_type(ty);
let (param_list, _, assignments) = if typ.has_default {
alef_codegen::shared::config_constructor_parts(&typ.fields, &map_fn)
} else {
constructor_parts(&typ.fields, &map_fn)
};
let field_count = typ.fields.iter().filter(|f| f.cfg.is_none()).count();
let allow_attr = if field_count > 7 {
"#[allow(clippy::too_many_arguments)]\n"
} else {
""
};
format!(
"{allow_attr}#[wasm_bindgen(constructor)]\npub fn new({param_list}) -> Js{} {{\n Js{} {{ {assignments} }}\n}}",
typ.name, typ.name
)
}
fn gen_getter(field: &FieldDef, mapper: &WasmMapper, enum_names: &AHashSet<String>) -> String {
let field_type = if field.optional {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
let js_name = to_node_name(&field.name);
let js_name_attr = if js_name != field.name {
format!(", js_name = \"{}\"", js_name)
} else {
String::new()
};
let effective_ty = if field.optional {
&field.ty
} else {
&field.ty
};
let return_expr = if is_copy_type(effective_ty, enum_names) {
format!("self.{}", field.name)
} else {
format!("self.{}.clone()", field.name)
};
format!(
"#[wasm_bindgen(getter{js_name_attr})]\npub fn {}(&self) -> {} {{\n {}\n}}",
field.name, field_type, return_expr
)
}
fn gen_setter(field: &FieldDef, mapper: &WasmMapper) -> String {
let field_type = if field.optional {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
let js_name = to_node_name(&field.name);
let js_name_attr = if js_name != field.name {
format!(", js_name = \"{}\"", js_name)
} else {
String::new()
};
format!(
"#[wasm_bindgen(setter{js_name_attr})]\npub fn set_{}(&mut self, value: {}) {{\n self.{} = value;\n}}",
field.name, field_type, field.name
)
}
fn gen_method(
method: &MethodDef,
mapper: &WasmMapper,
type_name: &str,
core_import: &str,
opaque_types: &AHashSet<String>,
) -> String {
let params: Vec<String> = method
.params
.iter()
.map(|p| {
let ty = mapper.map_type(&p.ty);
if p.optional {
format!("{}: Option<{}>", p.name, ty)
} else {
format!("{}: {}", p.name, ty)
}
})
.collect();
let return_type = mapper.map_type(&method.return_type);
let return_annotation = mapper.wrap_return(&return_type, method.error_type.is_some());
let js_name = to_node_name(&method.name);
let js_name_attr = if js_name != method.name {
format!("(js_name = \"{}\")", js_name)
} else {
String::new()
};
let can_delegate = shared::can_auto_delegate(method, opaque_types);
let mut attrs = String::new();
let effective_param_count = if method.is_static {
method.params.len()
} else {
method.params.len() + 1
};
if effective_param_count > 7 {
attrs.push_str("#[allow(clippy::too_many_arguments)]\n");
}
if method.error_type.is_some() {
attrs.push_str("#[allow(clippy::missing_errors_doc)]\n");
}
if generators::is_trait_method_name(&method.name) {
attrs.push_str("#[allow(clippy::should_implement_trait)]\n");
}
if method.is_async {
let call_args = generators::gen_call_args(&method.params, opaque_types);
let core_call = format!(
"{core_import}::{type_name}::from(self.clone()).{method_name}({call_args})",
method_name = method.name
);
let body = if method.error_type.is_some() {
format!(
"let result = {core_call}.await\n \
.map_err(|e| JsValue::from_str(&e.to_string()))?;\n \
Ok({}::from(result))",
return_type
)
} else {
format!(
"let result = {core_call}.await;\n \
Ok({}::from(result))",
return_type
)
};
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub async fn {}(&self, {}) -> {} {{\n \
{body}\n}}",
method.name,
params.join(", "),
return_annotation
)
} else if method.is_static {
let body = if can_delegate {
let call_args = generators::gen_call_args(&method.params, opaque_types);
let core_call = format!("{core_import}::{type_name}::{}({call_args})", method.name);
if method.error_type.is_some() {
let wrap = wasm_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
false,
method.returns_ref,
);
format!("let result = {core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok({wrap})")
} else {
wasm_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
false,
method.returns_ref,
)
}
} else {
gen_wasm_unimplemented_body(&method.return_type, &method.name, method.error_type.is_some())
};
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub fn {}({}) -> {} {{\n \
{body}\n}}",
method.name,
params.join(", "),
return_annotation
)
} else {
let body = if can_delegate {
let call_args = generators::gen_call_args(&method.params, opaque_types);
let core_call = format!(
"{core_import}::{type_name}::from(self.clone()).{method_name}({call_args})",
method_name = method.name
);
if method.error_type.is_some() {
let wrap = wasm_wrap_return(
"result",
&method.return_type,
type_name,
opaque_types,
false,
method.returns_ref,
);
format!("let result = {core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok({wrap})")
} else {
wasm_wrap_return(
&core_call,
&method.return_type,
type_name,
opaque_types,
false,
method.returns_ref,
)
}
} else {
gen_wasm_unimplemented_body(&method.return_type, &method.name, method.error_type.is_some())
};
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub fn {}(&self, {}) -> {} {{\n \
{body}\n}}",
method.name,
params.join(", "),
return_annotation
)
}
}
fn gen_enum(enum_def: &EnumDef) -> String {
let js_name = format!("Js{}", enum_def.name);
let mut lines = vec![
"#[wasm_bindgen]".to_string(),
"#[derive(Clone, Copy, PartialEq, Eq)]".to_string(),
format!("pub enum {} {{", js_name),
];
for (idx, variant) in enum_def.variants.iter().enumerate() {
lines.push(format!(" {} = {},", variant.name, idx));
}
lines.push("}".to_string());
if let Some(first) = enum_def.variants.first() {
lines.push(String::new());
lines.push("#[allow(clippy::derivable_impls)]".to_string());
lines.push(format!("impl Default for {} {{", js_name));
lines.push(format!(" fn default() -> Self {{ Self::{} }}", first.name));
lines.push("}".to_string());
}
lines.join("\n")
}
fn gen_function(func: &FunctionDef, mapper: &WasmMapper, core_import: &str, opaque_types: &AHashSet<String>) -> String {
let params: Vec<String> = func
.params
.iter()
.map(|p| {
let ty = mapper.map_type(&p.ty);
if p.optional {
format!("{}: Option<{}>", p.name, ty)
} else {
format!("{}: {}", p.name, ty)
}
})
.collect();
let return_type = mapper.map_type(&func.return_type);
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 can_delegate = shared::can_auto_delegate_function(func, opaque_types);
let mut attrs = String::new();
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");
}
if func.is_async {
let call_args = generators::gen_call_args(&func.params, opaque_types);
let core_call = format!("{core_import}::{}({call_args})", func.name);
let return_expr = match &func.return_type {
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
format!(
"result.into_iter().map(|v| {} {{ inner: Arc::new(v) }}).collect::<Vec<_>>()",
mapper.map_type(inner)
)
}
TypeRef::Named(_) => {
let inner_mapped = mapper.map_type(inner);
format!("result.into_iter().map({inner_mapped}::from).collect::<Vec<_>>()")
}
_ => "result".to_string(),
},
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
let prefixed = mapper.map_type(&func.return_type);
format!("{prefixed} {{ inner: Arc::new(result) }}")
}
TypeRef::Named(_) => {
format!("{return_type}::from(result)")
}
TypeRef::Unit => "result".to_string(),
_ => "result".to_string(),
};
let body = if func.error_type.is_some() {
format!(
"let result = {core_call}.await\n \
.map_err(|e| JsValue::from_str(&e.to_string()))?;\n \
Ok({return_expr})"
)
} else {
format!(
"let result = {core_call}.await;\n \
{return_expr}"
)
};
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub async fn {}({}) -> {} {{\n \
{body}\n}}",
func.name,
params.join(", "),
return_annotation
)
} else if can_delegate {
let call_args = generators::gen_call_args(&func.params, opaque_types);
let core_call = format!("{core_import}::{}({call_args})", func.name);
let body = if func.error_type.is_some() {
let wrap = wasm_wrap_return_fn("result", &func.return_type, opaque_types, func.returns_ref);
format!("let result = {core_call}.map_err(|e| JsValue::from_str(&e.to_string()))?;\n Ok({wrap})")
} else {
wasm_wrap_return_fn(&core_call, &func.return_type, opaque_types, func.returns_ref)
};
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub fn {}({}) -> {} {{\n \
{body}\n}}",
func.name,
params.join(", "),
return_annotation
)
} else {
let body = gen_wasm_unimplemented_body(&func.return_type, &func.name, func.error_type.is_some());
format!(
"{attrs}#[wasm_bindgen{js_name_attr}]\npub fn {}({}) -> {} {{\n \
{body}\n}}",
func.name,
params.join(", "),
return_annotation
)
}
}
fn gen_wasm_unimplemented_body(return_type: &TypeRef, fn_name: &str, has_error: bool) -> String {
let err_msg = format!("Not implemented: {fn_name}");
if has_error {
format!("Err(JsValue::from_str(\"{err_msg}\"))")
} else {
match return_type {
TypeRef::Unit => "()".to_string(),
TypeRef::String | TypeRef::Char | TypeRef::Path => format!("String::from(\"[unimplemented: {fn_name}]\")"),
TypeRef::Bytes => "Vec::new()".to_string(),
TypeRef::Primitive(p) => match p {
alef_core::ir::PrimitiveType::Bool => "false".to_string(),
_ => "0".to_string(),
},
TypeRef::Optional(_) => "None".to_string(),
TypeRef::Vec(_) => "Vec::new()".to_string(),
TypeRef::Map(_, _) => "Default::default()".to_string(),
TypeRef::Duration => "0u64".to_string(),
TypeRef::Named(_) | TypeRef::Json => format!("panic!(\"alef: {fn_name} not auto-delegatable\")"),
}
}
}
fn wasm_wrap_return(
expr: &str,
return_type: &TypeRef,
type_name: &str,
opaque_types: &AHashSet<String>,
self_is_opaque: bool,
returns_ref: bool,
) -> String {
match return_type {
TypeRef::Named(n) if n == type_name && self_is_opaque => {
if returns_ref {
format!("Self {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Self {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Js{n} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v.clone()) }})")
} else {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v) }})")
}
}
_ => generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
false,
),
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v.clone()) }}).collect()")
} else {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v) }}).collect()")
}
}
_ => generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
false,
),
},
_ => generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
false,
),
}
}
fn wasm_wrap_return_fn(
expr: &str,
return_type: &TypeRef,
opaque_types: &AHashSet<String>,
returns_ref: bool,
) -> String {
match return_type {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("Js{n} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("Js{n} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into()")
} else {
expr.to_string()
}
}
TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
TypeRef::Json => format!("{expr}.to_string()"),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v.clone()) }})")
} else {
format!("{expr}.map(|v| Js{name} {{ inner: Arc::new(v) }})")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.map(|v| v.clone().into())")
} else {
format!("{expr}.map(Into::into)")
}
}
TypeRef::Path => {
format!("{expr}.map(Into::into)")
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.map(Into::into)")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v.clone()) }}).collect()")
} else {
format!("{expr}.into_iter().map(|v| Js{name} {{ inner: Arc::new(v) }}).collect()")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| v.clone().into()).collect()")
} else {
format!("{expr}.into_iter().map(Into::into).collect()")
}
}
TypeRef::Path => {
format!("{expr}.into_iter().map(Into::into).collect()")
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into_iter().map(Into::into).collect()")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
_ => expr.to_string(),
}
}