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

+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
}