ext_trait_proc_macros/
mod.rs

1//! Crate not intended for direct use.
2//! Use https:://docs.rs/ext-trait instead.
3#![allow(nonstandard_style, unused_braces, unused_imports)]
4
5use ::core::{
6    mem,
7    ops::Not as _,
8};
9use ::proc_macro::{
10    TokenStream,
11};
12use ::proc_macro2::{
13    Span,
14    TokenStream as TokenStream2,
15    TokenTree as TT,
16};
17use ::quote::{
18    format_ident,
19    quote,
20    quote_spanned,
21    ToTokens,
22};
23use ::syn::{*,
24    parse::{Parse, Parser, ParseStream},
25    punctuated::Punctuated,
26    spanned::Spanned,
27    Result, // Explicitly shadow it
28};
29
30///
31#[proc_macro_attribute] pub
32fn extension (
33    attrs: TokenStream,
34    input: TokenStream,
35) -> TokenStream
36{
37    extension_impl(attrs.into(), input.into())
38    //  .map(|ret| { println!("{}", ret); ret })
39        .unwrap_or_else(|err| {
40            let mut errors =
41                err .into_iter()
42                    .map(|err| Error::new(
43                        err.span(),
44                        format_args!("`#[extension(trait …)]`: {}", err),
45                    ))
46            ;
47            let mut err = errors.next().unwrap();
48            errors.for_each(|cur| err.combine(cur));
49            err.to_compile_error()
50        })
51        .into()
52}
53
54struct Attrs {
55    pub_: Visibility,
56    trait_: Token![trait],
57    TraitName: Ident,
58}
59
60impl Parse for Attrs {
61    fn parse (input: ParseStream<'_>)
62      -> Result<Attrs>
63    {
64        Ok(Self {
65            pub_: input.parse()?,
66            trait_: input.parse()?,
67            TraitName: input.parse()?,
68        })
69    }
70}
71
72/// Example
73#[cfg(any())]
74const _: () = {
75    use ::ext_trait::extension;
76
77    #[extension(trait disregard_err)]
78    impl<T, E> Result<T, E> {
79        fn disregard_err(self) -> Option<T> { self }
80    }
81};
82
83fn extension_impl (
84    attrs: TokenStream2,
85    input: TokenStream2,
86) -> Result<TokenStream2>
87{
88    let trait_def_span = attrs.span();
89    let Attrs { pub_, trait_, TraitName } = parse2(attrs)?;
90    let ref mut item_impl: ItemImpl = parse2(input)?;
91    let attrs = mem::take(&mut item_impl.attrs);
92    item_impl.attrs = attrs_to_forward_to_impl_block(&attrs);
93    let (intro_generics, fwd_generics, where_clause) = item_impl.generics.split_for_impl();
94    match Option::replace(
95        &mut item_impl.trait_,
96        (None, parse_quote!( #TraitName #fwd_generics ), <_>::default()),
97    )
98    {
99        | Some((_, _, extraneous_for)) => return Err(Error::new_spanned(
100            extraneous_for,
101            "expected inherent `impl<…> Type<…>` syntax",
102        )),
103        | _ => {},
104    }
105    let ref item_impl = item_impl;
106    let each_entry = item_impl.items.iter().map(|it| Ok(match it {
107        // We don't deny `pub_` and `default_` annotations *directly*:
108        // instead, we forward their extraneous presence to the `trait`
109        // definition, so as to trigger a grammar error from the following
110        // rust parser pass, which ought to yield a way nicer error message.
111        | ImplItem::Const(ImplItemConst {
112            vis: pub_,
113            defaultness: default_,
114            const_token: const_,
115            ident: CONST_NAME @ _,
116            ty: Ty @ _,
117            attrs,
118            ..
119        }) => {
120            let attrs = attrs_to_forward_to_trait_items(attrs);
121            quote!(
122                #(#attrs)*
123                #pub_
124                #default_
125                #const_ #CONST_NAME: #Ty;
126            )
127        }
128
129        | ImplItem::Method(ImplItemMethod {
130            vis: pub_,
131            defaultness: default_,
132            sig,
133            attrs,
134            ..
135        }) => {
136            let attrs = attrs_to_forward_to_trait_items(attrs);
137            let mut sig = sig.clone();
138            sig.inputs.iter_mut().for_each(|fn_arg| match fn_arg {
139                | FnArg::Receiver(Receiver { reference, mutability, .. }) => {
140                    if reference.is_none() {
141                        *mutability = None;
142                    }
143                },
144                | FnArg::Typed(PatType { pat, .. }) => {
145                    *pat = parse_quote!( _ );
146                },
147            });
148            quote!(
149                #(#attrs)*
150                #pub_
151                #default_
152                #sig;
153            )
154        },
155
156        | ImplItem::Type(ImplItemType {
157            vis: pub_,
158            defaultness: default_,
159            type_token: type_,
160            ident: TypeName @ _,
161            generics,
162            semi_token: SEMICOLON @ _,
163            attrs,
164            ..
165        }) => {
166            let attrs = attrs_to_forward_to_trait_items(attrs);
167            quote! (
168                #(#attrs)*
169                #pub_
170                #default_
171                #type_ #TypeName #generics
172                :
173                    ?::ext_trait::__::core::marker::Sized
174                #SEMICOLON
175            )
176        },
177
178        | _ => return Err(Error::new_spanned(it, "unsupported `impl` entry")),
179    })).collect::<Result<Vec<_>>>()?;
180    let ItemImpl { self_ty: ref Receiver, .. } = *item_impl;
181    let docs_addendum = format!(r#"
182
183This is an extension trait for the following impl:
184```rust ,ignore
185#[extension(pub trait {TraitName})]
186impl{intro_generics} for {Receiver}
187{maybe_where}{where_clauses}
188```"#,
189        intro_generics = intro_generics.to_token_stream(),
190        Receiver = Receiver.to_token_stream(),
191        maybe_where = if where_clause.is_some() { "where" } else { "" },
192        where_clauses =
193            where_clause
194                .iter()
195                .flat_map(|w| w.predicates.iter().map(|p| format!("\n    {}", p.to_token_stream())))
196                .collect::<String>()
197        ,
198    );
199    Ok(quote_spanned!(trait_def_span=>
200        #(#attrs)*
201        #[doc = #docs_addendum]
202        #[allow(nonstandard_style)]
203        #pub_
204        #trait_ #TraitName #intro_generics
205        #where_clause
206        {
207            #(#each_entry)*
208        }
209
210        #item_impl
211    ))
212}
213
214fn attrs_to_forward_to_impl_block(
215    trait_attrs: &[Attribute],
216) -> Vec<Attribute>
217{
218    const IMPL_BLOCK_ATTRS_ALLOW_LIST: &[&str] = &[
219        "doc",
220        "allow",
221        "warn",
222        "deny",
223        "forbid",
224        "async_trait",
225    ];
226
227    trait_attrs.iter().filter(|attr| IMPL_BLOCK_ATTRS_ALLOW_LIST.iter().any(|ident| {
228        attr.path.is_ident(ident)
229    })).cloned().collect()
230}
231
232fn attrs_to_forward_to_trait_items(
233    impl_block_assoc_item_attrs: &[Attribute],
234) -> Vec<&Attribute>
235{
236    const TRAIT_ITEMS_ATTRS_DENY_LIST: &[&str] = &[
237        "inline",
238    ];
239
240    impl_block_assoc_item_attrs.iter().filter(|attr| TRAIT_ITEMS_ATTRS_DENY_LIST.iter().all(|ident| {
241        attr.path.is_ident(ident).not()
242    })).collect()
243}