use crate::core::ir::{DefaultValue, FieldDef, TypeDef, TypeRef};
use super::shared::{constructor_fields, default_value_for_field_in_type, use_unwrap_or_default};
const MAGNUS_MAX_ARITY: usize = 15;
pub fn gen_magnus_kwargs_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let _ = MAGNUS_MAX_ARITY;
gen_magnus_hash_constructor(typ, type_mapper)
}
fn as_type_path_prefix(type_str: &str) -> String {
if type_str.contains('<') {
format!("<{type_str}>")
} else {
type_str.to_string()
}
}
fn try_convert_or_raise(field_name: &str, type_prefix: &str) -> String {
format!(
"{type_prefix}::try_convert(v).map_err(|e| magnus::Error::new(unsafe {{ magnus::Ruby::get_unchecked() }}.exception_type_error(), format!(\"invalid value for `{field_name}`: {{}}\", e)))?"
)
}
fn gen_magnus_hash_constructor(typ: &TypeDef, type_mapper: &dyn Fn(&TypeRef) -> String) -> String {
let fields: Vec<_> = constructor_fields(typ)
.map(|field| {
let is_optional = field_is_optional_in_rust(field);
let effective_inner_ty = match &field.ty {
TypeRef::Optional(inner) if is_optional => inner.as_ref(),
ty => ty,
};
let inner_type = type_mapper(effective_inner_ty);
let type_prefix = as_type_path_prefix(&inner_type);
let has_callable_default = matches!(
&field.typed_default,
Some(DefaultValue::FunctionCall(_) | DefaultValue::PublicFunctionCall(_))
);
let try_convert = try_convert_or_raise(&field.name, &type_prefix);
let assignment = if is_optional {
format!(
"match kwargs.get(ruby.to_symbol(\"{}\")) {{ Some(v) => Some({}), None => None }},",
field.name, try_convert
)
} else if use_unwrap_or_default(field) {
format!(
"match kwargs.get(ruby.to_symbol(\"{}\")) {{ Some(v) => {}, None => Default::default() }},",
field.name, try_convert
)
} else if matches!(effective_inner_ty, TypeRef::Named(_))
&& !matches!(&field.typed_default, Some(DefaultValue::EnumVariant(_)))
&& !has_callable_default
{
format!(
"match kwargs.get(ruby.to_symbol(\"{}\")) {{ Some(v) => {}, None => return Err(magnus::Error::new(unsafe {{ magnus::Ruby::get_unchecked() }}.exception_arg_error(), \"missing required field: {}\")) }},",
field.name, try_convert, field.name
)
} else {
let default_str = if inner_type == "String" {
if let Some(DefaultValue::EnumVariant(variant)) = &field.typed_default {
use heck::ToSnakeCase;
format!("\"{}\".to_string()", variant.to_snake_case())
} else {
default_value_for_field_in_type(field, "rust", typ)
}
} else {
default_value_for_field_in_type(field, "rust", typ)
};
let default_expr = if has_callable_default && matches!(effective_inner_ty, TypeRef::Named(_)) {
format!("{default_str}.into()")
} else {
default_str
};
format!(
"match kwargs.get(ruby.to_symbol(\"{}\")) {{ Some(v) => {}, None => {} }},",
field.name, try_convert, default_expr
)
};
minijinja::context! {
name => field.name.clone(),
assignment => assignment,
cfg => field.cfg.as_deref(),
}
})
.collect();
crate::codegen::template_env::render(
"config_gen/magnus_hash_constructor.jinja",
minijinja::context! {
fields => fields,
},
)
}
fn field_is_optional_in_rust(field: &FieldDef) -> bool {
field.optional || matches!(&field.ty, TypeRef::Optional(_))
}