|
1 | 1 | use proc_macro::TokenStream; |
2 | 2 | use proc_macro2::{Group, TokenStream as TokenStream2, TokenTree}; |
3 | 3 | use quote::quote; |
4 | | -use syn::parse::Parser; |
| 4 | +use syn::parse::{Parse, ParseStream, Parser, Result}; |
5 | 5 | use syn::visit_mut::VisitMut; |
6 | 6 |
|
7 | 7 | struct Scrub<'a> { |
@@ -34,6 +34,80 @@ impl<'a> Scrub<'a> { |
34 | 34 | } |
35 | 35 | } |
36 | 36 |
|
| 37 | +struct Partial<T>(T, TokenStream2); |
| 38 | + |
| 39 | +impl<T: Parse> Parse for Partial<T> { |
| 40 | + fn parse(input: ParseStream) -> Result<Self> { |
| 41 | + Ok(Partial(input.parse()?, input.parse()?)) |
| 42 | + } |
| 43 | +} |
| 44 | + |
| 45 | +fn visit_token_stream_impl( |
| 46 | + visitor: &mut Scrub<'_>, |
| 47 | + tokens: TokenStream2, |
| 48 | + modified: &mut bool, |
| 49 | + out: &mut TokenStream2, |
| 50 | +) { |
| 51 | + use quote::ToTokens; |
| 52 | + use quote::TokenStreamExt; |
| 53 | + |
| 54 | + let mut tokens = tokens.into_iter().peekable(); |
| 55 | + while let Some(tt) = tokens.next() { |
| 56 | + match tt { |
| 57 | + TokenTree::Ident(i) if i == "yield" => { |
| 58 | + let stream = std::iter::once(TokenTree::Ident(i)).chain(tokens).collect(); |
| 59 | + match syn::parse2(stream) { |
| 60 | + Ok(Partial(yield_expr, rest)) => { |
| 61 | + let mut expr = syn::Expr::Yield(yield_expr); |
| 62 | + visitor.visit_expr_mut(&mut expr); |
| 63 | + expr.to_tokens(out); |
| 64 | + *modified = true; |
| 65 | + tokens = rest.into_iter().peekable(); |
| 66 | + } |
| 67 | + Err(e) => { |
| 68 | + out.append_all(&mut e.to_compile_error().into_iter()); |
| 69 | + *modified = true; |
| 70 | + return; |
| 71 | + } |
| 72 | + } |
| 73 | + } |
| 74 | + TokenTree::Ident(i) if i == "stream" || i == "try_stream" => { |
| 75 | + out.append(TokenTree::Ident(i)); |
| 76 | + match tokens.peek() { |
| 77 | + Some(TokenTree::Punct(p)) if p.as_char() == '!' => { |
| 78 | + out.extend(tokens.next()); // ! |
| 79 | + if let Some(TokenTree::Group(_)) = tokens.peek() { |
| 80 | + out.extend(tokens.next()); // { .. } or [ .. ] or ( .. ) |
| 81 | + } |
| 82 | + } |
| 83 | + _ => {} |
| 84 | + } |
| 85 | + } |
| 86 | + TokenTree::Group(group) => { |
| 87 | + let mut content = group.stream(); |
| 88 | + *modified |= visitor.visit_token_stream(&mut content); |
| 89 | + let mut new = Group::new(group.delimiter(), content); |
| 90 | + new.set_span(group.span()); |
| 91 | + out.append(new); |
| 92 | + } |
| 93 | + other => out.append(other), |
| 94 | + } |
| 95 | + } |
| 96 | +} |
| 97 | + |
| 98 | +impl Scrub<'_> { |
| 99 | + fn visit_token_stream(&mut self, tokens: &mut TokenStream2) -> bool { |
| 100 | + let (mut out, mut modified) = (TokenStream2::new(), false); |
| 101 | + visit_token_stream_impl(self, tokens.clone(), &mut modified, &mut out); |
| 102 | + |
| 103 | + if modified { |
| 104 | + *tokens = out; |
| 105 | + } |
| 106 | + |
| 107 | + modified |
| 108 | + } |
| 109 | +} |
| 110 | + |
37 | 111 | impl VisitMut for Scrub<'_> { |
38 | 112 | fn visit_expr_mut(&mut self, i: &mut syn::Expr) { |
39 | 113 | match i { |
@@ -109,8 +183,20 @@ impl VisitMut for Scrub<'_> { |
109 | 183 | } |
110 | 184 | } |
111 | 185 |
|
112 | | - fn visit_item_mut(&mut self, _: &mut syn::Item) { |
113 | | - // Don't transform inner items. |
| 186 | + fn visit_macro_mut(&mut self, mac: &mut syn::Macro) { |
| 187 | + let mac_ident = mac.path.segments.last().map(|p| &p.ident); |
| 188 | + if mac_ident.map_or(false, |i| i == "stream" || i == "try_stream") { |
| 189 | + return; |
| 190 | + } |
| 191 | + |
| 192 | + self.visit_token_stream(&mut mac.tokens); |
| 193 | + } |
| 194 | + |
| 195 | + fn visit_item_mut(&mut self, i: &mut syn::Item) { |
| 196 | + // Recurse into macros but otherwise don't transform inner items. |
| 197 | + if let syn::Item::Macro(i) = i { |
| 198 | + self.visit_macro_mut(&mut i.mac); |
| 199 | + } |
114 | 200 | } |
115 | 201 | } |
116 | 202 |
|
|
0 commit comments