use crate::builder::StructBuilder;
use crate::generators::RustBindingConfig;
use crate::type_mapper::TypeMapper;
use alef_core::ir::{CoreWrapper, TypeDef, TypeRef};
pub fn can_generate_default_impl(typ: &TypeDef, known_default_types: &std::collections::HashSet<&str>) -> bool {
for field in &typ.fields {
if field.cfg.is_some() {
continue; }
if !field_type_has_default(&field.ty, known_default_types) {
return false;
}
}
true
}
fn field_type_has_default(ty: &TypeRef, known_default_types: &std::collections::HashSet<&str>) -> bool {
match ty {
TypeRef::Primitive(_)
| TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Path
| TypeRef::Unit
| TypeRef::Duration
| TypeRef::Json => true,
TypeRef::Optional(inner) => field_type_has_default(inner, known_default_types),
TypeRef::Vec(inner) => field_type_has_default(inner, known_default_types),
TypeRef::Map(k, v) => {
field_type_has_default(k, known_default_types) && field_type_has_default(v, known_default_types)
}
TypeRef::Named(name) => known_default_types.contains(name.as_str()),
}
}
fn has_similar_names(names: &[&String]) -> bool {
for (i, &name1) in names.iter().enumerate() {
for &name2 in &names[i + 1..] {
if name1.len() == name2.len() && diff_count(name1, name2) <= 2 {
return true;
}
}
}
false
}
fn diff_count(s1: &str, s2: &str) -> usize {
s1.chars().zip(s2.chars()).filter(|(c1, c2)| c1 != c2).count()
}
pub fn field_references_opaque_type(ty: &TypeRef, opaque_names: &[String]) -> bool {
match ty {
TypeRef::Named(name) => opaque_names.contains(name),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_opaque_type(inner, opaque_names),
TypeRef::Map(k, v) => {
field_references_opaque_type(k, opaque_names) || field_references_opaque_type(v, opaque_names)
}
_ => false,
}
}
pub fn gen_struct_with_per_field_attrs(
typ: &TypeDef,
mapper: &dyn TypeMapper,
cfg: &RustBindingConfig,
extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
) -> String {
let mut sb = StructBuilder::new(&typ.name);
for attr in cfg.struct_attrs {
sb.add_attr(attr);
}
let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
if has_similar_names(&field_names) {
sb.add_attr("allow(clippy::similar_names)");
}
for d in cfg.struct_derives {
sb.add_derive(d);
}
let opaque_fields: Vec<&str> = typ
.fields
.iter()
.filter(|f| {
f.cfg.is_none()
&& field_references_opaque_type(&f.ty, cfg.opaque_type_names)
&& !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
})
.map(|f| f.name.as_str())
.collect();
sb.add_derive("Default");
sb.add_derive("serde::Serialize");
sb.add_derive("serde::Deserialize");
let has_serde = true;
for field in &typ.fields {
if field.cfg.is_some() {
continue;
}
let force_optional = cfg.option_duration_on_defaults
&& typ.has_default
&& !field.optional
&& matches!(field.ty, TypeRef::Duration);
let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
attrs.extend(extra_field_attrs(field));
let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
attrs.push("serde(skip)".to_string());
}
sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
}
sb.build()
}
pub fn gen_struct_with_rename(
typ: &TypeDef,
mapper: &dyn TypeMapper,
cfg: &RustBindingConfig,
extra_field_attrs: impl Fn(&alef_core::ir::FieldDef) -> Vec<String>,
field_name_override: impl Fn(&alef_core::ir::FieldDef) -> Option<String>,
) -> String {
let mut sb = StructBuilder::new(&typ.name);
for attr in cfg.struct_attrs {
sb.add_attr(attr);
}
let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
if has_similar_names(&field_names) {
sb.add_attr("allow(clippy::similar_names)");
}
for d in cfg.struct_derives {
sb.add_derive(d);
}
let opaque_fields: Vec<&str> = typ
.fields
.iter()
.filter(|f| {
f.cfg.is_none()
&& field_references_opaque_type(&f.ty, cfg.opaque_type_names)
&& !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
})
.map(|f| f.name.as_str())
.collect();
sb.add_derive("Default");
sb.add_derive("serde::Serialize");
sb.add_derive("serde::Deserialize");
let has_serde = true;
for field in &typ.fields {
if field.cfg.is_some() {
continue;
}
let force_optional = cfg.option_duration_on_defaults
&& typ.has_default
&& !field.optional
&& matches!(field.ty, TypeRef::Duration);
let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
let name_override = field_name_override(field);
let extra_attrs = extra_field_attrs(field);
let mut attrs: Vec<String> = if name_override.is_some() && !extra_attrs.is_empty() {
extra_attrs
} else {
let mut a: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
a.extend(extra_attrs);
a
};
let skip_sanitized_field = field.sanitized && field.core_wrapper != CoreWrapper::Cow;
if has_serde && (opaque_fields.contains(&field.name.as_str()) || skip_sanitized_field) {
attrs.push("serde(skip)".to_string());
}
if has_serde
&& !attrs.iter().any(|a| a.starts_with("serde(rename"))
&& !attrs.iter().any(|a| a == "serde(skip)")
{
if let Some(rename) = &field.serde_rename {
attrs.push(format!("serde(rename = \"{rename}\")"));
}
}
let emit_name = name_override.unwrap_or_else(|| field.name.clone());
sb.add_field_with_doc(&emit_name, &ty, attrs, &field.doc);
}
sb.build()
}
pub fn gen_struct(typ: &TypeDef, mapper: &dyn TypeMapper, cfg: &RustBindingConfig) -> String {
let mut sb = StructBuilder::new(&typ.name);
for attr in cfg.struct_attrs {
sb.add_attr(attr);
}
let field_names: Vec<_> = typ.fields.iter().filter(|f| f.cfg.is_none()).map(|f| &f.name).collect();
if has_similar_names(&field_names) {
sb.add_attr("allow(clippy::similar_names)");
}
for d in cfg.struct_derives {
sb.add_derive(d);
}
let _opaque_fields: Vec<&str> = typ
.fields
.iter()
.filter(|f| {
f.cfg.is_none()
&& field_references_opaque_type(&f.ty, cfg.opaque_type_names)
&& !field_references_opaque_type(&f.ty, cfg.serializable_opaque_type_names)
})
.map(|f| f.name.as_str())
.collect();
sb.add_derive("Default");
sb.add_derive("serde::Serialize");
sb.add_derive("serde::Deserialize");
let _has_serde = true;
for field in &typ.fields {
if field.cfg.is_some() {
continue;
}
let force_optional = cfg.option_duration_on_defaults
&& typ.has_default
&& !field.optional
&& matches!(field.ty, TypeRef::Duration);
let ty = if (field.optional || force_optional) && !matches!(field.ty, TypeRef::Optional(_)) {
mapper.optional(&mapper.map_type(&field.ty))
} else {
mapper.map_type(&field.ty)
};
let mut attrs: Vec<String> = cfg.field_attrs.iter().map(|a| a.to_string()).collect();
if let Some(rename) = &field.serde_rename {
if !attrs.iter().any(|a| a.starts_with("serde(rename")) {
attrs.push(format!("serde(rename = \"{rename}\")"));
}
}
sb.add_field_with_doc(&field.name, &ty, attrs, &field.doc);
}
sb.build()
}
pub fn gen_struct_default_impl(typ: &TypeDef, name_prefix: &str) -> String {
let full_name = format!("{}{}", name_prefix, typ.name);
let fields: Vec<_> = typ
.fields
.iter()
.filter_map(|field| {
if field.cfg.is_some() {
return None;
}
let default_val = match &field.ty {
TypeRef::Optional(_) => "None".to_string(),
_ => "Default::default()".to_string(),
};
Some(minijinja::context! {
name => field.name.clone(),
default_val => default_val
})
})
.collect();
crate::template_env::render(
"structs/default_impl.jinja",
minijinja::context! {
full_name => full_name,
fields => fields
},
)
}
pub fn type_needs_mutex(typ: &TypeDef) -> bool {
typ.methods
.iter()
.any(|m| m.receiver == Some(alef_core::ir::ReceiverKind::RefMut))
}
pub fn type_needs_tokio_mutex(typ: &TypeDef) -> bool {
use alef_core::ir::ReceiverKind;
if !type_needs_mutex(typ) {
return false;
}
let refmut_methods = typ.methods.iter().filter(|m| m.receiver == Some(ReceiverKind::RefMut));
let mut any = false;
for m in refmut_methods {
any = true;
if !m.is_async {
return false;
}
}
any
}
pub fn gen_opaque_struct(typ: &TypeDef, cfg: &RustBindingConfig) -> String {
let needs_mutex = type_needs_mutex(typ);
let core_path = typ.rust_path.replace('-', "_");
let has_unresolvable_generics = core_path.contains('<');
let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
let omit_inner = all_methods_sanitized && has_unresolvable_generics;
let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
let has_derives = !cfg.struct_derives.is_empty();
let inner_type = if typ.is_trait {
format!("Arc<dyn {core_path} + Send + Sync>")
} else if needs_mutex {
format!("Arc<std::sync::Mutex<{core_path}>>")
} else {
format!("Arc<{core_path}>")
};
crate::template_env::render(
"structs/opaque_struct.jinja",
minijinja::context! {
struct_name => typ.name.clone(),
has_derives => has_derives,
struct_attrs => struct_attrs,
omit_inner => omit_inner,
inner_type => inner_type,
},
)
}
pub fn gen_opaque_struct_prefixed(typ: &TypeDef, cfg: &RustBindingConfig, prefix: &str) -> String {
let needs_mutex = type_needs_mutex(typ);
let core_path = typ.rust_path.replace('-', "_");
let has_unresolvable_generics = core_path.contains('<');
let all_methods_sanitized = !typ.methods.is_empty() && typ.methods.iter().all(|m| m.sanitized);
let omit_inner = all_methods_sanitized && has_unresolvable_generics;
let struct_attrs: Vec<_> = cfg.struct_attrs.iter().map(|s| s.to_string()).collect();
let has_derives = !cfg.struct_derives.is_empty();
let struct_name = format!("{prefix}{}", typ.name);
let inner_type = if typ.is_trait {
format!("Arc<dyn {core_path} + Send + Sync>")
} else if needs_mutex {
format!("Arc<std::sync::Mutex<{core_path}>>")
} else {
format!("Arc<{core_path}>")
};
crate::template_env::render(
"structs/opaque_struct.jinja",
minijinja::context! {
struct_name => struct_name,
has_derives => has_derives,
struct_attrs => struct_attrs,
omit_inner => omit_inner,
inner_type => inner_type,
},
)
}
#[cfg(test)]
mod tests {
use super::{type_needs_mutex, type_needs_tokio_mutex};
use alef_core::ir::{MethodDef, ReceiverKind, TypeDef, TypeRef};
fn method(name: &str, receiver: Option<ReceiverKind>, is_async: bool) -> MethodDef {
MethodDef {
name: name.into(),
params: vec![],
return_type: TypeRef::Unit,
is_async,
is_static: false,
error_type: None,
doc: String::new(),
receiver,
sanitized: false,
trait_source: None,
returns_ref: false,
returns_cow: false,
return_newtype_wrapper: None,
has_default_impl: false,
}
}
fn type_with_methods(name: &str, methods: Vec<MethodDef>) -> TypeDef {
TypeDef {
name: name.into(),
rust_path: format!("my_crate::{name}"),
original_rust_path: String::new(),
fields: vec![],
methods,
is_opaque: true,
is_clone: false,
is_copy: false,
is_trait: false,
has_default: false,
has_stripped_cfg_fields: false,
is_return_type: false,
serde_rename_all: None,
has_serde: false,
super_traits: vec![],
doc: String::new(),
cfg: None,
}
}
#[test]
fn tokio_mutex_when_all_refmut_methods_async() {
let typ = type_with_methods(
"WebSocketConnection",
vec![
method("send_text", Some(ReceiverKind::RefMut), true),
method("receive_text", Some(ReceiverKind::RefMut), true),
method("close", None, true),
],
);
assert!(type_needs_mutex(&typ));
assert!(type_needs_tokio_mutex(&typ));
}
#[test]
fn no_tokio_mutex_when_any_refmut_is_sync() {
let typ = type_with_methods(
"Mixed",
vec![
method("async_op", Some(ReceiverKind::RefMut), true),
method("sync_op", Some(ReceiverKind::RefMut), false),
],
);
assert!(type_needs_mutex(&typ));
assert!(!type_needs_tokio_mutex(&typ));
}
#[test]
fn no_tokio_mutex_when_no_refmut() {
let typ = type_with_methods("ReadOnly", vec![method("get", Some(ReceiverKind::Ref), true)]);
assert!(!type_needs_mutex(&typ));
assert!(!type_needs_tokio_mutex(&typ));
}
#[test]
fn no_tokio_mutex_when_empty_methods() {
let typ = type_with_methods("Empty", vec![]);
assert!(!type_needs_mutex(&typ));
assert!(!type_needs_tokio_mutex(&typ));
}
}