with_locals_proc_macros/
mod.rs

1#![allow(nonstandard_style)]
2
3extern crate proc_macro;
4
5use ::proc_macro::{
6    TokenStream,
7};
8use ::proc_macro2::{
9    Span,
10    TokenStream as TokenStream2,
11};
12use ::quote::{
13    format_ident,
14    quote,
15    quote_spanned,
16    ToTokens,
17};
18use ::syn::{*,
19    parse::{
20        // Nothing,
21        Parse,
22        Parser,
23        ParseStream,
24    },
25    // punctuated::Punctuated,
26    spanned::Spanned,
27    Result,
28    visit_mut::{self, VisitMut},
29};
30
31use ::core::{
32    mem,
33    ops::Not as _,
34};
35
36use self::{
37    helpers::{Fields as __, FnLike, LifetimeVisitor},
38};
39
40#[macro_use]
41mod helpers;
42
43mod attrs;
44include!("handle_returning_locals.rs");
45mod handle_let_bindings;
46mod wrap_statements_inside_closure_body;
47
48type Str = ::std::borrow::Cow<'static, str>;
49
50use attrs::Attrs;
51
52/// See [the main documentation of the crate for info about this attribute](
53/// https://docs.rs/with_locals).
54#[proc_macro_attribute] pub
55fn with (
56    attrs: TokenStream,
57    input: TokenStream,
58) -> TokenStream
59{
60    let ref attrs = parse_macro_input!(attrs as Attrs);
61    #[cfg(feature = "expand-macros")]
62    let mut name = String::new();
63    match parse::<TraitItemMethod>(input.clone()) {
64        | Ok(mut method) => {
65            #[cfg(feature = "expand-macros")] {
66                name = method.sig.ident.to_string();
67            }
68            handle_fn_like(attrs, &mut method, None)
69                .map(|()| method.into_token_stream())
70        },
71        | Err(_) => match parse(input) {
72            | Ok(Item::Impl(item)) => {
73                #[cfg(feature = "expand-macros")] {
74                    name = item.self_ty.to_token_stream().to_string();
75                }
76                with_impl(attrs, item)
77            },
78            | Ok(Item::Trait(item)) => {
79                #[cfg(feature = "expand-macros")] {
80                    name = item.ident.to_string();
81                }
82                with_trait(attrs, item)
83            },
84            | Ok(Item::Fn(mut fun)) => {
85                #[cfg(feature = "expand-macros")] {
86                    name = fun.fields().sig.ident.to_string();
87                }
88                handle_fn_like(attrs, &mut fun, None)
89                    .map(|()| fun.into_token_stream())
90            },
91            | _otherwise => Err(Error::new(Span::call_site(), "\
92                `#[with]` can only be applied to \
93                an `fn`, a `trait`, or an `impl`.\
94            ")),
95        },
96    }
97    .map_or_else(
98        |err| err.to_compile_error().into(),
99        |ret| {
100            #[cfg(feature = "expand-macros")] {
101                helpers::pretty_print_tokenstream(
102                    &ret,
103                    &name,
104                );
105            }
106
107            ret.into()
108        },
109    )
110}
111
112fn handle_fn_like<Fun : FnLike> (
113    attrs: &'_ Attrs,
114    fun: &'_ mut Fun,
115    outer_scope: Option<(&'_ Generics, ::func_wrap::ImplOrTrait<'_>)>
116) -> Result<()>
117{
118    handle_returning_locals(fun, attrs, outer_scope)?;
119    if let Some(block) = fun.fields().block {
120        handle_let_bindings::f(block, attrs)?;
121    }
122    Ok(())
123}
124
125fn with_impl (outer_with_attrs: &'_ Attrs, mut impl_: ItemImpl)
126  -> Result<TokenStream2>
127{
128    let outer_scope = (
129        &impl_.generics,
130        ::func_wrap::ImplOrTrait::ImplMethod {
131            implementor: &impl_.self_ty,
132            trait_name: impl_.trait_.as_ref().map(|(_, it, _)| it)
133        },
134    );
135    impl_.items.iter_mut().try_for_each(|it| match it {
136        | &mut ImplItem::Method(ref mut method) => {
137            let mut attr = None;
138            let mut err = None;
139            method
140                .attrs
141                .retain(|cur_attr| if cur_attr.path.is_ident("with") {
142                    let prev = attr.replace(cur_attr.clone());
143                    if let Some(prev) = prev {
144                        err = Some(Error::new_spanned(prev,
145                            "Duplicate `#[with]` attribute",
146                        ));
147                    }
148                    false // remove the attribute
149                } else {
150                    true
151                })
152            ;
153            if let Some(err) = err { return Err(err); }
154            let storage;
155            let attrs: &Attrs = match attr {
156                Some(attr) => {
157                    storage = attr.parse_args::<Attrs>()?;
158                    &storage
159                },
160                None => outer_with_attrs,
161            };
162            handle_fn_like(&attrs, method, Some(outer_scope))
163        },
164        | _ => Ok(()),
165    })?;
166    Ok(impl_.into_token_stream())
167}
168
169
170fn with_trait (outer_with_attrs: &'_ Attrs, mut trait_: ItemTrait)
171  -> Result<TokenStream2>
172{
173    let outer_scope = (
174        &trait_.generics,
175        ::func_wrap::ImplOrTrait::DefaultMethod { trait_name: &trait_.ident },
176    );
177    trait_.items.iter_mut().try_for_each(|it| match it {
178        | &mut TraitItem::Method(ref mut method) => {
179            let mut attr = None;
180            let mut err = None;
181            method
182                .attrs
183                .retain(|cur_attr| if cur_attr.path.is_ident("with") {
184                    let prev = attr.replace(cur_attr.clone());
185                    if let Some(prev) = prev {
186                        err = Some(Error::new_spanned(prev,
187                            "Duplicate `#[with]` attribute",
188                        ));
189                    }
190                    false // remove the attribute
191                } else {
192                    true
193                })
194            ;
195            if let Some(err) = err { return Err(err); }
196            let storage;
197            let attrs: &Attrs = match attr {
198                Some(attr) => {
199                    storage = attr.parse_args::<Attrs>()?;
200                    &storage
201                },
202                None => outer_with_attrs,
203            };
204            handle_fn_like(&attrs, method, Some(outer_scope))
205        },
206        | _ => Ok(()),
207    })?;
208    Ok(trait_.into_token_stream())
209}