1use ::proc_macro::TokenStream;
16use ::proc_macro2::TokenStream as TokenStream2;
17use ::quote::quote;
18use ::std::collections::HashSet;
19use ::syn::spanned::Spanned;
20use ::syn::visit::{Visit, visit_ident, visit_path, visit_path_arguments};
21use ::syn::visit_mut::{
22 VisitMut, visit_expr_match_mut, visit_expr_mut, visit_item_impl_mut, visit_item_mut,
23 visit_item_struct_mut, visit_item_type_mut, visit_path_mut,
24};
25use ::syn::{
26 BinOp, Block, Expr, ExprBinary, ExprBlock, ExprLit, ExprParen, ExprPath, ExprUnary,
27 GenericArgument, GenericParam, Generics, Ident, ImplItem, Item, ItemImpl, ItemStruct, Lit,
28 LitBool, Path, PathArguments, PathSegment, Stmt, Type, TypePath, UnOp, WhereClause,
29 WherePredicate, parse, parse_macro_input,
30};
31
32#[proc_macro_attribute]
61pub fn quither(args: TokenStream, input: TokenStream) -> TokenStream {
62 let args_expr_opt: Option<Expr> = (!args.is_empty()).then(|| parse(args).unwrap());
63
64 let ast = parse_macro_input!(input as Item);
65 let mut results = Vec::<TokenStream2>::new();
66 for (has_either, has_neither, has_both) in [
67 (true, false, false),
68 (false, true, false),
69 (false, false, true),
70 (true, true, false),
71 (true, false, true),
72 (false, true, true),
73 (true, true, true),
74 ] {
75 let mut ast = ast.clone();
76 let mut processor = CodeProcessor {
77 has_either,
78 has_neither,
79 has_both,
80 };
81 if let Some(false) = args_expr_opt
82 .as_ref()
83 .and_then(|args_expr| processor.check_quither_condition(&args_expr))
84 {
85 continue;
86 }
87 processor.visit_item_mut(&mut ast);
88
89 let generated_item = quote! { #ast };
90 results.push(generated_item);
91 }
92 quote! {
93 #(#results)*
94 }
95 .into()
96}
97
98struct CodeProcessor {
99 has_either: bool,
100 has_neither: bool,
101 has_both: bool,
102}
103
104impl VisitMut for CodeProcessor {
105 fn visit_path_mut(&mut self, node: &mut Path) {
106 for segment in node.segments.iter_mut() {
109 self.replace_quither_path_segment(segment, |ident, new_part| {
110 ident.to_string().replace("Xither", new_part)
111 });
112 }
113 visit_path_mut(self, node);
114 }
115
116 fn visit_expr_mut(&mut self, expr: &mut Expr) {
117 self.replace_has_quither_expr(expr);
118 visit_expr_mut(self, expr);
119 }
120
121 fn visit_item_mut(&mut self, item: &mut Item) {
122 visit_item_mut(self, item);
123 }
124
125 fn visit_item_struct_mut(&mut self, item_struct: &mut ItemStruct) {
126 visit_item_struct_mut(self, item_struct);
127 self.replace_quither_type_definition(&mut item_struct.ident, &mut item_struct.generics);
128 }
129
130 fn visit_item_type_mut(&mut self, item_type: &mut syn::ItemType) {
131 visit_item_type_mut(self, item_type);
132 self.replace_quither_type_definition(&mut item_type.ident, &mut item_type.generics);
133 }
134
135 fn visit_item_impl_mut(&mut self, item_impl: &mut ItemImpl) {
136 visit_item_impl_mut(self, item_impl);
137
138 item_impl.items.retain_mut(|item| {
139 let attr_vec = match item {
140 ImplItem::Fn(item_fn) => &mut item_fn.attrs,
141 ImplItem::Const(item_const) => &mut item_const.attrs,
142 ImplItem::Type(item_type) => &mut item_type.attrs,
143 ImplItem::Macro(item_macro) => &mut item_macro.attrs,
144 _ => return true,
145 };
146 let quither_attr_result =
147 find_first_and_remove_vec_mut(attr_vec, |attr| self.check_attr_is_true(attr));
148 match quither_attr_result {
149 Some(true) => true,
150 Some(false) => false, None => true,
152 }
153 });
154
155 let unused_type_params = self
158 .remove_unused_type_params(item_impl)
159 .collect::<Vec<_>>();
160 if let Some(where_clause) = &mut item_impl.generics.where_clause {
161 self.remove_where_clause_for_type_params(where_clause, unused_type_params.iter());
162 }
163 }
164
165 fn visit_expr_match_mut(&mut self, ma: &mut syn::ExprMatch) {
166 visit_expr_match_mut(self, ma);
167 ma.arms.retain_mut(|arm| {
168 let attr_vec = &mut arm.attrs;
169 find_first_and_remove_vec_mut(attr_vec, |attr| self.check_attr_is_true(attr))
170 .unwrap_or(true)
171 });
172 }
173}
174
175impl CodeProcessor {
176 fn replace_quither_path_segment<F>(&self, segment: &mut PathSegment, new_name_gen: F)
177 where
178 F: FnOnce(&str, &str) -> String,
179 {
180 let ident_string = segment.ident.to_string();
181 if !ident_string.contains("Xither") {
182 return;
183 }
184 let Some(bool_args) = self.implicit_345th_bool_arguments_for_path_segment(segment) else {
185 return;
186 };
187 let new_name_part = Self::quither_name_gen(bool_args);
188 segment.ident = Ident::new(
189 &new_name_gen(&ident_string, new_name_part),
190 segment.ident.span(),
191 );
192 if bool_args == (false, true, false) {
193 segment.arguments = PathArguments::None
195 } else if let PathArguments::AngleBracketed(syn_args) = &mut segment.arguments {
196 while syn_args.args.len() > 2 {
199 syn_args.args.pop();
200 }
201 if syn_args.args.is_empty() {
202 segment.arguments = PathArguments::None
203 }
204 }
205 }
206
207 fn replace_quither_type_definition(&self, ident: &mut Ident, params: &mut Generics) {
208 if !self.has_either && !self.has_both {
209 params.params.clear();
211 params.where_clause = None;
212 }
213
214 let ident_str = ident.to_string();
215 if let Some(_) = ident_str.find("Xither") {
216 let new_ident_str = ident_str.replace(
217 "Xither",
218 Self::quither_name_gen((self.has_either, self.has_neither, self.has_both)),
219 );
220 *ident = Ident::new(&new_ident_str, ident.span());
221 }
222 }
223
224 fn replace_has_quither_expr(&self, expr: &mut Expr) {
225 let Expr::Path(ExprPath { path, .. }) = expr else {
226 return;
227 };
228 let Some(ident) = path.get_ident() else {
229 return;
230 };
231 let found_value = if ident == "has_either" {
232 Some(self.has_either)
233 } else if ident == "has_neither" {
234 Some(self.has_neither)
235 } else if ident == "has_both" {
236 Some(self.has_both)
237 } else {
238 None
239 };
240 if let Some(found_value) = found_value {
241 *expr = Expr::Lit(ExprLit {
242 lit: Lit::Bool(LitBool {
243 value: found_value,
244 span: expr.span(),
245 }),
246 attrs: Vec::new(),
247 });
248 }
249 }
250
251 fn implicit_345th_bool_arguments_for_path_segment(
252 &self,
253 segment: &syn::PathSegment,
254 ) -> Option<(bool, bool, bool)> {
255 if let PathArguments::AngleBracketed(syn_args) = &segment.arguments {
256 let args = syn_args.args.clone().into_pairs().collect::<Vec<_>>();
257 if args.len() == 5 {
258 let has_either = self.generic_argument_as_a_bool(&args[2].value())?;
259 let has_neither = self.generic_argument_as_a_bool(&args[3].value())?;
260 let has_both = self.generic_argument_as_a_bool(&args[4].value())?;
261 return Some((has_either, has_neither, has_both));
262 }
263 }
264 Some((self.has_either, self.has_neither, self.has_both))
265 }
266
267 fn generic_argument_as_a_bool(&self, arg: &GenericArgument) -> Option<bool> {
268 if let GenericArgument::Const(arg_expr) = arg {
269 self.check_quither_condition(&arg_expr)
270 } else if let GenericArgument::Type(Type::Path(TypePath { path, .. })) = arg {
271 self.check_quither_condition_for_path(&path)
272 } else {
273 None
274 }
275 }
276
277 fn remove_unused_type_params(&self, item_impl: &mut ItemImpl) -> impl Iterator<Item = Ident> {
278 let mut type_param_finder = TypeParamFinder::default();
279 type_param_finder.visit_type(&item_impl.self_ty);
280 if let Some((_, trait_path, _)) = &item_impl.trait_ {
281 type_param_finder.visit_path(trait_path);
282 }
283 let (used, unused) = item_impl
284 .generics
285 .params
286 .iter()
287 .cloned()
288 .partition::<Vec<_>, _>(|param| {
289 let GenericParam::Type(tp) = param else {
290 return false;
291 };
292 type_param_finder.does_appear(&tp.ident)
293 });
294 item_impl.generics.params = used.into_iter().collect();
295 item_impl.generics.params.pop_punct();
296 unused.into_iter().filter_map(|param| {
297 let GenericParam::Type(tp) = param else {
298 return None;
299 };
300 Some(tp.ident)
301 })
302 }
303
304 fn remove_where_clause_for_type_params<'a>(
305 &self,
306 where_clause: &mut WhereClause,
307 unused_type_params: impl Iterator<Item = &'a Ident> + Clone,
308 ) {
309 where_clause.predicates = where_clause
310 .predicates
311 .iter()
312 .cloned()
313 .filter_map(|pred| {
314 let WherePredicate::Type(tp) = &pred else {
315 return Some(pred);
316 };
317 let mut finder = TypeParamFinder::default();
318 finder.visit_type(&tp.bounded_ty);
319 if unused_type_params
320 .clone()
321 .any(|param| finder.does_appear(param))
322 {
323 None
326 } else {
327 Some(pred)
328 }
329 })
330 .collect();
331 where_clause.predicates.pop_punct();
332 }
333
334 fn check_attr_is_true(&self, attr: &syn::Attribute) -> Option<bool> {
335 let attr_path = attr.meta.path();
336 if attr_path.is_ident("either") {
337 return Some(self.has_either);
338 } else if attr_path.is_ident("neither") {
339 return Some(self.has_neither);
340 } else if attr_path.is_ident("both") {
341 return Some(self.has_both);
342 } else if attr_path.is_ident("quither") {
343 return self.check_quither_condition(&attr.parse_args().ok()?);
344 } else {
345 return None;
346 }
347 }
348
349 fn check_quither_condition(&self, args: &Expr) -> Option<bool> {
350 match args {
351 Expr::Binary(ExprBinary {
352 left, right, op, ..
353 }) => {
354 let left = self.check_quither_condition(left)?;
355 let right = self.check_quither_condition(right)?;
356 match op {
357 BinOp::And(_) => Some(left && right),
358 BinOp::Or(_) => Some(left || right),
359 _ => None,
360 }
361 }
362 Expr::Unary(ExprUnary {
363 expr,
364 op: UnOp::Not(_),
365 ..
366 }) => self.check_quither_condition(expr).map(|b| !b),
367 Expr::Paren(ExprParen { expr, .. }) => self.check_quither_condition(expr),
368 Expr::Block(ExprBlock {
369 block: Block { stmts, .. },
370 ..
371 }) => {
372 if stmts.len() != 1 {
373 return None;
374 }
375 let Some(Stmt::Expr(expr, _)) = stmts.first() else {
376 return None;
377 };
378 self.check_quither_condition(expr)
379 }
380 Expr::Path(ExprPath { path, .. }) => self.check_quither_condition_for_path(path),
381 Expr::Lit(ExprLit {
382 lit: Lit::Bool(LitBool { value, .. }),
383 ..
384 }) => Some(*value),
385 _ => None,
386 }
387 }
388
389 fn check_quither_condition_for_path(&self, path: &Path) -> Option<bool> {
390 if path.is_ident("has_either") {
391 Some(self.has_either)
392 } else if path.is_ident("has_neither") {
393 Some(self.has_neither)
394 } else if path.is_ident("has_both") {
395 Some(self.has_both)
396 } else {
397 None
398 }
399 }
400
401 fn quither_name_gen(bool_args: (bool, bool, bool)) -> &'static str {
402 match bool_args {
403 (true, true, true) => "Quither",
404 (true, true, false) => "EitherOrNeither",
405 (true, false, true) => "EitherOrBoth",
406 (true, false, false) => "Either",
407 (false, true, true) => "NeitherOrBoth",
408 (false, true, false) => "Neither",
409 (false, false, true) => "Both",
410 (false, false, false) => "Unreachable",
411 }
412 }
413}
414
415#[derive(Default, Debug)]
416struct TypeParamFinder {
417 idents: HashSet<Ident>,
418}
419
420impl TypeParamFinder {
421 fn does_appear(&self, ident: &Ident) -> bool {
422 self.idents.contains(ident)
423 }
424}
425
426impl<'ast> Visit<'ast> for TypeParamFinder {
427 fn visit_path(&mut self, path: &'ast Path) {
428 visit_path(self, path);
429 if let Some(ident) = path.get_ident() {
430 self.idents.insert(ident.clone());
431 }
432 }
433
434 fn visit_ident(&mut self, ident: &'ast Ident) {
435 visit_ident(self, ident);
436 self.idents.insert(ident.clone());
437 }
438
439 fn visit_path_segment(&mut self, segment: &'ast PathSegment) {
440 visit_path_arguments(self, &segment.arguments);
444 }
445}
446
447fn find_first_and_remove_vec_mut<T, U, F>(vec: &mut Vec<T>, f: F) -> Option<U>
448where
449 F: Fn(&mut T) -> Option<U>,
450{
451 for (i, item) in vec.iter_mut().enumerate() {
452 if let Some(u) = f(item) {
453 vec.remove(i);
454 return Some(u);
455 }
456 }
457 None
458}
459
460#[test]
461fn test_quither_condition_value() {
462 use ::syn::parse_quote;
463
464 let cp = CodeProcessor {
465 has_either: true,
466 has_neither: false,
467 has_both: true,
468 };
469 assert_eq!(
470 Some(true),
471 cp.check_quither_condition(&parse_quote! { true })
472 );
473 assert_eq!(
474 Some(true),
475 cp.check_quither_condition(&parse_quote! { has_either })
476 );
477 assert_eq!(
478 Some(true),
479 cp.check_quither_condition(&parse_quote! { { true } })
480 );
481 assert_eq!(
482 Some(false),
483 cp.check_quither_condition(&parse_quote! { { has_either && has_neither } })
484 );
485}
486
487#[test]
488fn test_visit_path_mut() {
489 use ::proc_macro2::Span;
490 use ::syn::parse_quote_spanned;
491
492 let mut cp = CodeProcessor {
493 has_either: true,
494 has_neither: false,
495 has_both: true,
496 };
497 let span = Span::call_site();
498
499 let mut path = parse_quote_spanned! { span => Xither<L, R> };
500 cp.visit_path_mut(&mut path);
501 assert_eq!(path, parse_quote_spanned! { span => EitherOrBoth<L, R> });
502
503 let mut path = parse_quote_spanned! { span => Xither<L, R, false, false, true> };
504 cp.visit_path_mut(&mut path);
505 assert_eq!(path, parse_quote_spanned! { span => Both<L, R, > });
506
507 let mut path = parse_quote_spanned! { span => Xither<L, R, has_both, has_both, has_neither> };
508 cp.visit_path_mut(&mut path);
509 assert_eq!(
510 path,
511 parse_quote_spanned! { span => EitherOrNeither<L, R, > }
512 );
513}