use ahash::{AHashMap, AHashSet};
use alef_core::ir::{ApiSurface, EnumDef, FieldDef, PrimitiveType, TypeDef, TypeRef};
use crate::conversions::ConversionConfig;
pub fn input_type_names(surface: &ApiSurface) -> AHashSet<String> {
let mut names = AHashSet::new();
for func in &surface.functions {
for param in &func.params {
collect_named_types(¶m.ty, &mut names);
}
}
for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
for method in &typ.methods {
for param in &method.params {
collect_named_types(¶m.ty, &mut names);
}
}
}
for func in &surface.functions {
collect_named_types(&func.return_type, &mut names);
}
for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
for method in &typ.methods {
collect_named_types(&method.return_type, &mut names);
}
}
for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
if !typ.is_opaque && !typ.methods.is_empty() {
for field in &typ.fields {
if !field.sanitized {
collect_named_types(&field.ty, &mut names);
}
}
}
}
for e in &surface.enums {
if names.contains(&e.name) {
for variant in &e.variants {
for field in &variant.fields {
collect_named_types(&field.ty, &mut names);
}
}
}
}
let mut changed = true;
while changed {
changed = false;
let snapshot: Vec<String> = names.iter().cloned().collect();
for name in &snapshot {
if let Some(typ) = surface.types.iter().find(|t| t.name == *name) {
for field in &typ.fields {
let mut field_names = AHashSet::new();
collect_named_types(&field.ty, &mut field_names);
for n in field_names {
if names.insert(n) {
changed = true;
}
}
}
}
if let Some(e) = surface.enums.iter().find(|e| e.name == *name) {
for variant in &e.variants {
for field in &variant.fields {
let mut field_names = AHashSet::new();
collect_named_types(&field.ty, &mut field_names);
for n in field_names {
if names.insert(n) {
changed = true;
}
}
}
}
}
}
}
names
}
fn collect_named_types(ty: &TypeRef, out: &mut AHashSet<String>) {
match ty {
TypeRef::Named(name) => {
out.insert(name.clone());
}
TypeRef::Optional(inner) | TypeRef::Vec(inner) => collect_named_types(inner, out),
TypeRef::Map(k, v) => {
collect_named_types(k, out);
collect_named_types(v, out);
}
_ => {}
}
}
pub fn field_references_excluded_type(ty: &TypeRef, exclude_types: &[String]) -> bool {
match ty {
TypeRef::Named(name) => exclude_types.iter().any(|e| e == name),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => field_references_excluded_type(inner, exclude_types),
TypeRef::Map(k, v) => {
field_references_excluded_type(k, exclude_types) || field_references_excluded_type(v, exclude_types)
}
_ => false,
}
}
pub(crate) fn needs_i64_cast(p: &PrimitiveType) -> bool {
matches!(p, PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize)
}
pub(crate) fn needs_i32_cast(p: &PrimitiveType) -> bool {
matches!(
p,
PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 | PrimitiveType::I8 | PrimitiveType::I16
)
}
pub(crate) fn needs_f64_cast(p: &PrimitiveType) -> bool {
matches!(
p,
PrimitiveType::U64 | PrimitiveType::I64 | PrimitiveType::Usize | PrimitiveType::Isize | PrimitiveType::F32
)
}
pub(crate) fn core_prim_str(p: &PrimitiveType) -> &'static str {
match p {
PrimitiveType::U64 => "u64",
PrimitiveType::Usize => "usize",
PrimitiveType::Isize => "isize",
PrimitiveType::F32 => "f32",
PrimitiveType::Bool => "bool",
PrimitiveType::U8 => "u8",
PrimitiveType::U16 => "u16",
PrimitiveType::U32 => "u32",
PrimitiveType::I8 => "i8",
PrimitiveType::I16 => "i16",
PrimitiveType::I32 => "i32",
PrimitiveType::I64 => "i64",
PrimitiveType::F64 => "f64",
}
}
pub(crate) fn binding_prim_str(p: &PrimitiveType) -> &'static str {
match p {
PrimitiveType::U64 | PrimitiveType::Usize | PrimitiveType::Isize => "i64",
PrimitiveType::F32 => "f64",
PrimitiveType::Bool => "bool",
PrimitiveType::U8 | PrimitiveType::U16 | PrimitiveType::U32 => "i32",
PrimitiveType::I8 | PrimitiveType::I16 | PrimitiveType::I32 => "i32",
PrimitiveType::I64 => "i64",
PrimitiveType::F64 => "f64",
}
}
pub fn core_to_binding_convertible_types(surface: &ApiSurface) -> AHashSet<String> {
let convertible_enums: AHashSet<&str> = surface
.enums
.iter()
.filter(|e| can_generate_enum_conversion_from_core(e))
.map(|e| e.name.as_str())
.collect();
let opaque_type_names: AHashSet<&str> = surface
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.as_str())
.collect();
let data_enum_names: AHashSet<&str> = surface
.enums
.iter()
.filter(|e| e.variants.iter().any(|v| !v.fields.is_empty()))
.map(|e| e.name.as_str())
.collect();
let (enum_paths, type_paths) = build_rust_path_maps(surface);
let mut convertible: AHashSet<String> = surface
.types
.iter()
.filter(|t| !t.is_opaque)
.map(|t| t.name.clone())
.collect();
let mut changed = true;
while changed {
changed = false;
let snapshot: Vec<String> = convertible.iter().cloned().collect();
let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
known.extend(&opaque_type_names);
known.extend(&data_enum_names);
let mut to_remove = Vec::new();
for type_name in &snapshot {
if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
let ok = typ.fields.iter().all(|f| {
if f.sanitized {
true
} else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
false
} else {
is_field_convertible(&f.ty, &convertible_enums, &known)
}
});
if !ok {
to_remove.push(type_name.clone());
}
}
}
for name in to_remove {
if convertible.remove(&name) {
changed = true;
}
}
}
convertible
}
pub fn convertible_types(surface: &ApiSurface) -> AHashSet<String> {
let convertible_enums: AHashSet<&str> = surface
.enums
.iter()
.filter(|e| can_generate_enum_conversion(e))
.map(|e| e.name.as_str())
.collect();
let _all_type_names: AHashSet<&str> = surface.types.iter().map(|t| t.name.as_str()).collect();
let default_type_names: AHashSet<&str> = surface
.types
.iter()
.filter(|t| t.has_default)
.map(|t| t.name.as_str())
.collect();
let mut convertible: AHashSet<String> = surface
.types
.iter()
.filter(|t| !t.is_opaque)
.map(|t| t.name.clone())
.collect();
let opaque_type_names: AHashSet<&str> = surface
.types
.iter()
.filter(|t| t.is_opaque)
.map(|t| t.name.as_str())
.collect();
let data_enum_names: AHashSet<&str> = surface
.enums
.iter()
.filter(|e| e.variants.iter().any(|v| !v.fields.is_empty()))
.map(|e| e.name.as_str())
.collect();
let (enum_paths, type_paths) = build_rust_path_maps(surface);
let mut changed = true;
while changed {
changed = false;
let snapshot: Vec<String> = convertible.iter().cloned().collect();
let mut known: AHashSet<&str> = convertible.iter().map(|s| s.as_str()).collect();
known.extend(&opaque_type_names);
known.extend(&data_enum_names);
let mut to_remove = Vec::new();
for type_name in &snapshot {
if let Some(typ) = surface.types.iter().find(|t| t.name == *type_name) {
let ok = typ.fields.iter().all(|f| {
if f.sanitized {
sanitized_field_has_default(&f.ty, &default_type_names)
} else if field_has_path_mismatch(f, &enum_paths, &type_paths) {
false
} else {
is_field_convertible(&f.ty, &convertible_enums, &known)
}
});
if !ok {
to_remove.push(type_name.clone());
}
}
}
for name in to_remove {
if convertible.remove(&name) {
changed = true;
}
}
}
convertible
}
fn sanitized_field_has_default(ty: &TypeRef, default_types: &AHashSet<&str>) -> bool {
match ty {
TypeRef::Primitive(_)
| TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Path
| TypeRef::Unit
| TypeRef::Duration
| TypeRef::Json => true,
TypeRef::Optional(_) => true,
TypeRef::Vec(_) => true,
TypeRef::Map(_, _) => true,
TypeRef::Named(name) => {
if is_tuple_type_name(name) {
true
} else {
default_types.contains(name.as_str())
}
}
}
}
pub fn can_generate_conversion(typ: &TypeDef, convertible: &AHashSet<String>) -> bool {
convertible.contains(&typ.name)
}
pub(crate) fn is_field_convertible(
ty: &TypeRef,
convertible_enums: &AHashSet<&str>,
known_types: &AHashSet<&str>,
) -> bool {
match ty {
TypeRef::Primitive(_)
| TypeRef::String
| TypeRef::Char
| TypeRef::Bytes
| TypeRef::Path
| TypeRef::Unit
| TypeRef::Duration => true,
TypeRef::Json => true,
TypeRef::Optional(inner) | TypeRef::Vec(inner) => is_field_convertible(inner, convertible_enums, known_types),
TypeRef::Map(k, v) => {
is_field_convertible(k, convertible_enums, known_types)
&& is_field_convertible(v, convertible_enums, known_types)
}
TypeRef::Named(name) if is_tuple_type_name(name) => true,
TypeRef::Named(name) => convertible_enums.contains(name.as_str()) || known_types.contains(name.as_str()),
}
}
fn field_has_path_mismatch(
field: &FieldDef,
enum_rust_paths: &AHashMap<&str, &str>,
type_rust_paths: &AHashMap<&str, &str>,
) -> bool {
let name = match &field.ty {
TypeRef::Named(n) => n.as_str(),
TypeRef::Optional(inner) | TypeRef::Vec(inner) => match inner.as_ref() {
TypeRef::Named(n) => n.as_str(),
_ => return false,
},
_ => return false,
};
if let Some(field_path) = &field.type_rust_path {
if let Some(enum_path) = enum_rust_paths.get(name) {
if !paths_compatible(field_path, enum_path) {
return true;
}
}
if let Some(type_path) = type_rust_paths.get(name) {
if !paths_compatible(field_path, type_path) {
return true;
}
}
}
false
}
fn paths_compatible(a: &str, b: &str) -> bool {
if a == b {
return true;
}
let a_norm = a.replace('-', "_");
let b_norm = b.replace('-', "_");
if a_norm == b_norm {
return true;
}
if a_norm.ends_with(&b_norm) || b_norm.ends_with(&a_norm) {
return true;
}
let a_root = a_norm.split("::").next().unwrap_or("");
let b_root = b_norm.split("::").next().unwrap_or("");
let a_name = a_norm.rsplit("::").next().unwrap_or("");
let b_name = b_norm.rsplit("::").next().unwrap_or("");
a_root == b_root && a_name == b_name
}
fn build_rust_path_maps(surface: &ApiSurface) -> (AHashMap<&str, &str>, AHashMap<&str, &str>) {
let enum_paths: AHashMap<&str, &str> = surface
.enums
.iter()
.map(|e| (e.name.as_str(), e.rust_path.as_str()))
.collect();
let type_paths: AHashMap<&str, &str> = surface
.types
.iter()
.map(|t| (t.name.as_str(), t.rust_path.as_str()))
.collect();
(enum_paths, type_paths)
}
pub fn can_generate_enum_conversion(enum_def: &EnumDef) -> bool {
!enum_def.variants.is_empty()
}
pub fn can_generate_enum_conversion_from_core(enum_def: &EnumDef) -> bool {
!enum_def.variants.is_empty()
}
pub fn is_tuple_variant(fields: &[FieldDef]) -> bool {
!fields.is_empty()
&& fields[0]
.name
.strip_prefix('_')
.is_some_and(|rest: &str| rest.chars().all(|c: char| c.is_ascii_digit()))
}
pub fn is_newtype(typ: &TypeDef) -> bool {
typ.fields.len() == 1 && typ.fields[0].name == "_0"
}
pub(crate) fn is_tuple_type_name(name: &str) -> bool {
name.starts_with('(')
}
pub fn core_type_path(typ: &TypeDef, core_import: &str) -> String {
core_type_path_remapped(typ, core_import, &[])
}
pub fn core_type_path_remapped(typ: &TypeDef, core_import: &str, remaps: &[(&str, &str)]) -> String {
let path = typ.rust_path.replace('-', "_");
if path.contains("::") {
apply_crate_remaps(&path, remaps)
} else {
format!("{core_import}::{}", typ.name)
}
}
pub fn apply_crate_remaps(path: &str, remaps: &[(&str, &str)]) -> String {
if remaps.is_empty() {
return path.to_string();
}
if let Some(sep) = path.find("::") {
let leading = &path[..sep];
if let Some(&(_, override_crate)) = remaps.iter().find(|(orig, _)| *orig == leading) {
return format!("{override_crate}{}", &path[sep..]);
}
}
path.to_string()
}
pub fn has_sanitized_fields(typ: &TypeDef) -> bool {
typ.fields.iter().any(|f| f.sanitized)
}
pub fn core_enum_path(enum_def: &EnumDef, core_import: &str) -> String {
core_enum_path_remapped(enum_def, core_import, &[])
}
pub fn core_enum_path_remapped(enum_def: &EnumDef, core_import: &str, remaps: &[(&str, &str)]) -> String {
let path = enum_def.rust_path.replace('-', "_");
if path.starts_with(core_import) || path.contains("::") {
apply_crate_remaps(&path, remaps)
} else {
format!("{core_import}::{}", enum_def.name)
}
}
pub fn build_type_path_map(surface: &ApiSurface, core_import: &str) -> AHashMap<String, String> {
let mut map = AHashMap::new();
for typ in surface.types.iter().filter(|typ| !typ.is_trait) {
let path = typ.rust_path.replace('-', "_");
let resolved = if path.starts_with(core_import) {
path
} else {
format!("{core_import}::{}", typ.name)
};
map.insert(typ.name.clone(), resolved);
}
for en in &surface.enums {
let path = en.rust_path.replace('-', "_");
let resolved = if path.starts_with(core_import) {
path
} else {
format!("{core_import}::{}", en.name)
};
map.insert(en.name.clone(), resolved);
}
map
}
pub fn resolve_named_path(name: &str, core_import: &str, path_map: &AHashMap<String, String>) -> String {
if let Some(path) = path_map.get(name) {
path.clone()
} else {
format!("{core_import}::{name}")
}
}
pub fn binding_to_core_match_arm(binding_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
binding_to_core_match_arm_ext(binding_prefix, variant_name, fields, false)
}
pub fn binding_to_core_match_arm_ext_cfg(
binding_prefix: &str,
variant_name: &str,
fields: &[FieldDef],
binding_has_data: bool,
config: &ConversionConfig,
) -> String {
use super::binding_to_core::field_conversion_to_core_cfg;
if fields.is_empty() {
format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
} else if !binding_has_data {
if is_tuple_variant(fields) {
let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
format!(
"{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
defaults.join(", ")
)
} else {
let defaults: Vec<String> = fields
.iter()
.map(|f| format!("{}: Default::default()", f.name))
.collect();
format!(
"{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
defaults.join(", ")
)
}
} else if is_tuple_variant(fields) {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let binding_pattern = field_names.join(", ");
let core_args: Vec<String> = fields
.iter()
.map(|f| {
let name = &f.name;
if f.sanitized {
let expr = if let TypeRef::Vec(_) = &f.ty {
format!("{name}.iter().filter_map(|s| serde_json::from_str(s).ok()).collect()")
} else {
format!("serde_json::from_str(&{name}).unwrap_or_default()")
};
return if f.is_boxed { format!("Box::new({expr})") } else { expr };
}
if !config.exclude_types.is_empty() && field_references_excluded_type(&f.ty, config.exclude_types) {
let expr = format!("serde_json::from_str(&{name}).unwrap_or_default()");
return if f.is_boxed { format!("Box::new({expr})") } else { expr };
}
let conv = field_conversion_to_core_cfg(name, &f.ty, f.optional, config);
let expr = if let Some(expr) = conv.strip_prefix(&format!("{name}: ")) {
let expr = expr.replace(&format!("val.{name}"), name);
expr.to_string()
} else {
conv
};
if f.is_boxed { format!("Box::new({expr})") } else { expr }
})
.collect();
format!(
"{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
core_args.join(", ")
)
} else {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let pattern = field_names.join(", ");
let core_fields: Vec<String> = fields
.iter()
.map(|f| {
if f.sanitized {
if let TypeRef::Vec(_) = &f.ty {
return format!(
"{}: {}.iter().filter_map(|s| serde_json::from_str(s).ok()).collect()",
f.name, f.name
);
}
return format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name);
}
let conv = field_conversion_to_core_cfg(&f.name, &f.ty, f.optional, config);
if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
let expr = expr.replace(&format!("val.{}", f.name), &f.name);
format!("{}: {}", f.name, expr)
} else {
conv
}
})
.collect();
format!(
"{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
core_fields.join(", ")
)
}
}
pub fn binding_to_core_match_arm_ext(
binding_prefix: &str,
variant_name: &str,
fields: &[FieldDef],
binding_has_data: bool,
) -> String {
if fields.is_empty() {
format!("{binding_prefix}::{variant_name} => Self::{variant_name},")
} else if !binding_has_data {
if is_tuple_variant(fields) {
let defaults: Vec<&str> = fields.iter().map(|_| "Default::default()").collect();
format!(
"{binding_prefix}::{variant_name} => Self::{variant_name}({}),",
defaults.join(", ")
)
} else {
let defaults: Vec<String> = fields
.iter()
.map(|f| format!("{}: Default::default()", f.name))
.collect();
format!(
"{binding_prefix}::{variant_name} => Self::{variant_name} {{ {} }},",
defaults.join(", ")
)
}
} else if is_tuple_variant(fields) {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let binding_pattern = field_names.join(", ");
let core_args: Vec<String> = fields
.iter()
.map(|f| {
let name = &f.name;
let expr = if matches!(&f.ty, TypeRef::Named(_)) {
format!("{name}.into()")
} else if f.sanitized {
format!("serde_json::from_str(&{name}).unwrap_or_default()")
} else {
name.clone()
};
if f.is_boxed { format!("Box::new({expr})") } else { expr }
})
.collect();
format!(
"{binding_prefix}::{variant_name} {{ {binding_pattern} }} => Self::{variant_name}({}),",
core_args.join(", ")
)
} else {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let pattern = field_names.join(", ");
let core_fields: Vec<String> = fields
.iter()
.map(|f| {
if matches!(&f.ty, TypeRef::Named(_)) {
format!("{}: {}.into()", f.name, f.name)
} else if f.sanitized {
format!("{}: serde_json::from_str(&{}).unwrap_or_default()", f.name, f.name)
} else {
format!("{0}: {0}", f.name)
}
})
.collect();
format!(
"{binding_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
core_fields.join(", ")
)
}
}
pub fn core_to_binding_match_arm(core_prefix: &str, variant_name: &str, fields: &[FieldDef]) -> String {
core_to_binding_match_arm_ext(core_prefix, variant_name, fields, false)
}
pub fn core_to_binding_match_arm_ext_cfg(
core_prefix: &str,
variant_name: &str,
fields: &[FieldDef],
binding_has_data: bool,
config: &ConversionConfig,
) -> String {
use super::core_to_binding::field_conversion_from_core_cfg;
use ahash::AHashSet;
if fields.is_empty() {
format!("{core_prefix}::{variant_name} => Self::{variant_name},")
} else if !binding_has_data {
if is_tuple_variant(fields) {
format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
} else {
format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
}
} else if is_tuple_variant(fields) {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let core_pattern = field_names.join(", ");
let binding_fields: Vec<String> = fields
.iter()
.map(|f| {
let conv =
field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
let mut expr = expr.replace(&format!("val.{}", f.name), &f.name);
if f.is_boxed {
expr = expr.replace(&format!("{}.into()", f.name), &format!("(*{}).into()", f.name));
}
format!("{}: {}", f.name, expr)
} else {
conv
}
})
.collect();
format!(
"{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
binding_fields.join(", ")
)
} else {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let pattern = field_names.join(", ");
let binding_fields: Vec<String> = fields
.iter()
.map(|f| {
let conv =
field_conversion_from_core_cfg(&f.name, &f.ty, f.optional, f.sanitized, &AHashSet::new(), config);
if let Some(expr) = conv.strip_prefix(&format!("{}: ", f.name)) {
let expr = expr.replace(&format!("val.{}", f.name), &f.name);
format!("{}: {}", f.name, expr)
} else {
conv
}
})
.collect();
format!(
"{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
binding_fields.join(", ")
)
}
}
pub fn core_to_binding_match_arm_ext(
core_prefix: &str,
variant_name: &str,
fields: &[FieldDef],
binding_has_data: bool,
) -> String {
if fields.is_empty() {
format!("{core_prefix}::{variant_name} => Self::{variant_name},")
} else if !binding_has_data {
if is_tuple_variant(fields) {
format!("{core_prefix}::{variant_name}(..) => Self::{variant_name},")
} else {
format!("{core_prefix}::{variant_name} {{ .. }} => Self::{variant_name},")
}
} else if is_tuple_variant(fields) {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let core_pattern = field_names.join(", ");
let binding_fields: Vec<String> = fields
.iter()
.map(|f| {
let name = &f.name;
let expr = if f.is_boxed && matches!(&f.ty, TypeRef::Named(_)) {
format!("(*{name}).into()")
} else if f.is_boxed {
format!("*{name}")
} else if matches!(&f.ty, TypeRef::Named(_)) {
format!("{name}.into()")
} else if f.sanitized {
format!("serde_json::to_string(&{name}).unwrap_or_default()")
} else {
name.clone()
};
format!("{name}: {expr}")
})
.collect();
format!(
"{core_prefix}::{variant_name}({core_pattern}) => Self::{variant_name} {{ {} }},",
binding_fields.join(", ")
)
} else {
let field_names: Vec<&str> = fields.iter().map(|f| f.name.as_str()).collect();
let pattern = field_names.join(", ");
let binding_fields: Vec<String> = fields
.iter()
.map(|f| {
if matches!(&f.ty, TypeRef::Named(_)) {
format!("{}: {}.into()", f.name, f.name)
} else if f.sanitized {
format!("{}: serde_json::to_string(&{}).unwrap_or_default()", f.name, f.name)
} else {
format!("{0}: {0}", f.name)
}
})
.collect();
format!(
"{core_prefix}::{variant_name} {{ {pattern} }} => Self::{variant_name} {{ {} }},",
binding_fields.join(", ")
)
}
}