use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{Ident, Type};
use crate::parse::{BorderKind, FieldCategory, FieldMeta, LayerMeta};
pub(crate) fn gen_validate(
opt_name: &Ident,
fields: &[FieldMeta],
layer: &LayerMeta,
) -> TokenStream {
let resolved_name = layer
.resolved_name
.clone()
.unwrap_or_else(|| format_ident!("Resolved{}", opt_name));
let field_inits = gen_field_inits(fields, layer);
quote! {
#[allow(dead_code)]
#[allow(clippy::ptr_arg)] impl #resolved_name {
pub(crate) fn validate_widget(
source: &#opt_name,
prefix: &str,
_dpi: f32,
missing: &mut Vec<String>,
) -> Self {
Self {
#field_inits
}
}
}
}
}
fn is_border_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty
&& let Some(seg) = type_path.path.segments.last()
{
return seg.ident == "ResolvedWidgetBorder";
}
false
}
fn gen_field_inits(fields: &[FieldMeta], layer: &LayerMeta) -> TokenStream {
let inits: Vec<TokenStream> = fields
.iter()
.map(|f| {
let ident = &f.ident;
let field_name = ident.to_string();
match &f.category {
FieldCategory::Option => {
let inner = extract_option_inner_ty(&f.ty);
let fallback = fallback_for_ty(&inner, &field_name);
quote! {
#ident: crate::resolve::validate_helpers::require(
&source.#ident,
&format!("{}.{}", prefix, #field_name),
missing,
#fallback,
),
}
}
FieldCategory::SoftOption => {
quote! {
#ident: source.#ident,
}
}
FieldCategory::Nested { resolved_ty } => {
if is_border_type(resolved_ty) {
let kind_variant = match layer.border_kind {
BorderKind::Full => quote! { Full },
BorderKind::Partial => quote! { Partial },
BorderKind::None => quote! { None },
};
quote! {
#ident: crate::resolve::validate_helpers::validate_border(
&source.#ident,
&format!("{}.{}", prefix, #field_name),
crate::resolve::validate_helpers::BorderKind::#kind_variant,
missing,
),
}
} else {
let opt_ty = extract_option_inner_ty(&f.ty);
quote! {
#ident: <#opt_ty as crate::resolve::validate_helpers::ValidateNested>::validate_nested(
&source.#ident,
&format!("{}.{}", prefix, #field_name),
_dpi,
missing,
),
}
}
}
}
})
.collect();
quote! { #(#inits)* }
}
fn fallback_for_ty(ty: &Type, field_name: &str) -> TokenStream {
if let Type::Path(type_path) = ty
&& let Some(seg) = type_path.path.segments.last()
{
let name = seg.ident.to_string();
return match name.as_str() {
"Rgba" => quote! { crate::color::Rgba::TRANSPARENT },
"f32" => quote! { 0.0f32 },
"u16" => quote! { 0u16 },
"bool" => quote! { false },
"Arc" => quote! { std::sync::Arc::<str>::from("") },
"String" => quote! { String::new() },
"DialogButtonOrder" => {
quote! { crate::model::DialogButtonOrder::PrimaryRight }
}
other => {
let msg = format!(
"ThemeWidget derive: field `{}` has unsupported Option inner type `{}` \
for the G1 fallback mapping. Add a branch to \
`native-theme-derive::gen_validate::fallback_for_ty` that maps this type \
to its zero-value sentinel.",
field_name, other
);
quote! { compile_error!(#msg) }
}
};
}
let msg = format!(
"ThemeWidget derive: field `{}` has a non-path Option inner type. \
Only simple named types (Rgba, f32, u16, bool, Arc<str>) are supported \
as G1 fallback sentinels.",
field_name
);
quote! { compile_error!(#msg) }
}
fn extract_option_inner_ty(ty: &syn::Type) -> syn::Type {
if let syn::Type::Path(type_path) = ty
&& let Some(seg) = type_path.path.segments.last()
&& seg.ident == "Option"
&& let syn::PathArguments::AngleBracketed(args) = &seg.arguments
&& let Some(syn::GenericArgument::Type(inner)) = args.args.first()
{
return inner.clone();
}
ty.clone()
}