fairjax 0.1.0

Fair join pattern matching
Documentation
use crate::parse::sub_pattern::SubPatternDefinition;
use crate::traits::SubPattern;
use proc_macro2::TokenStream;
use quote::quote_spanned;
use syn::{Pat, PatRest, punctuated::Punctuated, spanned::Spanned, token::DotDot};

pub trait SubPatternCodeGen {
    fn generate<'a>(sub_pattern: &'a dyn SubPattern, anonymous: bool) -> TokenStream;
}

pub struct SubPatternCompiler;

impl SubPatternCodeGen for SubPatternCompiler {
    fn generate(sub_pattern: &dyn SubPattern, anonymous: bool) -> TokenStream {
        match sub_pattern.get() {
            SubPatternDefinition::Ident(ident) => quote_spanned!(ident.span() => #ident),
            SubPatternDefinition::Path(p) => quote_spanned!(p.span() => #p),
            SubPatternDefinition::TupleStruct(mut ts) => {
                if anonymous {
                    ts.elems = Punctuated::from_iter(ts.elems.iter().map(|_| {
                        Pat::Wild(syn::PatWild {
                            attrs: vec![],
                            underscore_token: syn::token::Underscore::default(),
                        })
                    }));
                }
                quote_spanned!(ts.span() => #ts)
            }
            SubPatternDefinition::Struct(mut s) => {
                if anonymous {
                    s.fields = Punctuated::new();
                    s.rest = Some(PatRest {
                        attrs: vec![],
                        dot2_token: DotDot::default(),
                    });
                }
                quote_spanned!(s.span() => #s)
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use proc_macro_utils::assert_tokens;
    use quote::format_ident;
    use syn::{
        FieldPat, Pat, PatIdent, PatPath, PatStruct, PatTupleStruct, Path, parse_quote,
        punctuated::Punctuated, token,
    };

    fn pat_ident(name: &str) -> PatIdent {
        PatIdent {
            attrs: vec![],
            by_ref: None,
            mutability: None,
            ident: format_ident!("{}", name),
            subpat: None,
        }
    }

    fn pat_tuple_struct(path: Path, fields: Vec<&str>) -> PatTupleStruct {
        PatTupleStruct {
            attrs: vec![],
            qself: None,
            path,
            paren_token: token::Paren::default(),
            elems: Punctuated::from_iter(
                fields.into_iter().map(|name| Pat::Ident(pat_ident(name))),
            ),
        }
    }

    fn pat_struct(path: Path, fields: Vec<&str>, rest: bool) -> PatStruct {
        PatStruct {
            attrs: vec![],
            qself: None,
            path,
            brace_token: token::Brace::default(),
            fields: Punctuated::from_iter(fields.into_iter().map(|name| FieldPat {
                attrs: vec![],
                member: syn::Member::Named(format_ident!("{}", name)),
                colon_token: None,
                pat: Box::new(Pat::Ident(pat_ident(name))),
            })),
            rest: rest.then(|| PatRest {
                attrs: vec![],
                dot2_token: DotDot::default(),
            }),
        }
    }

    #[test]
    fn test_generate_path() {
        let pat_path: PatPath = parse_quote!(Foo);
        let sub_pattern = SubPatternDefinition::Path(pat_path);
        let tokens = SubPatternCompiler::generate(&sub_pattern, false);
        assert_tokens!(tokens, { Foo });
    }

    #[test]
    fn test_generate_tuple_struct() {
        let pat_tuple_struct = pat_tuple_struct(parse_quote!(Bar), vec!["a", "b"]);
        let sub_pattern = SubPatternDefinition::TupleStruct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, false);
        assert_tokens!(tokens, { Bar(a, b) });
    }

    #[test]
    fn test_generate_tuple_struct_anonymus() {
        let pat_tuple_struct = pat_tuple_struct(parse_quote!(Bar), vec!["a", "b"]);
        let sub_pattern = SubPatternDefinition::TupleStruct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, true);
        assert_tokens!(tokens, { Bar(_, _) });
    }

    #[test]
    fn test_generate_struct() {
        let pat_tuple_struct = pat_struct(parse_quote!(Foo), vec!["a", "b"], false);
        let sub_pattern = SubPatternDefinition::Struct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, false);
        assert_tokens!(tokens, { Foo { a, b } });
    }

    #[test]
    fn test_generate_struct_partial_pattern() {
        let pat_tuple_struct = pat_struct(parse_quote!(Foo), vec!["a"], true);
        let sub_pattern = SubPatternDefinition::Struct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, false);
        assert_tokens!(tokens, { Foo { a, .. } });
    }

    #[test]
    fn test_generate_struct_anonymus() {
        let pat_tuple_struct = pat_struct(parse_quote!(Foo), vec!["a", "b"], false);
        let sub_pattern = SubPatternDefinition::Struct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, true);
        assert_tokens!(tokens, { Foo { .. } });
    }

    #[test]
    fn test_generate_struct_anonymus_partial_pattern() {
        let pat_tuple_struct = pat_struct(parse_quote!(Foo), vec!["a"], true);
        let sub_pattern = SubPatternDefinition::Struct(pat_tuple_struct);

        let tokens = SubPatternCompiler::generate(&sub_pattern, true);
        assert_tokens!(tokens, { Foo { .. } });
    }
}