From b8dd01d6ce05f851f33497e8d899df308d315afb Mon Sep 17 00:00:00 2001 From: Mahmoud Mazouz Date: Tue, 7 May 2024 11:46:14 +0200 Subject: [PATCH] feat: Improve proc-macro `zenoh_macros::unstable` (#1016) --- commons/zenoh-macros/src/lib.rs | 122 ++++++++++++++++++++++++++++---- zenoh/Cargo.toml | 3 +- zenoh/src/lib.rs | 2 + zenoh/src/query.rs | 10 +-- 4 files changed, 114 insertions(+), 23 deletions(-) diff --git a/commons/zenoh-macros/src/lib.rs b/commons/zenoh-macros/src/lib.rs index 4a69f5c4e3..774bebc80a 100644 --- a/commons/zenoh-macros/src/lib.rs +++ b/commons/zenoh-macros/src/lib.rs @@ -18,8 +18,8 @@ //! //! [Click here for Zenoh's documentation](../zenoh/index.html) use proc_macro::TokenStream; -use quote::quote; -use syn::LitStr; +use quote::{quote, ToTokens}; +use syn::{parse_macro_input, parse_quote, Attribute, Error, Item, LitStr, TraitItem}; use zenoh_keyexpr::{ format::{ macro_support::{self, SegmentBuilder}, @@ -59,19 +59,113 @@ pub fn rustc_version_release(_tokens: TokenStream) -> TokenStream { (quote! {(#release, #commit)}).into() } +/// An enumeration of items supported by the [`unstable`] attribute. +enum UnstableItem { + /// Wrapper around [`syn::Item`]. + Item(Item), + /// Wrapper around [`syn::TraitItem`]. + TraitItem(TraitItem), +} + +macro_rules! parse_unstable_item { + ($tokens:ident) => {{ + let item: Item = parse_macro_input!($tokens as Item); + + if matches!(item, Item::Verbatim(_)) { + let tokens = TokenStream::from(item.to_token_stream()); + let trait_item: TraitItem = parse_macro_input!(tokens as TraitItem); + + if matches!(trait_item, TraitItem::Verbatim(_)) { + Err(Error::new_spanned( + trait_item, + "the `unstable` proc-macro attribute only supports items and trait items", + )) + } else { + Ok(UnstableItem::TraitItem(trait_item)) + } + } else { + Ok(UnstableItem::Item(item)) + } + }}; +} + +impl UnstableItem { + /// Mutably borrows the attribute list of this item. + fn attributes_mut(&mut self) -> Result<&mut Vec, Error> { + match self { + UnstableItem::Item(item) => match item { + Item::Const(item) => Ok(&mut item.attrs), + Item::Enum(item) => Ok(&mut item.attrs), + Item::ExternCrate(item) => Ok(&mut item.attrs), + Item::Fn(item) => Ok(&mut item.attrs), + Item::ForeignMod(item) => Ok(&mut item.attrs), + Item::Impl(item) => Ok(&mut item.attrs), + Item::Macro(item) => Ok(&mut item.attrs), + Item::Mod(item) => Ok(&mut item.attrs), + Item::Static(item) => Ok(&mut item.attrs), + Item::Struct(item) => Ok(&mut item.attrs), + Item::Trait(item) => Ok(&mut item.attrs), + Item::TraitAlias(item) => Ok(&mut item.attrs), + Item::Type(item) => Ok(&mut item.attrs), + Item::Union(item) => Ok(&mut item.attrs), + Item::Use(item) => Ok(&mut item.attrs), + other => Err(Error::new_spanned( + other, + "item is not supported by the `unstable` proc-macro attribute", + )), + }, + UnstableItem::TraitItem(trait_item) => match trait_item { + TraitItem::Const(trait_item) => Ok(&mut trait_item.attrs), + TraitItem::Fn(trait_item) => Ok(&mut trait_item.attrs), + TraitItem::Type(trait_item) => Ok(&mut trait_item.attrs), + TraitItem::Macro(trait_item) => Ok(&mut trait_item.attrs), + other => Err(Error::new_spanned( + other, + "item is not supported by the `unstable` proc-macro attribute", + )), + }, + } + } + + /// Converts this item to a `proc_macro2::TokenStream`. + fn to_token_stream(&self) -> proc_macro2::TokenStream { + match self { + UnstableItem::Item(item) => item.to_token_stream(), + UnstableItem::TraitItem(trait_item) => trait_item.to_token_stream(), + } + } +} + #[proc_macro_attribute] -pub fn unstable(_attr: TokenStream, item: TokenStream) -> TokenStream { - let item = proc_macro2::TokenStream::from(item); - TokenStream::from(quote! { - #[cfg(feature = "unstable")] - ///
- /// 🔬 - /// This API has been marked as unstable: it works as advertised, but we may change it in a future release. - /// To use it, you must enable zenoh's unstable feature flag. - ///
- /// - #item - }) +pub fn unstable(_attr: TokenStream, tokens: TokenStream) -> TokenStream { + let mut item = match parse_unstable_item!(tokens) { + Ok(item) => item, + Err(err) => return err.into_compile_error().into(), + }; + + let attrs = match item.attributes_mut() { + Ok(attrs) => attrs, + Err(err) => return err.into_compile_error().into(), + }; + + if attrs.iter().any(is_doc_attribute) { + // See: https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html#adding-a-warning-block + let message = "
This API has been marked as unstable: it works as advertised, but it may be changed in a future release.
"; + let note: Attribute = parse_quote!(#[doc = #message]); + attrs.push(note); + } + + let feature_gate: Attribute = parse_quote!(#[cfg(feature = "unstable")]); + attrs.push(feature_gate); + + TokenStream::from(item.to_token_stream()) +} + +/// Returns `true` if the attribute is a `#[doc = "..."]` attribute. +fn is_doc_attribute(attr: &Attribute) -> bool { + attr.path() + .get_ident() + .is_some_and(|ident| &ident.to_string() == "doc") } fn keformat_support(source: &str) -> proc_macro2::TokenStream { diff --git a/zenoh/Cargo.toml b/zenoh/Cargo.toml index 7c9288731f..a8bf041c11 100644 --- a/zenoh/Cargo.toml +++ b/zenoh/Cargo.toml @@ -77,7 +77,7 @@ form_urlencoded = { workspace = true } futures = { workspace = true } git-version = { workspace = true } lazy_static = { workspace = true } -tracing = {workspace = true} +tracing = { workspace = true } ordered-float = { workspace = true } paste = { workspace = true } petgraph = { workspace = true } @@ -119,6 +119,7 @@ name = "zenoh" # NOTE: if you change this, also change it in .github/workflows/release.yml in "doc" job. [package.metadata.docs.rs] features = ["unstable"] +rustdoc-args = ["--cfg", "doc_auto_cfg"] [package.metadata.deb] name = "zenoh" diff --git a/zenoh/src/lib.rs b/zenoh/src/lib.rs index 8a41464267..3693218291 100644 --- a/zenoh/src/lib.rs +++ b/zenoh/src/lib.rs @@ -12,6 +12,8 @@ // ZettaScale Zenoh Team, // +#![cfg_attr(doc_auto_cfg, feature(doc_auto_cfg))] + //! [Zenoh](https://zenoh.io) /zeno/ is a stack that unifies data in motion, data at //! rest and computations. It elegantly blends traditional pub/sub with geo distributed //! storage, queries and computations, while retaining a level of time and space efficiency diff --git a/zenoh/src/query.rs b/zenoh/src/query.rs index f75df8c50e..8ac43ee4ba 100644 --- a/zenoh/src/query.rs +++ b/zenoh/src/query.rs @@ -355,19 +355,13 @@ pub(crate) const _REPLY_KEY_EXPR_ANY_SEL_PARAM: &str = "_anyke"; pub const REPLY_KEY_EXPR_ANY_SEL_PARAM: &str = _REPLY_KEY_EXPR_ANY_SEL_PARAM; #[zenoh_macros::unstable] -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum ReplyKeyExpr { Any, + #[default] MatchingQuery, } -#[zenoh_macros::unstable] -impl Default for ReplyKeyExpr { - fn default() -> Self { - ReplyKeyExpr::MatchingQuery - } -} - impl Resolvable for GetBuilder<'_, '_, Handler> where Handler: IntoCallbackReceiverPair<'static, Reply> + Send,