use crate::{
codegen::{
conversions::helpers::{
sanitized_field_to_binding_expr, sanitized_map_field_to_core_expr, sanitized_vec_field_to_core_expr,
},
naming::wire_variant_value,
},
core::ir::{EnumDef, TypeRef},
};
use super::enums::{
tagged_enum_binding_struct_fields, tagged_enum_field_is_tuple, tagged_enum_field_name,
tagged_enum_mixed_named_fields, variant_data_field_names,
};
use super::functions::{core_prim_str, needs_napi_cast};
fn sanitized_binding_to_core_expr(binding_field_name: &str, ty: &TypeRef, optional: bool) -> String {
let is_vec_vec_string = matches!(
ty,
TypeRef::Vec(outer) if matches!(outer.as_ref(), TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::String))
);
if is_vec_vec_string {
return if optional {
format!(
"val.{binding_field_name}.map(|v| {})",
sanitized_vec_field_to_core_expr("v", ty)
)
} else {
sanitized_vec_field_to_core_expr(&format!("val.{binding_field_name}.as_deref().unwrap_or_default()"), ty)
};
}
if optional {
if let Some(inner) = sanitized_map_field_to_core_expr("m", ty) {
return format!("val.{binding_field_name}.map(|m| {inner})");
}
} else if let Some(expr) =
sanitized_map_field_to_core_expr(&format!("val.{binding_field_name}.unwrap_or_default()"), ty)
{
return expr;
}
"Default::default()".to_string()
}
fn sanitized_core_to_binding_expr(f: &str, ty: &TypeRef, optional: bool) -> String {
if optional {
return match sanitized_field_to_binding_expr("v", ty) {
Some(inner) => format!("{f}: {f}.map(|v| {inner})"),
None => format!("{f}: None"),
};
}
match sanitized_field_to_binding_expr(f, ty) {
Some(expr) => format!("{f}: Some({expr})"),
None => format!("{f}: None"),
}
}
pub(super) fn gen_tagged_enum_binding_to_core(
enum_def: &EnumDef,
core_import: &str,
prefix: &str,
struct_names: &ahash::AHashSet<String>,
) -> String {
let core_path = crate::codegen::conversions::core_enum_path(enum_def, core_import);
let binding_name = format!("{prefix}{}", enum_def.name);
let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
let fields_with_binding_struct = tagged_enum_binding_struct_fields(enum_def, struct_names);
let mixed_named_fields = tagged_enum_mixed_named_fields(enum_def);
let variants = enum_def
.variants
.iter()
.map(|variant| {
let tag_value = wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
);
let is_tuple = crate::codegen::conversions::is_tuple_variant(&variant.fields);
let is_empty = variant.fields.is_empty();
if is_empty {
minijinja::context! {
name => variant.name.clone(),
tag_value => tag_value.to_string(),
is_empty => true,
is_tuple => false,
}
} else {
let field_exprs: Vec<String> = variant
.fields
.iter()
.map(|f| {
let binding_field_name = tagged_enum_field_name(variant, f);
let has_binding = fields_with_binding_struct.contains(f.name.as_str());
let is_single_tuple_named = variant.fields.len() == 1
&& tagged_enum_field_is_tuple(f)
&& matches!(&f.ty, TypeRef::Named(_));
let is_mixed = !is_single_tuple_named && mixed_named_fields.contains(&f.name);
if f.sanitized {
let expr = sanitized_binding_to_core_expr(&binding_field_name, &f.ty, f.optional);
if f.is_boxed { format!("Box::new({expr})") } else { expr }
} else if f.optional {
match &f.ty {
TypeRef::Path => {
format!("val.{binding_field_name}.map(std::path::PathBuf::from)")
}
TypeRef::Named(n) if is_mixed => {
let core_type = format!("{core_import}::{n}");
format!(
"val.{}.and_then(|s| serde_json::from_str::<{core_type}>(&s).ok())",
binding_field_name
)
}
TypeRef::Named(_) if has_binding => {
format!("val.{binding_field_name}.map(|v| v.into())")
}
TypeRef::Named(_) => {
format!("val.{binding_field_name}.map(|v| v.into())")
}
TypeRef::Primitive(p) if needs_napi_cast(p) => {
let core_ty = core_prim_str(p);
format!("val.{binding_field_name}.map(|v| v as {core_ty})")
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
format!(
"val.{binding_field_name}.map(|v| v.into_iter().map(Into::into).collect())"
)
}
_ => {
format!("val.{binding_field_name}")
}
}
} else {
let expr = match &f.ty {
TypeRef::Named(n) if is_mixed => {
let core_type = format!("{core_import}::{n}");
format!(
"val.{}.and_then(|s| serde_json::from_str::<{core_type}>(&s).ok()).unwrap_or_default()",
binding_field_name
)
}
TypeRef::Named(_) if has_binding => {
format!("val.{binding_field_name}.map(|v| v.into()).unwrap_or_default()")
}
TypeRef::Named(_) => {
format!("val.{binding_field_name}.map(|v| v.into()).unwrap_or_default()")
}
TypeRef::Path => {
format!(
"val.{binding_field_name}.map(std::path::PathBuf::from).unwrap_or_default()"
)
}
TypeRef::Primitive(p) if needs_napi_cast(p) => {
let core_ty = core_prim_str(p);
format!("val.{binding_field_name}.map(|v| v as {core_ty}).unwrap_or_default()")
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
format!(
"val.{binding_field_name}.map(|v| v.into_iter().map(Into::into).collect()).unwrap_or_default()"
)
}
_ => {
format!("val.{binding_field_name}.unwrap_or_default()")
}
};
if f.is_boxed { format!("Box::new({expr})") } else { expr }
}
})
.collect();
let field_inits: Vec<String> = variant
.fields
.iter()
.zip(field_exprs.iter())
.map(|(f, expr)| format!("{}: {expr}", f.name))
.collect();
minijinja::context! {
name => variant.name.clone(),
tag_value => tag_value,
is_empty => false,
is_tuple => is_tuple,
field_exprs => field_exprs,
field_inits => field_inits,
}
}
})
.collect::<Vec<_>>();
let default_variant = enum_def.variants.first().map(|first| {
let is_tuple = crate::codegen::conversions::is_tuple_variant(&first.fields);
let is_empty = first.fields.is_empty();
if is_empty {
minijinja::context! {
name => first.name.clone(),
is_empty => true,
is_tuple => false,
}
} else if is_tuple {
let defaults: Vec<&str> = first.fields.iter().map(|_| "Default::default()").collect();
minijinja::context! {
name => first.name.clone(),
is_empty => false,
is_tuple => true,
defaults => defaults,
}
} else {
let default_fields: Vec<String> = first
.fields
.iter()
.map(|f| format!("{}: Default::default()", f.name))
.collect();
minijinja::context! {
name => first.name.clone(),
is_empty => false,
is_tuple => false,
default_fields => default_fields,
}
}
});
crate::backends::napi::template_env::render(
"gen_tagged_enum_binding_to_core.jinja",
minijinja::context! {
binding_name => binding_name,
core_path => core_path,
tag_field => tag_field,
variants => variants,
default_variant => default_variant,
},
)
}
pub(super) fn gen_tagged_enum_core_to_binding(
enum_def: &EnumDef,
core_import: &str,
prefix: &str,
struct_names: &ahash::AHashSet<String>,
) -> String {
let core_path = crate::codegen::conversions::core_enum_path(enum_def, core_import);
let binding_name = format!("{prefix}{}", enum_def.name);
let tag_field = enum_def.serde_tag.as_deref().unwrap_or("type");
let fields_with_binding_struct = tagged_enum_binding_struct_fields(enum_def, struct_names);
let mixed_named_fields = tagged_enum_mixed_named_fields(enum_def);
let all_fields: Vec<String> = {
let mut fields = std::collections::BTreeSet::new();
for v in &enum_def.variants {
for f in &v.fields {
if tagged_enum_field_is_tuple(f) && matches!(&f.ty, crate::core::ir::TypeRef::Named(_)) {
continue;
}
fields.insert(tagged_enum_field_name(v, f));
}
}
fields.into_iter().collect()
};
let synth_field_names = variant_data_field_names(enum_def);
let variants = enum_def
.variants
.iter()
.map(|variant| {
let tag_value = wire_variant_value(
&variant.name,
variant.serde_rename.as_deref(),
enum_def.serde_rename_all.as_deref(),
);
let this_synth_field = if variant.fields.len() == 1 {
let field = &variant.fields[0];
if tagged_enum_field_is_tuple(field) && matches!(&field.ty, crate::core::ir::TypeRef::Named(_)) {
Some(tagged_enum_field_name(variant, field))
} else {
None
}
} else {
None
};
if variant.fields.is_empty() {
let mut all_fields_none: Vec<String> = all_fields.iter().map(|f| format!("{f}: None")).collect();
for sf in &synth_field_names {
all_fields_none.push(format!("{sf}: None"));
}
minijinja::context! {
name => variant.name.clone(),
tag_value => tag_value.to_string(),
is_empty => true,
is_tuple => false,
all_fields_none => all_fields_none,
}
} else {
use crate::core::ir::TypeRef;
let is_tuple = crate::codegen::conversions::is_tuple_variant(&variant.fields);
let variant_field_map: std::collections::BTreeMap<String, &crate::core::ir::FieldDef> = variant
.fields
.iter()
.map(|f| (tagged_enum_field_name(variant, f), f))
.collect();
let destructured: Vec<String> = variant
.fields
.iter()
.map(|f| {
let binding_field_name = tagged_enum_field_name(variant, f);
if f.sanitized && sanitized_field_to_binding_expr("_", &f.ty).is_none() {
if is_tuple {
format!("_{binding_field_name}")
} else {
format!("{}: _{}", f.name, f.name)
}
} else {
binding_field_name
}
})
.collect();
let mut field_inits: Vec<String> = all_fields
.iter()
.map(|f| {
if let Some(field) = variant_field_map.get(f) {
let has_binding = fields_with_binding_struct.contains(f.as_str());
let is_mixed = mixed_named_fields.contains(field.name.as_str());
let boxed_deref = if field.is_boxed { "*" } else { "" };
if field.sanitized {
sanitized_core_to_binding_expr(f, &field.ty, field.optional)
} else if field.optional {
match &field.ty {
TypeRef::Path => format!("{f}: {f}.map(|p| p.to_string_lossy().to_string())"),
TypeRef::Named(_) if is_mixed => {
format!("{f}: {f}.and_then(|v| serde_json::to_string(&v).ok())")
}
TypeRef::Named(_) if has_binding => {
format!("{f}: {f}.map(|v| (*v).into())",)
}
TypeRef::Named(_) => {
format!("{f}: {f}.map(|v| v.into())")
}
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
format!("{f}: {f}.map(|v| v.into_iter().map(Into::into).collect())")
}
_ => f.clone(),
}
} else {
match &field.ty {
TypeRef::Named(_) if is_mixed => {
format!("{f}: serde_json::to_string(&{f}).ok()")
}
TypeRef::Named(_) if has_binding => {
format!("{f}: Some(({boxed_deref}{f}).into())")
}
TypeRef::Named(_) => format!("{f}: Some(({boxed_deref}{f}).into())"),
TypeRef::Path => format!("{f}: Some({f}.to_string_lossy().to_string())"),
TypeRef::Primitive(p) if needs_napi_cast(p) => match p {
crate::core::ir::PrimitiveType::F32 => format!("{f}: Some({f} as f64)"),
crate::core::ir::PrimitiveType::U64
| crate::core::ir::PrimitiveType::Usize
| crate::core::ir::PrimitiveType::Isize => format!("{f}: Some({f} as i64)"),
_ => format!("{f}: Some({f})"),
},
TypeRef::Vec(inner) if matches!(inner.as_ref(), TypeRef::Named(_)) => {
format!("{f}: Some({f}.into_iter().map(Into::into).collect())")
}
_ => format!("{f}: Some({f})"),
}
}
} else {
format!("{f}: None")
}
})
.collect();
for sf in &synth_field_names {
if this_synth_field.as_deref() == Some(sf.as_str()) {
let field = &variant.fields[0];
let var_name = tagged_enum_field_name(variant, field);
let is_boxed = field.is_boxed;
if is_boxed {
field_inits.push(format!("{sf}: Some((*{var_name}).into())"));
} else {
field_inits.push(format!("{sf}: Some({var_name}.into())"));
}
} else {
field_inits.push(format!("{sf}: None"));
}
}
minijinja::context! {
name => variant.name.clone(),
tag_value => tag_value,
is_empty => false,
is_tuple => is_tuple,
destructured => destructured,
field_inits => field_inits,
}
}
})
.collect::<Vec<_>>();
let has_excluded_variants = !enum_def.excluded_variants.is_empty();
crate::backends::napi::template_env::render(
"gen_tagged_enum_core_to_binding.jinja",
minijinja::context! {
binding_name => binding_name,
core_path => core_path,
tag_field => tag_field,
variants => variants,
has_excluded_variants => has_excluded_variants,
},
)
}