use crate::generators::{AsyncPattern, RustBindingConfig};
use ahash::AHashSet;
use alef_core::ir::{CoreWrapper, ParamDef, TypeDef, TypeRef};
use std::fmt::Write;
fn arc_wrap(val: &str, name: &str, mutex_types: &AHashSet<String>) -> String {
if mutex_types.contains(name) {
format!("Arc::new(std::sync::Mutex::new({val}))")
} else {
format!("Arc::new({val})")
}
}
#[allow(clippy::too_many_arguments)]
pub fn wrap_return_with_mutex(
expr: &str,
return_type: &TypeRef,
type_name: &str,
opaque_types: &AHashSet<String>,
mutex_types: &AHashSet<String>,
self_is_opaque: bool,
returns_ref: bool,
returns_cow: bool,
) -> String {
let self_arc = arc_wrap("", type_name, mutex_types); let _ = self_arc; match return_type {
TypeRef::Named(n) if n == type_name && self_is_opaque => {
let inner = if returns_cow {
format!("{expr}.into_owned()")
} else if returns_ref {
format!("{expr}.clone()")
} else {
expr.to_string()
};
format!("Self {{ inner: {} }}", arc_wrap(&inner, type_name, mutex_types))
}
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
let inner = if returns_cow {
format!("{expr}.into_owned()")
} else if returns_ref {
format!("{expr}.clone()")
} else {
expr.to_string()
};
format!("{n} {{ inner: {} }}", arc_wrap(&inner, n, mutex_types))
}
TypeRef::Named(_) => {
if returns_cow {
format!("{expr}.into_owned().into()")
} else if returns_ref {
format!("{expr}.clone().into()")
} else {
format!("{expr}.into()")
}
}
TypeRef::String => {
if returns_ref {
format!("{expr}.into()")
} else {
expr.to_string()
}
}
TypeRef::Bytes => format!("{expr}.to_vec()"),
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()) => {
let wrap = arc_wrap("v", n, mutex_types);
if returns_ref {
format!(
"{expr}.map(|v| {n} {{ inner: {} }})",
arc_wrap("v.clone()", n, mutex_types)
)
} else {
format!("{expr}.map(|v| {n} {{ inner: {wrap} }})")
}
}
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)"),
TypeRef::Vec(vec_inner) => match vec_inner.as_ref() {
TypeRef::Named(n) if opaque_types.contains(n.as_str()) => {
if returns_ref {
let wrap = arc_wrap("x.clone()", n, mutex_types);
format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
} else {
let wrap = arc_wrap("x", n, mutex_types);
format!("{expr}.map(|v| v.into_iter().map(|x| {n} {{ inner: {wrap} }}).collect())")
}
}
TypeRef::Named(_) => {
if returns_ref {
format!("{expr}.map(|v| v.into_iter().map(|x| x.clone().into()).collect())")
} else {
format!("{expr}.map(|v| v.into_iter().map(Into::into).collect())")
}
}
_ => expr.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 {
let wrap = arc_wrap("v.clone()", n, mutex_types);
format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).collect()")
} else {
let wrap = arc_wrap("v", n, mutex_types);
format!("{expr}.into_iter().map(|v| {n} {{ inner: {wrap} }}).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 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 {
wrap_return_with_mutex(
expr,
return_type,
type_name,
opaque_types,
&AHashSet::new(),
self_is_opaque,
returns_ref,
returns_cow,
)
}
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 && p.optional {
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 {
if p.is_ref {
format!("{}.as_ref()", p.name)
} else {
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 {
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::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)
}
}
TypeRef::Json => {
if p.optional {
format!("{}.as_ref().and_then(|s| serde_json::from_str(s).ok())", p.name)
} else if promoted {
format!("serde_json::from_str(&{}{}).unwrap_or_default()", p.name, unwrap_suffix)
} else {
format!("serde_json::from_str(&{}).unwrap_or_default()", p.name)
}
}
TypeRef::Vec(inner) => {
if matches!(inner.as_ref(), TypeRef::Named(_)) {
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()
}
} else if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else if p.is_mut && p.optional {
format!("{}.as_deref_mut()", p.name)
} else if p.is_mut {
format!("&mut {}", p.name)
} else if p.is_ref && p.optional {
format!("{}.as_deref()", p.name)
} else if p.is_ref {
format!("&{}", p.name)
} else {
p.name.clone()
}
}
_ => {
if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else if p.is_mut && p.optional {
format!("{}.as_deref_mut()", p.name)
} else if p.is_mut {
format!("&mut {}", p.name)
} else if p.is_ref && p.optional {
format!("{}.as_deref()", p.name)
} else if p.is_ref {
format!("&{}", p.name)
} 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.optional && p.is_ref {
format!("{}_core", p.name)
} else if p.is_ref {
format!("&{}_core", p.name)
} else {
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 {
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::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)
}
}
TypeRef::Vec(inner) => {
if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
if p.optional && p.is_ref {
format!("{}_core.as_deref()", p.name)
} else if p.optional {
format!("{}_core", p.name)
} else if p.is_ref {
format!("&{}_core", p.name)
} else {
format!("{}_core", p.name)
}
} else if matches!(inner.as_ref(), TypeRef::Named(_)) {
if p.optional && p.is_ref {
format!("{}_core.as_deref()", p.name)
} else if p.optional {
format!("{}_core", p.name)
} else if p.is_ref {
format!("&{}_core", p.name)
} else {
format!("{}_core", p.name)
}
} else if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref {
if p.optional {
format!("{}.as_deref()", p.name)
} else {
format!("&{}_refs", p.name)
}
} else if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else if p.is_ref && p.optional {
format!("{}.as_deref()", p.name)
} else if p.is_ref {
format!("&{}", p.name)
} else {
p.name.clone()
}
}
_ => {
if promoted {
format!("{}{}", p.name, unwrap_suffix)
} else if p.is_ref && p.optional {
format!("{}.as_deref()", p.name)
} else if p.is_ref {
format!("&{}", p.name)
} else {
p.name.clone()
}
}
}
})
.collect::<Vec<_>>()
.join(", ")
}
pub fn gen_named_let_bindings_pub(params: &[ParamDef], opaque_types: &AHashSet<String>, core_import: &str) -> String {
gen_named_let_bindings(params, opaque_types, core_import)
}
pub fn gen_named_let_bindings_no_promote(
params: &[ParamDef],
opaque_types: &AHashSet<String>,
core_import: &str,
) -> String {
gen_named_let_bindings_inner(params, opaque_types, core_import, false)
}
pub(super) fn gen_named_let_bindings(
params: &[ParamDef],
opaque_types: &AHashSet<String>,
core_import: &str,
) -> String {
gen_named_let_bindings_inner(params, opaque_types, core_import, true)
}
fn gen_named_let_bindings_inner(
params: &[ParamDef],
opaque_types: &AHashSet<String>,
core_import: &str,
promote: bool,
) -> String {
let mut bindings = String::new();
for (idx, p) in params.iter().enumerate() {
match &p.ty {
TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
let promoted = promote && crate::shared::is_promoted_optional(params, idx);
let core_type_path = format!("{}::{}", core_import, name);
if p.optional {
if p.is_ref {
write!(
bindings,
"let {name}_owned: Option<{core_type_path}> = {name}.map(Into::into);\n let {name}_core = {name}_owned.as_ref();\n ",
name = p.name
)
.ok();
} else {
write!(
bindings,
"let {}_core: Option<{core_type_path}> = {}.map(Into::into);\n ",
p.name, p.name
)
.ok();
}
} else if promoted {
write!(
bindings,
"let {}_core: {core_type_path} = {}.expect(\"'{}' is required\").into();\n ",
p.name, p.name, p.name
)
.ok();
} else {
write!(
bindings,
"let {}_core: {core_type_path} = {}.into();\n ",
p.name, p.name
)
.ok();
}
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(n) if !opaque_types.contains(n.as_str())) => {
let promoted = promote && crate::shared::is_promoted_optional(params, idx);
if p.optional && p.is_ref {
write!(
bindings,
"let {}_core: Option<Vec<_>> = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect());\n ",
p.name, p.name
)
.ok();
} else if p.optional {
write!(
bindings,
"let {}_core = {}.as_ref().map(|v| v.iter().map(|x| x.clone().into()).collect()).unwrap_or_default();\n ",
p.name, p.name
)
.ok();
} else if promoted {
write!(
bindings,
"let {}_core: Vec<_> = {}.expect(\"'{}' is required\").into_iter().map(Into::into).collect();\n ",
p.name, p.name, p.name
)
.ok();
} else if p.is_ref {
write!(
bindings,
"let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n ",
p.name, p.name
)
.ok();
} else {
write!(
bindings,
"let {}_core: Vec<_> = {}.into_iter().map(Into::into).collect();\n ",
p.name, p.name
)
.ok();
}
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref => {
write!(
bindings,
"let {n}_refs: Vec<&str> = {n}.iter().map(|s| s.as_str()).collect();\n ",
n = p.name,
)
.ok();
}
TypeRef::Vec(inner)
if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() =>
{
if p.optional {
write!(
bindings,
"let {n}_core: Option<Vec<_>> = {n}.map(|strs| \
strs.into_iter()\n \
.filter_map(|s| serde_json::from_str(&s).ok())\n \
.collect()\n \
);\n ",
n = p.name,
)
.ok();
} else {
write!(
bindings,
"let {n}_core: Vec<_> = {n}.into_iter()\n \
.filter_map(|s| serde_json::from_str(&s).ok())\n \
.collect();\n ",
n = 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 {
match &p.ty {
TypeRef::Named(name) 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();
}
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(name) = inner.as_ref() {
if !opaque_types.contains(name.as_str()) {
let core_path = format!("{}::{}", core_import, name);
if p.optional {
write!(
bindings,
"let {name}_core: Option<Vec<{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: Vec<{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();
}
}
} else if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
if p.optional {
write!(
bindings,
"let {n}_core: Option<Vec<_>> = {n}.map(|strs| {{\n\
{indent} strs.into_iter()\n\
{indent} .map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
{indent} .collect::<Result<Vec<_>, _>>()\n\
{indent}}}).transpose()?;\n{indent}",
n = p.name,
err_conv = err_conv,
indent = indent,
)
.ok();
} else {
write!(
bindings,
"let {n}_core: Vec<_> = {n}.into_iter()\n\
{indent}.map(|s| serde_json::from_str::<_>(&s){err_conv})\n\
{indent}.collect::<Result<Vec<_>, _>>()?;\n{indent}",
n = p.name,
err_conv = err_conv,
indent = indent,
)
.ok();
}
}
}
_ => {}
}
}
bindings
}
pub fn has_named_params(params: &[ParamDef], opaque_types: &AHashSet<String>) -> bool {
params.iter().any(|p| match &p.ty {
TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => true,
TypeRef::Vec(inner) => {
matches!(inner.as_ref(), TypeRef::Named(name) if !opaque_types.contains(name.as_str()))
|| (matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref)
|| (matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some())
}
_ => false,
})
}
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, option_duration_on_defaults: bool) -> String {
gen_lossy_binding_to_core_fields_inner(typ, core_import, false, option_duration_on_defaults)
}
pub fn gen_lossy_binding_to_core_fields_mut(
typ: &TypeDef,
core_import: &str,
option_duration_on_defaults: bool,
) -> String {
gen_lossy_binding_to_core_fields_inner(typ, core_import, true, option_duration_on_defaults)
}
fn gen_lossy_binding_to_core_fields_inner(
typ: &TypeDef,
core_import: &str,
needs_mut: bool,
option_duration_on_defaults: bool,
) -> String {
let core_path = crate::conversions::core_type_path(typ, core_import);
let mut_kw = if needs_mut { "mut " } else { "" };
let allow = if typ.has_stripped_cfg_fields {
"#[allow(clippy::needless_update)]\n "
} else {
""
};
let mut out = format!("{allow}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_millis)")
} else if option_duration_on_defaults && typ.has_default {
format!("self.{name}.map(std::time::Duration::from_millis).unwrap_or_default()")
} else {
format!("std::time::Duration::from_millis(self.{name})")
}
}
TypeRef::String => format!("self.{name}.clone()"),
TypeRef::Bytes => {
if field.core_wrapper == CoreWrapper::Bytes {
format!("self.{name}.clone().into()")
} else {
format!("self.{name}.clone()")
}
}
TypeRef::Char => {
if field.optional {
format!("self.{name}.as_ref().and_then(|s| s.chars().next())")
} else {
format!("self.{name}.chars().next().unwrap_or('*')")
}
}
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) => {
let base = match inner.as_ref() {
TypeRef::Named(_) => {
format!("self.{name}.clone().map(Into::into)")
}
TypeRef::Duration => {
format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
}
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()"),
};
if field.optional {
format!("({base}).map(Some)")
} else {
base
}
}
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
}
#[allow(clippy::too_many_arguments)]
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,
return_type: Option<&str>,
) -> 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, extra_binding) = if is_unit_return && !has_error {
("()".to_string(), String::new())
} else if return_wrap.contains(".into()") || return_wrap.contains("::from(") {
let wrapped_var = "wrapped_result";
let binding = if let Some(ret_type) = return_type {
format!("let {wrapped_var}: {ret_type} = {return_wrap};\n ")
} else {
format!("let {wrapped_var} = {return_wrap};\n ")
};
(wrapped_var.to_string(), binding)
} else {
(return_wrap.to_string(), String::new())
};
format!(
"pyo3_async_runtimes::tokio::future_into_py(py, async move {{\n \
{result_handling}\n \
{extra_binding}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],
opaque_types: &AHashSet<String>,
) -> 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(),
alef_core::ir::PrimitiveType::F32 => "0.0f32".to_string(),
alef_core::ir::PrimitiveType::F64 => "0.0f64".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(name) => {
if opaque_types.contains(name.as_str()) {
format!("todo!(\"{err_msg}\")")
} else {
"Default::default()".to_string()
}
}
TypeRef::Json => {
"Default::default()".to_string()
}
}
};
format!("{suppress}{body}")
}