feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"0f881b1debfcbb2e2dcbac6889ba2b704cccf89ed07cfdbc163a6a3baf64ed4c",".github/workflows/rust.yml":"aaefe089547089fb3015482ca5ed0f6127630a13858a4b8f87d7b30f754f24fd",".rustfmt.toml":"49d27bdee7fdfa70ce80d636daf95c185c3454da8293963844f9f47b16d81b49",".versionrc":"96d3c71c6c827359bae1387f84e1feeea9796bc5338e69116d3b0e1ae0fa8178","Cargo.lock":"f0c5e49df4b3416bd95fded4bd5dc8cf9a30df8da3782a45aa74530485c26a28","Cargo.toml":"888faffdd606335c3a5956d8f815390f8fd6c7128dccee834fa69c8aa9a7cc35","Cargo.toml.orig":"c92dc50944131328434d5fdbd40282cce5021792ee8de5e9a6bf89b21560875f","LICENSE":"1041942721c04225d9d7d932a2d5e8dafa2f0585f6471373af522bca69b9799d","README.md":"00835b701dd11f199817859444719bf536b136bb12bc1b97448f7e96e6544f9c","examples/service_client.rs":"4f902609ec3ed9a4fceb3bb3b9850df97cffaae29e175ee28edce22f180beee3","examples/service_client_both.rs":"0be4fbab808b727ae8f55b151e7556278ad9d0d05abaf7d86ad03e300cbd6eba","src/debug.rs":"eca0ea329d0513aae27a551bb432af63ae7857b4ed5e2dfe55512f2956f035f7","src/doctests.rs":"7d22866c548843b8ded903b8b69852aec95306cdb6994fb0456c9686d24b2988","src/lib.rs":"ac9b69b6b3c9c75dddd01fb2baa86379f0f31243c8c5d9608b529de2f1ea0394","src/macros.rs":"dfc1d6938863ba12f4d41db2d83770d63286999998edc04591afb45b5fe0e2cf","src/params.rs":"8f0dba24a7261aa5c9eb3c523b6a626246d0d783a2f1430400b3c1aacefacb0a","src/utils.rs":"08bece94dae4e648c05469d6ac381fd26d4db7096fb5de415c67dd9cc40400ad","src/visit_ext.rs":"e183d53f2e466a246d1b9d5616ceeb59f811d281239758e692aaa2744e8cd83b","src/visitor_async.rs":"c34237dc0435fd18e99ba8d86ef43a0246f14e44cd21a298f1315f8aedc408be","src/visitor_content.rs":"74344701237ac81cfb973c73dc21093a9c66e083a91eaa628469662a4c8ea9c2","tests/doctests.rs":"a3e0f8ce4dbfa92297ca6353da89990c31f41087e0a0288f8c81f79c86cc2a6f","tests/test.rs":"479fe748e7a8db9e14f97d5b334f7545d8caefb2069ea0be4170b05525449aa1","tests/ui/01-maybe-async.rs":"05ef981ca2f51b6b49838c81c48f2658774839198d6e2097060879f6f96cadc0","tests/ui/02-must-be-async.rs":"545218e6316197451af0e232a04465e8225dc9645330329f333afc475d1ed99c","tests/ui/03-must-be-sync.rs":"5b03775f18fbcb7c5acaf929ac70123746d1b34d4dd350148b6e915025bb5f62","tests/ui/04-unit-test-util.rs":"615e121b31bfbf68b225d3d8497754767d7d30e2e24ca9b553edea958727bdec","tests/ui/05-replace-future-generic-type-with-output.rs":"d677901f021742d2271e9c8581025a538e43624e6b7af40778c52b50d975f926","tests/ui/06-sync_impl_async_impl.rs":"4d98fdde60232eb4563745cbd33ce74a526eee742240ef87713041eb6cec8ff7","tests/ui/test_fail/01-empty-test.rs":"1a3a5530b5d0c0dd7e25b8ea8374c9446ab92a27a521504b34630b777b8ce7d9","tests/ui/test_fail/01-empty-test.stderr":"420a368b6fce1f5237b77a4dbca71081fc2e76b9267376ee98fb81aa246c4bb6","tests/ui/test_fail/02-unknown-path.rs":"d00438c3332ffb79d1d99311f9895c6bb9abcda83ac13b7979d6e9e3fd2575a1","tests/ui/test_fail/02-unknown-path.stderr":"e0b6dacaaf926dd7d363f200ed4780815706537f6343d6797b085139db116133","tests/ui/test_fail/03-async-gt2.rs":"dc95152506fe275143ebe4a08da31237d89c06061e111671731f134dbea872a4","tests/ui/test_fail/03-async-gt2.stderr":"316c26307e95ae41bd4796d80a94285ce7323cb2517a55e062d813e8968d3303","tests/ui/test_fail/04-bad-sync-cond.rs":"56ce741aef3c795440937da79dbef1808ca6fbb130b5cf57b7698309f31730cd","tests/ui/test_fail/04-bad-sync-cond.stderr":"7bd708d4ba7c85eb93c004c3d9309ce9cf54660d998356def7c567d53a26b355","tests/unit-test-util.rs":"ce5c5ca0bd2ce69234b75ca28b5ad3a734f90784e6e43af7b05ee24afa6da39a"},"package":"a1e083394889336bc66a4eaf1011ffbfa74893e910f902a9f271fa624c61e1b2"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "384f6bd800420a8f42993271e5d7907848d29da9"
},
"path_in_vcs": ""
}
+22
View File
@@ -0,0 +1,22 @@
name: Rust
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
env:
CARGO_TERM_COLOR: always
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build
run: cargo build --verbose
- name: Run tests
run: cargo test --verbose
+7
View File
@@ -0,0 +1,7 @@
reorder_imports = true
format_code_in_doc_comments = true
normalize_doc_attributes = true
wrap_comments = true
comment_width = 100
format_strings = true
imports_granularity = "Crate"
+14
View File
@@ -0,0 +1,14 @@
{
"bumpFiles": [
{
"filename": "Cargo.toml",
"updater": ".standard-version/cargo-updater.js"
}
],
"packageFiles": [
{
"filename": "Cargo.toml",
"updater": ".standard-version/cargo-updater.js"
}
]
}
+1170
View File
File diff suppressed because it is too large. Load diff
+84
View File
@@ -0,0 +1,84 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "maybe-async-cfg"
version = "0.2.4"
authors = ["Sergey V. Kokorin <nvksv@nvksv.ru>"]
description = "A procedure macro to unify sync and async implementations depending on the features of your crate"
documentation = "https://docs.rs/maybe-async-cfg"
readme = "README.md"
keywords = [
"maybe",
"async",
"futures",
"macros",
"proc_macro",
]
license = "MIT"
repository = "https://github.com/nvksv/maybe-async-cfg"
[lib]
proc-macro = true
[[test]]
name = "doctests"
required-features = ["doctests"]
[dependencies.proc-macro-error]
version = "1.0"
[dependencies.proc-macro2]
version = "1.0"
[dependencies.pulldown-cmark]
version = "0.11"
optional = true
default-features = false
[dependencies.quote]
version = "1.0"
[dependencies.syn]
version = "1.0"
features = [
"visit-mut",
"full",
"extra-traits",
]
[dev-dependencies.async-std]
version = "1"
features = ["attributes"]
[dev-dependencies.async-trait]
version = "0.1"
[dev-dependencies.tokio]
version = "1"
features = [
"macros",
"rt-multi-thread",
]
[dev-dependencies.trybuild]
version = "1"
features = ["diff"]
[features]
debug = []
default = ["doctests"]
doctests = ["pulldown-cmark"]
no-debug = []
[badges.maintenance]
status = "actively-developed"
+19
View File
@@ -0,0 +1,19 @@
Copyright (c) 2020 Guoli Lyu
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+227
View File
@@ -0,0 +1,227 @@
<!-- cargo-sync-readme start -->
# Maybe-Async-Cfg Procedure Macro
**Why bother writing similar code twice for blocking and async code?**
[![Build Status](https://github.com/nvksv/maybe-async-cfg/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/nvksv/maybe-async-cfg/actions)
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
[![Latest Version](https://img.shields.io/crates/v/maybe-async-cfg.svg)](https://crates.io/crates/maybe-async-cfg)
[![maybe-async](https://docs.rs/maybe-async-cfg/badge.svg)](https://docs.rs/maybe-async-cfg)
When implementing both sync and async versions of API in a crate, most API of the two version
are almost the same except for some async/await keyword.
`maybe-async-cfg` help unifying async and sync implementation by **procedural macro**.
- Write async code with normal `async`, `await`, and let `maybe_async_cfg` handles those `async`
and `await` when you need a blocking code.
- Add `maybe` attributes and specify feature-based conditions under which sync or async code
should be generated.
- Use `only_if` (or `remove_if`) to keep code in specified version if necessary.
The `maybe` procedural macro can be applied to the following codes:
- use declaration
- trait declaration
- trait implementation
- function definition
- struct and enum definition
- modules
**RECOMMENDATION**: Enable **resolver ver2** in your crate, which is introduced in Rust 1.51. If
not, two crates in dependency with conflict version (one async and another blocking) can fail
compilation.
## Motivation
The async/await language feature alters the async world of rust. Comparing with the map/and_then
style, now the async code really resembles sync version code.
In many crates, the async and sync version of crates shares the same API, but the minor
difference that all async code must be awaited prevent the unification of async and sync code.
In other words, we are forced to write an async and an sync implementation respectively.
## Macros in Detail
To use `maybe-async-cfg`, we must know which block of codes is only used on sync implementation,
and which on async. These two versions of the implementation should share the same function
signatures except for async/await keywords.
Use `maybe` macro for code that is the same in both async and sync versions except for
async/await keywords. Specify in the macro parameters the conditions (based on features) under
which async and/or sync versions of the code should appear.
- attribute macro **`maybe`**
Offers a unified way to provide sync and async conversion on demand depending on features,
enabled for your crate, with **async first** policy.
```toml
[dependencies]
maybe_async_cfg = "0.2"
[features]
use_sync = []
use_async = []
```
In this and all the following examples, we use two features. But you can use any conditions
that are convenient for you, for example, replacing `feature="use_sync"` with
`not(feature="use_async")` everywhere. Feel free, `maybe-async-cfg` does not analyze the
conditions in any way, just substituting them as is.
Add the `maybe` attribute before all the items that need to be changed in different versions
of the code (sync or async).
Want to keep async code? Specify the `async` parameter with the condition (based on
features) when your code should be async.
Wanna convert async code to sync? Specify the `sync` parameter with the condition when the
sync code should be generated.
```rust
#[maybe_async_cfg::maybe(
idents(Foo),
sync(feature="use_sync"),
async(feature="use_async")
)]
struct Struct {
f: Foo,
}
```
After conversion:
```rust
#[cfg(feature="use_sync")]
struct StructSync {
f: FooSync,
}
#[cfg(feature="use_async")]
struct StructAsync {
f: FooAsync,
}
```
- procedural macro **`content`**
The `content` macro allows you to specify common parameters for many `maybe` macros. Use the
internal `default` attribute with the required parameters inside the `content` macro.
```rust
maybe_async_cfg::content!{
#![maybe_async_cfg::default(
idents(Foo, Bar),
)]
#[maybe_async_cfg::maybe(
sync(feature="use_sync"),
async(feature="use_async")
)]
struct Struct {
f: Foo,
}
#[maybe_async_cfg::maybe(
sync(feature="use_sync"),
async(feature="use_async")
)]
async fn func(b: Bar) {
todo!()
}
} // content!
```
After conversion:
```rust
#[cfg(feature="use_sync")]
struct StructSync {
f: FooSync,
}
#[cfg(feature="use_async")]
struct StructAsync {
f: FooAsync,
}
#[cfg(feature="use_sync")]
fn func_sync(b: BarSync) {
todo!()
}
#[cfg(feature="use_async")]
async fn func_async(b: BarAsync) {
todo!()
}
```
## Doctests
When writing doctests, you can mark them as applicable only in the corresponding code version.
To do this, specify `only_if(`_VARIANT_KEY_`)` in the doctest attributes. Then in all other
versions of the code, this doctest will be replaced with an empty string.
```rust
#[maybe_async_cfg::maybe(
idents(Foo),
sync(feature="use_sync"),
async(feature="use_async")
)]
/// This is a structure.
/// ```rust, only_if(sync)
/// let s = StructSync{ f: FooSync::new() };
/// ```
/// ```rust, only_if(async)
/// let s = StructAsync{ f: FooAsync::new().await };
/// ```
struct Struct {
f: Foo,
}
```
After conversion:
```rust
#[cfg(feature="use_sync")]
/// This is a structure.
/// ```rust, only_if(sync)
/// let s = StructSync{ f: FooSync::new() };
/// ```
///
struct StructSync {
f: FooSync,
}
#[cfg(feature="use_async")]
/// This is a structure.
///
/// ```rust, only_if(async)
/// let s = StructAsync{ f: FooAsync::new().await };
/// ```
struct StructAsync {
f: FooAsync,
}
```
## Examples
### rust client for services
When implementing rust client for any services, like awz3. The higher level API of async and
sync version is almost the same, such as creating or deleting a bucket, retrieving an object and
etc.
The example `service_client` is a proof of concept that `maybe_async_cfg` can actually free us
from writing almost the same code for sync and async. We can toggle between a sync AWZ3 client
and async one by `is_sync` feature gate when we add `maybe-async-cfg` to dependency.
## Acknowledgements
This crate is a redesigned fork of these wonderful crates:
- [fMeow/maybe-async-rs](https://github.com/fMeow/maybe-async-rs)
- [marioortizmanero/maybe-async-rs](https://github.com/marioortizmanero/maybe-async-rs)
Thanks!
# License
MIT
<!-- cargo-sync-readme end -->
+114
View File
@@ -0,0 +1,114 @@
#![allow(dead_code, unused_variables)]
maybe_async_cfg::content!{
#![maybe_async_cfg::default(
idents(
InnerClient,
ServiceClient,
)
)]
type Response = String;
type Url = &'static str;
type Method = String;
/// To use `maybe-async-cfg`, we must know which block of codes is only used on
/// blocking implementation, and which on async. These two implementation should
/// share the same API except for async/await keywords.
/// This will generate two traits: `InnerClientSync` and `InnerClientAsync`
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
async(feature = "is_async", async_trait::async_trait),
)]
trait InnerClient {
async fn request(method: Method, url: Url, data: String) -> Response;
#[inline]
async fn post(url: Url, data: String) -> Response {
Self::request(String::from("post"), url, data).await
}
#[inline]
async fn delete(url: Url, data: String) -> Response {
Self::request(String::from("delete"), url, data).await
}
}
/// This will generate a `ServiceClientSync`, which will implement
/// `InnerClientSync`, and a `ServiceClientAsync`, which will implement
/// `InnerClientAsync`.
///
/// If we had a single `ServiceClient` which implemented both `InnerClientSync`
/// and `InnerClientAsync`, calls to methods like `request` would be ambiguous
/// when both async and sync were enabled.
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(feature = "is_async"))]
pub struct ServiceClient;
/// Synchronous implementation.
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(feature = "is_async"))]
#[maybe_async_cfg::only_if(sync)]
impl InnerClient for ServiceClient {
fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for sync, like use `reqwest::blocking` to send
// the request
String::from("pretend we have a response")
}
}
/// Asynchronous implementation only.
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
async(feature = "is_async", async_trait::async_trait),
)]
#[maybe_async_cfg::only_if(async)]
impl InnerClient for ServiceClient {
async fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for async, like use `reqwest::client` or
// `async_std` to send the request
String::from("pretend we have a response")
}
}
/// Code of upstream API are almost the same for sync and async, except for
/// async/await keyword. This will generate the same `impl` but for both
/// `ServiceClientAsync` and `ServiceClientSync`.
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(feature = "is_async"))]
impl ServiceClient {
async fn create_bucket(name: String) -> Response {
Self::post("http://correct_url4create", String::from("my_bucket")).await
// When `is_sync` is toggle on, this block will compiles to:
// Self::post("http://correct_url4create", String::from("my_bucket"))
}
async fn delete_bucket(name: String) -> Response {
Self::delete("http://correct_url4delete", String::from("my_bucket")).await
}
// and another thousands of functions that interact with service side
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(feature = "is_async"))]
#[maybe_async_cfg::only_if(sync)]
fn run_sync() {
println!("sync impl running");
let _ = ServiceClientSync::create_bucket("bucket".to_owned());
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(feature = "is_async"))]
#[maybe_async_cfg::only_if(async)]
async fn run_async() {
println!("async impl running");
let _ = ServiceClientAsync::create_bucket("bucket".to_owned()).await;
}
#[tokio::main]
async fn main() {
#[cfg(feature = "is_sync")]
run_sync();
#[cfg(feature = "is_async")]
run_async().await;
}
}
+116
View File
@@ -0,0 +1,116 @@
//! Manually expanded version of `service_client.rs` for both sync and async
//! features.
#![allow(dead_code, unused_variables)]
type Response = String;
type Url = &'static str;
type Method = String;
/// To use `maybe-async-cfg`, we must know which block of codes is only used on
/// blocking implementation, and which on async. These two implementation should
/// share the same API except for async/await keywords.
/// InnerClient are used to actually send request,
/// which differ a lot between sync and async.
#[cfg(feature = "is_sync")]
trait InnerClientSync {
fn request(method: Method, url: Url, data: String) -> Response;
#[inline]
fn post(url: Url, data: String) -> Response {
Self::request(String::from("post"), url, data)
}
#[inline]
fn delete(url: Url, data: String) -> Response {
Self::request(String::from("delete"), url, data)
}
}
#[cfg(feature = "is_async")]
#[async_trait::async_trait]
trait InnerClientAsync {
async fn request(method: Method, url: Url, data: String) -> Response;
#[inline]
async fn post(url: Url, data: String) -> Response {
Self::request(String::from("post"), url, data).await
}
#[inline]
async fn delete(url: Url, data: String) -> Response {
Self::request(String::from("delete"), url, data).await
}
}
/// The higher level API for end user, synchronous version.
#[cfg(feature = "is_sync")]
pub struct ServiceClientSync;
/// The higher level API for end user, asynchronous version.
#[cfg(feature = "is_async")]
pub struct ServiceClientAsync;
/// Synchronous implementation, only compiles when `is_sync` feature is on.
#[cfg(feature = "is_sync")]
impl InnerClientSync for ServiceClientSync {
fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for sync, like use
// `reqwest::blocking` to send request
String::from("pretend we have a response")
}
}
/// Asynchronous implementation, only compiles when `is_async` feature is on.
#[cfg(feature = "is_async")]
#[async_trait::async_trait]
impl InnerClientAsync for ServiceClientAsync {
async fn request(method: Method, url: Url, data: String) -> Response {
// your implementation for async, like use `reqwest::client`
// or `async_std` to send request
String::from("pretend we have a response")
}
}
/// Code of upstream API are almost the same for sync and async,
/// except for async/await keyword.
#[cfg(feature = "is_sync")]
impl ServiceClientSync {
fn create_bucket(name: String) -> Response {
Self::post("http://correct_url4create", String::from("my_bucket"))
}
fn delete_bucket(name: String) -> Response {
Self::delete("http://correct_url4delete", String::from("my_bucket"))
}
// and another thousands of functions that interact with service side
}
#[cfg(feature = "is_async")]
impl ServiceClientAsync {
async fn create_bucket(name: String) -> Response {
Self::post("http://correct_url4create", String::from("my_bucket")).await
}
async fn delete_bucket(name: String) -> Response {
Self::delete("http://correct_url4delete", String::from("my_bucket")).await
}
// and another thousands of functions that interact with service side
}
#[cfg(feature = "is_sync")]
fn run_sync() {
println!("sync impl running");
let _ = ServiceClientSync::create_bucket("bucket".to_owned());
}
#[cfg(feature = "is_async")]
async fn run_async() {
println!("async impl running");
let _ = ServiceClientAsync::create_bucket("bucket".to_owned()).await;
}
#[tokio::main]
async fn main() {
#[cfg(feature = "is_sync")]
run_sync();
#[cfg(feature = "is_async")]
run_async().await;
}
+80
View File
@@ -0,0 +1,80 @@
#[cfg(feature="debug")]
pub mod inner {
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use crate::params::MacroParameters;
////////////////////////////////////////////////////////////////////////////////////////////////
pub fn dump_maybe(args: &TokenStream, input: &TokenStream) {
println!("--vvv------------------------------------------");
println!("maybe:");
println!("args:");
println!("{}", args);
println!("input:");
println!("{}", input);
println!("--^^^------------------------------------------");
println!("");
}
pub fn dump_tokens(name: &str, ts: &TokenStream) {
println!("--vvv------------------------------------------");
println!("{}:", name);
println!("{}", ts);
println!("--^^^------------------------------------------");
println!("");
}
pub fn dump_tokens2(name: &str, ts: &TokenStream2) {
println!("--vvv------------------------------------------");
println!("{}:", name);
println!("{}", ts);
println!("--^^^------------------------------------------");
println!("");
}
pub fn dump_params(name: &str, params: &MacroParameters) {
println!("--vvv------------------------------------------");
println!("{}:", name);
println!("{:#?}", params);
println!("--^^^------------------------------------------");
println!("");
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
macro_rules! dump_maybe {
($args:expr, $input:expr) => {
#[cfg(feature="debug")]
crate::debug::inner::dump_maybe($args, $input);
}
}
pub(crate) use dump_maybe;
macro_rules! dump_tokens {
($name:expr, $ts:expr) => {
#[cfg(feature="debug")]
crate::debug::inner::dump_tokens($name, $ts);
}
}
pub(crate) use dump_tokens;
macro_rules! dump_tokens2 {
($name:expr, $ts:expr) => {
#[cfg(feature="debug")]
crate::debug::inner::dump_tokens2($name, $ts);
}
}
pub(crate) use dump_tokens2;
macro_rules! dump_params {
($name:expr, $params:expr) => {
#[cfg(feature="debug")]
crate::debug::inner::dump_params($name, $params);
}
}
pub(crate) use dump_params;
+213
View File
@@ -0,0 +1,213 @@
use pulldown_cmark::{Parser, Event, Tag, TagEnd, CodeBlockKind};
fn as_lang_tokens(string: &str) -> impl Iterator<Item = &str> {
// Pandoc, which Rust once used for generating documentation,
// expects lang strings to be surrounded by `{}` and for each token
// to be proceeded by a `.`. Since some of these lang strings are still
// loose in the wild, we strip a pair of surrounding `{}` from the lang
// string and a leading `.` from each token.
let string = string.trim();
let first = string.chars().next();
let last = string.chars().last();
let string = if first == Some('{') && last == Some('}') {
&string[1..string.len() - 1]
} else {
string
};
string
.split(|c| c == ',' || c == ' ' || c == '\t')
.map(str::trim)
.map(|token| token.strip_prefix('.').unwrap_or(token))
.filter(|token| !token.is_empty())
}
fn parse_lang(lang: &str) -> Option<(String, String)> {
let our_prefix = "only_if(";
let mut has_our_attr = false;
let mut key = String::new();
let mut new_lang = String::new();
for token in as_lang_tokens(lang) {
if token.starts_with(our_prefix) && token.ends_with(")") {
has_our_attr = true;
key = token[our_prefix.len() .. token.len()-1].to_string();
continue;
}
if !new_lang.is_empty() {
new_lang.push_str(", ");
}
new_lang.push_str(token);
}
if has_our_attr {
Some((key, new_lang))
} else {
None
}
}
fn paste_code(new_lang: &str, code: &str, indent: Option<&str>) -> String {
let mut res = String::new();
res.push_str("\n``` ");
res.push_str(new_lang);
res.push_str("\n");
res.push_str(code);
res.push_str("```");
if let Some(indent) = indent {
let mut first = true;
res = res.lines().map(|s| {
if first {
first = false;
return s.to_string()
};
let mut res = String::new();
res.push('\n');
res.push_str(indent);
res.push_str(s);
res
}).collect();
}
res
}
fn get_indent_from_content(content: &str) -> Option<String> {
let content = content.as_bytes();
let mut len = content.len();
if !(content[len-1] == b'`' && content[len-2] == b'`' && content[len-3] == b'`') {
return None;
}
len -= 3;
let mut rres: Vec<u8> = vec![];
while len >= 1 {
let b = content[len-1];
if b.is_ascii_whitespace() && !(b == b'\n' || b == b'\r') {
rres.push(b);
len -= 1;
} else {
break
}
}
let res: String = rres.into_iter().rev().map(|b| b as char).collect();
Some(res)
}
pub fn process_doctests(doc: &str, processor: impl Fn(&str, &str) -> Option<Option<String>>) -> Option<String> {
let parser = Parser::new(doc);
let mut prev_offset = 0usize;
let mut level = 0usize;
let mut block_key = String::new();
let mut block_new_lang = String::new();
let mut inside_code = false;
let mut code = String::new();
let mut has_changes: bool = false;
let mut new_doc = String::new();
for (event, ref offset) in parser.into_offset_iter() {
match event {
Event::Start(Tag::CodeBlock(ref kind)) => {
level += 1;
if level == 1 {
match match kind {
CodeBlockKind::Fenced(ref lang) => {
parse_lang(lang)
},
CodeBlockKind::Indented => {
None
}
} {
Some((key, new_lang)) => {
let mut new_start = offset.start;
let mut success = false;
while prev_offset < new_start {
let b = doc.as_bytes()[new_start-1];
if !b.is_ascii_whitespace() {
break;
}
if b == b'\n' {
new_start -= 1;
success = true;
break;
}
new_start -= 1;
}
if !success {
new_start = offset.start;
}
new_doc.push_str(&doc[prev_offset..new_start]);
prev_offset = new_start;
block_key = key;
block_new_lang = new_lang;
code.clear();
inside_code = true;
},
None => {},
};
}
},
Event::End(TagEnd::CodeBlock) => {
if level == 1 && inside_code {
let content = &doc[prev_offset..offset.end];
prev_offset = offset.end;
match processor(block_key.as_str(), code.as_str()) {
Some(Some(new_code)) => {
let indent = get_indent_from_content(content);
let new_code = paste_code(block_new_lang.as_str(), new_code.as_str(), indent.as_ref().map(|s| s.as_str()));
new_doc.push_str(new_code.as_str());
has_changes = true;
},
Some(None) => {
has_changes = true;
},
None => {
new_doc.push_str(content);
}
}
inside_code = false;
};
level -= 1;
},
Event::Text(ref text) if inside_code => {
code.push_str(&text);
},
_ => {}
}
};
if has_changes {
if prev_offset < doc.len() {
new_doc.push_str(&doc[prev_offset..doc.len()]);
}
Some(new_doc)
} else {
None
}
}
+712
View File
@@ -0,0 +1,712 @@
//!
//! # Maybe-Async-Cfg Procedure Macro
//!
//! **Why bother writing similar code twice for blocking and async code?**
//!
//! [![Build Status](https://github.com/nvksv/maybe-async-cfg/actions/workflows/rust.yml/badge.svg?branch=main)](https://github.com/nvksv/maybe-async-cfg/actions)
//! [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
//! [![Latest Version](https://img.shields.io/crates/v/maybe-async-cfg.svg)](https://crates.io/crates/maybe-async-cfg)
//! [![maybe-async](https://docs.rs/maybe-async-cfg/badge.svg)](https://docs.rs/maybe-async-cfg)
//!
//! When implementing both sync and async versions of API in a crate, most API of the two version
//! are almost the same except for some async/await keyword.
//!
//! `maybe-async-cfg` help unifying async and sync implementation by **procedural macro**.
//! - Write async code with normal `async`, `await`, and let `maybe_async_cfg` handles those `async`
//! and `await` when you need a blocking code.
//! - Add `maybe` attributes and specify feature-based conditions under which sync or async code
//! should be generated.
//! - Use `only_if` (or `remove_if`) to keep code in specified version if necessary.
//!
//! The `maybe` procedural macro can be applied to the following codes:
//! - use declaration
//! - trait declaration
//! - trait implementation
//! - function definition
//! - struct and enum definition
//! - modules
//!
//! **RECOMMENDATION**: Enable **resolver ver2** in your crate, which is introduced in Rust 1.51. If
//! not, two crates in dependency with conflict version (one async and another blocking) can fail
//! compilation.
//!
//!
//! ## Motivation
//!
//! The async/await language feature alters the async world of rust. Comparing with the map/and_then
//! style, now the async code really resembles sync version code.
//!
//! In many crates, the async and sync version of crates shares the same API, but the minor
//! difference that all async code must be awaited prevent the unification of async and sync code.
//! In other words, we are forced to write an async and an sync implementation respectively.
//!
//!
//! ## Macros in Detail
//!
//! To use `maybe-async-cfg`, we must know which block of codes is only used on sync implementation,
//! and which on async. These two versions of the implementation should share the same function
//! signatures except for async/await keywords.
//!
//! Use `maybe` macro for code that is the same in both async and sync versions except for
//! async/await keywords. Specify in the macro parameters the conditions (based on features) under
//! which async and/or sync versions of the code should appear.
//!
//! - attribute macro **`maybe`**
//!
//! Offers a unified way to provide sync and async conversion on demand depending on features,
//! enabled for your crate, with **async first** policy.
//!
//! ```toml
//! [dependencies]
//! maybe_async_cfg = "0.2"
//!
//! [features]
//! use_sync = []
//! use_async = []
//! ```
//!
//! In this and all the following examples, we use two features. But you can use any conditions
//! that are convenient for you, for example, replacing `feature="use_sync"` with
//! `not(feature="use_async")` everywhere. Feel free, `maybe-async-cfg` does not analyze the
//! conditions in any way, just substituting them as is.
//!
//! Add the `maybe` attribute before all the items that need to be changed in different versions
//! of the code (sync or async).
//!
//! Want to keep async code? Specify the `async` parameter with the condition (based on
//! features) when your code should be async.
//!
//! Wanna convert async code to sync? Specify the `sync` parameter with the condition when the
//! sync code should be generated.
//!
//! ```rust
//! #[maybe_async_cfg::maybe(
//! idents(Foo),
//! sync(feature="use_sync"),
//! async(feature="use_async")
//! )]
//! struct Struct {
//! f: Foo,
//! }
//! ```
//! After conversion:
//! ```rust
//! #[cfg(feature="use_sync")]
//! struct StructSync {
//! f: FooSync,
//! }
//! #[cfg(feature="use_async")]
//! struct StructAsync {
//! f: FooAsync,
//! }
//! ```
//!
//! - procedural macro **`content`**
//!
//! The `content` macro allows you to specify common parameters for many `maybe` macros. Use the
//! internal `default` attribute with the required parameters inside the `content` macro.
//!
//! ```rust
//! maybe_async_cfg::content!{
//! #![maybe_async_cfg::default(
//! idents(Foo, Bar),
//! )]
//!
//! #[maybe_async_cfg::maybe(
//! sync(feature="use_sync"),
//! async(feature="use_async")
//! )]
//! struct Struct {
//! f: Foo,
//! }
//!
//! #[maybe_async_cfg::maybe(
//! sync(feature="use_sync"),
//! async(feature="use_async")
//! )]
//! async fn func(b: Bar) {
//! todo!()
//! }
//! } // content!
//! ```
//! After conversion:
//! ```rust
//! #[cfg(feature="use_sync")]
//! struct StructSync {
//! f: FooSync,
//! }
//! #[cfg(feature="use_async")]
//! struct StructAsync {
//! f: FooAsync,
//! }
//!
//! #[cfg(feature="use_sync")]
//! fn func_sync(b: BarSync) {
//! todo!()
//! }
//! #[cfg(feature="use_async")]
//! async fn func_async(b: BarAsync) {
//! todo!()
//! }
//! ```
//!
//! ## Doctests
//!
//! When writing doctests, you can mark them as applicable only in the corresponding code version.
//! To do this, specify `only_if(`_VARIANT_KEY_`)` in the doctest attributes. Then in all other
//! versions of the code, this doctest will be replaced with an empty string.
//!
//! ```rust
//! #[maybe_async_cfg::maybe(
//! idents(Foo),
//! sync(feature="use_sync"),
//! async(feature="use_async")
//! )]
//! /// This is a structure.
//! /// ```rust, only_if(sync)
//! /// let s = StructSync{ f: FooSync::new() };
//! /// ```
//! /// ```rust, only_if(async)
//! /// let s = StructAsync{ f: FooAsync::new().await };
//! /// ```
//! struct Struct {
//! f: Foo,
//! }
//! ```
//! After conversion:
//! ```rust
//! #[cfg(feature="use_sync")]
//! /// This is a structure.
//! /// ```rust, only_if(sync)
//! /// let s = StructSync{ f: FooSync::new() };
//! /// ```
//! ///
//! struct StructSync {
//! f: FooSync,
//! }
//! #[cfg(feature="use_async")]
//! /// This is a structure.
//! ///
//! /// ```rust, only_if(async)
//! /// let s = StructAsync{ f: FooAsync::new().await };
//! /// ```
//! struct StructAsync {
//! f: FooAsync,
//! }
//! ```
//!
//! ## Examples
//!
//! ### rust client for services
//!
//! When implementing rust client for any services, like awz3. The higher level API of async and
//! sync version is almost the same, such as creating or deleting a bucket, retrieving an object and
//! etc.
//!
//! The example `service_client` is a proof of concept that `maybe_async_cfg` can actually free us
//! from writing almost the same code for sync and async. We can toggle between a sync AWZ3 client
//! and async one by `is_sync` feature gate when we add `maybe-async-cfg` to dependency.
//!
//!
//! ## Acknowledgements
//!
//! This crate is a redesigned fork of these wonderful crates:
//!
//! - [fMeow/maybe-async-rs](https://github.com/fMeow/maybe-async-rs)
//!
//! - [marioortizmanero/maybe-async-rs](https://github.com/marioortizmanero/maybe-async-rs)
//!
//! Thanks!
//!
//!
//! # License
//! MIT
#![deny(missing_docs)]
#![deny(rustdoc::missing_crate_level_docs)]
// note: the `rustdoc::missing_doc_code_examples` lint is unstable
//#![deny(rustdoc::missing_doc_code_examples)]
use proc_macro::TokenStream;
use proc_macro_error::proc_macro_error;
mod macros;
mod params;
mod utils;
mod visit_ext;
mod visitor_async;
mod visitor_content;
#[cfg(feature = "doctests")]
mod doctests;
mod debug;
const DEFAULT_CRATE_NAME: &'static str = "maybe_async_cfg";
const MACRO_MAYBE_NAME: &'static str = "maybe";
const MACRO_ONLY_IF_NAME: &'static str = "only_if";
const MACRO_REMOVE_IF_NAME: &'static str = "remove_if";
const MACRO_NOOP_NAME: &'static str = "noop";
const MACRO_REMOVE_NAME: &'static str = "remove";
const MACRO_DEFAULT_NAME: &'static str = "default";
const STANDARD_MACROS: &'static [&'static str] = &[
"dbg",
"print",
"println",
"assert",
"assert_eq",
"assert_ne",
];
/// Marks the code that can be presented in several versions.
///
/// ### The `maybe` macro has the following parameters:
///
/// - `disable`
///
/// The macro with `disable` parameter will do nothing, like `noop`. Use it to write and debug
/// initial async code.
///
/// - `prefix`
///
/// The name of `maybe-async-cfg` crate. If not set, `"maybe_async_cfg"` will be used.
///
/// - `sync`, `async`
///
/// Defines versions of the code: the item to which the attribute `maybe` refers will be
/// replaced with multiple copies (one for each version), which will be modified according to
/// the version kind and its parameters.
///
/// For the `sync` version, the item will be converted from async to sync code by deleting
/// the `async` and `await` keywords. The types `Future<Output=XXX>` will also be replaced with just
/// `XXX`. For the `async` version, the item will be left async.
///
/// In any case, the item will be converted according to all the parameters described below. For
/// functions, structs/enums and traits, the name will be changed as if it is mentioned in the
/// `idents` list (if it is not explicitly specified there and if `keep_self` is not present).
///
/// - All other parameters will be passed to all versions (with merging).
///
/// Therefore, those parts of the version parameters that match in all versions can be specified
/// here. For example, this is the expected behavior for the `idents` list.
///
/// ### Every version has the following parameters:
///
/// - `disable`
///
/// Ignore this version entirely.
///
/// - `key`
///
/// Defines unique name of the version to use it in `only_if`/`remove_if` conditions. If
/// omitted, `sync`/`async` will be used.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// sync(key="foo", feature="use_sync"),
/// async(key="bar", feature="use_async"),
/// )]
/// struct Struct {
/// f: usize,
///
/// // This field will only be present in sync version
/// #[maybe_async_cfg::only_if(key="foo")]
/// sync_only_field: bool,
/// }
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// struct StructSync {
/// f: usize,
/// sync_only_field: bool,
/// }
/// #[cfg(feature="use_async")]
/// struct StructAsync {
/// f: usize,
/// }
/// ```
///
/// - `cfg`
///
/// Defines the condition (based on features), under which the current version should appear.
///
/// Note: conditions like `feature = "..."`, `not(...)`, `all(...)`, `any(...)` will be
/// processed correctly, even if the `cfg(...)` was omitted.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// sync(cfg(feature="use_sync")),
/// async(feature="use_async")
/// )]
/// struct Struct {
/// f: Foo,
/// }
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// struct StructSync {
/// f: Foo,
/// }
/// #[cfg(feature="use_async")]
/// struct StructAsync {
/// f: Foo,
/// }
/// ```
///
/// - `idents`
///
/// Defines a list of identifiers that should be renamed depending on the version of the code.
///
/// Each identifier can have the following clarifying parameters:
///
/// - `snake`, `fn`, `mod`
///
/// means that this is the snake-case name of the function or module and it should be
/// converted by adding the suffixes `"_sync"`/`"_async"` (otherwise, the suffixes
/// `"Sync"`/`"Async"` will be used).
///
/// - `use`
///
/// in `use` lists, using this identifier will result in renaming via the `as` expression,
/// rather than a simple replacement as is. In other cases, a simple replacement will be used.
///
/// - `keep`
///
/// this identifier will not be converted anywhere
///
/// - `sync`, `async`
///
/// specifies the name that will be used in the corresponding version of the code. Overrides
/// the standard scheme of suffixes used by default. If the parameter value is omitted,
/// the identifier will not be renamed in this case.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// idents(
/// Foo,
/// Bar,
/// baz(fn),
/// Qux(use),
/// waldo(sync, async="async_waldo"),
/// xyzzy(fn, use, sync="xizzy_the_sync_func"),
/// ),
/// sync(feature="use_sync"),
/// async(feature="use_async"),
/// )]
/// async fn func() {
/// struct Foo {}
/// use extcrate::{
/// Bar,
/// baz,
/// Qux,
/// waldo::{
/// plugh,
/// xyzzy
/// }
/// };
/// let _ = baz( Foo {}, Bar::new() ).await;
/// let _ = xizzy( Qux::flob(b).await );
/// }
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// fn func_sync() {
/// struct FooSync {}
/// use extcrate::{
/// BarSync,
/// baz_sync,
/// Qux as QuxSync,
/// waldo::{
/// plugh,
/// xyzzy as xizzy_the_sync_func
/// }
/// };
/// let _ = baz_sync( FooSync {}, BarSync::new() );
/// let _ = xizzy_the_sync_func( QuxSync::flob() );
/// }
/// #[cfg(feature="use_async")]
/// async fn func_async() {
/// struct FooAsync {}
/// use extcrate::{
/// BarAsync,
/// baz_async,
/// Qux as QuxAsync,
/// async_waldo::{
/// plugh,
/// xyzzy as xyzzy_async
/// }
/// };
/// let _ = baz_async( FooAsync {}, BarAsync::new() ).await;
/// let _ = xyzzy_async( QuxAsync::flob().await );
/// }
/// ```
///
/// - `keep_self`
///
/// Do not change name of item to which attribute `maybe` refers.
///
/// - `self`
///
/// Defines the name that will be assigned to the item in this variant.
///
/// - `send`
///
/// If `send = "Send"` or `send = "true"` is present, the attribute
/// `#[async_trait::async_trait]` will be added before the async code. If `send = "?Send"` or
/// `send = "false"` then `#[async_trait::async_trait(?Send)]` will be added.
///
/// - `drop_attrs`
///
/// Remove any attributes with specified names.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// sync(feature="use_sync", drop_attrs(attr)),
/// async(feature="use_async"),
/// )]
/// struct Struct {
/// f: usize,
///
/// // This attribute will be removed in sync version
/// #[attr(param)]
/// field1: bool,
/// }
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// struct StructSync {
/// f: usize,
/// field1: bool,
/// }
/// #[cfg(feature="use_async")]
/// struct StructAsync {
/// f: usize,
/// #[attr(param)]
/// field1: bool,
/// }
/// ```
///
/// - `replace_features`
///
/// Replace one feature name with another.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// sync(feature="use_sync", replace_feature("secure", "secure_sync")),
/// async(feature="use_async"),
/// )]
/// struct Struct {
/// f: usize,
/// // In sync version "secure" feature will be replaced with "secure_sync" feature
/// #[cfg(feature="secure")]
/// field: bool,
/// }
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// struct StructSync {
/// f: usize,
/// #[cfg(feature="secure_sync")]
/// field: bool,
/// }
/// #[cfg(feature="use_async")]
/// struct StructAsync {
/// f: usize,
/// #[cfg(feature="secure")]
/// field: bool,
/// }
/// ```
///
/// - `inner`, `outer`
///
/// Adds some attributes to the generated code. Inner attributes will appear below attribute
/// `#[cfg(...)]`, outer attributes will appear above it.
///
/// Note: if the version parameter is not parsed as a parameter of some other type, it will be
/// interpreted as an inner attribute.
///
/// Useful for testing: just write `test` in version parameters.
///
/// ```rust
/// #[maybe_async_cfg::maybe(
/// sync(feature="secure_sync", test, "resource(path = \"/foo/bar\")", outer(xizzy)),
/// async(feature="secure_sync", inner(baz(qux), async_attributes::test)),
/// )]
/// async fn test_func() {
/// todo!()
/// }
/// ```
/// After conversion:
/// ```rust
/// #[xizzy]
/// #[cfg(feature="use_sync")]
/// #[test]
/// #[resource(path = "/foo/bar")]
/// fn test_func_sync() {
/// todo!()
/// }
/// #[cfg(feature="use_async")]
/// #[baz(qux)]
/// #[async_attributes::test]
/// async fn test_func_async() {
/// todo!()
/// }
/// ```
///
/// - In other cases, the following rules apply:
///
/// - name-value pairs (`xxx = "yyy"`) with a name other than `key`, `prefix`, `send` and
/// `feature` will produce an error.
///
/// - `feature = "..."`, `not(...)`, `all(...)`, `any(...)` will be interpreted as condition for
/// current version (as wrapped in `cfg(...)`).
///
/// - all another parameters will be interpreted as inner attribute for current version (as
/// wrapped in `inner(...)`).
///
/// ### Formal syntax
///
/// > _ParametersList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_Parameter_ (`,` _Parameter_)<sup>\*</sup>
/// >
/// > _Parameter_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;`disable`\
/// > &nbsp;&nbsp;|&nbsp;`keep_self`\
/// > &nbsp;&nbsp;|&nbsp;`prefix` `=` _STRING_LITERAL_\
/// > &nbsp;&nbsp;|&nbsp;(`sync` | `async`) `(` _VersionParametersList_ `)`\
/// > &nbsp;&nbsp;|&nbsp;`idents` `(` _IdentsList_ `)`\
/// >
/// > _VersionParametersList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_VersionParameter_ (`,` _VersionParameter_)<sup>\*</sup>
/// >
/// > _VersionParameter_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;`disable`\
/// > &nbsp;&nbsp;|&nbsp;`keep_self`\
/// > &nbsp;&nbsp;|&nbsp;`key` `=` _STRING_LITERAL_\
/// > &nbsp;&nbsp;|&nbsp;`feature` `=` _STRING_LITERAL_\
/// > &nbsp;&nbsp;|&nbsp;`self` `=` _STRING_LITERAL_\
/// > &nbsp;&nbsp;|&nbsp;`send` `=` (`""` | `"Send"` | `"true"` | `"?Send"` | `"false"`)\
/// > &nbsp;&nbsp;|&nbsp;(`cfg` | `any` | `all` | `not`) `(` _ANY_CFG_CONDITION_ `)`\
/// > &nbsp;&nbsp;|&nbsp;`idents` `(` _IdentsList_ `)`\
/// > &nbsp;&nbsp;|&nbsp;(`outer` | `inner`) `(` _AttributesList_ `)`\
/// > &nbsp;&nbsp;|&nbsp;`replace_feature` `(` _STRING_LITERAL_ `,` _STRING_LITERAL_ `)`\
/// > &nbsp;&nbsp;|&nbsp;`drop_attrs` `(` _IdentifiersList_ `)`\
/// > &nbsp;&nbsp;|&nbsp;_Attribute_
/// >
/// > _Path_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_IDENTIFIER_ (`::` _IDENTIFIER_)<sup>\+</sup>
/// >
/// > _IdentifiersList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_IDENTIFIER_ (`,` _IDENTIFIER_)<sup>\*</sup>
/// >
/// > _IdentsList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_Ident_ (`,` _Ident_)<sup>\*</sup>
/// >
/// > _Ident_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_IDENTIFIER_ (`(` _IdentParametersList_ `)`)<sup>\?</sup>
/// >
/// > _IdentParametersList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_IdentParameter_ (`,` _IdentParameter_)<sup>\*</sup>
/// >
/// > _IdentParameter_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;`keep`\
/// > &nbsp;&nbsp;|&nbsp;`use`\
/// > &nbsp;&nbsp;|&nbsp;(`snake` | `fn` | `mod` )\
/// > &nbsp;&nbsp;|&nbsp;`use`\
/// > &nbsp;&nbsp;|&nbsp;(`sync` | `async` | _IDENTIFIER_) (`=` _STRING_LITERAL_)<sup>\?</sup>
/// >
/// > _AttributesList_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;_Attribute_ (`,` _Attribute_)<sup>\*</sup>
/// >
/// > _Attribute_ :\
/// > &nbsp;&nbsp;&nbsp;&nbsp;(_IDENTIFIER_ | _Path_) (`(` _ANY_VALID_ARGS_ `)`)<sup>\?</sup>\
/// > &nbsp;&nbsp;|&nbsp;_STRING_LITERAL_
#[proc_macro_error]
#[proc_macro_attribute]
pub fn maybe(args: TokenStream, input: TokenStream) -> TokenStream {
macros::maybe(args, input)
}
/// Marks conditional content that should only be used in the specified version of the code.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn only_if(_: TokenStream, body: TokenStream) -> TokenStream {
body
}
/// Marks conditional content that should be used in all versions of the code except the specified
/// one.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn remove_if(_: TokenStream, body: TokenStream) -> TokenStream {
body
}
/// Does nothing (leaves content intact).
#[proc_macro_error]
#[proc_macro_attribute]
pub fn noop(_: TokenStream, body: TokenStream) -> TokenStream {
body
}
/// Removes marked content.
#[proc_macro_error]
#[proc_macro_attribute]
pub fn remove(_: TokenStream, _: TokenStream) -> TokenStream {
TokenStream::new()
}
/// A wrapper for code with common `maybe` parameters
///
/// The `content` macro allows you to specify common parameters for many `maybe` macros. Use the
/// internal `default` attribute with the required parameters inside the `content` macro.
///
/// ```rust
/// maybe_async_cfg::content!{
/// #![maybe_async_cfg::default(
/// idents(Foo, Bar),
/// )]
///
/// #[maybe_async_cfg::maybe(sync(feature="use_sync"), async(feature="use_async"))]
/// struct Struct {
/// f: Foo,
/// }
///
/// #[maybe_async_cfg::maybe(sync(feature="use_sync"), async(feature="use_async"))]
/// async fn func(b: Bar) {
/// todo!()
/// }
/// } // content!
/// ```
/// After conversion:
/// ```rust
/// #[cfg(feature="use_sync")]
/// struct StructSync {
/// f: FooSync,
/// }
/// #[cfg(feature="use_async")]
/// struct StructAsync {
/// f: FooAsync,
/// }
///
/// #[cfg(feature="use_sync")]
/// fn func_sync(b: BarSync) {
/// todo!()
/// }
/// #[cfg(feature="use_async")]
/// async fn func_async(b: BarAsync) {
/// todo!()
/// }
/// ```
///
#[proc_macro_error]
#[proc_macro]
pub fn content(body: TokenStream) -> TokenStream {
macros::content(body)
}
+177
View File
@@ -0,0 +1,177 @@
use proc_macro_error::abort;
#[allow(unused_imports)]
use std::iter::FromIterator;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use syn::{
parse_macro_input, spanned::Spanned, visit_mut::VisitMut,
};
#[allow(unused_imports)]
use quote::{quote, ToTokens};
use crate::{
MACRO_MAYBE_NAME,
params::{ConvertMode, MacroParameters},
utils::{unwrap_or_error},
visit_ext::Visitor,
visitor_async::{
AsyncAwaitVisitor, remove_asyncness_on_trait, remove_asyncness_on_impl,
remove_asyncness_on_fn},
visitor_content::ContentVisitor,
debug::*
};
////////////////////////////////////////////////////////////////////////////////////////////////////
pub fn maybe(args: TokenStream, input: TokenStream) -> TokenStream {
dump_maybe!(&args, &input);
let params = unwrap_or_error!(MacroParameters::from_tokens(args));
dump_params!("maybe params", &params);
if params.disable_get() {
return input;
}
if let Some(convert_mode) = params.mode_get() {
return convert(params, input, convert_mode)
}
let mut tokens = TokenStream::new();
for version in &params.versions {
let mut ts = TokenStream2::new();
match version.kind {
ConvertMode::IntoAsync | ConvertMode::IntoSync => {
let _ = unwrap_or_error!(version
.params
.extend_tokenstream2_with_cfg_outer_attrs(&mut ts));
let name = params.make_self_path(MACRO_MAYBE_NAME);
let args = version.params.to_tokens(Some(version.kind));
ts.extend(quote!(#[#name(#args)]));
let _ =
unwrap_or_error!(version.params.extend_tokenstream2_with_inner_attrs(&mut ts));
}
}
let ts: TokenStream = ts.into();
tokens.extend(ts);
tokens.extend(input.clone());
}
dump_tokens!("maybe after", &tokens);
tokens
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub fn convert(mut params: MacroParameters, input: TokenStream, convert_mode: ConvertMode) -> TokenStream {
dump_tokens!("convert before", &input);
let mut file = parse_macro_input!(input as syn::File);
for item in &mut file.items {
match item {
syn::Item::Impl(item) => convert_impl(&mut params, item, convert_mode),
syn::Item::Struct(item) => convert_struct(&mut params, item, convert_mode),
syn::Item::Enum(item) => convert_enum(&mut params, item, convert_mode),
syn::Item::Trait(item) => convert_trait(&mut params, item, convert_mode),
syn::Item::Fn(item) => convert_fn(&mut params, item, convert_mode),
syn::Item::Use(item) => convert_use(&mut params, item, convert_mode),
syn::Item::Mod(item) => convert_mod(&mut params, item, convert_mode),
_ => {
abort!(item.span(), "Allowed impl, struct, enum, trait, fn or use items only");
}
}
}
let ts = quote!(#file);
dump_tokens2!("convert after", &ts);
ts.into()
}
fn convert_impl(params: &mut MacroParameters, item: &mut syn::ItemImpl, convert_mode: ConvertMode) {
match &mut *item.self_ty {
syn::Type::Path(syn::TypePath { path, .. }) => {
if let Some(last) = path.segments.last_mut() {
params.original_self_name_set(last.ident.to_string(), false);
}
}
_ => {}
};
if !params.recursive_asyncness_removal_get() {
remove_asyncness_on_impl( item, convert_mode, params.send_get() );
}
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_impl_mut(item)
}
fn convert_struct(params: &mut MacroParameters, item: &mut syn::ItemStruct, convert_mode: ConvertMode) {
params.original_self_name_set(item.ident.to_string(), false);
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_struct_mut(item)
}
fn convert_enum(params: &mut MacroParameters, item: &mut syn::ItemEnum, convert_mode: ConvertMode) {
params.original_self_name_set(item.ident.to_string(), false);
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_enum_mut(item)
}
fn convert_trait(params: &mut MacroParameters, item: &mut syn::ItemTrait, convert_mode: ConvertMode) {
params.original_self_name_set(item.ident.to_string(), false);
if !params.recursive_asyncness_removal_get() {
remove_asyncness_on_trait(item, convert_mode);
}
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_trait_mut(item)
}
fn convert_fn(params: &mut MacroParameters, item: &mut syn::ItemFn, convert_mode: ConvertMode) {
params.original_self_name_set(item.sig.ident.to_string(), true);
if !params.recursive_asyncness_removal_get() {
remove_asyncness_on_fn(item, convert_mode);
}
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_fn_mut(item)
}
fn convert_use(params: &mut MacroParameters, item: &mut syn::ItemUse, convert_mode: ConvertMode) {
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_use_mut(item)
}
fn convert_mod(params: &mut MacroParameters, item: &mut syn::ItemMod, convert_mode: ConvertMode) {
params.original_self_name_set(item.ident.to_string(), true);
let mut visitor = Visitor::new(AsyncAwaitVisitor::new(params, convert_mode));
visitor.visit_item_mod_mut(item)
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub fn content(body: TokenStream) -> TokenStream {
dump_tokens!("content before", &body);
let mut visitor = Visitor::new(ContentVisitor::new());
let ts: TokenStream = visitor.process(body.into()).into();
dump_tokens!("content after", &ts);
ts
}
File diff suppressed because it is too large. Load diff
+201
View File
@@ -0,0 +1,201 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use syn::{
parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
token::Comma,
Expr,
Ident,
Meta,
NestedMeta,
Token,
};
use quote::ToTokens;
////////////////////////////////////////////////////////////////////////////////////////////////////
macro_rules! unwrap_or_error {
($res:expr) => {
match $res {
Ok(p) => p,
Err(err) => {
abort!(err)
}
}
};
}
pub(crate) use unwrap_or_error;
// macro_rules! unwrap2_or_error {
// ($res:expr) => {
// match $res {
// Ok(p) => p,
// Err(err) => {
// return proc_macro2::TokenStream::from(syn::Error::from(err).to_compile_error());
// }
// }
// };
// }
// pub(crate) use unwrap2_or_error;
macro_rules! set_error_and_return {
($err:expr) => {{
emit_error!($err);
return;
}};
($err:expr, $ret:expr) => {{
emit_error!($err);
return $ret;
}};
}
pub(crate) use set_error_and_return;
macro_rules! unwrap_or_set_error_and_return {
($res:expr) => {
match $res {
Ok(p) => p,
Err(err) => set_error_and_return!(err),
}
};
($res:expr, $ret:expr) => {
match $res {
Ok(p) => p,
Err(err) => set_error_and_return!(err, $ret),
}
};
}
pub(crate) use unwrap_or_set_error_and_return;
////////////////////////////////////////////////////////////////////////////////////////////////////
pub(crate) fn make_path(name: &str) -> syn::Path {
let mut segments = Punctuated::<syn::PathSegment, syn::token::Colon2>::new();
segments.push_value(syn::PathSegment {
ident: Ident::new(name, Span::call_site()),
arguments: syn::PathArguments::None,
});
syn::Path {
leading_colon: None,
segments,
}
}
pub(crate) fn make_nestedmeta_namevalue(name: &str, value: &str) -> syn::NestedMeta {
NestedMeta::Meta(Meta::NameValue(syn::MetaNameValue {
path: make_path(name),
eq_token: Token![=](Span::call_site()),
lit: syn::Lit::Str(syn::LitStr::new(value, Span::call_site())),
}))
}
pub(crate) fn make_nestedmeta_list(
name: &str,
nested: Punctuated<NestedMeta, syn::token::Comma>,
) -> syn::NestedMeta {
NestedMeta::Meta(Meta::List(syn::MetaList {
path: make_path(name),
paren_token: syn::token::Paren(proc_macro2::Span::call_site()),
nested,
}))
}
pub(crate) fn make_attr_from_str<S: AsRef<str>>(s: S, span: Span) -> syn::Result<syn::Attribute> {
let stream: TokenStream2 = format!("#[{}]", s.as_ref()).parse()?;
let mut attrs: VecOfAttrs = syn::parse(stream.into())?;
let attr = match attrs.attrs.len() {
1 => attrs.attrs.remove(0),
_ => return Err(syn::Error::new(span, "Expected attribute")),
};
Ok(attr)
}
pub(crate) fn make_attr_ts_from_str<S: AsRef<str>>(s: S, span: Span) -> syn::Result<TokenStream2> {
Ok(make_attr_from_str(s, span)?.to_token_stream())
}
////////////////////////////////////////////////////////////////////////////////////////////////////
struct VecOfAttrs {
pub attrs: Vec<syn::Attribute>,
}
impl syn::parse::Parse for VecOfAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(VecOfAttrs {
attrs: input.call(syn::Attribute::parse_outer)?,
})
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct AttributeArgsInParens {
_paren: syn::token::Paren,
pub args: Punctuated<NestedMeta, Token![,]>,
}
impl syn::parse::Parse for AttributeArgsInParens {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let content;
Ok(Self {
_paren: parenthesized!(content in input),
args: content.parse_terminated(NestedMeta::parse)?,
})
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct PunctuatedList {
pub list: Punctuated<Expr, Comma>,
}
impl Parse for PunctuatedList {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(PunctuatedList {
list: Punctuated::<Expr, Comma>::parse_terminated(input)?,
})
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct DebugByDisplay<T: std::fmt::Display>(pub T);
impl<T: std::fmt::Display> std::fmt::Debug for DebugByDisplay<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
T::fmt(&self.0, f)
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct OptionToTokens<T: ToTokens>(pub Option<T>);
impl<T: ToTokens> std::fmt::Debug for OptionToTokens<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.as_ref().map(|m| DebugByDisplay(m.to_token_stream())).fmt(f)
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct EqStr {
#[allow(dead_code)]
pub eq: Token![=],
pub str: syn::LitStr,
}
impl Parse for EqStr {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(EqStr {
eq: input.parse()?,
str: input.parse()?,
})
}
}
+354
View File
@@ -0,0 +1,354 @@
#[allow(unused_imports)]
use std::iter::FromIterator;
#[allow(unused_imports)]
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_error::{emit_error, abort};
use quote::quote;
use syn::{
visit_mut::{self, VisitMut},
};
use crate::utils::unwrap_or_error;
////////////////////////////////////////////////////////////////////////////////////////////////////
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IdentMode {
Use,
Other,
}
pub trait VisitMutExt {
fn process_attrs(&mut self, _attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
Ok(())
}
fn process_ident(&mut self, _ident: &mut syn::Ident, _mode: IdentMode) -> syn::Result<()> {
Ok(())
}
fn process_attribute(&mut self, _node: &mut syn::Attribute) -> syn::Result<()> {
Ok(())
}
fn process_expr(&mut self, _node: &mut syn::Expr) -> syn::Result<()> {
Ok(())
}
fn process_file(&mut self, _node: &mut syn::File) -> syn::Result<()> {
Ok(())
}
fn process_item(&mut self, _node: &mut syn::Item) -> syn::Result<()> {
Ok(())
}
fn process_item_impl(&mut self, _node: &mut syn::ItemImpl) -> syn::Result<()> {
Ok(())
}
fn process_item_trait(&mut self, _node: &mut syn::ItemTrait) -> syn::Result<()> {
Ok(())
}
fn process_item_fn(&mut self, _node: &mut syn::ItemFn) -> syn::Result<()> {
Ok(())
}
fn process_macro(&mut self, _node: &mut syn::Macro) -> syn::Result<()> {
Ok(())
}
fn process_path_segment(&mut self, _node: &mut syn::PathSegment) -> syn::Result<()> {
Ok(())
}
fn process_type_param(&mut self, _node: &mut syn::TypeParam) -> syn::Result<()> {
Ok(())
}
fn process_use_tree(&mut self, _node: &mut syn::UseTree) -> syn::Result<()> {
Ok(())
}
fn after_process_item(&mut self, _node: &mut syn::Item) -> syn::Result<()> {
Ok(())
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
pub struct Visitor<T> {
pub inner: T,
// err: Option<syn::Error>,
}
impl<T> Visitor<T> {
pub fn new(inner: T) -> Self {
Self { inner }
}
pub fn process(&mut self, item: TokenStream2) -> TokenStream2
where
Self: VisitMutExt,
{
let mut syntax_tree: syn::File = unwrap_or_error!(syn::parse(item.into()));
self.visit_file_mut(&mut syntax_tree);
let ts = quote!(#syntax_tree);
ts
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////
macro_rules! impl_fn {
(@expr $value:expr, value) => {
$value
};
(@expr $value:expr, (_, value)) => {
&mut $value.1
};
(@func $self:expr, $node:ident, $proc:ident(node $(.$path:ident)? as Some($expr:tt) $(, $mode:expr)?) ) => {
if let Some(value) = impl_fn!(@arg $node $(.$path)?) {
match $self.$proc( impl_fn!(@expr value, $expr) $(, $mode)? ) {
Ok(_) => {},
Err(e) => {
emit_error!(e)
}
};
};
};
(@func $self:expr, $node:ident, $proc:ident(node $(.$path:ident)? $(, $mode:expr)?) ) => {
match $self.$proc( impl_fn!(@arg $node $(.$path)?) $(, $mode)? ) {
Ok(_) => {},
Err(e) => {
emit_error!(e)
}
};
};
(@funcs $self:expr, $node:ident, { $($proc:ident $params:tt ;)+ } ) => {
$(
impl_fn!(@func $self, $node, $proc $params);
)+
};
(@arg $node:ident $(.$path:ident)+) => {
&mut $node $(.$path)+
};
(@arg $node:ident) => {
$node
};
($name:ident, $ty:ty, ) => {
fn $name(&mut self, node: &mut $ty) {
visit_mut::$name(self, node);
}
};
($name:ident, $ty:ty, $before:tt) => {
fn $name(&mut self, node: &mut $ty) {
impl_fn!(@funcs self, node, $before);
visit_mut::$name(self, node);
}
};
($name:ident, $ty:ty, $before:tt, $after:tt) => {
fn $name(&mut self, node: &mut $ty) {
impl_fn!(@funcs self, node, $before);
visit_mut::$name(self, node);
impl_fn!(@funcs self, node, $after);
}
};
}
#[rustfmt::skip]
impl<T> VisitMut for Visitor<T> where Self: VisitMutExt,
{
impl_fn!(visit_abi_mut, syn::Abi, );
impl_fn!(visit_angle_bracketed_generic_arguments_mut,
syn::AngleBracketedGenericArguments,
);
impl_fn!(visit_arm_mut, syn::Arm, { process_attrs(node.attrs); });
impl_fn!(visit_attr_style_mut, syn::AttrStyle, );
impl_fn!(visit_attribute_mut, syn::Attribute, { process_attribute(node); });
impl_fn!(visit_bare_fn_arg_mut, syn::BareFnArg, { process_attrs(node.attrs); });
impl_fn!(visit_bin_op_mut, syn::BinOp, );
impl_fn!(visit_binding_mut, syn::Binding, { process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_block_mut, syn::Block, );
impl_fn!(visit_bound_lifetimes_mut, syn::BoundLifetimes, );
impl_fn!(visit_const_param_mut, syn::ConstParam, { process_attrs(node.attrs); });
impl_fn!(visit_constraint_mut, syn::Constraint, );
impl_fn!(visit_data_mut, syn::Data, );
impl_fn!(visit_data_enum_mut, syn::DataEnum, );
impl_fn!(visit_data_struct_mut, syn::DataStruct, );
impl_fn!(visit_data_union_mut, syn::DataUnion, );
impl_fn!(visit_derive_input_mut, syn::DeriveInput, { process_attrs(node.attrs); });
impl_fn!(visit_expr_mut, syn::Expr, { process_expr(node); });
impl_fn!(visit_expr_array_mut, syn::ExprArray, { process_attrs(node.attrs); });
impl_fn!(visit_expr_assign_mut, syn::ExprAssign, { process_attrs(node.attrs); });
impl_fn!(visit_expr_assign_op_mut, syn::ExprAssignOp, { process_attrs(node.attrs); });
impl_fn!(visit_expr_async_mut, syn::ExprAsync, { process_attrs(node.attrs); });
impl_fn!(visit_expr_await_mut, syn::ExprAwait, { process_attrs(node.attrs); });
impl_fn!(visit_expr_binary_mut, syn::ExprBinary, { process_attrs(node.attrs); });
impl_fn!(visit_expr_block_mut, syn::ExprBlock, { process_attrs(node.attrs); });
impl_fn!(visit_expr_box_mut, syn::ExprBox, { process_attrs(node.attrs); });
impl_fn!(visit_expr_break_mut, syn::ExprBreak, { process_attrs(node.attrs); });
impl_fn!(visit_expr_call_mut, syn::ExprCall, { process_attrs(node.attrs); });
impl_fn!(visit_expr_cast_mut, syn::ExprCast, { process_attrs(node.attrs); });
impl_fn!(visit_expr_closure_mut, syn::ExprClosure, { process_attrs(node.attrs); });
impl_fn!(visit_expr_continue_mut, syn::ExprContinue, { process_attrs(node.attrs); });
impl_fn!(visit_expr_field_mut, syn::ExprField, { process_attrs(node.attrs); });
impl_fn!(visit_expr_for_loop_mut, syn::ExprForLoop, { process_attrs(node.attrs); });
impl_fn!(visit_expr_group_mut, syn::ExprGroup, { process_attrs(node.attrs); });
impl_fn!(visit_expr_if_mut, syn::ExprIf, { process_attrs(node.attrs); });
impl_fn!(visit_expr_index_mut, syn::ExprIndex, { process_attrs(node.attrs); });
impl_fn!(visit_expr_let_mut, syn::ExprLet, { process_attrs(node.attrs); });
impl_fn!(visit_expr_lit_mut, syn::ExprLit, { process_attrs(node.attrs); });
impl_fn!(visit_expr_loop_mut, syn::ExprLoop, { process_attrs(node.attrs); });
impl_fn!(visit_expr_macro_mut, syn::ExprMacro, { process_attrs(node.attrs); });
impl_fn!(visit_expr_match_mut, syn::ExprMatch, { process_attrs(node.attrs); });
impl_fn!(visit_expr_method_call_mut, syn::ExprMethodCall, { process_attrs(node.attrs); });
impl_fn!(visit_expr_paren_mut, syn::ExprParen, { process_attrs(node.attrs); });
impl_fn!(visit_expr_path_mut, syn::ExprPath, { process_attrs(node.attrs); });
impl_fn!(visit_expr_range_mut, syn::ExprRange, { process_attrs(node.attrs); });
impl_fn!(visit_expr_reference_mut, syn::ExprReference, { process_attrs(node.attrs); });
impl_fn!(visit_expr_repeat_mut, syn::ExprRepeat, { process_attrs(node.attrs); });
impl_fn!(visit_expr_return_mut, syn::ExprReturn, { process_attrs(node.attrs); });
impl_fn!(visit_expr_struct_mut, syn::ExprStruct, { process_attrs(node.attrs); });
impl_fn!(visit_expr_try_mut, syn::ExprTry, { process_attrs(node.attrs); });
impl_fn!(visit_expr_try_block_mut, syn::ExprTryBlock, { process_attrs(node.attrs); });
impl_fn!(visit_expr_tuple_mut, syn::ExprTuple, { process_attrs(node.attrs); });
impl_fn!(visit_expr_type_mut, syn::ExprType, { process_attrs(node.attrs); });
impl_fn!(visit_expr_unary_mut, syn::ExprUnary, { process_attrs(node.attrs); });
impl_fn!(visit_expr_unsafe_mut, syn::ExprUnsafe, { process_attrs(node.attrs); });
impl_fn!(visit_expr_while_mut, syn::ExprWhile, { process_attrs(node.attrs); });
impl_fn!(visit_expr_yield_mut, syn::ExprYield, { process_attrs(node.attrs); });
impl_fn!(visit_field_mut, syn::Field, { process_attrs(node.attrs); });
impl_fn!(visit_field_pat_mut, syn::FieldPat, { process_attrs(node.attrs); });
impl_fn!(visit_field_value_mut, syn::FieldValue, { process_attrs(node.attrs); });
impl_fn!(visit_fields_mut, syn::Fields, );
impl_fn!(visit_fields_named_mut, syn::FieldsNamed, );
impl_fn!(visit_fields_unnamed_mut, syn::FieldsUnnamed, );
impl_fn!(visit_file_mut, syn::File, { process_attrs(node.attrs); process_file(node); });
impl_fn!(visit_fn_arg_mut, syn::FnArg, );
impl_fn!(visit_foreign_item_mut, syn::ForeignItem, );
impl_fn!(visit_foreign_item_fn_mut, syn::ForeignItemFn, { process_attrs(node.attrs); });
impl_fn!(visit_foreign_item_macro_mut, syn::ForeignItemMacro, { process_attrs(node.attrs); });
impl_fn!(visit_foreign_item_static_mut, syn::ForeignItemStatic, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_foreign_item_type_mut, syn::ForeignItemType, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_generic_argument_mut, syn::GenericArgument, );
impl_fn!(visit_generic_method_argument_mut,
syn::GenericMethodArgument,
);
impl_fn!(visit_generic_param_mut, syn::GenericParam, );
impl_fn!(visit_generics_mut, syn::Generics, );
impl_fn!(visit_ident_mut, syn::Ident, );
impl_fn!(visit_impl_item_mut, syn::ImplItem, );
impl_fn!(visit_impl_item_const_mut, syn::ImplItemConst, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_impl_item_macro_mut, syn::ImplItemMacro, { process_attrs(node.attrs); });
impl_fn!(visit_impl_item_method_mut, syn::ImplItemMethod, { process_attrs(node.attrs); });
impl_fn!(visit_impl_item_type_mut, syn::ImplItemType, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_index_mut, syn::Index, );
impl_fn!(visit_item_mut, syn::Item, { process_item(node); }, { after_process_item(node); });
impl_fn!(visit_item_const_mut, syn::ItemConst, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_enum_mut, syn::ItemEnum, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_extern_crate_mut, syn::ItemExternCrate, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); process_ident(node.rename as Some((_, value)), IdentMode::Other); });
impl_fn!(visit_item_fn_mut, syn::ItemFn, { process_attrs(node.attrs); process_item_fn(node); });
impl_fn!(visit_item_foreign_mod_mut, syn::ItemForeignMod, { process_attrs(node.attrs); });
impl_fn!(visit_item_impl_mut, syn::ItemImpl, { process_attrs(node.attrs); process_item_impl(node); });
impl_fn!(visit_item_macro_mut, syn::ItemMacro, { process_attrs(node.attrs); process_ident(node.ident as Some(value), IdentMode::Other); });
impl_fn!(visit_item_macro2_mut, syn::ItemMacro2, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_mod_mut, syn::ItemMod, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_static_mut, syn::ItemStatic, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_struct_mut, syn::ItemStruct, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_trait_mut, syn::ItemTrait, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); process_item_trait(node); });
impl_fn!(visit_item_trait_alias_mut, syn::ItemTraitAlias, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_type_mut, syn::ItemType, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_union_mut, syn::ItemUnion, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_item_use_mut, syn::ItemUse, { process_attrs(node.attrs); });
impl_fn!(visit_label_mut, syn::Label, );
impl_fn!(visit_lifetime_mut, syn::Lifetime, );
impl_fn!(visit_lifetime_def_mut, syn::LifetimeDef, { process_attrs(node.attrs); });
impl_fn!(visit_lit_mut, syn::Lit, );
impl_fn!(visit_lit_bool_mut, syn::LitBool, );
impl_fn!(visit_lit_byte_mut, syn::LitByte, );
impl_fn!(visit_lit_byte_str_mut, syn::LitByteStr, );
impl_fn!(visit_lit_char_mut, syn::LitChar, );
impl_fn!(visit_lit_float_mut, syn::LitFloat, );
impl_fn!(visit_lit_int_mut, syn::LitInt, );
impl_fn!(visit_lit_str_mut, syn::LitStr, );
impl_fn!(visit_local_mut, syn::Local, { process_attrs(node.attrs); });
impl_fn!(visit_macro_mut, syn::Macro, { process_macro(node); });
impl_fn!(visit_macro_delimiter_mut, syn::MacroDelimiter, );
impl_fn!(visit_member_mut, syn::Member, );
impl_fn!(visit_meta_mut, syn::Meta, );
impl_fn!(visit_meta_list_mut, syn::MetaList, );
impl_fn!(visit_meta_name_value_mut, syn::MetaNameValue, );
impl_fn!(visit_method_turbofish_mut, syn::MethodTurbofish, );
impl_fn!(visit_nested_meta_mut, syn::NestedMeta, );
impl_fn!(visit_parenthesized_generic_arguments_mut,
syn::ParenthesizedGenericArguments,
);
impl_fn!(visit_pat_mut, syn::Pat, );
impl_fn!(visit_pat_box_mut, syn::PatBox, { process_attrs(node.attrs); });
impl_fn!(visit_pat_ident_mut, syn::PatIdent, { process_attrs(node.attrs); });
impl_fn!(visit_pat_lit_mut, syn::PatLit, { process_attrs(node.attrs); });
impl_fn!(visit_pat_macro_mut, syn::PatMacro, { process_attrs(node.attrs); });
impl_fn!(visit_pat_or_mut, syn::PatOr, { process_attrs(node.attrs); });
impl_fn!(visit_pat_path_mut, syn::PatPath, { process_attrs(node.attrs); });
impl_fn!(visit_pat_range_mut, syn::PatRange, { process_attrs(node.attrs); });
impl_fn!(visit_pat_reference_mut, syn::PatReference, { process_attrs(node.attrs); });
impl_fn!(visit_pat_rest_mut, syn::PatRest, { process_attrs(node.attrs); });
impl_fn!(visit_pat_slice_mut, syn::PatSlice, { process_attrs(node.attrs); });
impl_fn!(visit_pat_struct_mut, syn::PatStruct, { process_attrs(node.attrs); });
impl_fn!(visit_pat_tuple_mut, syn::PatTuple, { process_attrs(node.attrs); });
impl_fn!(visit_pat_tuple_struct_mut, syn::PatTupleStruct, { process_attrs(node.attrs); });
impl_fn!(visit_pat_type_mut, syn::PatType, { process_attrs(node.attrs); });
impl_fn!(visit_pat_wild_mut, syn::PatWild, { process_attrs(node.attrs); });
impl_fn!(visit_path_mut, syn::Path, );
impl_fn!(visit_path_arguments_mut, syn::PathArguments, );
impl_fn!(visit_path_segment_mut, syn::PathSegment, { process_path_segment(node); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_predicate_eq_mut, syn::PredicateEq, );
impl_fn!(visit_predicate_lifetime_mut, syn::PredicateLifetime, );
impl_fn!(visit_predicate_type_mut, syn::PredicateType, );
impl_fn!(visit_qself_mut, syn::QSelf, );
impl_fn!(visit_range_limits_mut, syn::RangeLimits, );
impl_fn!(visit_receiver_mut, syn::Receiver, { process_attrs(node.attrs); });
impl_fn!(visit_return_type_mut, syn::ReturnType, );
impl_fn!(visit_signature_mut, syn::Signature, { process_ident(node.ident, IdentMode::Other); } );
impl_fn!(visit_span_mut, Span, );
impl_fn!(visit_stmt_mut, syn::Stmt, );
impl_fn!(visit_trait_bound_mut, syn::TraitBound, );
impl_fn!(visit_trait_bound_modifier_mut,syn::TraitBoundModifier,);
impl_fn!(visit_trait_item_mut, syn::TraitItem, );
impl_fn!(visit_trait_item_const_mut, syn::TraitItemConst, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_trait_item_macro_mut, syn::TraitItemMacro, { process_attrs(node.attrs); });
impl_fn!(visit_trait_item_method_mut, syn::TraitItemMethod, { process_attrs(node.attrs); });
impl_fn!(visit_trait_item_type_mut, syn::TraitItemType, { process_attrs(node.attrs); process_ident(node.ident, IdentMode::Other); });
impl_fn!(visit_type_mut, syn::Type, );
impl_fn!(visit_type_array_mut, syn::TypeArray, );
impl_fn!(visit_type_bare_fn_mut, syn::TypeBareFn, );
impl_fn!(visit_type_group_mut, syn::TypeGroup, );
impl_fn!(visit_type_impl_trait_mut, syn::TypeImplTrait, );
impl_fn!(visit_type_infer_mut, syn::TypeInfer, );
impl_fn!(visit_type_macro_mut, syn::TypeMacro, );
impl_fn!(visit_type_never_mut, syn::TypeNever, );
impl_fn!(visit_type_param_mut, syn::TypeParam, { process_attrs(node.attrs); process_type_param(node); });
impl_fn!(visit_type_param_bound_mut, syn::TypeParamBound, );
impl_fn!(visit_type_paren_mut, syn::TypeParen, );
impl_fn!(visit_type_path_mut, syn::TypePath, );
impl_fn!(visit_type_ptr_mut, syn::TypePtr, );
impl_fn!(visit_type_reference_mut, syn::TypeReference, );
impl_fn!(visit_type_slice_mut, syn::TypeSlice, );
impl_fn!(visit_type_trait_object_mut, syn::TypeTraitObject, );
impl_fn!(visit_type_tuple_mut, syn::TypeTuple, );
impl_fn!(visit_un_op_mut, syn::UnOp, );
impl_fn!(visit_use_glob_mut, syn::UseGlob, );
impl_fn!(visit_use_group_mut, syn::UseGroup, );
impl_fn!(visit_use_name_mut, syn::UseName, { process_ident(node.ident, IdentMode::Use); });
impl_fn!(visit_use_path_mut, syn::UsePath, { process_ident(node.ident, IdentMode::Use); });
impl_fn!(visit_use_rename_mut, syn::UseRename, { process_ident(node.ident, IdentMode::Use); process_ident(node.rename, IdentMode::Use); });
impl_fn!(visit_use_tree_mut, syn::UseTree, { process_use_tree(node); });
impl_fn!(visit_variadic_mut, syn::Variadic, { process_attrs(node.attrs); });
impl_fn!(visit_variant_mut, syn::Variant, { process_attrs(node.attrs); });
impl_fn!(visit_vis_crate_mut, syn::VisCrate, );
impl_fn!(visit_vis_public_mut, syn::VisPublic, );
impl_fn!(visit_vis_restricted_mut, syn::VisRestricted, );
impl_fn!(visit_visibility_mut, syn::Visibility, );
impl_fn!(visit_where_clause_mut, syn::WhereClause, );
impl_fn!(visit_where_predicate_mut, syn::WherePredicate, );
}
+657
View File
@@ -0,0 +1,657 @@
#[allow(unused_imports)]
use std::{collections::HashMap, iter::FromIterator};
#[allow(unused_imports)]
use proc_macro::{TokenStream};
#[cfg(feature = "doctests")]
use proc_macro2::Span;
use quote::{quote, ToTokens};
use syn::{visit_mut::VisitMut, spanned::Spanned};
use crate::{
MACRO_NOOP_NAME, MACRO_REMOVE_NAME, MACRO_ONLY_IF_NAME, MACRO_REMOVE_IF_NAME,
params::{ConvertMode, MacroParameters},
utils::{AttributeArgsInParens, PunctuatedList, make_attr_from_str},
visit_ext::{IdentMode, VisitMutExt, Visitor},
};
#[cfg(feature = "doctests")]
use crate::{
doctests::process_doctests,
utils::{EqStr, make_path},
};
pub struct AsyncAwaitVisitor<'p> {
convert_mode: ConvertMode,
params: &'p mut MacroParameters,
generics: Vec<HashMap<String, syn::PathSegment>>,
}
impl<'p> AsyncAwaitVisitor<'p> {
pub fn new(params: &'p mut MacroParameters, convert_mode: ConvertMode) -> Self {
Self {
convert_mode,
params,
generics: vec![],
}
}
fn generics_get<S: AsRef<str>>(&self, key: S) -> Option<&syn::PathSegment> {
for gens in &self.generics {
if let Some(ps) = gens.get(key.as_ref()) {
return Some(ps);
}
}
None
}
}
fn search_future_trait_bound(bound: &syn::TypeParamBound) -> Option<syn::PathSegment> {
if let syn::TypeParamBound::Trait(trait_bound) = bound {
let segment = &trait_bound.path.segments[trait_bound.path.segments.len() - 1];
let name = segment.ident.to_string();
if name.eq("Future") {
// match Future<Output=Type>
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
// binding: Output=Type
if let syn::GenericArgument::Binding(binding) = &args.args[0] {
if let syn::Type::Path(p) = &binding.ty {
return Some(p.path.segments[0].clone());
}
}
}
}
};
None
}
pub fn remove_asyncness_on_trait(item: &mut syn::ItemTrait, convert_mode: ConvertMode) {
match convert_mode {
ConvertMode::IntoSync => {
for inner in &mut item.items {
if let syn::TraitItem::Method(ref mut method) = inner {
if method.sig.asyncness.is_some() {
method.sig.asyncness = None;
}
}
}
}
ConvertMode::IntoAsync => {}
}
}
pub fn remove_asyncness_on_impl(item: &mut syn::ItemImpl, convert_mode: ConvertMode, send: Option<bool>) {
match convert_mode {
ConvertMode::IntoSync => {
for inner in &mut item.items {
if let syn::ImplItem::Method(ref mut method) = inner {
if method.sig.asyncness.is_some() {
method.sig.asyncness = None;
}
}
}
}
ConvertMode::IntoAsync => {
if let Some(send) = send {
let attr_str = if send {
"async_trait::async_trait"
} else {
"async_trait::async_trait(?Send)"
};
let attr = make_attr_from_str(attr_str, item.span()).unwrap();
item.attrs.push(attr);
}
}
}
}
pub fn remove_asyncness_on_fn(item: &mut syn::ItemFn, convert_mode: ConvertMode) {
match convert_mode {
ConvertMode::IntoSync => {
if item.sig.asyncness.is_some() {
item.sig.asyncness = None;
}
}
ConvertMode::IntoAsync => {}
}
}
impl<'p> AsyncAwaitVisitor<'p> {
fn process_replace_features_meta(&self, meta: &mut syn::Meta) -> syn::Result<bool> {
let mut changed = false;
match meta {
syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(s),
..
}) => {
if let Some(ident) = path.get_ident() {
if ident.to_string() == "feature" {
let prev = s.value();
if let Some(new) = self.params.replace_features_get(&prev) {
*s = syn::LitStr::new(new, s.span());
changed = true;
}
}
}
}
syn::Meta::List(list) => {
for nm in &mut list.nested {
if let syn::NestedMeta::Meta(m) = nm {
changed |= self.process_replace_features_meta(m)?;
}
}
}
_ => {}
}
Ok(changed)
}
fn process_attribute_if(&mut self, attr: &mut syn::Attribute, not: bool) -> syn::Result<()> {
let args =
syn::parse_macro_input::parse::<AttributeArgsInParens>(attr.tokens.clone().into())?;
let arg = match &args.args.len() {
0 => {
return Err(syn::Error::new_spanned(
attr.to_token_stream(),
"Expected ident",
))
}
1 => &args.args[0],
_ => {
return Err(syn::Error::new_spanned(
args.args[1].to_token_stream(),
"Too many arguments",
))
}
};
let key = match arg {
syn::NestedMeta::Lit(syn::Lit::Str(s)) => s.value(),
syn::NestedMeta::Meta(syn::Meta::Path(ref p)) => {
if let Some(s) = p.get_ident() {
s.to_string()
} else {
return Err(syn::Error::new_spanned(
arg.to_token_stream(),
"Wrong ident",
));
}
}
syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
path,
lit: syn::Lit::Str(value),
..
})) if path.is_ident("key") => value.value(),
_ => {
return Err(syn::Error::new_spanned(
arg.to_token_stream(),
"Wrong ident",
))
}
};
let success = if let Some(current_key) = self.params.key_get() {
(current_key == &key) ^ not
} else {
false
};
let new_name = if success { MACRO_NOOP_NAME } else { MACRO_REMOVE_NAME };
attr.path = self.params.make_self_path(new_name);
Ok(())
}
#[cfg(feature = "doctests")]
fn process_doc_attrs(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
let mut acc: Vec<syn::Attribute> = vec![];
let mut acc_temp: Vec<syn::Attribute> = vec![];
let mut lines: Vec<String> = vec![];
let mut inside_doc = false;
fn process_docs(acc: &mut Vec<syn::Attribute>, acc_temp: &mut Vec<syn::Attribute>, lines: &mut Vec<String>, params: &MacroParameters) {
assert!(!lines.is_empty());
let mut first = true;
let doc: String = lines.iter().map(|s| {
if first {
first = false;
s.clone()
} else {
let mut ss = String::from("\n");
ss.push_str( s.as_str() );
ss
}
}).collect();
let processor = |key: &str, code: &str| -> Option<Option<String>> {
if let Some(param_key) = params.key_get() {
if param_key == key {
Some(Some(code.to_string()))
} else {
Some(None)
}
} else {
None
}
};
if let Some(doc) = process_doctests(doc.as_str(), processor) {
let mut acc_temp_drain = acc_temp.drain(..);
for line in doc.lines() {
let tokens = quote!(= #line);
let attr = if let Some(mut attr) = acc_temp_drain.next() {
attr.tokens = tokens;
attr
} else {
let sp = Span::call_site();
syn::Attribute {
pound_token: syn::Token![#]([sp]),
style: syn::AttrStyle::Outer,
bracket_token: syn::token::Bracket(sp),
path: make_path("doc"),
tokens,
}
};
acc.push(attr);
}
} else {
for attr in acc_temp.drain(..) {
acc.push(attr);
}
}
}
for attr in attrs.drain(..) {
match (inside_doc, attr.path.is_ident("doc")) {
(false, false) => {
acc.push(attr);
},
(false, true) => {
let es = syn::parse2::<EqStr>(attr.tokens.clone())?;
let doc = es.str.value();
lines.push(doc);
acc_temp.push(attr);
inside_doc = true;
},
(true, false) => {
process_docs(&mut acc, &mut acc_temp, &mut lines, &self.params);
acc_temp.clear();
lines.clear();
inside_doc = false;
acc.push(attr);
},
(true, true) => {
let es = syn::parse2::<EqStr>(attr.tokens.clone())?;
let doc = es.str.value();
lines.push(doc);
acc_temp.push(attr);
}
}
};
if inside_doc {
process_docs(&mut acc, &mut acc_temp, &mut lines, &self.params);
}
let _ = std::mem::replace(attrs, acc);
Ok(())
}
fn process_attrs(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
#[cfg(feature = "doctests")]
self.process_doc_attrs(attrs)?;
for attr in attrs.iter_mut() {
if let Some(name) = self.params.is_our_attr(attr) {
match name.as_str() {
MACRO_ONLY_IF_NAME => self.process_attribute_if(attr, false)?,
MACRO_REMOVE_IF_NAME => self.process_attribute_if(attr, true)?,
_ => {
// Attribute stays unchanged. Unknown attributes will be
// rejected by compiter later.
}
}
}
}
if !self.params.drop_attrs_is_empty() {
attrs.retain(|attr| {
if let Some(ident) = attr.path.get_ident() {
let ident = ident.to_string();
!self.params.drop_attrs_contains(&ident)
} else {
true
}
});
}
if !self.params.replace_features_is_empty() {
for attr in attrs {
if let Some(ident) = attr.path.get_ident() {
if ident.to_string() == "cfg" {
if let Ok(mut meta) = attr.parse_meta() {
if self.process_replace_features_meta(&mut meta)? {
if let syn::Meta::List(syn::MetaList { nested, .. }) = meta {
attr.tokens = quote!((#nested));
}
}
}
}
}
}
}
Ok(())
}
fn process_expr(&mut self, node: &mut syn::Expr) -> syn::Result<()> {
match self.convert_mode {
ConvertMode::IntoSync => {
// async -> sync, remove async_impl blocks
match node {
syn::Expr::Await(expr) => {
*node = (*expr.base).clone()
}
syn::Expr::Async(expr) => {
let inner = &expr.block;
let sync_expr = if inner.stmts.len() == 1 {
// remove useless braces when there is only one statement
let stmt = &inner.stmts.get(0).unwrap();
// convert statement to Expr
syn::parse_quote!(#stmt)
} else {
syn::Expr::Block(syn::ExprBlock {
attrs: expr.attrs.clone(),
block: inner.clone(),
label: None,
})
};
*node = sync_expr;
}
_ => {}
}
}
ConvertMode::IntoAsync => {
// stay async, just remove sync_impl blocks
match node {
_ => {}
}
}
};
Ok(())
}
fn process_item(&mut self, node: &mut syn::Item) -> syn::Result<()> {
match self.convert_mode {
ConvertMode::IntoSync => {
// find generic parameter of Future and replace it with its Output type
if let syn::Item::Fn(item_fn) = node {
let mut gens: HashMap<String, syn::PathSegment> = HashMap::new();
// generic params: <T:Future<Output=()>, F>
for param in &item_fn.sig.generics.params {
// generic param: T:Future<Output=()>
if let syn::GenericParam::Type(type_param) = param {
let generic_type_name = &type_param.ident;
// bound: Future<Output=()>
for bound in &type_param.bounds {
if let Some(ps) = search_future_trait_bound(bound) {
gens.insert(generic_type_name.to_string(), ps);
}
}
}
}
if let Some(where_clause) = &item_fn.sig.generics.where_clause {
for predicate in &where_clause.predicates {
if let syn::WherePredicate::Type(predicate_type) = predicate {
let generic_type_name =
if let syn::Type::Path(p) = &predicate_type.bounded_ty {
&p.path.segments[0].ident
} else {
panic!("Please submit an issue");
};
for bound in &predicate_type.bounds {
if let Some(ps) = search_future_trait_bound(bound) {
gens.insert(generic_type_name.to_string(), ps);
}
}
}
}
}
self.generics.push(gens);
}
if let syn::Item::Fn(item_fn) = node {
// remove generic type from generics <T, F>
let args = item_fn
.sig
.generics
.params
.iter()
.filter_map(|param| {
if let syn::GenericParam::Type(type_param) = &param {
if let Some(_) = self.generics_get(type_param.ident.to_string()) {
return None;
}
};
Some(param)
})
.collect::<Vec<_>>();
item_fn.sig.generics.params = syn::punctuated::Punctuated::from_iter(
args.into_iter().map(|p| p.clone()).collect::<Vec<_>>(),
);
// remove generic type from where clause
if let Some(where_clause) = &mut item_fn.sig.generics.where_clause {
let new_where_clause = where_clause
.predicates
.iter()
.filter_map(|predicate| {
if let syn::WherePredicate::Type(predicate_type) = predicate {
if let syn::Type::Path(p) = &predicate_type.bounded_ty {
if let Some(_) =
self.generics_get(p.path.segments[0].ident.to_string())
{
return None;
}
}
};
Some(predicate)
})
.collect::<Vec<_>>();
where_clause.predicates = syn::punctuated::Punctuated::from_iter(
new_where_clause
.into_iter()
.map(|c| c.clone())
.collect::<Vec<_>>(),
);
};
}
}
ConvertMode::IntoAsync => {}
};
Ok(())
}
fn after_process_item(&mut self, node: &mut syn::Item) -> syn::Result<()> {
match self.convert_mode {
ConvertMode::IntoSync => {
// find generic parameter of Future and replace it with its Output type
if let syn::Item::Fn(_item_fn) = node {
self.generics.pop();
}
}
_ => {}
}
Ok(())
}
fn process_item_impl(&mut self, node: &mut syn::ItemImpl) -> syn::Result<()> {
if self.params.recursive_asyncness_removal_get() {
remove_asyncness_on_impl( node, self.convert_mode, self.params.send_get() );
};
Ok(())
}
fn process_item_trait(&mut self, node: &mut syn::ItemTrait) -> syn::Result<()> {
if self.params.recursive_asyncness_removal_get() {
remove_asyncness_on_trait( node, self.convert_mode );
};
Ok(())
}
fn process_item_fn(&mut self, node: &mut syn::ItemFn) -> syn::Result<()> {
if self.params.recursive_asyncness_removal_get() {
remove_asyncness_on_fn( node, self.convert_mode );
};
Ok(())
}
fn process_path_segment(&mut self, node: &mut syn::PathSegment) -> syn::Result<()> {
let ident = &mut node.ident;
let ident_s = ident.to_string();
// replace generic type with target type
if let Some(ps) = self.generics_get(&ident_s) {
*node = ps.clone();
return Ok(());
}
Ok(())
}
fn process_ident(&mut self, ident: &mut syn::Ident, mode: IdentMode) -> syn::Result<()> {
if mode == IdentMode::Use {
return Ok(());
};
if let Some(ir) = self.params.idents_get(ident.to_string()) {
*ident = ir.ident_add_suffix(ident, self.convert_mode, self.params.key_get());
return Ok(());
}
Ok(())
}
fn process_type_param(&mut self, node: &mut syn::TypeParam) -> syn::Result<()> {
let ident = &mut node.ident;
if let Some(ir) = self.params.idents_get(&ident.to_string()) {
*ident = ir.ident_add_suffix(ident, self.convert_mode, self.params.key_get());
}
Ok(())
}
fn process_use_tree(&mut self, node: &mut syn::UseTree) -> syn::Result<()> {
match node {
syn::UseTree::Path(syn::UsePath { ident, .. }) => {
if let Some(ir) = self.params.idents_get(&ident.to_string()) {
if !ir.use_mode {
*ident = ir.ident_add_suffix(ident, self.convert_mode, self.params.key_get());
}
}
}
syn::UseTree::Name(name) => {
let ident = &mut name.ident;
if let Some(ir) = self.params.idents_get(&ident.to_string()) {
if ir.use_mode {
*node = syn::UseTree::Rename(syn::UseRename {
ident: ident.clone(),
as_token: syn::Token![as](ident.span()),
rename: ir.ident_add_suffix(ident, self.convert_mode, self.params.key_get()),
});
} else {
*ident = ir.ident_add_suffix(ident, self.convert_mode, self.params.key_get());
}
}
}
_ => {}
};
Ok(())
}
}
impl<'p> VisitMutExt for Visitor<AsyncAwaitVisitor<'p>> {
fn process_attrs(&mut self, attrs: &mut Vec<syn::Attribute>) -> syn::Result<()> {
self.inner.process_attrs(attrs)
}
fn process_ident(&mut self, ident: &mut syn::Ident, mode: IdentMode) -> syn::Result<()> {
self.inner.process_ident(ident, mode)
}
fn process_expr(&mut self, node: &mut syn::Expr) -> syn::Result<()> {
self.inner.process_expr(node)
}
fn process_item(&mut self, node: &mut syn::Item) -> syn::Result<()> {
self.inner.process_item(node)
}
fn process_item_impl(&mut self, node: &mut syn::ItemImpl) -> syn::Result<()> {
self.inner.process_item_impl(node)
}
fn process_item_trait(&mut self, node: &mut syn::ItemTrait) -> syn::Result<()> {
self.inner.process_item_trait(node)
}
fn process_item_fn(&mut self, node: &mut syn::ItemFn) -> syn::Result<()> {
self.inner.process_item_fn(node)
}
fn after_process_item(&mut self, node: &mut syn::Item) -> syn::Result<()> {
self.inner.after_process_item(node)
}
fn process_macro(&mut self, node: &mut syn::Macro) -> syn::Result<()> {
if let Some(ident) = node.path.get_ident() {
if self
.inner
.params
.standard_macros()
.contains(&ident.to_string().as_str())
{
let mut args = syn::parse2::<PunctuatedList>(node.tokens.clone())?;
for arg in &mut args.list {
self.visit_expr_mut(arg);
}
node.tokens = args.list.into_token_stream();
}
};
Ok(())
}
fn process_path_segment(&mut self, node: &mut syn::PathSegment) -> syn::Result<()> {
self.inner.process_path_segment(node)
}
fn process_type_param(&mut self, node: &mut syn::TypeParam) -> syn::Result<()> {
self.inner.process_type_param(node)
}
fn process_use_tree(&mut self, node: &mut syn::UseTree) -> syn::Result<()> {
self.inner.process_use_tree(node)
}
}
impl<'p> AsyncAwaitVisitor<'p> {}
+223
View File
@@ -0,0 +1,223 @@
#[allow(unused_imports)]
use std::iter::FromIterator;
#[allow(unused_imports)]
use proc_macro::TokenStream;
use proc_macro2::{Delimiter, Group, TokenStream as TokenStream2, TokenTree};
use proc_macro_error::emit_error;
use quote::quote;
use crate::{
params::MacroParameters,
utils::{set_error_and_return, unwrap_or_set_error_and_return},
visit_ext::{VisitMutExt, Visitor},
DEFAULT_CRATE_NAME, MACRO_MAYBE_NAME, MACRO_DEFAULT_NAME,
};
pub struct ContentVisitor {
pub params: MacroParameters,
}
impl ContentVisitor {
pub fn new() -> Self {
Self {
params: MacroParameters::new(),
}
}
fn process_attribute_maybe(&mut self, node: &mut syn::Attribute) -> syn::Result<()> {
let mut params = MacroParameters::from_tokens_in_parens(node.tokens.clone().into())?;
MacroParameters::apply_parent(&mut params, &self.params)?;
let tokens = params.to_tokens(None);
node.tokens = quote!((#tokens));
Ok(())
}
fn process_file(&mut self, node: &mut syn::File) -> syn::Result<()> {
node.attrs.retain(|attr| {
if let Some(prefix) = is_default_attr(attr) {
self.params = unwrap_or_set_error_and_return!(
MacroParameters::from_tokens_in_parens(attr.tokens.clone().into()),
false
);
self.params.prefix_set(prefix);
false
} else {
true
}
});
Ok(())
}
fn process_attribute(&mut self, node: &mut syn::Attribute) -> syn::Result<()> {
if let Some(name) = self.params.is_our_attr(node) {
match name.as_str() {
MACRO_MAYBE_NAME => self.process_attribute_maybe(node)?,
_ => {}
}
}
Ok(())
}
fn extend_with_group(parent: &mut TokenStream2, child: TokenStream2, delim: Delimiter) {
let g = TokenTree::Group(Group::new(delim, child));
parent.extend(vec![g]);
}
fn search_maybe_in_tokenstream(&self, ts: TokenStream2, initial_state: u8) -> TokenStream2 {
let mut state: u8 = initial_state;
let mut result = TokenStream2::new();
if state == 7 {
let mut params = match MacroParameters::from_tokens(ts.clone().into()) {
Ok(p) => p,
Err(_) => return ts,
};
match MacroParameters::apply_parent(&mut params, &self.params) {
Ok(p) => p,
Err(_) => return ts,
};
return params.to_tokens(None).into();
}
let mut iter = ts.into_iter();
while let Some(tt) = iter.next() {
match state {
0 => {
match &tt {
TokenTree::Punct(p) if p.as_char() == '#' => {
result.extend(vec![tt]);
state = 1;
continue;
}
TokenTree::Group(g) => {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 0),
g.delimiter(),
);
state = 0;
continue;
}
_ => {}
};
}
1 => {
if let TokenTree::Group(g) = &tt {
if g.delimiter() == Delimiter::Bracket {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 2),
g.delimiter(),
);
state = 0;
continue;
}
}
}
2 => {
if let TokenTree::Ident(ident) = &tt {
if ident.to_string() == DEFAULT_CRATE_NAME {
result.extend(vec![tt]);
state = 3;
continue;
}
}
}
3 => {
if let TokenTree::Punct(p) = &tt {
if p.as_char() == ':' {
result.extend(vec![tt]);
state = 4;
continue;
}
};
}
4 => {
if let TokenTree::Punct(p) = &tt {
if p.as_char() == ':' {
result.extend(vec![tt]);
state = 5;
continue;
}
};
}
5 => {
if let TokenTree::Ident(ident) = &tt {
if ident.to_string() == MACRO_MAYBE_NAME {
result.extend(vec![tt]);
state = 6;
continue;
}
}
}
6 => {
if let TokenTree::Group(g) = &tt {
if g.delimiter() == Delimiter::Parenthesis {
Self::extend_with_group(
&mut result,
self.search_maybe_in_tokenstream(g.stream(), 7),
g.delimiter(),
);
state = 0;
continue;
}
}
}
_ => unreachable!(),
};
result.extend(vec![tt]);
state = 0;
}
result
}
}
impl VisitMutExt for Visitor<ContentVisitor> {
fn process_attribute(&mut self, node: &mut syn::Attribute) -> syn::Result<()> {
self.inner.process_attribute(node)
}
fn process_file(&mut self, node: &mut syn::File) -> syn::Result<()> {
self.inner.process_file(node)
}
fn process_macro(&mut self, node: &mut syn::Macro) -> syn::Result<()> {
if node.path.is_ident("macro_rules") {
node.tokens = self
.inner
.search_maybe_in_tokenstream(node.tokens.clone(), 0);
}
Ok(())
}
}
fn is_default_attr(attr: &syn::Attribute) -> Option<String> {
if let syn::AttrStyle::Inner(_) = attr.style {
if attr.path.leading_colon.is_none() && attr.path.segments.len() == 2 {
let first_segment = &attr.path.segments[0];
let last_segment = &attr.path.segments[1];
if first_segment.arguments == syn::PathArguments::None
&& last_segment.arguments == syn::PathArguments::None
{
let first = first_segment.ident.to_string();
let last = last_segment.ident.to_string();
if last == MACRO_DEFAULT_NAME {
return Some(first);
}
}
}
};
None
}
+8
View File
@@ -0,0 +1,8 @@
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(not(feature = "is_sync")))]
#[allow(dead_code)]
trait T {
/// This is a doctest
fn f() {
todo!()
}
}
+15
View File
@@ -0,0 +1,15 @@
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/ui/01-maybe-async.rs");
t.pass("tests/ui/02-must-be-async.rs");
t.pass("tests/ui/03-must-be-sync.rs");
t.pass("tests/ui/04-unit-test-util.rs");
t.pass("tests/ui/05-replace-future-generic-type-with-output.rs");
t.pass("tests/ui/06-sync_impl_async_impl.rs");
t.compile_fail("tests/ui/test_fail/01-empty-test.rs");
t.compile_fail("tests/ui/test_fail/02-unknown-path.rs");
t.compile_fail("tests/ui/test_fail/03-async-gt2.rs");
t.compile_fail("tests/ui/test_fail/04-bad-sync-cond.rs");
}
+64
View File
@@ -0,0 +1,64 @@
#![allow(dead_code)]
#[maybe_async_cfg::maybe(send="Send", sync(feature = "is_sync"), async(not(feature = "is_sync"), async_trait::async_trait))]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(send="?Send", sync(feature = "is_sync"), async(not(feature = "is_sync"), async_trait::async_trait))]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"), async(not(feature = "is_sync")))]
async fn async_fn() {}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"), async(not(feature = "is_sync")))]
pub async fn pub_async_fn() {
}
struct Struct;
#[maybe_async_cfg::maybe(keep_self, idents(Trait), sync(feature = "is_sync"), async(not(feature = "is_sync"), async_trait::async_trait))]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() -> std::result::Result<(), ()> {
let s = Struct;
s.declare_async();
s.async_fn();
async_fn();
pub_async_fn();
Ok(())
}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {
let s = Struct;
s.declare_async().await;
s.async_fn().await;
async_fn().await;
pub_async_fn().await;
}
+76
View File
@@ -0,0 +1,76 @@
#![allow(dead_code)]
#[maybe_async_cfg::maybe(send="Send", async(not(feature = "is_sync"), async_trait::async_trait))]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(send="?Send", async(not(feature = "is_sync"), async_trait::async_trait))]
trait NotSendTrait {
async fn declare_async_not_send(&self);
async fn async_fn_not_send(&self) {
self.declare_async_not_send().await
}
}
#[maybe_async_cfg::maybe(send="Send", async(not(feature = "is_sync"), async_trait::async_trait))]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(keep_self, async(not(feature = "is_sync")))]
async fn async_fn() {}
#[maybe_async_cfg::maybe(keep_self, async(not(feature = "is_sync")))]
pub async fn pub_async_fn() {}
struct Struct;
#[maybe_async_cfg::maybe(keep_self, idents(Trait), async(not(feature = "is_sync"), async_trait::async_trait))]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[maybe_async_cfg::maybe(keep_self, idents(NotSendTrait), send="?Send", async(not(feature = "is_sync"), async_trait::async_trait))]
impl NotSendTrait for Struct {
async fn declare_async_not_send(&self) {}
async fn async_fn_not_send(&self) {
async { self.declare_async_not_send().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() {}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {
let s = Struct;
s.declare_async().await;
s.async_fn().await;
async_fn().await;
pub_async_fn().await;
}
+57
View File
@@ -0,0 +1,57 @@
#![allow(dead_code)]
#[maybe_async_cfg::maybe(sync(feature = "is_sync"))]
trait Trait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync"))]
pub trait PubTrait {
fn sync_fn() {}
async fn declare_async(&self);
async fn async_fn(&self) {
self.declare_async().await
}
}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"))]
async fn async_fn() {}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"))]
pub async fn pub_async_fn() {}
struct Struct;
#[maybe_async_cfg::maybe(keep_self, idents(Trait), sync(feature = "is_sync"))]
impl Trait for Struct {
fn sync_fn() {}
async fn declare_async(&self) {}
async fn async_fn(&self) {
async { self.declare_async().await }.await
}
}
#[cfg(feature = "is_sync")]
fn main() -> std::result::Result<(), ()> {
let s = Struct;
s.declare_async();
s.async_fn();
async_fn();
pub_async_fn();
Ok(())
}
#[cfg(not(feature = "is_sync"))]
#[async_std::main]
async fn main() {}
+37
View File
@@ -0,0 +1,37 @@
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(not(feature = "is_sync")))]
async fn async_fn() -> bool {
true
}
#[maybe_async_cfg::maybe(
idents(async_fn),
sync(feature = "is_sync", test),
async(all(not(feature="is_sync"), feature = "async_std"), async_std::test),
async(all(not(feature="is_sync"), feature = "tokio"), tokio::test)
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync", test), async(not(feature = "is_sync"), async_std::test))]
async fn test_async_fn2() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync", test), async(not(feature = "is_sync"), async_std::test))]
async fn test_async_fn3() {
let res = async_fn().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(sync(feature = "is_sync", test), async(not(feature = "is_sync"), async_std::test))]
async fn test_async_fn4() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}
@@ -0,0 +1,34 @@
#![allow(unused_imports)]
use std::future::Future;
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"), async(not(feature = "is_sync")))]
pub async fn with_fn<T, F: Sync + std::future::Future<Output = Result<(), ()>>>(
test: T,
) -> Result<(), ()>
where
T: FnOnce() -> F,
{
test().await
}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"), async(not(feature = "is_sync")))]
pub async fn with_fn_where<T, F>(test: T) -> Result<(), ()>
where
T: FnOnce() -> F,
F: Sync + Future<Output = Result<(), ()>>,
{
test().await
}
#[maybe_async_cfg::maybe(keep_self, sync(feature = "is_sync"))]
fn main() {
with_fn(|| Ok(())).unwrap();
with_fn_where(|| Ok(())).unwrap();
}
#[maybe_async_cfg::maybe(keep_self, async(not(feature = "is_sync")))]
#[tokio::main]
async fn main() {
with_fn(|| async { Ok(()) }).await.unwrap();
with_fn_where(|| async { Ok(()) }).await.unwrap();
}
@@ -0,0 +1,48 @@
#![allow(dead_code, unused_variables)]
/// InnerClient differ a lot between sync and async.
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(not(feature = "is_sync"), async_trait::async_trait))]
trait Trait {
async fn maybe_async_fn();
}
/// The higher level API for end user.
pub struct Struct;
/// Synchronous implementation, only compiles when `is_sync` feature is off.
/// Else the compiler will complain that *request is defined multiple times* and
/// blabla.
#[maybe_async_cfg::maybe(keep_self, idents(Trait), sync(feature = "is_sync"), async(not(feature = "is_sync")))]
#[maybe_async::only_if(key="sync")]
impl Trait for Struct {
fn maybe_async_fn() { }
}
/// Asynchronous implementation, only compiles when `is_sync` feature is off.
#[maybe_async_cfg::maybe(keep_self, idents(Trait), sync(feature = "is_sync"), async(not(feature = "is_sync"), async_trait::async_trait))]
#[maybe_async::only_if(key="async")]
impl Trait for Struct {
async fn maybe_async_fn() { }
}
#[maybe_async_cfg::maybe(keep_self, idents(Trait, another_maybe_async_fn(fn)), sync(feature = "is_sync"), async(not(feature = "is_sync")))]
impl Struct {
async fn another_maybe_async_fn() {
<Self as Trait>::maybe_async_fn().await
// When `is_sync` is toggle on, this block will compiles to:
// Self::maybe_async_fn()
}
}
#[maybe_async_cfg::maybe(keep_self, idents(another_maybe_async_fn(fn)), sync(feature = "is_sync"), async(not(feature = "is_sync")))]
#[maybe_async::only_if(key="sync")]
fn main() {
let _ = Struct::another_maybe_async_fn();
}
#[maybe_async_cfg::maybe(keep_self, idents(another_maybe_async_fn(fn)), sync(feature = "is_sync"), async(not(feature = "is_sync")))]
#[maybe_async::only_if(key="async")]
#[tokio::main]
async fn main() {
let _ = Struct::another_maybe_async_fn().await;
}
@@ -0,0 +1,20 @@
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
)]
async fn async_fn() -> bool {
true
}
#[maybe_async_cfg::maybe(
idents(async_fn(fn)),
sync(feature = "is_sync"),
async(not(feature = "is_sync")),
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}
@@ -0,0 +1,5 @@
error[E0425]: cannot find function `async_fn_async` in this scope
--> tests/ui/test_fail/01-empty-test.rs:14:15
|
14 | let res = async_fn().await;
| ^^^^^^^^ not found in this scope
@@ -0,0 +1,21 @@
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
unknown(not(feature = "is_sync")),
)]
async fn async_fn() -> bool {
true
}
#[maybe_async_cfg::maybe(
idents(async_fn(fn)),
sync(feature = "is_sync"),
async(not(feature = "is_sync")),
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}
@@ -0,0 +1,5 @@
error[E0425]: cannot find function `async_fn_async` in this scope
--> tests/ui/test_fail/02-unknown-path.rs:15:15
|
15 | let res = async_fn().await;
| ^^^^^^^^ not found in this scope
@@ -0,0 +1,23 @@
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
async(not(feature = "is_sync")),
)]
async fn async_fn() -> bool {
true
}
//#[maybe_async::test(feature="is_sync", async(feature="async", async_std::test, added))]
#[maybe_async_cfg::maybe(
idents(async_fn(fn)),
sync(feature = "is_sync"),
async(not(feature = "is_sync")),
async(not(feature = "is_sync")),
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}
@@ -0,0 +1,10 @@
error[E0428]: the name `test_async_fn_async` is defined multiple times
--> tests/ui/test_fail/03-async-gt2.rs:16:1
|
16 | async fn test_async_fn() {
| ^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `test_async_fn_async` redefined here
| previous definition of the value `test_async_fn_async` here
|
= note: `test_async_fn_async` must be defined only once in the value namespace of this module
@@ -0,0 +1,23 @@
#[maybe_async_cfg::maybe(
sync(feature = "is_sync"),
async(not(feature = "is_sync")),
)]
async fn async_fn() -> bool {
true
}
// bad sync condition
//#[maybe_async::test(unknown(feature="async", async_std::test))]
#[maybe_async_cfg::maybe(
idents(async_fn(fn)),
sync(unknown = "is_sync"),
async(not(feature = "is_sync")),
)]
async fn test_async_fn() {
let res = async_fn().await;
assert_eq!(res, true);
}
fn main() {
}
@@ -0,0 +1,5 @@
error: Wrong name for name-value pair: unknown
--> tests/ui/test_fail/04-bad-sync-cond.rs:13:10
|
13 | sync(unknown = "is_sync"),
| ^^^^^^^^^^^^^^^^^^^
+36
View File
@@ -0,0 +1,36 @@
#[maybe_async_cfg::maybe(sync(feature = "is_sync"), async(not(feature = "is_sync")))]
async fn some_function() -> bool {
true
}
#[maybe_async_cfg::maybe(
idents(some_function(fn)),
sync(feature = "is_sync", test),
async(not(feature = "is_sync"), async_std::test)
)]
async fn test_async_fn() {
let res = some_function().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(
idents(some_function(fn)),
sync(feature = "is_sync", test),
async(not(feature = "is_sync"), tokio::test)
)]
async fn test_async_fn2() {
let res = some_function().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(idents(some_function(fn)), sync(feature = "is_sync", test))]
async fn test_async_fn3() {
let res = some_function().await;
assert_eq!(res, true);
}
#[maybe_async_cfg::maybe(idents(some_function(fn)), sync(feature = "is_sync", test))]
async fn test_sync_fn() {
let res = some_function();
assert_eq!(res, true);
}