next_gen_proc_macros/
mod.rs

1//! Crate not intended for direct use.
2//! Use https://docs.rs/next-gen instead.
3// Templated by `cargo-generate` using https://github.com/danielhenrymantilla/proc-macro-template
4#![allow(nonstandard_style, unused_imports)]
5
6use ::core::{
7    mem,
8    ops::Not as _,
9};
10use ::proc_macro::{
11    TokenStream,
12};
13use ::proc_macro2::{
14    Span,
15    TokenStream as TokenStream2,
16    TokenTree as TT,
17};
18use ::quote::{
19    format_ident,
20    quote,
21    quote_spanned,
22    ToTokens,
23};
24use ::syn::{*,
25    parse::{Parse, Parser, ParseStream},
26    punctuated::Punctuated,
27    Result, // Explicitly shadow it
28    spanned::Spanned,
29};
30
31mod utils;
32
33// #[macro_use]
34// mod macros;
35
36#[proc_macro_attribute] pub
37fn generator (
38    attrs: TokenStream,
39    input: TokenStream,
40) -> TokenStream
41{
42    generator_impl(attrs.into(), input.into())
43        .map(|ret| {
44            #[cfg(feature = "verbose-expansions")] {
45                utils::pretty_print_tokenstream(&ret);
46            }
47            ret
48        })
49        .unwrap_or_else(|err| {
50            let mut errors =
51                err .into_iter()
52                    .map(|err| Error::new(
53                        err.span(),
54                        format_args!("`#[next_gen::generator]`: {}", err),
55                    ))
56            ;
57            let mut err = errors.next().unwrap();
58            errors.for_each(|cur| err.combine(cur));
59            err.to_compile_error()
60        })
61        .into()
62}
63
64fn generator_impl (
65    params: TokenStream2,
66    input: TokenStream2,
67) -> Result<TokenStream2>
68{
69    struct Params {
70        yield_ty: Type,
71        resume: Option<(Type, Option<Pat>)>,
72    }
73    impl Parse for Params {
74        fn parse (input: ParseStream<'_>)
75          -> Result<Params>
76        {
77            mod kw {
78                ::syn::custom_keyword!(resume);
79            }
80            let mut yield_ty: Option<Type> = None;
81            let mut resume: Option<(Type, Option<Pat>)> = None;
82            while input.is_empty().not() {
83                let snoopy = input.lookahead1();
84                match () {
85                    | _case if snoopy.peek(Token![yield]) => {
86                        if yield_ty.is_some() {
87                            return Err(input.error("already provided"));
88                        }
89                        let _: Token![yield] = input.parse().unwrap();
90                        let parenthesized; parenthesized!(parenthesized in input);
91                        yield_ty.replace(parenthesized.parse()?);
92                        let _: Option<Token![,]> = parenthesized.parse()?;
93                    },
94                    | _case if snoopy.peek(kw::resume) => {
95                        if resume.is_some() {
96                            return Err(input.error("already provided"));
97                        }
98                        let _: kw::resume = input.parse().unwrap();
99                        let resume_ty: Type = {
100                            let parenthesized; parenthesized!(parenthesized in input);
101                            let it = parenthesized.parse()?;
102                            let _: Option<Token![,]> = parenthesized.parse()?;
103                            it
104                        };
105                        let mut resume_pat = None;
106                        if input.parse::<Option<Token![as]>>()?.is_some() {
107                            resume_pat.replace(input.parse()?);
108                        }
109                        resume.replace((resume_ty, resume_pat));
110                    },
111                    // Slightly improve the error message for extraneous
112                    // trailing stuff.
113                    | _case if yield_ty.is_some() && resume.is_some() => break,
114                    | _default => return Err(snoopy.error()),
115                }
116                let _: Option<Token![,]> = input.parse()?;
117            }
118            let yield_ty = if let Some(it) = yield_ty { it } else {
119                return Err(input.error("missing `yield(<yield type>)`"));
120            };
121            Ok(Self { yield_ty, resume })
122        }
123    }
124
125    let Params {
126        yield_ty: YieldTy @ _,
127        resume,
128    } = parse2(params)?;
129    let mut fun: ItemFn = parse2(input)?;
130    let ItemFn {
131        ref mut block,
132        ref mut sig,
133        ..
134    } = fun;
135
136    let __yield_slot__ = Ident::new(
137        "__yield_slot__",
138        ::proc_macro::Span::mixed_site().into(),
139    );
140
141    // Handle the signature
142    let resume_arg_pat = {
143        sig.asyncness = parse_quote!( async );
144
145        if let Some(receiver) = sig.receiver() {
146            return Err(Error::new_spanned(
147                receiver,
148                "`self` receivers are not supported yet",
149            ));
150        }
151
152        let (/* mut */ each_pat, /* mut */ EachTy @ _): (Vec<_>, Vec<_>) =
153            ::core::mem::take(&mut sig.inputs)
154                .into_iter()
155                .map(|fn_arg| match fn_arg {
156                    | FnArg::Receiver(_) => unreachable!(),
157                    | FnArg::Typed(PatType { pat, ty, .. }) => (*pat, ty),
158                })
159                .unzip()
160        ;
161        let (resume_arg_pat, ResumeArg @ _) = match resume {
162            | Some((ResumeArg @ _, initial_resume_arg_pat)) => (
163                initial_resume_arg_pat.unwrap_or_else(|| parse_quote!( _ )),
164                ResumeArg.into_token_stream(),
165            ),
166            | None => (
167                parse_quote!( _ ),
168                quote!(),
169            ),
170        };
171        sig.inputs = parse_quote!(
172            #__yield_slot__: ::next_gen::__::__Internals_YieldSlot_DoNotUse__<'_, #YieldTy, #ResumeArg>,
173            (
174                #(#each_pat ,)*
175            ): (
176                #(#EachTy ,)*
177            ),
178        );
179        resume_arg_pat
180    };
181
182    // Update block to generate `yield_!` macro.
183    {
184        *block = parse_quote!({
185            macro_rules! yield_ {(
186                $value:expr $(,)?
187            ) => (
188                #__yield_slot__.__put($value).await
189            )}
190
191            let #resume_arg_pat = #__yield_slot__.__take_initial_arg();
192
193            #block
194        });
195    }
196
197    Ok(fun.into_token_stream())
198}