maybe_async_cfg2/lib.rs
1//!
2//! # maybe-async-cfg2
3//!
4//! **Don't repeat yourself when writing blocking and async code.**
5//!
6//! [](https://github.com/korbin/maybe-async-cfg2/actions)
7//! [](./LICENSE)
8//! [](https://crates.io/crates/maybe-async-cfg2)
9//! [](https://docs.rs/maybe-async-cfg2)
10//!
11//! When implementing both sync and async variants of an API in a crate, the APIs of the two
12//! variants are almost the same except for async/await keywords.
13//!
14//! `maybe-async-cfg2` helps unify async and sync implementations using a **procedural macro**.
15//! - Write async code with normal `async` and `await` keywords, and let `maybe_async_cfg2` handle
16//! removing them when blocking code is needed.
17//! - Add `maybe` attributes and specify feature conditions in the macro parameters to determine
18//! which variant of code should be generated.
19//! - Use `only_if` (or `remove_if`) to keep code in a specific variant when necessary.
20//!
21//! The `maybe` procedural macro can be applied to the following code:
22//! - use declarations
23//! - trait declarations
24//! - trait implementations
25//! - function definitions
26//! - struct and enum definitions
27//! - modules
28//!
29//! **RECOMMENDATION**: Use resolver version 2 in `Cargo.toml`, which was introduced in Rust 1.51.
30//! Without it, two crates in a dependency with conflicting versions (one async and another
31//! blocking) can fail compilation.
32//!
33//! ```toml
34//! [package]
35//! resolver = "2"
36//! # or when using workspaces
37//! [workspace]
38//! resolver = "2"
39//! ```
40//!
41//!
42//! ## Motivation
43//!
44//! The async/await language feature transformed the async world of Rust. Compared with the
45//! map/and_then style, async code now more closely resembles sync code.
46//!
47//! In many crates, the async and sync variants share the same API, but the minor
48//! difference that all async code must be awaited prevents the unification of async and sync code.
49//! In other words, it is necessary to write an async and a sync implementation respectively.
50//!
51//!
52//! ## Macros in Detail
53//!
54//! To use `maybe-async-cfg2`, it is necessary to distinguish which code is used exclusively in the
55//! sync vs. async variants. These two variants of the implementation should share the same function
56//! signatures except for async/await keywords.
57//!
58//! Use the `maybe` macro for code that is the *same* in both async and sync variants.
59//! Specify in the macro parameters the conditions (based on features) under which async and/or sync
60//! variants of the code should appear.
61//!
62//! - attribute macro **`maybe`**
63//!
64//! Offers a unified way to provide sync and async conversion on demand depending on enabled
65//! feature flags, with an **async first** policy.
66//!
67//! ```toml
68//! [dependencies]
69//! maybe_async_cfg2 = "0.3"
70//!
71//! [features]
72//! use_sync = []
73//! use_async = []
74//! ```
75//!
76//! In this and all the following examples, two features are used. Any conditions
77//! can be used, for example, replacing `feature="use_sync"` with
78//! `not(feature="use_async")` everywhere. `maybe-async-cfg2` does not analyze the
79//! conditions in any way, just substituting them as is.
80//!
81//! Add the `maybe` attribute before all items that must be different in sync vs. async code.
82//!
83//! To keep async code, specify the `async` parameter with the condition (based on
84//! features) for when the code should be async.
85//!
86//! To convert async code to sync, specify the `sync` parameter with the condition when
87//! sync code should be generated.
88//!
89//! ```rust
90//! #[maybe_async_cfg2::maybe(
91//! idents(Foo),
92//! sync(feature="use_sync"),
93//! async(feature="use_async")
94//! )]
95//! struct Struct {
96//! f: Foo,
97//! }
98//! ```
99//! After conversion:
100//! ```rust
101//! #[cfg(feature="use_sync")]
102//! struct StructSync {
103//! f: FooSync,
104//! }
105//! #[cfg(feature="use_async")]
106//! struct StructAsync {
107//! f: FooAsync,
108//! }
109//! ```
110//!
111//! - procedural macro **`content`**
112//!
113//! The `content` macro allows specifying common parameters for many `maybe` macros. Use the
114//! internal `default` attribute with the required parameters inside the `content` macro.
115//!
116//! ```rust
117//! maybe_async_cfg2::content!{
118//! #![maybe_async_cfg2::default(
119//! idents(Foo, Bar),
120//! )]
121//!
122//! #[maybe_async_cfg2::maybe(
123//! sync(feature="use_sync"),
124//! async(feature="use_async")
125//! )]
126//! struct Struct {
127//! f: Foo,
128//! }
129//!
130//! #[maybe_async_cfg2::maybe(
131//! sync(feature="use_sync"),
132//! async(feature="use_async")
133//! )]
134//! async fn func(b: Bar) {
135//! todo!()
136//! }
137//! } // content!
138//! ```
139//! After conversion:
140//! ```rust
141//! #[cfg(feature="use_sync")]
142//! struct StructSync {
143//! f: FooSync,
144//! }
145//! #[cfg(feature="use_async")]
146//! struct StructAsync {
147//! f: FooAsync,
148//! }
149//!
150//! #[cfg(feature="use_sync")]
151//! fn func_sync(b: BarSync) {
152//! todo!()
153//! }
154//! #[cfg(feature="use_async")]
155//! async fn func_async(b: BarAsync) {
156//! todo!()
157//! }
158//! ```
159//!
160//! ## Doctests
161//!
162//! When writing doctests, they can be marked as applicable only in the corresponding code variant.
163//! To do this, specify `only_if(`_VARIANT_KEY_`)` in the doctest attributes. Then in all other
164//! variants, this doctest will be replaced with an empty string.
165//!
166//! ```rust
167//! #[maybe_async_cfg2::maybe(
168//! idents(Foo),
169//! sync(feature = "use_sync"),
170//! async(feature = "use_async")
171//! )]
172//! /// This is a structure.
173//! /// ```rust, only_if(sync)
174//! /// let s = StructSync{ f: FooSync::new() };
175//! /// ```
176//! /// ```rust, only_if(async)
177//! /// let s = StructAsync{ f: FooAsync::new().await };
178//! /// ```
179//! struct Struct {
180//! f: Foo,
181//! }
182//! ```
183//! After conversion:
184//! ```rust
185//! #[cfg(feature = "use_sync")]
186//! /// This is a structure.
187//! /// ```rust, only_if(sync)
188//! /// let s = StructSync{ f: FooSync::new() };
189//! /// ```
190//! struct StructSync {
191//! f: FooSync,
192//! }
193//! #[cfg(feature = "use_async")]
194//! /// This is a structure.
195//! ///
196//! /// ```rust, only_if(async)
197//! /// let s = StructAsync{ f: FooAsync::new().await };
198//! /// ```
199//! struct StructAsync {
200//! f: FooAsync,
201//! }
202//! ```
203//!
204//! ## Examples
205//!
206//! ### Rust client for services
207//!
208//! When implementing a Rust client for any service, like awz3, the higher-level API of async and
209//! sync variants is almost the same, such as creating or deleting a bucket, retrieving an object,
210//! etc.
211//!
212//! The example `service_client` is a proof of concept that `maybe_async_cfg2` can actually free us
213//! from writing duplicate code for sync and async variants. The `is_sync` feature gate
214//! allows toggling between sync and async AWZ3 client implementations.
215//!
216//!
217//! ## Acknowledgements
218//!
219//! This crate is a redesigned fork of these wonderful crates:
220//!
221//! - [fMeow/maybe-async-rs](https://github.com/fMeow/maybe-async-rs)
222//!
223//! - [marioortizmanero/maybe-async-rs](https://github.com/marioortizmanero/maybe-async-rs)
224//!
225//! Thanks!
226//!
227//!
228//! # License
229//! MIT
230#![deny(missing_docs)]
231#![deny(rustdoc::missing_crate_level_docs)]
232
233// note: the `rustdoc::missing_doc_code_examples` lint is unstable
234//#![deny(rustdoc::missing_doc_code_examples)]
235
236use manyhow::manyhow;
237use proc_macro::TokenStream;
238
239mod macros;
240mod params;
241mod utils;
242mod visit_ext;
243mod visitor_async;
244mod visitor_content;
245
246#[cfg(feature = "doctests")]
247mod doctests;
248
249mod debug;
250
251const DEFAULT_CRATE_NAME: &'static str = "maybe_async_cfg2";
252const MACRO_MAYBE_NAME: &'static str = "maybe";
253const MACRO_ONLY_IF_NAME: &'static str = "only_if";
254const MACRO_REMOVE_IF_NAME: &'static str = "remove_if";
255const MACRO_NOOP_NAME: &'static str = "noop";
256const MACRO_REMOVE_NAME: &'static str = "remove";
257const MACRO_DEFAULT_NAME: &'static str = "default";
258
259const STANDARD_MACROS: &'static [&'static str] = &[
260 "dbg",
261 "print",
262 "println",
263 "assert",
264 "assert_eq",
265 "assert_ne",
266];
267
268/// Marks code that can be presented in several variants.
269///
270/// ### The `maybe` macro has the following parameters:
271///
272/// - `disable`
273///
274/// The macro with `disable` parameter will do nothing, like `noop`. Use it to write and debug
275/// initial async code.
276///
277/// - `prefix`
278///
279/// The name of `maybe-async-cfg2` crate. If not set, `"maybe_async_cfg2"` will be used.
280///
281/// - `sync`, `async`
282///
283/// Defines variants of code: the item to which the attribute `maybe` refers will be
284/// replaced with multiple copies (one for each variant), which will be modified according to
285/// the variant kind and its parameters.
286///
287/// For the `sync` variant, the item will be converted from async to sync code by deleting
288/// the `async` and `await` keywords. Types `Future<Output=XXX>` will also be replaced with just
289/// `XXX`. For the `async` variant, the item will be left async.
290///
291/// In any case, the item will be converted according to all parameters described below. For
292/// functions, structs/enums and traits, the name will be changed as if it is mentioned in the
293/// `idents` list (if it is not explicitly specified there and if `keep_self` is not present).
294///
295/// - All other parameters will be passed to all variants (with merging).
296///
297/// Therefore, those parts of the variant parameters that match in all variants can be specified
298/// here. For example, this is the expected behavior for the `idents` list.
299///
300/// ### Every variant has the following parameters:
301///
302/// - `disable`
303///
304/// Ignore this variant entirely.
305///
306/// - `key`
307///
308/// Defines unique name of the variant to use it in `only_if`/`remove_if` conditions. If
309/// omitted, `sync`/`async` will be used.
310///
311/// ```rust
312/// #[maybe_async_cfg2::maybe(
313/// sync(key="foo", feature="use_sync"),
314/// async(key="bar", feature="use_async"),
315/// )]
316/// struct Struct {
317/// f: usize,
318///
319/// // This field will only be present in sync variant
320/// #[maybe_async_cfg2::only_if(key="foo")]
321/// sync_only_field: bool,
322/// }
323/// ```
324/// After conversion:
325/// ```rust
326/// #[cfg(feature="use_sync")]
327/// struct StructSync {
328/// f: usize,
329/// sync_only_field: bool,
330/// }
331/// #[cfg(feature="use_async")]
332/// struct StructAsync {
333/// f: usize,
334/// }
335/// ```
336///
337/// - `cfg`
338///
339/// Defines the condition (based on features), under which the current variant should appear.
340///
341/// Note: conditions like `feature = "..."`, `not(...)`, `all(...)`, `any(...)` will be
342/// processed correctly, even if the `cfg(...)` was omitted.
343///
344/// ```rust
345/// #[maybe_async_cfg2::maybe(
346/// sync(cfg(feature="use_sync")),
347/// async(feature="use_async")
348/// )]
349/// struct Struct {
350/// f: Foo,
351/// }
352/// ```
353/// After conversion:
354/// ```rust
355/// #[cfg(feature="use_sync")]
356/// struct StructSync {
357/// f: Foo,
358/// }
359/// #[cfg(feature="use_async")]
360/// struct StructAsync {
361/// f: Foo,
362/// }
363/// ```
364///
365/// - `idents`
366///
367/// Defines a list of identifiers that should be renamed depending on the variant of code.
368///
369/// Each identifier can have the following clarifying parameters:
370///
371/// - `snake`, `fn`, `mod`
372///
373/// means that this is the snake-case name of the function or module and it should be
374/// converted by adding the suffixes `"_sync"`/`"_async"` (otherwise, the suffixes
375/// `"Sync"`/`"Async"` will be used).
376///
377/// - `use`
378///
379/// in `use` lists, using this identifier will result in renaming via the `as` expression,
380/// rather than a simple replacement as is. In other cases, a simple replacement will be used.
381///
382/// - `keep`
383///
384/// this identifier will not be converted anywhere
385///
386/// - `sync`, `async`
387///
388/// specifies the name that will be used in the corresponding variant of code. Overrides
389/// the standard scheme of suffixes used by default. If the parameter value is omitted,
390/// the identifier will not be renamed in this case.
391///
392/// ```rust
393/// #[maybe_async_cfg2::maybe(
394/// idents(
395/// Foo,
396/// Bar,
397/// baz(fn),
398/// Qux(use),
399/// waldo(sync, async="async_waldo"),
400/// xyzzy(fn, use, sync="xizzy_the_sync_func"),
401/// ),
402/// sync(feature="use_sync"),
403/// async(feature="use_async"),
404/// )]
405/// async fn func() {
406/// struct Foo {}
407/// use extcrate::{
408/// Bar,
409/// baz,
410/// Qux,
411/// waldo::{
412/// plugh,
413/// xyzzy
414/// }
415/// };
416/// let _ = baz( Foo {}, Bar::new() ).await;
417/// let _ = xizzy( Qux::flob(b).await );
418/// }
419/// ```
420/// After conversion:
421/// ```rust
422/// #[cfg(feature="use_sync")]
423/// fn func_sync() {
424/// struct FooSync {}
425/// use extcrate::{
426/// BarSync,
427/// baz_sync,
428/// Qux as QuxSync,
429/// waldo::{
430/// plugh,
431/// xyzzy as xizzy_the_sync_func
432/// }
433/// };
434/// let _ = baz_sync( FooSync {}, BarSync::new() );
435/// let _ = xizzy_the_sync_func( QuxSync::flob() );
436/// }
437/// #[cfg(feature="use_async")]
438/// async fn func_async() {
439/// struct FooAsync {}
440/// use extcrate::{
441/// BarAsync,
442/// baz_async,
443/// Qux as QuxAsync,
444/// async_waldo::{
445/// plugh,
446/// xyzzy as xyzzy_async
447/// }
448/// };
449/// let _ = baz_async( FooAsync {}, BarAsync::new() ).await;
450/// let _ = xyzzy_async( QuxAsync::flob().await );
451/// }
452/// ```
453///
454/// - `keep_self`
455///
456/// Do not change name of item to which attribute `maybe` refers.
457///
458/// - `self`
459///
460/// Defines the name that will be assigned to the item in this variant.
461///
462/// - `send`
463///
464/// If `send = "Send"` or `send = "true"` is present, the attribute
465/// `#[async_trait::async_trait]` will be added before the async code. If `send = "?Send"` or
466/// `send = "false"` then `#[async_trait::async_trait(?Send)]` will be added.
467///
468/// - `drop_attrs`
469///
470/// Remove any attributes with specified names.
471///
472/// ```rust
473/// #[maybe_async_cfg2::maybe(
474/// sync(feature="use_sync", drop_attrs(attr)),
475/// async(feature="use_async"),
476/// )]
477/// struct Struct {
478/// f: usize,
479///
480/// // This attribute will be removed in sync variant
481/// #[attr(param)]
482/// field1: bool,
483/// }
484/// ```
485/// After conversion:
486/// ```rust
487/// #[cfg(feature="use_sync")]
488/// struct StructSync {
489/// f: usize,
490/// field1: bool,
491/// }
492/// #[cfg(feature="use_async")]
493/// struct StructAsync {
494/// f: usize,
495/// #[attr(param)]
496/// field1: bool,
497/// }
498/// ```
499///
500/// - `replace_features`
501///
502/// Replace one feature name with another.
503///
504/// ```rust
505/// #[maybe_async_cfg2::maybe(
506/// sync(feature="use_sync", replace_feature("secure", "secure_sync")),
507/// async(feature="use_async"),
508/// )]
509/// struct Struct {
510/// f: usize,
511/// // In sync variant "secure" feature will be replaced with "secure_sync" feature
512/// #[cfg(feature="secure")]
513/// field: bool,
514/// }
515/// ```
516/// After conversion:
517/// ```rust
518/// #[cfg(feature="use_sync")]
519/// struct StructSync {
520/// f: usize,
521/// #[cfg(feature="secure_sync")]
522/// field: bool,
523/// }
524/// #[cfg(feature="use_async")]
525/// struct StructAsync {
526/// f: usize,
527/// #[cfg(feature="secure")]
528/// field: bool,
529/// }
530/// ```
531///
532/// - `inner`, `outer`
533///
534/// Adds some attributes to the generated code. Inner attributes will appear below attribute
535/// `#[cfg(...)]`, outer attributes will appear above it.
536///
537/// Note: if the variant parameter is not parsed as a parameter of some other type, it will be
538/// interpreted as an inner attribute.
539///
540/// Useful for testing: just write `test` in variant parameters.
541///
542/// ```rust
543/// #[maybe_async_cfg2::maybe(
544/// sync(feature="secure_sync", test, "resource(path = \"/foo/bar\")", outer(xizzy)),
545/// async(feature="secure_sync", inner(baz(qux), async_attributes::test)),
546/// )]
547/// async fn test_func() {
548/// todo!()
549/// }
550/// ```
551/// After conversion:
552/// ```rust
553/// #[xizzy]
554/// #[cfg(feature="use_sync")]
555/// #[test]
556/// #[resource(path = "/foo/bar")]
557/// fn test_func_sync() {
558/// todo!()
559/// }
560/// #[cfg(feature="use_async")]
561/// #[baz(qux)]
562/// #[async_attributes::test]
563/// async fn test_func_async() {
564/// todo!()
565/// }
566/// ```
567///
568/// - In other cases, the following rules apply:
569/// - name-value pairs (`xxx = "yyy"`) with a name other than `key`, `prefix`, `send` and
570/// `feature` will produce an error.
571///
572/// - `feature = "..."`, `not(...)`, `all(...)`, `any(...)` will be interpreted as condition for
573/// current variant (as wrapped in `cfg(...)`).
574///
575/// - all another parameters will be interpreted as inner attribute for current variant (as
576/// wrapped in `inner(...)`).
577///
578/// ### Formal syntax
579///
580/// > _ParametersList_ :\
581/// > _Parameter_ (`,` _Parameter_)<sup>\*</sup>
582/// >
583/// > _Parameter_ :\
584/// > `disable`\
585/// > | `keep_self`\
586/// > | `prefix` `=` _STRING_LITERAL_\
587/// > | (`sync` | `async`) `(` _VersionParametersList_ `)`\
588/// > | `idents` `(` _IdentsList_ `)`\
589/// >
590/// > _VersionParametersList_ :\
591/// > _VersionParameter_ (`,` _VersionParameter_)<sup>\*</sup>
592/// >
593/// > _VersionParameter_ :\
594/// > `disable`\
595/// > | `keep_self`\
596/// > | `key` `=` _STRING_LITERAL_\
597/// > | `feature` `=` _STRING_LITERAL_\
598/// > | `self` `=` _STRING_LITERAL_\
599/// > | `send` `=` (`""` | `"Send"` | `"true"` | `"?Send"` | `"false"`)\
600/// > | (`cfg` | `any` | `all` | `not`) `(` _ANY_CFG_CONDITION_ `)`\
601/// > | `idents` `(` _IdentsList_ `)`\
602/// > | (`outer` | `inner`) `(` _AttributesList_ `)`\
603/// > | `replace_feature` `(` _STRING_LITERAL_ `,` _STRING_LITERAL_ `)`\
604/// > | `drop_attrs` `(` _IdentifiersList_ `)`\
605/// > | _Attribute_
606/// >
607/// > _Path_ :\
608/// > _IDENTIFIER_ (`::` _IDENTIFIER_)<sup>\+</sup>
609/// >
610/// > _IdentifiersList_ :\
611/// > _IDENTIFIER_ (`,` _IDENTIFIER_)<sup>\*</sup>
612/// >
613/// > _IdentsList_ :\
614/// > _Ident_ (`,` _Ident_)<sup>\*</sup>
615/// >
616/// > _Ident_ :\
617/// > _IDENTIFIER_ (`(` _IdentParametersList_ `)`)<sup>\?</sup>
618/// >
619/// > _IdentParametersList_ :\
620/// > _IdentParameter_ (`,` _IdentParameter_)<sup>\*</sup>
621/// >
622/// > _IdentParameter_ :\
623/// > `keep`\
624/// > | `use`\
625/// > | (`snake` | `fn` | `mod` )\
626/// > | `use`\
627/// > | (`sync` | `async` | _IDENTIFIER_) (`=` _STRING_LITERAL_)<sup>\?</sup>
628/// >
629/// > _AttributesList_ :\
630/// > _Attribute_ (`,` _Attribute_)<sup>\*</sup>
631/// >
632/// > _Attribute_ :\
633/// > (_IDENTIFIER_ | _Path_) (`(` _ANY_VALID_ARGS_ `)`)<sup>\?</sup>\
634/// > | _STRING_LITERAL_
635#[manyhow]
636#[proc_macro_attribute]
637pub fn maybe(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
638 macros::maybe(args, input)
639}
640
641/// Marks conditional content that should only be used in the specified variant of code.
642#[manyhow]
643#[proc_macro_attribute]
644pub fn only_if(_: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
645 Ok(body)
646}
647
648/// Marks conditional content that should be used in all variants of code except the specified
649/// one.
650#[manyhow]
651#[proc_macro_attribute]
652pub fn remove_if(_: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
653 Ok(body)
654}
655
656/// Does nothing (leaves content intact).
657#[manyhow]
658#[proc_macro_attribute]
659pub fn noop(_: TokenStream, body: TokenStream) -> syn::Result<TokenStream> {
660 Ok(body)
661}
662
663/// Removes marked content.
664#[manyhow]
665#[proc_macro_attribute]
666pub fn remove(_: TokenStream, _: TokenStream) -> syn::Result<TokenStream> {
667 Ok(TokenStream::new())
668}
669
670/// A wrapper for code with common `maybe` parameters
671///
672/// The `content` macro allows you to specify common parameters for many `maybe` macros. Use the
673/// internal `default` attribute with the required parameters inside the `content` macro.
674///
675/// ```rust
676/// maybe_async_cfg2::content! {
677/// #![maybe_async_cfg2::default(
678/// idents(Foo, Bar),
679/// )]
680///
681/// #[maybe_async_cfg2::maybe(sync(feature="use_sync"), async(feature="use_async"))]
682/// struct Struct {
683/// f: Foo,
684/// }
685///
686/// #[maybe_async_cfg2::maybe(sync(feature="use_sync"), async(feature="use_async"))]
687/// async fn func(b: Bar) {
688/// todo!()
689/// }
690/// } // content!
691/// ```
692/// After conversion:
693/// ```rust
694/// #[cfg(feature = "use_sync")]
695/// struct StructSync {
696/// f: FooSync,
697/// }
698/// #[cfg(feature = "use_async")]
699/// struct StructAsync {
700/// f: FooAsync,
701/// }
702///
703/// #[cfg(feature = "use_sync")]
704/// fn func_sync(b: BarSync) {
705/// todo!()
706/// }
707/// #[cfg(feature = "use_async")]
708/// async fn func_async(b: BarAsync) {
709/// todo!()
710/// }
711/// ```
712#[manyhow]
713#[proc_macro]
714pub fn content(body: TokenStream) -> syn::Result<TokenStream> {
715 macros::content(body)
716}