use crate::{
Ctx, ScalarBacking, backing_scalar, bool_tokens, ident, numeric_tokens, scalar_ctor, type_path,
};
use proc_macro2::TokenStream;
use quote::quote;
use ridl_ir::name::{camel_case, snake_case};
use ridl_ir::v2;
pub(crate) fn decl_default_expr(ctx: &Ctx, decl: &v2::Decl) -> Option<TokenStream> {
match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => type_def_default(&decl.name, td),
Some(v2::decl::Kind::StructDef(sd)) => struct_default(ctx, &decl.name, sd),
Some(v2::decl::Kind::EnumDef(ed)) => enum_default(&decl.name, ed),
Some(v2::decl::Kind::EnumSetDef(_)) => Some(enum_set_default(&decl.name)),
Some(v2::decl::Kind::UnionDef(ud)) => union_default(ctx, &decl.name, ud),
Some(_) | None => None,
}
}
struct Slot<'a> {
init_value: Option<&'a str>,
declared_init: Option<&'a str>,
flag: Option<bool>,
hint: &'a str,
}
fn type_def_default(name: &str, td: &v2::TypeDef) -> Option<TokenStream> {
let init = td.init.as_ref()?;
if !init.derivable {
return None;
}
let inner = scalar_default_value(backing_scalar(td), init.value.as_deref())?;
let name_id = ident(name);
let ctor = scalar_ctor(td);
Some(quote! { #name_id::#ctor(#inner) })
}
fn scalar_default_value(backing: ScalarBacking, value: Option<&str>) -> Option<TokenStream> {
match backing {
ScalarBacking::Float => Some(numeric_tokens(value?, true)),
ScalarBacking::Integer => Some(numeric_tokens(value?, false)),
ScalarBacking::Boolean => Some(bool_tokens(value.unwrap_or("false"))),
ScalarBacking::String => match value {
Some(text) if !text.is_empty() => Some(quote! { #text.to_string() }),
_ => Some(quote! { String::new() }),
},
ScalarBacking::Bytes => match value {
Some(text) if !text.is_empty() => None,
_ => Some(quote! { Vec::new() }),
},
}
}
fn struct_default(ctx: &Ctx, name: &str, sd: &v2::StructDef) -> Option<TokenStream> {
let name_id = ident(name);
let mut inits = Vec::new();
for member in &sd.members {
if let Some(v2::struct_member::Member::Field(field)) = &member.member {
let ft = field.r#type.as_ref()?;
let fname = ident(&snake_case(&field.name));
let hint = format!("{}{}", camel_case(name), camel_case(&field.name));
let init = field.init.as_ref();
let slot = Slot {
init_value: init.and_then(|i| i.value.as_deref()),
declared_init: field.declared_init.as_deref(),
flag: Some(init.map(|i| i.derivable).unwrap_or(false)),
hint: &hint,
};
let expr = slot_default(ctx, ft, &slot)?;
inits.push(quote! { #fname: #expr });
}
}
Some(quote! { #name_id { #(#inits),* } })
}
pub(crate) fn tuple_default_expr(
ctx: &Ctx,
name: &str,
tuple: &v2::TupleType,
) -> Option<TokenStream> {
let name_id = ident(name);
let mut inits = Vec::new();
for field in &tuple.fields {
let ft = field.r#type.as_ref()?;
let fname = ident(&snake_case(&field.name));
let hint = format!("{}{}", name, camel_case(&field.name));
let slot = Slot {
init_value: None,
declared_init: None,
flag: None,
hint: &hint,
};
let expr = slot_default(ctx, ft, &slot)?;
inits.push(quote! { #fname: #expr });
}
Some(quote! { #name_id { #(#inits),* } })
}
fn slot_default(ctx: &Ctx, ft: &v2::FieldType, slot: &Slot) -> Option<TokenStream> {
if ft.optional {
return Some(quote! { None });
}
match &ft.kind {
Some(v2::field_type::Kind::Named(reference)) => named_default(ctx, reference, slot),
Some(v2::field_type::Kind::Primitive(prim)) => primitive_default(*prim, slot),
Some(v2::field_type::Kind::InlineScalar(td)) => {
if slot.flag == Some(false) {
None
} else {
scalar_default_value(backing_scalar(td), slot.init_value)
}
}
Some(v2::field_type::Kind::Tuple(tuple)) => {
if tuple_default_expr(ctx, slot.hint, tuple).is_some() {
let id = ident(slot.hint);
Some(quote! { #id::default() })
} else {
None
}
}
Some(v2::field_type::Kind::Array(array)) => array_default(ctx, array, slot),
Some(v2::field_type::Kind::Map(map)) => map_default(ctx, map, slot),
Some(v2::field_type::Kind::Stream(_)) | None => None,
}
}
fn named_default(ctx: &Ctx, reference: &str, slot: &Slot) -> Option<TokenStream> {
if reference.contains('.') {
if slot.declared_init.is_some() {
None
} else if slot.flag == Some(true) {
let path = type_path(reference);
Some(quote! { #path::default() })
} else {
None
}
} else if let Some(decl) = ctx.lookup(reference) {
match &decl.kind {
Some(v2::decl::Kind::TypeDef(td)) => {
type_def_default(reference, td)?;
let path = type_path(reference);
if let Some(declared) = slot.declared_init {
let inner = scalar_default_value(backing_scalar(td), Some(declared))?;
let ctor = scalar_ctor(td);
Some(quote! { #path::#ctor(#inner) })
} else {
Some(quote! { #path::default() })
}
}
_ => named_same_package_default(ctx, reference),
}
} else {
None
}
}
fn named_same_package_default(ctx: &Ctx, reference: &str) -> Option<TokenStream> {
let decl = ctx.lookup(reference)?;
if !ctx.enter_default(reference) {
return None;
}
let derivable = decl_default_expr(ctx, decl).is_some();
ctx.leave_default(reference);
if derivable {
let path = type_path(reference);
Some(quote! { #path::default() })
} else {
None
}
}
fn primitive_default(prim: i32, slot: &Slot) -> Option<TokenStream> {
match v2::PrimitiveType::try_from(prim).unwrap_or(v2::PrimitiveType::Unspecified) {
v2::PrimitiveType::Integer => Some(numeric_tokens(slot.init_value.unwrap_or("0"), false)),
v2::PrimitiveType::Float => Some(numeric_tokens(slot.init_value.unwrap_or("0"), true)),
v2::PrimitiveType::Boolean => Some(bool_tokens(slot.init_value.unwrap_or("false"))),
v2::PrimitiveType::String if slot.flag != Some(false) => Some(quote! { String::new() }),
v2::PrimitiveType::Bytes if slot.flag != Some(false) => Some(quote! { Vec::new() }),
_ => None,
}
}
fn array_default(ctx: &Ctx, array: &v2::ArrayType, slot: &Slot) -> Option<TokenStream> {
let element = array.element.as_ref()?;
let hint = format!("{}Element", slot.hint);
let element_slot = Slot {
init_value: None,
declared_init: None,
flag: slot.flag,
hint: &hint,
};
if array.min == array.max {
if array.max == 0 {
return Some(quote! { [] });
}
let elem = slot_default(ctx, element, &element_slot)?;
Some(quote! { ::core::array::from_fn(|_| #elem) })
} else if array.min == 0 {
Some(quote! { Vec::new() })
} else {
let elem = slot_default(ctx, element, &element_slot)?;
let count = count_tokens(array.min);
Some(quote! { (0..#count).map(|_| #elem).collect() })
}
}
fn map_default(ctx: &Ctx, map: &v2::MapType, slot: &Slot) -> Option<TokenStream> {
if map.min == 0 {
return Some(quote! { Vec::new() });
}
let key_hint = format!("{}Key", slot.hint);
let value_hint = format!("{}Value", slot.hint);
let key_slot = Slot {
init_value: None,
declared_init: None,
flag: slot.flag,
hint: &key_hint,
};
let value_slot = Slot {
init_value: None,
declared_init: None,
flag: slot.flag,
hint: &value_hint,
};
let key = slot_default(ctx, map.key.as_ref()?, &key_slot)?;
let value = slot_default(ctx, map.value.as_ref()?, &value_slot)?;
let count = count_tokens(map.min);
Some(quote! { (0..#count).map(|_| (#key, #value)).collect() })
}
fn enum_default(name: &str, ed: &v2::EnumDef) -> Option<TokenStream> {
let chosen = ed
.values
.iter()
.find(|value| value.value == 0)
.or_else(|| ed.values.iter().min_by_key(|value| value.value))?;
let name_id = ident(name);
let variant = ident(&chosen.name);
Some(quote! { #name_id::#variant })
}
fn enum_set_default(name: &str) -> TokenStream {
let name_id = ident(name);
quote! { #name_id(0) }
}
fn union_default(ctx: &Ctx, name: &str, ud: &v2::UnionDef) -> Option<TokenStream> {
let first = ud.arms.first()?;
let arm_default = if first.type_ref.contains('.') {
None
} else {
named_same_package_default(ctx, &first.type_ref)
}?;
let name_id = ident(name);
let variant = ident(&camel_case(&first.name));
Some(quote! { #name_id::#variant(#arm_default) })
}
fn count_tokens(value: u64) -> TokenStream {
value.to_string().parse().unwrap_or_else(|_| quote! { 0 })
}