use crate::parse::sub_pattern::SubPatternDefinition;
use quote::ToTokens;
use syn::{FieldPat, Pat, PatRest, Token, punctuated::Punctuated, token::DotDot};
pub struct SubPatternCleaner;
impl SubPatternCleaner {
pub fn clean(sub_pattern: &mut SubPatternDefinition, uniting_vars: &Vec<String>) {
if matches!(sub_pattern, SubPatternDefinition::Ident(_)) {
unreachable!(
"Trying to clean Sub-Pattern: '{}' of type Ident, which must never happen",
sub_pattern.to_syn_pattern().to_token_stream().to_string(),
);
}
*sub_pattern = match Self::clean_rec(sub_pattern.to_syn_pattern(), uniting_vars) {
Pat::Path(path) => SubPatternDefinition::Path(path),
Pat::TupleStruct(tuple_struct) => SubPatternDefinition::TupleStruct(tuple_struct),
Pat::Struct(struct_pat) => SubPatternDefinition::Struct(struct_pat),
_ => unreachable!(),
};
}
fn clean_rec(pat: Pat, uniting_vars: &Vec<String>) -> Pat {
use Pat::*;
return match pat {
Ident(ident) if uniting_vars.contains(&ident.ident.to_string()) => Self::get_wildcard(),
Ident(syn::PatIdent {
attrs,
by_ref,
mutability,
ident,
subpat: Some((at, mut pat)),
}) => {
pat = Box::new(Self::clean_rec(*pat, uniting_vars));
Ident(syn::PatIdent {
attrs,
by_ref,
mutability,
ident,
subpat: Some((at, pat)),
})
}
Ident(ident) => Ident(ident),
Paren(mut pat_paren) => {
pat_paren.pat = Box::new(Self::clean_rec(*pat_paren.pat, uniting_vars));
Paren(pat_paren)
}
Slice(mut pat_slice) => {
pat_slice.elems = Self::clean_elems(pat_slice.elems, uniting_vars);
Slice(pat_slice)
}
Tuple(mut pat_tuple) => {
pat_tuple.elems = Self::clean_elems(pat_tuple.elems, uniting_vars);
Tuple(pat_tuple)
}
TupleStruct(mut pat_tuple_struct) => {
pat_tuple_struct.elems = Self::clean_elems(pat_tuple_struct.elems, uniting_vars);
TupleStruct(pat_tuple_struct)
}
Struct(mut pat_struct) => {
let initial_field_count = pat_struct.fields.len();
pat_struct.fields = Self::clean_fields(pat_struct.fields, uniting_vars);
if pat_struct.fields.len() != initial_field_count {
pat_struct.rest = Self::get_rest();
}
Struct(pat_struct)
}
_ => pat,
};
}
fn get_wildcard() -> Pat {
Pat::Wild(syn::PatWild {
attrs: vec![],
underscore_token: syn::token::Underscore::default(),
})
}
fn get_rest() -> Option<PatRest> {
Some(PatRest {
attrs: vec![],
dot2_token: DotDot::default(),
})
}
fn clean_elems(
elems: Punctuated<Pat, Token![,]>,
uniting_vars: &Vec<String>,
) -> Punctuated<Pat, Token![,]> {
let cleaned = elems
.into_iter()
.map(|pat| Self::clean_rec(pat, uniting_vars))
.collect::<Vec<_>>();
Punctuated::from_iter(cleaned)
}
fn clean_fields(
fields: Punctuated<FieldPat, Token![,]>,
uniting_vars: &Vec<String>,
) -> Punctuated<FieldPat, Token![,]> {
use Pat::*;
let cleaned = fields
.into_iter()
.filter_map(|mut field| match *field.pat {
Ident(ident) if uniting_vars.contains(&ident.ident.to_string()) => None,
Ident(_) => Some(field),
_ => {
field.pat = Box::new(Self::clean_rec(*field.pat, uniting_vars));
Some(field)
}
})
.collect::<Vec<_>>();
Punctuated::from_iter(cleaned)
}
}
#[cfg(test)]
mod tests {
use super::*;
use proc_macro_utils::assert_tokens;
use syn::{Pat, parse_quote};
#[test]
fn test_clean_rec_ident_match() {
let pat: Pat = parse_quote! { id };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { _ });
}
#[test]
fn test_clean_rec_ident_no_match() {
let pat: Pat = parse_quote! { foo };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { foo });
}
#[test]
fn test_clean_rec_paren() {
let pat: Pat = parse_quote! { (id) };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { (_) });
}
#[test]
fn test_clean_rec_slice() {
let pat: Pat = parse_quote! { [a, b, id, c, d] };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { [a, b, _, c, d] });
}
#[test]
fn test_clean_rec_tuple() {
let pat: Pat = parse_quote! { (a, b, id, c, d) };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { (a, b, _, c, d) });
}
#[test]
fn test_clean_rec_tuple_struct() {
let pat: Pat = parse_quote! { MyTupleStruct(a, b, id, c, d) };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { MyTupleStruct(a, b, _, c, d) });
}
#[test]
fn test_clean_rec_struct_match() {
let pat: Pat = parse_quote! { MyStruct{ a, b, id, c, d } };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { MyStruct { a, b, c, d, .. } });
}
#[test]
fn test_clean_rec_struct_no_match() {
let pat: Pat = parse_quote! { MyStruct{ a, b, foo, c, d } };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { MyStruct { a, b, foo, c, d } });
}
#[test]
fn test_clean_rec_path() {
let pat: Pat = parse_quote! { MyType::id };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { MyType::id });
}
#[test]
fn test_clean_rec_struct() {
let pat: Pat = parse_quote! { MyStruct { foo, id, bar } };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
assert_tokens!(result.to_token_stream(), { MyStruct { foo, bar, .. } });
}
#[test]
fn test_clean_rec_nested_tuple_struct_in_struct() {
let pat: Pat = parse_quote! { MyStruct { a, b, inner: MyTupleStruct(x, id, y), c } };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
#[rustfmt::skip] assert_tokens!(result.to_token_stream(), {
MyStruct {a, b, inner: MyTupleStruct(x, _, y), c }
});
}
#[test]
fn test_clean_rec_nested_struct_in_tuple() {
let pat: Pat = parse_quote! { (foo, Bar { id, x, y }, baz) };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
#[rustfmt::skip] assert_tokens!(result.to_token_stream(), { (foo, Bar { x, y, .. }, baz) });
}
#[test]
fn test_clean_rec_deeply_nested_patterns() {
let pat: Pat = parse_quote! { Outer { a, inner: (X { id, b }, [id, y, z]), d } };
let uniting_vars = vec!["id".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
#[rustfmt::skip] assert_tokens!(result.to_token_stream(), {
Outer {a, inner: (X { b, .. }, [_, y, z]), d }
});
}
#[test]
fn test_clean_rec_nested_multiple_uniting_vars() {
let pat: Pat = syn::parse_quote! {
Outer { a, inner: ( X(id, b, c), [d, e, id2, f]), g, id3 }
};
let uniting_vars = vec!["id".to_string(), "id2".to_string(), "id3".to_string()];
let result = SubPatternCleaner::clean_rec(pat, &uniting_vars);
#[rustfmt::skip]
assert_tokens!(result.to_token_stream(), {
Outer { a, inner: ( X(_, b, c), [d, e, _, f]), g, .. }
});
}
#[test]
#[should_panic]
fn test_clean_ident() {
let mut sub_pattern = SubPatternDefinition::parse(parse_quote! { id }).unwrap();
let uniting_vars = vec!["id".to_string()];
SubPatternCleaner::clean(&mut sub_pattern, &uniting_vars);
}
#[test]
fn test_clean_sub_pattern() {
let mut sub_pattern =
SubPatternDefinition::parse(parse_quote! { A(id, x, y, id2) }).unwrap();
let uniting_vars = vec!["id".to_string(), "id2".to_string()];
SubPatternCleaner::clean(&mut sub_pattern, &uniting_vars);
assert_tokens!(sub_pattern.to_syn_pattern().to_token_stream(), {
A(_, x, y, _)
});
}
}