use crate::type_map::PhpMapper;
use ahash::AHashSet;
use alef_codegen::conversions::ConversionConfig;
use alef_codegen::naming::to_php_name;
use alef_codegen::type_mapper::TypeMapper;
use alef_core::ir::{EnumDef, PrimitiveType, TypeDef, TypeRef};
use std::fmt::Write;
pub(crate) fn references_named_type(ty: &alef_core::ir::TypeRef, names: &AHashSet<String>) -> bool {
use alef_core::ir::TypeRef;
match ty {
TypeRef::Named(name) => names.contains(name.as_str()),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => references_named_type(inner, names),
TypeRef::Map(k, v) => references_named_type(k, names) || references_named_type(v, names),
_ => false,
}
}
pub(crate) fn has_enum_named_field(typ: &alef_core::ir::TypeDef, enum_names: &AHashSet<String>) -> bool {
fn type_ref_has_enum_named(ty: &alef_core::ir::TypeRef, enum_names: &AHashSet<String>) -> bool {
use alef_core::ir::TypeRef;
match ty {
TypeRef::Named(name) => enum_names.contains(name.as_str()),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => type_ref_has_enum_named(inner, enum_names),
TypeRef::Map(k, v) => type_ref_has_enum_named(k, enum_names) || type_ref_has_enum_named(v, enum_names),
_ => false,
}
}
typ.fields.iter().any(|f| type_ref_has_enum_named(&f.ty, enum_names))
}
pub(crate) fn gen_php_function_params(
params: &[alef_core::ir::ParamDef],
mapper: &PhpMapper,
_opaque_types: &AHashSet<String>,
bridge_type_aliases: &AHashSet<String>,
) -> String {
params
.iter()
.map(|p| {
let base_ty = mapper.map_type(&p.ty);
let ty = match &p.ty {
TypeRef::Named(name) => {
if bridge_type_aliases.contains(name.as_str()) {
if p.optional {
"Option<&mut ext_php_rs::types::ZendObject>".to_string()
} else {
"&mut ext_php_rs::types::ZendObject".to_string()
}
} else if mapper.enum_names.contains(name.as_str()) {
if p.optional {
format!("Option<{base_ty}>")
} else {
base_ty
}
} else if p.optional {
format!("Option<&{base_ty}>")
} else {
format!("&{base_ty}")
}
}
TypeRef::Vec(_inner) => {
if p.optional {
format!("Option<{base_ty}>")
} else {
base_ty
}
}
_ => {
if p.optional {
format!("Option<{base_ty}>")
} else {
base_ty
}
}
};
format!("{}: {}", to_php_name(&p.name), ty)
})
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn gen_php_call_args(params: &[alef_core::ir::ParamDef], opaque_types: &AHashSet<String>) -> String {
params
.iter()
.map(|p| {
let php_name = to_php_name(&p.name);
if let Some(newtype_path) = &p.newtype_wrapper {
return if p.optional {
format!("{php_name}.map({newtype_path})")
} else {
format!("{newtype_path}({php_name})")
};
}
match &p.ty {
TypeRef::Primitive(prim) if needs_i64_cast(prim) => {
let core_ty = core_prim_str(prim);
if p.optional {
format!("{php_name}.map(|v| v as {})", core_ty)
} else {
format!("{php_name} as {}", core_ty)
}
}
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if p.optional {
format!("{php_name}.as_ref().map(|v| &v.inner)")
} else {
format!("&{php_name}.inner")
}
}
TypeRef::Named(_) => {
if p.optional {
format!("{php_name}.map(|v| v.clone().into())")
} else {
format!("{php_name}.clone().into()")
}
}
TypeRef::String | TypeRef::Char => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}")
} else {
php_name
}
}
TypeRef::Path => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref().map(std::path::Path::new)")
} else {
format!("{php_name}.map(std::path::PathBuf::from)")
}
} else if p.is_ref {
format!("std::path::Path::new(&{php_name})")
} else {
format!("std::path::PathBuf::from({php_name})")
}
}
TypeRef::Bytes => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}")
} else {
php_name
}
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(name) = inner.as_ref() {
if !opaque_types.contains(name.as_str()) {
if p.is_ref {
if p.optional {
format!("{php_name}_core.as_ref().map(|v| &v[..])")
} else {
format!("&{php_name}_core[..]")
}
} else {
format!("{php_name}_core")
}
} else {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}[..]")
} else {
php_name
}
}
} else {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}[..]")
} else {
php_name
}
}
}
TypeRef::Duration => {
if p.optional {
format!("{php_name}.map(|v| std::time::Duration::from_millis(v.max(0) as u64))")
} else {
format!("std::time::Duration::from_millis({php_name}.max(0) as u64)")
}
}
_ => php_name,
}
})
.collect::<Vec<_>>()
.join(", ")
}
pub(crate) fn gen_php_named_let_bindings(
params: &[alef_core::ir::ParamDef],
opaque_types: &AHashSet<String>,
core_import: &str,
) -> String {
use minijinja::context;
let mut out = String::new();
for p in params {
let php_name = to_php_name(&p.name);
match &p.ty {
TypeRef::Named(name) if !opaque_types.contains(name.as_str()) => {
out.push_str(&crate::template_env::render(
"php_named_let_binding.jinja",
context! {
php_name => php_name,
is_optional => p.optional,
core_import => core_import,
type_name => name,
},
));
}
TypeRef::Vec(inner) => {
if let TypeRef::Named(name) = inner.as_ref() {
if !opaque_types.contains(name.as_str()) {
out.push_str(&crate::template_env::render(
"php_vec_named_struct_let_binding.jinja",
context! {
php_name => php_name,
is_optional => p.optional,
core_import => core_import,
struct_name => name,
},
));
}
} else if matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some() {
out.push_str(&crate::template_env::render(
"php_sanitized_vec_let_binding.jinja",
context! {
php_name => php_name,
is_optional => p.optional,
},
));
} else if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref {
out.push_str(&crate::template_env::render(
"php_vec_string_refs_let_binding.jinja",
context! {
php_name => php_name,
},
));
}
}
_ => {}
}
}
out
}
pub(crate) fn gen_php_call_args_with_let_bindings(
params: &[alef_core::ir::ParamDef],
opaque_types: &AHashSet<String>,
) -> String {
params
.iter()
.map(|p| {
let php_name = to_php_name(&p.name);
match &p.ty {
TypeRef::Primitive(prim) if needs_i64_cast(prim) => {
let core_ty = core_prim_str(prim);
if p.optional {
format!("{php_name}.map(|v| v as {})", core_ty)
} else {
format!("{php_name} as {}", core_ty)
}
}
TypeRef::Named(name) if opaque_types.contains(name.as_str()) => {
if p.optional {
format!("{php_name}.as_ref().map(|v| &v.inner)")
} else {
format!("&{php_name}.inner")
}
}
TypeRef::Named(_) => {
if p.is_ref {
if p.optional {
format!("{php_name}_core.as_ref()")
} else {
format!("&{php_name}_core")
}
} else {
format!("{php_name}_core")
}
}
TypeRef::String | TypeRef::Char => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}")
} else {
php_name
}
}
TypeRef::Path => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref().map(std::path::Path::new)")
} else {
format!("{php_name}.map(std::path::PathBuf::from)")
}
} else if p.is_ref {
format!("std::path::Path::new(&{php_name})")
} else {
format!("std::path::PathBuf::from({php_name})")
}
}
TypeRef::Bytes => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}")
} else {
php_name
}
}
TypeRef::Vec(inner) => {
let uses_binding = if let TypeRef::Named(name) = inner.as_ref() {
!opaque_types.contains(name.as_str())
} else {
false
};
let uses_sanitized_binding =
matches!(inner.as_ref(), TypeRef::String) && p.sanitized && p.original_type.is_some();
if uses_binding || uses_sanitized_binding {
if p.is_ref {
if p.optional {
format!("{php_name}_core.as_ref().map(|v| &v[..])")
} else {
format!("&{php_name}_core[..]")
}
} else {
format!("{php_name}_core")
}
} else if matches!(inner.as_ref(), TypeRef::String | TypeRef::Char) && p.is_ref {
format!("&{php_name}_refs")
} else {
if p.optional {
if p.is_ref {
format!("{php_name}.as_deref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}[..]")
} else {
php_name
}
}
}
TypeRef::Map(_, _) => {
if p.optional {
if p.is_ref {
format!("{php_name}.as_ref()")
} else {
php_name
}
} else if p.is_ref {
format!("&{php_name}")
} else {
php_name
}
}
TypeRef::Duration => {
if p.optional {
format!("{php_name}.map(|v| std::time::Duration::from_millis(v.max(0) as u64))")
} else {
format!("std::time::Duration::from_millis({php_name}.max(0) as u64)")
}
}
_ => php_name,
}
})
.collect::<Vec<_>>()
.join(", ")
}
fn needs_i64_cast(p: &PrimitiveType) -> bool {
matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
}
pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
match p {
PrimitiveType::U64 => "u64",
PrimitiveType::Usize => "usize",
PrimitiveType::Isize => "isize",
_ => unreachable!(),
}
}
pub(crate) fn php_wrap_return(
expr: &str,
return_type: &TypeRef,
type_name: &str,
opaque_types: &ahash::AHashSet<String>,
self_is_opaque: bool,
returns_ref: bool,
returns_cow: bool,
) -> String {
match return_type {
TypeRef::Primitive(p) if needs_i64_cast(p) => {
format!("{expr} as i64")
}
TypeRef::Duration => format!("{expr}.as_millis() as i64"),
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::Optional(inner) => match inner.as_ref() {
TypeRef::Primitive(p) if needs_i64_cast(p) => {
format!("{expr}.map(|v| v as i64)")
}
TypeRef::Duration => format!("{expr}.map(|d| d.as_millis() as i64)"),
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)")
}
}
_ => {
use alef_codegen::generators;
generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
returns_cow,
)
}
},
TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Primitive(p) if needs_i64_cast(p) => {
format!("{expr}.into_iter().map(|v| v as i64).collect()")
}
TypeRef::Vec(inner2) => {
if let TypeRef::Primitive(p) = inner2.as_ref() {
if needs_i64_cast(p) {
return format!(
"{expr}.into_iter().map(|row| row.into_iter().map(|x| x as i64).collect::<Vec<_>>()).collect::<Vec<_>>()"
);
}
}
use alef_codegen::generators;
generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
returns_cow,
)
}
_ => {
use alef_codegen::generators;
generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
returns_cow,
)
}
},
_ => {
use alef_codegen::generators;
generators::wrap_return(
expr,
return_type,
type_name,
opaque_types,
self_is_opaque,
returns_ref,
returns_cow,
)
}
}
}
pub(crate) fn gen_php_lossy_binding_to_core_fields(
typ: &TypeDef,
core_import: &str,
enum_names: &AHashSet<String>,
enums: &[EnumDef],
) -> String {
let core_path = alef_codegen::conversions::core_type_path(typ, core_import);
let allow = if typ.has_stripped_cfg_fields {
"#[allow(clippy::needless_update)]\n "
} else {
""
};
let mut out = format!("{allow}let 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 = if let Some(enum_name) = get_direct_enum_named(&field.ty, enum_names) {
gen_string_to_enum_expr(&format!("self.{name}"), &enum_name, field.optional, enums, core_import)
} else if let Some(enum_name) = get_vec_enum_named(&field.ty, enum_names) {
let elem_conv = gen_string_to_enum_expr("s", &enum_name, false, enums, core_import);
if field.optional {
format!("self.{name}.clone().map(|v| v.into_iter().map(|s| {elem_conv}).collect())")
} else {
format!("self.{name}.clone().into_iter().map(|s| {elem_conv}).collect()")
}
} else {
match &field.ty {
TypeRef::Primitive(p) if needs_i64_cast(p) => {
let core_ty = core_prim_str(p);
if field.optional {
format!("self.{name}.map(|v| v as {core_ty})")
} else {
format!("self.{name} as {core_ty}")
}
}
TypeRef::Primitive(_) => format!("self.{name}"),
TypeRef::Duration => {
if field.optional {
format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
} else if typ.has_default {
format!(
"self.{name}.map(|v| std::time::Duration::from_millis(v as u64)).unwrap_or_else(|| {core_path}::default().{name})"
)
} else {
format!("std::time::Duration::from_millis(self.{name} as u64)")
}
}
TypeRef::String | TypeRef::Char => format!("self.{name}.clone()"),
TypeRef::Bytes => format!("self.{name}.clone().into()"),
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()")
}
}
TypeRef::Primitive(p) if needs_i64_cast(p) => {
let core_ty = core_prim_str(p);
if field.optional {
format!("self.{name}.clone().map(|v| v.into_iter().map(|x| x as {core_ty}).collect())")
} else {
format!("self.{name}.clone().into_iter().map(|v| v as {core_ty}).collect()")
}
}
_ => format!("self.{name}.clone()"),
},
TypeRef::Optional(inner) => match inner.as_ref() {
TypeRef::Primitive(p) if needs_i64_cast(p) => {
let core_ty = core_prim_str(p);
format!("self.{name}.map(|v| v as {core_ty})")
}
TypeRef::Duration => {
format!("self.{name}.map(|v| std::time::Duration::from_millis(v as u64))")
}
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())")
}
TypeRef::Vec(vi) if matches!(vi.as_ref(), TypeRef::Primitive(p) if needs_i64_cast(p)) => {
if let TypeRef::Primitive(p) = vi.as_ref() {
let core_ty = core_prim_str(p);
format!("self.{name}.clone().map(|v| v.into_iter().map(|x| x as {core_ty}).collect())")
} else {
format!("self.{name}.clone()")
}
}
_ => format!("self.{name}.clone()"),
},
TypeRef::Map(_, v) if matches!(v.as_ref(), TypeRef::Json) => "Default::default()".to_string(),
TypeRef::Map(_, v) if matches!(v.as_ref(), TypeRef::Named(_)) => {
if field.optional {
format!("self.{name}.clone().map(|m| m.into_iter().map(|(k, v)| (k, v.into())).collect())")
} else {
format!("self.{name}.clone().into_iter().map(|(k, v)| (k, v.into())).collect()")
}
}
TypeRef::Map(_, _) => {
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 => "Default::default()".to_string(),
}
};
writeln!(out, " {name}: {expr},").ok();
}
}
if typ.has_stripped_cfg_fields {
out.push_str(" ..Default::default()\n");
}
out.push_str(" };\n ");
out
}
#[allow(dead_code)]
pub(crate) fn gen_convertible_enum_tainted(
types: &[TypeDef],
enum_tainted: &AHashSet<String>,
enum_names: &AHashSet<String>,
enums: &[EnumDef],
) -> AHashSet<String> {
let mut unconvertible: AHashSet<String> = AHashSet::new();
for typ in types {
if !enum_tainted.contains(&typ.name) {
continue;
}
for field in &typ.fields {
if let Some(enum_name) = get_direct_enum_named(&field.ty, enum_names) {
if let Some(enum_def) = enums.iter().find(|e| e.name == enum_name) {
if enum_def.variants.iter().any(|v| !v.fields.is_empty()) {
unconvertible.insert(typ.name.clone());
}
}
}
}
}
let mut changed = true;
while changed {
changed = false;
for typ in types {
if !enum_tainted.contains(&typ.name) || unconvertible.contains(&typ.name) {
continue;
}
if typ.fields.iter().any(|f| references_named_type(&f.ty, &unconvertible)) {
unconvertible.insert(typ.name.clone());
changed = true;
}
}
}
enum_tainted
.iter()
.filter(|name| !unconvertible.contains(name.as_str()))
.cloned()
.collect()
}
pub(crate) fn gen_enum_tainted_from_binding_to_core(
typ: &TypeDef,
core_import: &str,
enum_names: &AHashSet<String>,
_enum_tainted: &AHashSet<String>,
config: &ConversionConfig,
enums: &[EnumDef],
bridge_type_aliases: &AHashSet<String>,
) -> String {
let core_path = alef_codegen::conversions::core_type_path(typ, core_import);
let mut out = String::with_capacity(512);
writeln!(out, "#[allow(clippy::useless_conversion)]").ok();
writeln!(out, "impl From<{}> for {core_path} {{", typ.name).ok();
writeln!(out, " fn from(val: {}) -> Self {{", typ.name).ok();
writeln!(out, " Self {{").ok();
for field in &typ.fields {
let name = &field.name;
let is_bridge_named = match &field.ty {
alef_core::ir::TypeRef::Named(n) => bridge_type_aliases.contains(n.as_str()),
alef_core::ir::TypeRef::Optional(inner) => {
matches!(inner.as_ref(), alef_core::ir::TypeRef::Named(n) if bridge_type_aliases.contains(n.as_str()))
}
_ => false,
};
if is_bridge_named {
writeln!(out, " {name}: val.{name}.map(|v| (*v.inner).clone()),").ok();
} else if field.sanitized {
writeln!(out, " {name}: Default::default(),").ok();
} else if let Some(enum_name) = get_direct_enum_named(&field.ty, enum_names) {
let conversion =
gen_string_to_enum_expr(&format!("val.{name}"), &enum_name, field.optional, enums, core_import);
writeln!(out, " {name}: {conversion},").ok();
} else if let Some(enum_name) = get_vec_enum_named(&field.ty, enum_names) {
let elem_conversion = gen_string_to_enum_expr("s", &enum_name, false, enums, core_import);
if field.optional {
writeln!(
out,
" {name}: val.{name}.map(|v| v.into_iter().map(|s| {elem_conversion}).collect()),"
)
.ok();
} else {
writeln!(
out,
" {name}: val.{name}.into_iter().map(|s| {elem_conversion}).collect(),"
)
.ok();
}
} else if !field.optional
&& matches!(field.ty, TypeRef::Duration)
&& config.option_duration_on_defaults
&& typ.has_default
{
let cast = if config.cast_large_ints_to_i64 { " as u64" } else { "" };
writeln!(
out,
" {name}: val.{name}.map(|v| std::time::Duration::from_millis(v{cast})).unwrap_or_else(|| {core_path}::default().{name}),"
)
.ok();
} else {
let conversion =
alef_codegen::conversions::field_conversion_to_core_cfg(name, &field.ty, field.optional, config);
let conversion = if let Some(newtype_path) = &field.newtype_wrapper {
if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
match &field.ty {
TypeRef::Optional(_) => format!("{name}: ({expr}).map({newtype_path})"),
TypeRef::Vec(_) => format!("{name}: ({expr}).into_iter().map({newtype_path}).collect()"),
_ if field.optional => format!("{name}: ({expr}).map({newtype_path})"),
_ => format!("{name}: {newtype_path}({expr})"),
}
} else {
conversion
}
} else {
conversion
};
let conversion = if field.is_boxed && matches!(&field.ty, TypeRef::Named(_)) {
if let Some(expr) = conversion.strip_prefix(&format!("{name}: ")) {
if field.optional {
format!("{name}: {expr}.map(Box::new)")
} else {
format!("{name}: Box::new({expr})")
}
} else {
conversion
}
} else {
conversion
};
let conversion = alef_codegen::conversions::apply_core_wrapper_to_core(
&conversion,
name,
&field.core_wrapper,
&field.vec_inner_core_wrapper,
field.optional,
);
writeln!(out, " {conversion},").ok();
}
}
writeln!(out, " }}").ok();
writeln!(out, " }}").ok();
write!(out, "}}").ok();
out
}
fn get_direct_enum_named(ty: &TypeRef, enum_names: &AHashSet<String>) -> Option<String> {
match ty {
TypeRef::Named(name) if enum_names.contains(name.as_str()) => Some(name.clone()),
TypeRef::Optional(inner) => get_direct_enum_named(inner, enum_names),
_ => None,
}
}
fn get_vec_enum_named(ty: &TypeRef, enum_names: &AHashSet<String>) -> Option<String> {
match ty {
TypeRef::Vec(inner) => get_direct_enum_named(inner, enum_names),
TypeRef::Optional(inner) => get_vec_enum_named(inner, enum_names),
_ => None,
}
}
fn gen_string_to_enum_expr(
val_expr: &str,
enum_name: &str,
optional: bool,
enums: &[EnumDef],
core_import: &str,
) -> String {
let enum_def = match enums.iter().find(|e| e.name == enum_name) {
Some(e) => e,
None => return "Default::default()".to_string(),
};
let core_enum_path = alef_codegen::conversions::core_enum_path(enum_def, core_import);
if enum_def.variants.is_empty() {
return "Default::default()".to_string();
}
fn variant_expr(core_path: &str, variant: &alef_core::ir::EnumVariant) -> String {
if variant.fields.is_empty() {
format!("{core_path}::{}", variant.name)
} else if alef_codegen::conversions::is_tuple_variant(&variant.fields) {
let defaults: Vec<&str> = variant.fields.iter().map(|_| "Default::default()").collect();
format!("{core_path}::{}({})", variant.name, defaults.join(", "))
} else {
let defaults: Vec<String> = variant
.fields
.iter()
.map(|f| format!("{}: Default::default()", f.name))
.collect();
format!("{core_path}::{} {{ {} }}", variant.name, defaults.join(", "))
}
}
let has_default_variant = enum_def.variants.iter().any(|v| v.is_default);
let fallback_expr = if has_default_variant {
"Default::default()".to_string()
} else {
variant_expr(&core_enum_path, &enum_def.variants[0])
};
let mut match_arms = String::new();
for variant in &enum_def.variants {
let expr = variant_expr(&core_enum_path, variant);
write!(match_arms, "\"{}\" => {expr}, ", variant.name).ok();
}
write!(match_arms, "_ => {fallback_expr}").ok();
if optional {
format!("{val_expr}.as_deref().map(|s| match s {{ {match_arms} }})")
} else {
format!("match {val_expr}.as_str() {{ {match_arms} }}")
}
}
pub(crate) fn gen_tokio_runtime() -> String {
"static WORKER_RUNTIME: std::sync::LazyLock<tokio::runtime::Runtime> = std::sync::LazyLock::new(|| {
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.expect(\"Failed to create Tokio runtime\")
});"
.to_string()
}