Skip to main content

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::Fn(ImplItemFn {
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                    // Avoid `patterns_in_fns_without_body`, see
146                    // <https://github.com/rust-lang/rust/issues/35203>
147                    if let Pat::Ident(PatIdent { ident: arg_name, .. }) = &mut **pat {
148                        // We don't use full `_` discard here, so as to preserve the given
149                        // ident, for the sake of docs and autocomplete.
150                        // See <https://github.com/danielhenrymantilla/ext-trait.rs/issues/10>.
151                        **pat = parse_quote!(
152                            #arg_name
153                        );
154                    } else {
155                        **pat = parse_quote_spanned!(pat.span()=>
156                            _
157                        );
158                    }
159                },
160            });
161            quote!(
162                #(#attrs)*
163                #pub_
164                #default_
165                #sig;
166            )
167        },
168
169        | ImplItem::Type(ImplItemType {
170            vis: pub_,
171            defaultness: default_,
172            type_token: type_,
173            ident: TypeName @ _,
174            generics,
175            semi_token: SEMICOLON @ _,
176            attrs,
177            ..
178        }) => {
179            let attrs = attrs_to_forward_to_trait_items(attrs);
180            quote! (
181                #(#attrs)*
182                #pub_
183                #default_
184                #type_ #TypeName #generics
185                :
186                    ?::ext_trait::__::core::marker::Sized
187                #SEMICOLON
188            )
189        },
190
191        | _ => return Err(Error::new_spanned(it, "unsupported `impl` entry")),
192    })).collect::<Result<Vec<_>>>()?;
193    let ItemImpl { self_ty: ref Receiver, .. } = *item_impl;
194    let docs_addendum = format!(r#"
195
196This is an extension trait for the following impl:
197```rust ,ignore
198#[extension(pub trait {TraitName})]
199impl{intro_generics} for {Receiver}
200{maybe_where}{where_clauses}
201```"#,
202        intro_generics = intro_generics.to_token_stream(),
203        Receiver = Receiver.to_token_stream(),
204        maybe_where = if where_clause.is_some() { "where" } else { "" },
205        where_clauses =
206            where_clause
207                .iter()
208                .flat_map(|w| w.predicates.iter().map(|p| format!("\n    {}", p.to_token_stream())))
209                .collect::<String>()
210        ,
211    );
212    Ok(quote_spanned!(trait_def_span=>
213        #(#attrs)*
214        #[doc = #docs_addendum]
215        #[allow(nonstandard_style)]
216        #pub_
217        #trait_ #TraitName #intro_generics
218        #where_clause
219        {
220            #(#each_entry)*
221        }
222
223        #item_impl
224    ))
225}
226
227fn attrs_to_forward_to_impl_block(
228    trait_attrs: &[Attribute],
229) -> Vec<Attribute>
230{
231    const IMPL_BLOCK_ATTRS_ALLOW_LIST: &[&str] = &[
232        "doc",
233        "allow",
234        "warn",
235        "deny",
236        "forbid",
237        "async_trait",
238    ];
239
240    trait_attrs.iter().filter(|attr| IMPL_BLOCK_ATTRS_ALLOW_LIST.iter().any(|ident| {
241        attr.path().is_ident(ident)
242    })).cloned().collect()
243}
244
245fn attrs_to_forward_to_trait_items(
246    impl_block_assoc_item_attrs: &[Attribute],
247) -> Vec<&Attribute>
248{
249    const TRAIT_ITEMS_ATTRS_DENY_LIST: &[&str] = &[
250        "inline",
251    ];
252
253    impl_block_assoc_item_attrs.iter().filter(|attr| TRAIT_ITEMS_ATTRS_DENY_LIST.iter().all(|ident| {
254        attr.path().is_ident(ident).not()
255    })).collect()
256}