use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use crate::parse::{FieldCategory, FieldMeta, LayerMeta, RangeCheck};
pub(crate) fn gen_ranges(opt_name: &Ident, fields: &[FieldMeta], layer: &LayerMeta) -> TokenStream {
let resolved_name = layer
.resolved_name
.clone()
.unwrap_or_else(|| format_ident!("Resolved{}", opt_name));
let check_stmts = gen_check_stmts(fields);
quote! {
#[allow(clippy::ptr_arg)] impl #resolved_name {
pub(crate) fn check_ranges(
&self,
prefix: &str,
errors: &mut Vec<crate::error::RangeViolation>,
) {
#check_stmts
}
}
}
}
fn gen_check_stmts(fields: &[FieldMeta]) -> TokenStream {
let mut stmts = Vec::new();
for f in fields {
let ident = &f.ident;
let field_name = ident.to_string();
if let Some(ref check) = f.range_check {
let soft = matches!(f.category, FieldCategory::SoftOption);
let value = if soft {
quote! { __value }
} else {
quote! { self.#ident }
};
let stmt = match check {
RangeCheck::NonNegative => quote! {
crate::resolve::validate_helpers::check_non_negative(
#value,
prefix,
#field_name,
errors,
);
},
RangeCheck::Positive => quote! {
crate::resolve::validate_helpers::check_positive(
#value,
prefix,
#field_name,
errors,
);
},
RangeCheck::Range { min, max } => {
let min_lit = *min as f32;
let max_lit = *max as f32;
quote! {
crate::resolve::validate_helpers::check_range_f32(
#value,
#min_lit,
#max_lit,
prefix,
#field_name,
errors,
);
}
}
RangeCheck::RangeU16 { min, max } => {
let min_lit = *min;
let max_lit = *max;
quote! {
crate::resolve::validate_helpers::check_range_u16(
#value,
#min_lit,
#max_lit,
prefix,
#field_name,
errors,
);
}
}
};
if soft {
stmts.push(quote! {
if let Some(__value) = self.#ident {
#stmt
}
});
} else {
stmts.push(stmt);
}
}
if let Some(ref pair_ident) = f.min_max_pair {
let pair_name = pair_ident.to_string();
stmts.push(quote! {
crate::resolve::validate_helpers::check_min_max(
self.#ident,
self.#pair_ident,
prefix,
#field_name,
#pair_name,
errors,
);
});
}
if let FieldCategory::Nested { resolved_ty } = &f.category
&& is_resolved_widget_border(resolved_ty)
{
stmts.push(quote! {
crate::resolve::validate_helpers::check_padding(
&self.#ident.padding,
prefix,
#field_name,
errors,
);
});
}
if let FieldCategory::Nested { resolved_ty } = &f.category
&& is_resolved_font_spec(resolved_ty)
{
stmts.push(quote! {
{
let font = &self.#ident;
if !font.size.is_finite() || font.size <= 0.0 {
errors.push(crate::error::RangeViolation {
path: format!("{prefix}.{}.size", #field_name),
value: font.size as f64,
min: Some(f64::MIN_POSITIVE),
max: None,
});
}
if font.weight < 100 || font.weight > 900 {
errors.push(crate::error::RangeViolation {
path: format!("{prefix}.{}.weight", #field_name),
value: font.weight as f64,
min: Some(100.0),
max: Some(900.0),
});
}
}
});
}
}
quote! { #(#stmts)* }
}
fn is_resolved_font_spec(ty: &syn::Type) -> bool {
if let syn::Type::Path(type_path) = ty
&& let Some(seg) = type_path.path.segments.last()
{
return seg.ident == "ResolvedFontSpec";
}
false
}
fn is_resolved_widget_border(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
}