condition_matcher_derive/
lib.rs1use proc_macro::TokenStream;
19use quote::quote;
20use syn::{parse_macro_input, Data, DeriveInput, Fields, Type};
21
22#[proc_macro_derive(Matchable)]
44pub fn matchable_derive(input: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(input as DeriveInput);
46 let name = &input.ident;
47 let generics = &input.generics;
48 let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
49
50 let field_match_arms = match &input.data {
51 Data::Struct(data) => match &data.fields {
52 Fields::Named(fields) => {
53 let arms = fields.named.iter().map(|f| {
54 let field_name = &f.ident;
55 let field_name_str = field_name.as_ref().unwrap().to_string();
56 let field_type = &f.ty;
57
58 if is_option_type(field_type) {
60 quote! {
61 #field_name_str => self.#field_name.as_ref().map(|v| v as &dyn std::any::Any),
62 }
63 } else {
64 quote! {
65 #field_name_str => Some(&self.#field_name as &dyn std::any::Any),
66 }
67 }
68 });
69 quote! {
70 #(#arms)*
71 }
72 }
73 Fields::Unnamed(_) => {
74 quote! {}
76 }
77 Fields::Unit => {
78 quote! {}
79 }
80 },
81 Data::Enum(_) => {
82 quote! {}
83 }
84 Data::Union(_) => {
85 quote! {}
86 }
87 };
88
89 let has_option_fields = match &input.data {
91 Data::Struct(data) => match &data.fields {
92 Fields::Named(fields) => fields.named.iter().any(|f| is_option_type(&f.ty)),
93 _ => false,
94 },
95 _ => false,
96 };
97
98 let is_none_impl = if has_option_fields {
99 quote! {
100 fn is_none(&self) -> bool {
101 false
102 }
103 }
104 } else {
105 quote! {}
106 };
107
108 let length_impl = match &input.data {
110 Data::Struct(data) => match &data.fields {
111 Fields::Named(fields) => {
112 let has_len_field = fields.named.iter().any(|f| {
113 f.ident.as_ref().map(|i| i == "len" || i == "length").unwrap_or(false)
114 });
115 if has_len_field {
116 quote! {
117 fn get_length(&self) -> Option<usize> {
118 Some(self.len as usize)
119 }
120 }
121 } else {
122 quote! {}
123 }
124 }
125 _ => quote! {},
126 },
127 _ => quote! {},
128 };
129
130 let expanded = quote! {
131 impl #impl_generics Matchable for #name #ty_generics #where_clause {
132 fn get_field(&self, field: &str) -> Option<&dyn std::any::Any> {
133 match field {
134 #field_match_arms
135 _ => None,
136 }
137 }
138
139 #length_impl
140 #is_none_impl
141 }
142 };
143
144 TokenStream::from(expanded)
145}
146
147fn is_option_type(ty: &Type) -> bool {
149 if let Type::Path(type_path) = ty
150 && let Some(segment) = type_path.path.segments.last()
151 {
152 return segment.ident == "Option";
153 }
154 false
155}