fred_macros/
lib.rs

1#![warn(clippy::large_types_passed_by_value)]
2#![warn(clippy::large_stack_frames)]
3#![warn(clippy::large_futures)]
4#![cfg_attr(docsrs, deny(rustdoc::broken_intra_doc_links))]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6#![cfg_attr(docsrs, allow(unused_attributes))]
7#![doc = include_str!("../README.md")]
8
9#[allow(unused_extern_crates)]
10extern crate proc_macro;
11
12use proc_macro::TokenStream;
13
14#[cfg(feature = "enabled")]
15mod inner {
16  use proc_macro2::TokenStream;
17  use quote::ToTokens;
18  use syn::{
19    GenericParam,
20    ImplItem,
21    Item,
22    ItemFn,
23    ItemImpl,
24    ItemTrait,
25    ReturnType,
26    Signature,
27    TraitItem,
28    Type,
29    TypeParamBound,
30    WherePredicate,
31  };
32
33  fn filter_send_bounds(
34    bounds: impl IntoIterator<Item = TypeParamBound>,
35  ) -> impl IntoIterator<Item = TypeParamBound> {
36    bounds.into_iter().filter(|bound| {
37      // TODO handle other path kinds
38      if let TypeParamBound::Trait(bound) = bound {
39        !(bound.path.is_ident("Send") || bound.path.is_ident("Sync"))
40      } else {
41        true
42      }
43    })
44  }
45
46  fn rm_from_fn_sig(sig: &mut Signature) {
47    // remove Send from generic params
48    for param in sig.generics.params.iter_mut() {
49      if let GenericParam::Type(ty) = param {
50        ty.bounds = filter_send_bounds(ty.bounds.clone()).into_iter().collect();
51      }
52    }
53    // remove Send from where clause predicates
54    if let Some(ref mut where_clause) = sig.generics.where_clause {
55      for predicate in where_clause.predicates.iter_mut() {
56        if let WherePredicate::Type(predicate) = predicate {
57          predicate.bounds = filter_send_bounds(predicate.bounds.clone()).into_iter().collect();
58        }
59      }
60    }
61    // remove Send from any `impl Trait` return types
62    if let ReturnType::Type(_, ref mut ty) = sig.output {
63      if let Type::ImplTrait(ty) = ty.as_mut() {
64        ty.bounds = filter_send_bounds(ty.bounds.clone()).into_iter().collect();
65      }
66    }
67  }
68
69  fn rm_from_item_fn(mut ast: ItemFn) -> TokenStream {
70    rm_from_fn_sig(&mut ast.sig);
71    ast.into_token_stream()
72  }
73
74  fn rm_from_impl(mut ast: ItemImpl) -> TokenStream {
75    for item in ast.items.iter_mut() {
76      if let ImplItem::Fn(item) = item {
77        rm_from_fn_sig(&mut item.sig);
78      }
79    }
80    ast.into_token_stream()
81  }
82
83  fn rm_from_item_trait(mut ast: ItemTrait) -> TokenStream {
84    ast.supertraits = filter_send_bounds(ast.supertraits.clone()).into_iter().collect();
85
86    for item in ast.items.iter_mut() {
87      if let TraitItem::Fn(item) = item {
88        rm_from_fn_sig(&mut item.sig);
89      }
90    }
91
92    ast.into_token_stream()
93  }
94
95  pub(crate) fn rm_send(input: TokenStream) -> TokenStream {
96    match syn::parse2::<Item>(input.clone()) {
97      Ok(Item::Fn(item)) => rm_from_item_fn(item),
98      Ok(Item::Trait(item)) => rm_from_item_trait(item),
99      Ok(Item::Impl(item)) => rm_from_impl(item),
100      _ => input,
101    }
102  }
103}
104
105/// Conditionally removes `Send` and `Sync` bounds in generic arguments, where clause predicates, and
106/// `impl Trait` return types.
107///
108///
109/// ## Item (function) modification:
110///
111/// ```rust
112/// use fred_macros::rm_send_if;
113/// use std::future::Future;
114///
115/// trait T1 {}
116/// trait T2 {}
117///
118/// #[rm_send_if(feature = "glommio")]
119/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R> + Send
120/// where
121///   A: T1 + Send,
122///   B: T2 + Send,
123/// {
124///   unimplemented!()
125/// }
126///
127/// // will be modified to the following
128///
129/// #[cfg(feature = "glommio")]
130/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R>
131/// where
132///   A: T1,
133///   B: T2,
134/// {
135///   unimplemented!()
136/// }
137///
138/// #[cfg(not(feature = "glommio"))]
139/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R> + Send
140/// where
141///   A: T1 + Send,
142///   B: T2 + Send,
143/// {
144///   unimplemented!()
145/// }
146/// ```
147///
148/// ### Trait modification
149///
150/// ```rust
151/// use fred_macros::rm_send_if;
152/// use std::future::Future;
153///
154/// trait T1 {}
155/// trait T2 {}
156///
157/// /// Test trait documentation.
158/// #[rm_send_if(feature = "glommio")]
159/// pub trait T3: Clone + Send + Sync {
160///   /// Test fn documentation
161///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()> + Send
162///   where
163///     A: T1 + Send,
164///     B: T2 + Send + Sync,
165///   {
166///     async move { () }
167///   }
168/// }
169///
170/// // will be modified to the following
171///
172/// #[cfg(feature = "glommio")]
173/// pub trait T3: Clone {
174///   /// Test fn documentation
175///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()>
176///   where
177///     A: T1,
178///     B: T2,
179///   {
180///     async move { () }
181///   }
182/// }
183///
184/// #[cfg(not(feature = "glommio"))]
185/// pub trait T3: Clone + Send + Sync {
186///   /// Test fn documentation
187///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()> + Send
188///   where
189///     A: T1 + Send,
190///     B: T2 + Send + Sync,
191///   {
192///     async move { () }
193///   }
194/// }
195/// ```
196#[proc_macro_attribute]
197#[cfg(not(feature = "enabled"))]
198pub fn rm_send_if(_: TokenStream, input: TokenStream) -> TokenStream {
199  input
200}
201
202#[cfg(feature = "enabled")]
203fn wrap_cfg_attr(args: TokenStream, modified: TokenStream, input: TokenStream) -> TokenStream {
204  format!("#[cfg({args})]\n{modified}\n#[cfg(not({args}))]\n{input}")
205    .parse()
206    .unwrap()
207}
208
209/// Conditionally removes `Send` and `Sync` bounds in generic arguments, where clause predicates, and
210/// `impl Trait` return types.
211///
212///
213/// ## Item (function) modification:
214///
215/// ```rust
216/// use fred_macros::rm_send_if;
217/// use std::future::Future;
218///
219/// trait T1 {}
220/// trait T2 {}
221///
222/// #[rm_send_if(feature = "glommio")]
223/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R> + Send
224/// where
225///   A: T1 + Send,
226///   B: T2 + Send,
227/// {
228///   unimplemented!()
229/// }
230///
231/// // will be modified to the following
232///
233/// #[cfg(feature = "glommio")]
234/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R>
235/// where
236///   A: T1,
237///   B: T2,
238/// {
239///   unimplemented!()
240/// }
241///
242/// #[cfg(not(feature = "glommio"))]
243/// pub async fn foo<R, A, B>(a: A, b: B) -> impl Future<Output = R> + Send
244/// where
245///   A: T1 + Send,
246///   B: T2 + Send,
247/// {
248///   unimplemented!()
249/// }
250/// ```
251///
252/// ### Trait modification
253///
254/// ```rust
255/// use fred_macros::rm_send_if;
256/// use std::future::Future;
257///
258/// trait T1 {}
259/// trait T2 {}
260///
261/// /// Test trait documentation.
262/// #[rm_send_if(feature = "glommio")]
263/// pub trait T3: Clone + Send + Sync {
264///   /// Test fn documentation
265///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()> + Send
266///   where
267///     A: T1 + Send,
268///     B: T2 + Send + Sync,
269///   {
270///     async move { () }
271///   }
272/// }
273///
274/// // will be modified to the following
275///
276/// #[cfg(feature = "glommio")]
277/// pub trait T3: Clone {
278///   /// Test fn documentation
279///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()>
280///   where
281///     A: T1,
282///     B: T2,
283///   {
284///     async move { () }
285///   }
286/// }
287///
288/// #[cfg(not(feature = "glommio"))]
289/// pub trait T3: Clone + Send + Sync {
290///   /// Test fn documentation
291///   fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()> + Send
292///   where
293///     A: T1 + Send,
294///     B: T2 + Send + Sync,
295///   {
296///     async move { () }
297///   }
298/// }
299/// ```
300#[proc_macro_attribute]
301#[cfg(feature = "enabled")]
302pub fn rm_send_if(args: TokenStream, input: TokenStream) -> TokenStream {
303  let modified: TokenStream = inner::rm_send(input.clone().into()).into();
304  wrap_cfg_attr(args, modified, input)
305}
306
307#[cfg(test)]
308#[cfg(feature = "enabled")]
309mod tests {
310  use crate::inner::rm_send;
311  use quote::quote;
312
313  #[test]
314  fn should_remove_basic_send_and_sync() {
315    let input = quote! {
316      pub async fn foo<R, A, B: Baz>(a: B, b: B) -> impl Future<Output = R> + Send
317        where R: Send,
318              A: Foo + Send,
319              B: Bar + Send + Sync
320      {
321        async move { Ok(()) }
322      }
323    };
324    let modified = rm_send(input);
325    assert!(!modified.to_string().contains("Send"));
326    assert!(!modified.to_string().contains("Sync"));
327  }
328
329  #[test]
330  fn should_remove_into_send() {
331    let input = quote! {
332      pub async fn foo<R, A, B>(a: B, b: B) -> impl Future<Output = R> + Send
333        where R: Send,
334              A: Into<Foo> + Send,
335              B: Into<Bar> + Send
336      {
337        async move { Ok(()) }
338      }
339    };
340    let modified = rm_send(input);
341    assert!(!modified.to_string().contains("Send"));
342  }
343
344  #[test]
345  fn should_remove_try_into_send() {
346    let input = quote! {
347      pub async fn foo<R, A, B>(a: B, b: B) -> impl Future<Output = Result<R>> + Send
348        where R: Send,
349              A: TryInto<Foo> + Send,
350              A::Error: Into<Error> + Send,
351              B: TryInto<Bar> + Send,
352              B::Error: Into<Error> + Send
353      {
354        async move { Ok(()) }
355      }
356    };
357    let modified = rm_send(input);
358    assert!(!modified.to_string().contains("Send"));
359  }
360
361  #[test]
362  fn should_remove_fn_args_send() {
363    let input = quote! {
364      pub async fn foo<R, A, B, F>(a: B, b: B) -> impl Future<Output = Result<R>> + Send
365        where R: Send,
366              A: TryInto<Foo> + Send,
367              A::Error: Into<Error> + Send,
368              B: TryInto<Bar> + Send,
369              B::Error: Into<Error> + Send,
370              F: Fn(()) -> Result<(), ()> + Send + 'static
371      {
372        async move { Ok(()) }
373      }
374    };
375    let modified = rm_send(input);
376    assert!(!modified.to_string().contains("Send"));
377  }
378
379  #[test]
380  fn should_remove_trait_impl() {
381    let input = quote! {
382      impl T4 for Foo {
383        fn bar<A, B>(&self, _a: A, _b: B) -> impl Future<Output = ()> + Send
384        where
385          A: T1 + Send,
386          B: T2 + Send + Sync,
387        {
388          async move { () }
389        }
390      }
391    };
392
393    let modified = rm_send(input);
394    assert!(!modified.to_string().contains("Send"));
395  }
396}