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