#![allow(dead_code)] // TODO: remove // This is inspired from `synstructure`, but `synstructure` is not adapted in severals ways // including: // * `&mut` everywhere // * not generic, we use our own `ast`, `synstructure` only knows about `syn` // * missing information (what arm are we in?, what attributes? etc.) use proc_macro2::{self, TokenStream}; use quote::ToTokens; use syn; use ast; use attr; use quote; /// The type of binding to use when generating a pattern. #[derive(Debug, Copy, Clone)] pub enum BindingStyle { /// `x` Move, /// `mut x` MoveMut, /// `ref x` Ref, /// `ref mut x` RefMut, } impl BindingStyle { fn with_packed(self, is_packed: bool) -> BindingStyle { match self { BindingStyle::Move | BindingStyle::MoveMut => self, BindingStyle::Ref if is_packed => BindingStyle::Move, BindingStyle::RefMut if is_packed => BindingStyle::MoveMut, BindingStyle::Ref | BindingStyle::RefMut => self, } } } impl quote::ToTokens for BindingStyle { fn to_tokens(&self, tokens: &mut TokenStream) { match *self { BindingStyle::Move => (), BindingStyle::MoveMut => tokens.extend(quote!(mut)), BindingStyle::Ref => tokens.extend(quote!(ref)), BindingStyle::RefMut => { tokens.extend(quote!(ref mut)); } } } } #[derive(Debug)] pub struct BindingInfo<'a> { pub expr: TokenStream, pub ident: syn::Ident, pub field: &'a ast::Field<'a>, } #[derive(Debug)] pub struct CommonVariant<'a> { path: syn::Path, name: &'a syn::Ident, style: ast::Style, attrs: &'a attr::Input, } pub struct Matcher { binding_name: String, binding_style: BindingStyle, is_packed: bool, field_filter: T, } impl Matcher bool> { pub fn new(style: BindingStyle, is_packed: bool) -> Self { Matcher { binding_name: "__arg".into(), binding_style: style.with_packed(is_packed), is_packed, field_filter: |_| true, } } } impl bool> Matcher { pub fn with_name(self, name: String) -> Self { Matcher { binding_name: name, ..self } } pub fn with_field_filter

(self, field_filter: P) -> Matcher

{ Matcher { field_filter, binding_name: self.binding_name, binding_style: self.binding_style, is_packed: self.is_packed, } } pub fn build_arms(self, input: &ast::Input, binding_name: &str, f: F) -> TokenStream where F: Fn( syn::Path, usize, &syn::Ident, ast::Style, &attr::Input, Vec, ) -> TokenStream, { let variants = self.build_match_pattern(input, binding_name); // Now that we have the patterns, generate the actual branches of the match // expression let mut t = TokenStream::new(); for (i, (variant, (pat, bindings))) in variants.into_iter().enumerate() { let body = f( variant.path, i, variant.name, variant.style, variant.attrs, bindings, ); quote!(#pat => { #body }).to_tokens(&mut t); } t } pub fn build_2_arms( self, (left_matched_expr, right_matched_expr): (TokenStream, TokenStream), left: (&ast::Input, &str), right: (&ast::Input, &str), f: F, ) -> TokenStream where F: Fn( usize, CommonVariant, CommonVariant, (Vec, Vec), ) -> TokenStream, { let left_variants = self.build_match_pattern(left.0, left.1); let right_variants = self.build_match_pattern(right.0, right.1); assert_eq!(left_variants.len(), right_variants.len()); if left_variants.len() == 1 { let (left, (left_pat, left_bindings)) = left_variants.into_iter().next().unwrap(); let (right, (right_pat, right_bindings)) = right_variants.into_iter().next().unwrap(); let body = f(0, left, right, (left_bindings, right_bindings)); quote! { match #left_matched_expr { #left_pat => match #right_matched_expr { #right_pat => #body, }, } } } else { // Now that we have the patterns, generate the actual branches of the match // expression let mut t = TokenStream::new(); for (i, (left, right)) in left_variants.into_iter().zip(right_variants).enumerate() { let (left, (left_pat, left_bindings)) = left; let (right, (right_pat, right_bindings)) = right; let body = f(i, left, right, (left_bindings, right_bindings)); quote!((#left_pat, #right_pat) => { #body }).to_tokens(&mut t); } quote! { match (&#left_matched_expr, &#right_matched_expr) { #t _ => unreachable!(), } } } } /// Generate patterns for matching against all of the variants pub fn build_match_pattern<'a>( &self, input: &'a ast::Input, binding_name: &str, ) -> Vec<(CommonVariant<'a>, (TokenStream, Vec>))> { let ident = &input.ident; match input.body { ast::Body::Enum(ref variants) => variants .iter() .map(|variant| { let variant_ident = &variant.ident; let path = parse_quote!(#ident::#variant_ident); let pat = self.build_match_pattern_impl( &path, variant.style, &variant.fields, binding_name, ); ( CommonVariant { path, name: variant_ident, style: variant.style, attrs: &variant.attrs, }, pat, ) }) .collect(), ast::Body::Struct(style, ref vd) => { let path = parse_quote!(#ident); vec![( CommonVariant { path, name: ident, style, attrs: &input.attrs, }, self.build_match_pattern_impl(ident, style, vd, binding_name), )] } } } fn build_match_pattern_impl<'a, N>( &self, name: &N, style: ast::Style, fields: &'a [ast::Field<'a>], binding_name: &str, ) -> (TokenStream, Vec>) where N: quote::ToTokens, { let (stream, matches) = match style { ast::Style::Unit => (TokenStream::new(), Vec::new()), ast::Style::Tuple => { let (stream, matches) = fields.iter().enumerate().fold( (TokenStream::new(), Vec::new()), |(stream, matches), field| { self.build_inner_pattern( (stream, matches), field, binding_name, |f, ident, binding| { if (self.field_filter)(f) { quote!(#binding #ident ,) } else { quote!(_ ,) } }, ) }, ); (quote! { ( #stream ) }, matches) } ast::Style::Struct => { let (stream, matches) = fields.iter().enumerate().fold( (TokenStream::new(), Vec::new()), |(stream, matches), field| { self.build_inner_pattern( (stream, matches), field, binding_name, |field, ident, binding| { let field_name = field.ident.as_ref().unwrap(); if (self.field_filter)(field) { quote!(#field_name : #binding #ident ,) } else { quote!(#field_name : _ ,) } }, ) }, ); (quote! { { #stream } }, matches) } }; let mut all_tokens = TokenStream::new(); name.to_tokens(&mut all_tokens); all_tokens.extend(stream); (all_tokens, matches) } fn build_inner_pattern<'a>( &self, (mut stream, mut matches): (TokenStream, Vec>), (i, field): (usize, &'a ast::Field), binding_name: &str, f: impl FnOnce(&ast::Field, &syn::Ident, BindingStyle) -> TokenStream, ) -> (TokenStream, Vec>) { let binding_style = self.binding_style; let ident: syn::Ident = syn::Ident::new( &format!("{}_{}", binding_name, i), proc_macro2::Span::call_site(), ); let expr = syn::Expr::Path(syn::ExprPath { attrs: vec![], qself: None, path: syn::Path::from(ident.clone()) }); let expr = if self.is_packed { expr.into_token_stream() } else { quote!((*#expr)) }; f(field, &ident, binding_style).to_tokens(&mut stream); matches.push(BindingInfo { expr, ident, field, }); (stream, matches) } }