use crate::generators::{AsyncPattern, RustBindingConfig};
use ahash::AHashSet;
use alef_core::ir::{ParamDef, TypeDef, TypeRef};
use std::fmt::Write;
pub fn wrap_return(
expr: &str,
return_type: &TypeRef,
type_name: &str,
opaque_types: &AHashSet<String>,
self_is_opaque: bool,
returns_ref: bool,
returns_cow: bool,
) -> String {
match return_type {
TypeRef::Named(n) if n == type_name && self_is_opaque => {
if returns_cow {
format!("Self {{ inner: Arc::new({expr}.into_owned()) }}")
} else 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_cow {
format!("{n} {{ inner: Arc::new({expr}.into_owned()) }}")
} else if returns_ref {
format!("{n} {{ inner: Arc::new({expr}.clone()) }}")
} else {
format!("{n} {{ inner: Arc::new({expr}) }}")
}
}
TypeRef::Named(_) => {
if returns_cow {
format!("{expr}.into_owned().into()")
} else if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
TypeRef::String | TypeRef::Bytes => {
if returns_ref {
format!("{expr}.into()")
} else {
expr.to_string()
}
}
TypeRef::Path => format!("{expr}.to_string_lossy().to_string()"),
TypeRef::Duration => format!("{expr}.as_millis() as u64"),
TypeRef::Json => format!("{expr}.to_string()"),
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("{expr}.map(|v| {n} {{ inner: Arc::new(v.clone()) }})")
} else {
format!("{expr}.map(|v| {n} {{ 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::Bytes => {
if returns_ref {
format!("{expr}.map(Into::into)")
} else {
expr.to_string()
}
}
TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as u64)"),
TypeRef::Json => format!("{expr}.map(ToString::to_string)"),
_ => expr.to_string(),
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
format!("{expr}.into_iter().map(|v| {n} {{ inner: Arc::new(v.clone()) }}).collect()")
} else {
format!("{expr}.into_iter().map(|v| {n} {{ 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::Bytes => {
if returns_ref {
format!("{expr}.into_iter().map(Into::into).collect()")
} else {
expr.to_string()
}
}
_ => expr.to_string(),
},
_ => expr.to_string(),
}
}
pub fn apply_return_newtype_unwrap(expr: &str, return_newtype_wrapper: &Option<String>) -> String {
match return_newtype_wrapper {
Some(_) => format!("({expr}).0"),
None => expr.to_string(),
}
}
pub fn gen_call_args(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
params
.iter()
.enumerate()
.map(|(idx, p)| {
let promoted = crate::shared::is_promoted_optional(params, idx);
let unwrap_suffix = if promoted {
format!(".expect(\"'{}' is required\")", p.name)
} else {
String::new()
};
if let Some(newtype_path) = &p.newtype_wrapper {
return if p.optional {
format!("{}.map({newtype_path})", p.name)
} else if promoted {
format!("{newtype_path}({}{})", p.name, unwrap_suffix)
} else {
format!("{newtype_path}({})", p.name)
};
}
match &p.ty {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if p.optional {
format!("{}.as_ref().map(|v| &v.inner)", p.name)
} else if promoted {
format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
} else {
format!("&{}.inner", p.name)
}
}
TypeRef::Named(_) => {
if p.optional {
format!("{}.map(Into::into)", p.name)
} else if promoted {
format!("{}{}.into()", p.name, unwrap_suffix)
} else {
format!("{}.into()", p.name)
}
}
TypeRef::String | TypeRef::Char => {
if p.optional {
if p.is_ref {
format!("{}.as_deref()", p.name)
} else {
p.name.clone()
}
} else if promoted {
if p.is_ref {
format!("&{}{}", p.name, unwrap_suffix)
} else {
format!("{}{}", p.name, unwrap_suffix)
}
} else if p.is_ref {
format!("&{}", p.name)
} else {
p.name.clone()
}
}
TypeRef::Path => {
if p.optional && p.is_ref {
format!("{}.as_deref().map(std::path::Path::new)", p.name)
} else if p.optional {
format!("{}.map(std::path::PathBuf::from)", p.name)
} else if promoted {
format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
} else if p.is_ref {
format!("std::path::Path::new(&{})", p.name)
} else {
format!("std::path::PathBuf::from({})", p.name)
}
}
TypeRef::Bytes => {
if p.optional {
if p.is_ref {
format!("{}.as_deref()", p.name)
} else {
p.name.clone()
}
} else if promoted {
format!("&{}{}", p.name, unwrap_suffix)
} else {
format!("&{}", p.name)
}
}
TypeRef::Duration => {
if p.optional {
format!("{}.map(std::time::Duration::from_millis)", p.name)
} else if promoted {
format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
} else {
format!("std::time::Duration::from_millis({})", p.name)
}
}
_ => {
if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else {
p.name.clone()
}
}
}
})
.collect::<Vec<_>>()
.join(", ")
}
pub fn gen_call_args_with_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
params
.iter()
.enumerate()
.map(|(idx, p)| {
let promoted = crate::shared::is_promoted_optional(params, idx);
let unwrap_suffix = if promoted {
format!(".expect(\"'{}' is required\")", p.name)
} else {
String::new()
};
if let Some(newtype_path) = &p.newtype_wrapper {
return if p.optional {
format!("{}.map({newtype_path})", p.name)
} else if promoted {
format!("{newtype_path}({}{})", p.name, unwrap_suffix)
} else {
format!("{newtype_path}({})", p.name)
};
}
match &p.ty {
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if p.optional {
format!("{}.as_ref().map(|v| &v.inner)", p.name)
} else if promoted {
format!("{}{}.inner.as_ref()", p.name, unwrap_suffix)
} else {
format!("&{}.inner", p.name)
}
}
TypeRef::Named(_) if p.is_ref => {
format!("&{}_core", p.name)
}
TypeRef::Named(_) => {
format!("{}_core", p.name)
}
TypeRef::String | TypeRef::Char => {
if p.optional {
if p.is_ref {
format!("{}.as_deref()", p.name)
} else {
p.name.clone()
}
} else if promoted {
if p.is_ref {
format!("&{}{}", p.name, unwrap_suffix)
} else {
format!("{}{}", p.name, unwrap_suffix)
}
} else if p.is_ref {
format!("&{}", p.name)
} else {
p.name.clone()
}
}
TypeRef::Path => {
if promoted {
format!("std::path::PathBuf::from({}{})", p.name, unwrap_suffix)
} else if p.optional && p.is_ref {
format!("{}.as_deref().map(std::path::Path::new)", p.name)
} else if p.optional {
format!("{}.map(std::path::PathBuf::from)", p.name)
} else if p.is_ref {
format!("std::path::Path::new(&{})", p.name)
} else {
format!("std::path::PathBuf::from({})", p.name)
}
}
TypeRef::Bytes => {
if p.optional {
if p.is_ref {
format!("{}.as_deref()", p.name)
} else {
p.name.clone()
}
} else if promoted {
format!("&{}{}", p.name, unwrap_suffix)
} else {
format!("&{}", p.name)
}
}
TypeRef::Duration => {
if p.optional {
format!("{}.map(std::time::Duration::from_millis)", p.name)
} else if promoted {
format!("std::time::Duration::from_millis({}{})", p.name, unwrap_suffix)
} else {
format!("std::time::Duration::from_millis({})", p.name)
}
}
_ => {
if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else {
p.name.clone()
}
}
}
})
.collect::<Vec<_>>()
.join(", ")
}
pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
gen_named_let_bindings(params, opaque_types)
}
pub(super) fn gen_named_let_bindings(params: &[ParamDef], opaque_types: &AHashSet<String>) -> String {
let mut bindings = String::new();
for (idx, p) in params.iter().enumerate() {
if let TypeRef::Named(name) = &p.ty {
if !opaque_types.contains(name.as_str()) {
let promoted = crate::shared::is_promoted_optional(params, idx);
if p.optional {
write!(bindings, "let {}_core = {}.map(Into::into);\n ", p.name, p.name).ok();
} else if promoted {
write!(
bindings,
"let {}_core = {}.expect(\"'{}' is required\").into();\n ",
p.name, p.name, p.name
)
.ok();
} else {
write!(bindings, "let {}_core = {}.into();\n ", p.name, p.name).ok();
}
}
}
}
bindings
}
pub fn gen_serde_let_bindings(
params: &[ParamDef],
opaque_types: &AHashSet<String>,
core_import: &str,
err_conv: &str,
indent: &str,
) -> String {
let mut bindings = String::new();
for p in params {
if let TypeRef::Named(name) = &p.ty {
if !opaque_types.contains(name.as_str()) {
let core_path = format!("{}::{}", core_import, name);
if p.optional {
write!(
bindings,
"let {name}_core: Option<{core_path}> = {name}.map(|v| {{\n\
{indent} let json = serde_json::to_string(&v){err_conv}?;\n\
{indent} serde_json::from_str(&json){err_conv}\n\
{indent}}}).transpose()?;\n{indent}",
name = p.name,
core_path = core_path,
err_conv = err_conv,
indent = indent,
)
.ok();
} else {
write!(
bindings,
"let {name}_json = serde_json::to_string(&{name}){err_conv}?;\n\
{indent}let {name}_core: {core_path} = serde_json::from_str(&{name}_json){err_conv}?;\n{indent}",
name = p.name,
core_path = core_path,
err_conv = err_conv,
indent = indent,
)
.ok();
}
}
}
}
bindings
}
pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
params
.iter()
.any(|p| matches!(&p.ty, TypeRef::Named(name) if !opaque_types.contains(name.as_str())))
}
pub fn is_simple_non_opaque_param(ty: &TypeRef) -> bool {
match ty {
TypeRef::Primitive(_)
| TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Path
| TypeRef::Unit
| TypeRef::Duration => true,
TypeRef::Optional(inner) => is_simple_non_opaque_param(inner),
_ => false,
}
}
pub fn gen_lossy_binding_to_core_fields(typ: &TypeDef, core_import: &str) -> String {
gen_lossy_binding_to_core_fields_inner(typ, core_import, false)
}
pub fn gen_lossy_binding_to_core_fields_mut(typ: &TypeDef, core_import: &str) -> String {
gen_lossy_binding_to_core_fields_inner(typ, core_import, true)
}
fn gen_lossy_binding_to_core_fields_inner(typ: &TypeDef, core_import: &str, needs_mut: bool) -> String {
let core_path = crate::conversions::core_type_path(typ, core_import);
let mut_kw = if needs_mut { "mut " } else { "" };
let mut out = format!("let {mut_kw}core_self = {core_path} {{\n");
for field in &typ.fields {
let name = &field.name;
if field.sanitized {
writeln!(out, " {name}: Default::default(),").ok();
} else {
let expr = match &field.ty {
TypeRef::Primitive(_) => format!("self.{name}"),
TypeRef::Duration => {
if field.optional {
format!("self.{name}.map(std::time::Duration::from_secs)")
} else {
format!("std::time::Duration::from_millis(self.{name})")
}
}
TypeRef::String | TypeRef::Char | TypeRef::Bytes => format!("self.{name}.clone()"),
TypeRef::Path => {
if field.optional {
format!("self.{name}.clone().map(Into::into)")
} else {
format!("self.{name}.clone().into()")
}
}
TypeRef::Named(_) => {
if field.optional {
format!("self.{name}.clone().map(Into::into)")
} else {
format!("self.{name}.clone().into()")
}
}
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(_) => {
if field.optional {
format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
} else {
format!("self.{name}.clone().into_iter().map(Into::into).collect()")
}
}
_ => format!("self.{name}.clone()"),
},
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Named(_) => {
format!("self.{name}.clone().map(Into::into)")
}
TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Named(_)) => {
format!("self.{name}.clone().map(|v| v.into_iter().map(Into::into).collect())")
}
_ => format!("self.{name}.clone()"),
},
TypeRef::Map(_, v) => match v.as_ref() {
TypeRef::Json => {
if field.optional {
format!(
"self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| \
(k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect())"
)
} else {
format!(
"self.{name}.clone().into_iter().map(|(k, v)| \
(k, serde_json::from_str(&v).unwrap_or(serde_json::Value::String(v)))).collect()"
)
}
}
_ => {
if field.optional {
format!("self.{name}.clone().map(|m| m.into_iter().collect())")
} else {
format!("self.{name}.clone().into_iter().collect()")
}
}
},
TypeRef::Unit => format!("self.{name}.clone()"),
TypeRef::Json => {
if field.optional {
format!("self.{name}.as_ref().and_then(|s| serde_json::from_str(s).ok())")
} else {
format!("serde_json::from_str(&self.{name}).unwrap_or_default()")
}
}
};
let expr = if let Some(newtype_path) = &field.newtype_wrapper {
match &field.ty {
TypeRef::Optional(_) => format!("({expr}).map({newtype_path})"),
TypeRef::Vec(_) => format!("({expr}).into_iter().map({newtype_path}).collect()"),
_ if field.optional => format!("({expr}).map({newtype_path})"),
_ => format!("{newtype_path}({expr})"),
}
} else {
expr
};
writeln!(out, " {name}: {expr},").ok();
}
}
if typ.has_stripped_cfg_fields {
out.push_str(" ..Default::default()\n");
}
out.push_str(" };\n ");
out
}
pub fn gen_async_body(
core_call: &str,
cfg: &RustBindingConfig,
has_error: bool,
return_wrap: &str,
is_opaque: bool,
inner_clone_line: &str,
is_unit_return: bool,
) -> String {
let pattern_body = match cfg.async_pattern {
AsyncPattern::Pyo3FutureIntoPy => {
let result_handling = if has_error {
format!(
"let result = {core_call}.await\n \
.map_err(|e| PyErr::new::<PyRuntimeError, _>(e.to_string()))?;"
)
} else if is_unit_return {
format!("{core_call}.await;")
} else {
format!("let result = {core_call}.await;")
};
let ok_expr = if is_unit_return && !has_error {
"()"
} else {
return_wrap
};
format!(
"pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
{result_handling}\n \
Ok({ok_expr})\n }})"
)
}
AsyncPattern::WasmNativeAsync => {
let result_handling = if has_error {
format!(
"let result = {core_call}.await\n \
.map_err(|e| JsValue::from_str(&e.to_string()))?;"
)
} else if is_unit_return {
format!("{core_call}.await;")
} else {
format!("let result = {core_call}.await;")
};
let ok_expr = if is_unit_return && !has_error {
"()"
} else {
return_wrap
};
format!(
"{result_handling}\n \
Ok({ok_expr})"
)
}
AsyncPattern::NapiNativeAsync => {
let result_handling = if has_error {
format!(
"let result = {core_call}.await\n \
.map_err(|e| napi::Error::new(napi::Status::GenericFailure, e.to_string()))?;"
)
} else if is_unit_return {
format!("{core_call}.await;")
} else {
format!("let result = {core_call}.await;")
};
if !has_error && !is_unit_return {
format!(
"{result_handling}\n \
{return_wrap}"
)
} else {
let ok_expr = if is_unit_return && !has_error {
"()"
} else {
return_wrap
};
format!(
"{result_handling}\n \
Ok({ok_expr})"
)
}
}
AsyncPattern::TokioBlockOn => {
if has_error {
if is_opaque {
format!(
"let rt = tokio::runtime::Runtime::new()?;\n \
let result = rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})?;\n \
{return_wrap}"
)
} else {
format!(
"let rt = tokio::runtime::Runtime::new()?;\n \
rt.block_on(async {{ {core_call}.await.map_err(|e| e.into()) }})"
)
}
} else if is_opaque {
if is_unit_return {
format!(
"let rt = tokio::runtime::Runtime::new()?;\n \
rt.block_on(async {{ {core_call}.await }});"
)
} else {
format!(
"let rt = tokio::runtime::Runtime::new()?;\n \
let result = rt.block_on(async {{ {core_call}.await }});\n \
{return_wrap}"
)
}
} else {
format!(
"let rt = tokio::runtime::Runtime::new()?;\n \
rt.block_on(async {{ {core_call}.await }})"
)
}
}
AsyncPattern::None => "todo!(\"async not supported by backend\")".to_string(),
};
if inner_clone_line.is_empty() {
pattern_body
} else {
format!("{inner_clone_line}{pattern_body}")
}
}
pub fn gen_unimplemented_body(
return_type: &TypeRef,
fn_name: &str,
has_error: bool,
cfg: &RustBindingConfig,
params: &[ParamDef],
) -> String {
let suppress = if params.is_empty() {
String::new()
} else {
let names: Vec<&str> = params.iter().map(|p| p.name.as_str()).collect();
if names.len() == 1 {
format!("let _ = {};\n ", names[0])
} else {
format!("let _ = ({});\n ", names.join(", "))
}
};
let err_msg = format!("Not implemented: {fn_name}");
let body = if has_error {
match cfg.async_pattern {
AsyncPattern::Pyo3FutureIntoPy => {
format!("Err(pyo3::exceptions::PyNotImplementedError::new_err(\"{err_msg}\"))")
}
AsyncPattern::NapiNativeAsync => {
format!("Err(napi::Error::new(napi::Status::GenericFailure, \"{err_msg}\"))")
}
AsyncPattern::WasmNativeAsync => {
format!("Err(JsValue::from_str(\"{err_msg}\"))")
}
_ => format!("Err(\"{err_msg}\".to_string())"),
}
} 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 => "0".to_string(),
TypeRef::Named(_) | TypeRef::Json => {
format!(
"compile_error!(\"alef: {fn_name} returns a Named/Json type but has no error variant — cannot auto-delegate\")"
)
}
}
};
format!("{suppress}{body}")
}