Skip to main content

miniextendr_macros/
receiver_body.rs

1//! Rewrite an explicit handle receiver without changing independent Rust scopes.
2
3use proc_macro2::{Group, Ident, TokenStream, TokenTree};
4use syn::visit_mut::{self, VisitMut};
5
6pub(crate) fn rewrite(body: &mut syn::Block, receiver: &Ident) {
7    ReceiverBody { receiver }.visit_block_mut(body);
8}
9
10struct ReceiverBody<'a> {
11    receiver: &'a Ident,
12}
13
14impl VisitMut for ReceiverBody<'_> {
15    fn visit_expr_path_mut(&mut self, expr: &mut syn::ExprPath) {
16        if expr.qself.is_none() && expr.path.is_ident("self") {
17            expr.path = self.receiver.clone().into();
18        } else {
19            visit_mut::visit_expr_path_mut(self, expr);
20        }
21    }
22
23    fn visit_item_mut(&mut self, item: &mut syn::Item) {
24        // Nested functions, impls and modules have independent receivers and
25        // cannot capture this method's self. Local macros can capture it.
26        if let syn::Item::Macro(item) = item {
27            self.visit_macro_mut(&mut item.mac);
28        }
29    }
30
31    fn visit_macro_mut(&mut self, mac: &mut syn::Macro) {
32        // Macro arguments are opaque syntax. Preserve the macro's path and
33        // module-qualified self:: paths inside its tokens.
34        mac.tokens = rewrite_macro_tokens(mac.tokens.clone(), self.receiver);
35    }
36}
37
38fn rewrite_macro_tokens(tokens: TokenStream, receiver: &Ident) -> TokenStream {
39    let tokens: Vec<_> = tokens.into_iter().collect();
40    tokens.iter().enumerate().map(|(i, token)| match token {
41        TokenTree::Ident(ident) if ident == "self" => {
42            let module_path = #[allow(non_exhaustive_omitted_patterns)] match tokens.get(i + 1) {
    Some(TokenTree::Punct(p)) if p.as_char() == ':' => true,
    _ => false,
}matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if p.as_char() == ':')
43                && #[allow(non_exhaustive_omitted_patterns)] match tokens.get(i + 2) {
    Some(TokenTree::Punct(p)) if p.as_char() == ':' => true,
    _ => false,
}matches!(tokens.get(i + 2), Some(TokenTree::Punct(p)) if p.as_char() == ':');
44            if module_path {
45                token.clone()
46            } else {
47                TokenTree::Ident(receiver.clone())
48            }
49        }
50        TokenTree::Group(group) => {
51            let mut rewritten = Group::new(group.delimiter(), rewrite_macro_tokens(group.stream(), receiver));
52            rewritten.set_span(group.span());
53            TokenTree::Group(rewritten)
54        }
55        _ => token.clone(),
56    }).collect()
57}
58
59#[cfg(test)]
60mod tests {
61    use super::*;
62    use quote::quote;
63
64    #[test]
65    fn preserves_module_paths_and_nested_receivers() {
66        let mut body: syn::Block = syn::parse_quote!({
67            struct Local(i32);
68            impl Local {
69                fn read(&self) -> i32 {
70                    self.0
71                }
72            }
73            use self::support::OFFSET;
74            let read = || self.value;
75            assert_eq!(self.value, self::support::value());
76            self.value + read() + Local(OFFSET).read()
77        });
78        rewrite(&mut body, &syn::parse_quote!(__receiver));
79        let expected: syn::Block = syn::parse_quote!({
80            struct Local(i32);
81            impl Local {
82                fn read(&self) -> i32 {
83                    self.0
84                }
85            }
86            use self::support::OFFSET;
87            let read = || __receiver.value;
88            assert_eq!(__receiver.value, self::support::value());
89            __receiver.value + read() + Local(OFFSET).read()
90        });
91        assert_eq!(quote!(#body).to_string(), quote!(#expected).to_string());
92    }
93
94    #[test]
95    fn local_macros_can_capture_the_outer_receiver() {
96        let mut body: syn::Block = syn::parse_quote!({
97            macro_rules! read {
98                () => {
99                    self.value + self::OFFSET
100                };
101            }
102            read!()
103        });
104        rewrite(&mut body, &syn::parse_quote!(__receiver));
105        let expected: syn::Block = syn::parse_quote!({
106            macro_rules! read {
107                () => {
108                    __receiver.value + self::OFFSET
109                };
110            }
111            read!()
112        });
113        assert_eq!(quote!(#body).to_string(), quote!(#expected).to_string());
114    }
115}