use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{
parse_macro_input, parse_quote, spanned::Spanned, Attribute, Error, Item, ItemImpl, LitStr,
TraitItem,
};
use zenoh_keyexpr::{
format::{
macro_support::{self, SegmentBuilder},
KeFormat,
},
key_expr::keyexpr,
};
const RUSTC_VERSION: &str = include_str!(concat!(env!("OUT_DIR"), "/version.rs"));
#[proc_macro]
pub fn rustc_version_release(_tokens: TokenStream) -> TokenStream {
let release = RUSTC_VERSION
.split('\n')
.filter_map(|l| {
let line = l.trim();
if line.is_empty() {
None
} else {
Some(line)
}
})
.find_map(|l| l.strip_prefix("release: "))
.unwrap();
let commit = RUSTC_VERSION
.split('\n')
.filter_map(|l| {
let line = l.trim();
if line.is_empty() {
None
} else {
Some(line)
}
})
.find_map(|l| l.strip_prefix("commit-hash: "))
.unwrap();
(quote! {(#release, #commit)}).into()
}
#[allow(clippy::large_enum_variant)]
enum AnnotableItem {
Item(Item),
TraitItem(TraitItem),
}
macro_rules! parse_annotable_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(AnnotableItem::TraitItem(trait_item))
}
} else {
Ok(AnnotableItem::Item(item))
}
}};
}
impl AnnotableItem {
fn attributes_mut(&mut self) -> Result<&mut Vec<Attribute>, Error> {
match self {
AnnotableItem::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` or `internal` proc-macro attribute",
)),
},
AnnotableItem::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` or `internal` proc-macro attribute",
)),
},
}
}
fn to_token_stream(&self) -> proc_macro2::TokenStream {
match self {
AnnotableItem::Item(item) => item.to_token_stream(),
AnnotableItem::TraitItem(trait_item) => trait_item.to_token_stream(),
}
}
}
fn add_unstable_warning(item: &mut AnnotableItem) -> Result<(), Error> {
let attrs = item.attributes_mut()?;
let mut old_attrs = std::mem::take(attrs).into_iter();
for attr in old_attrs.by_ref() {
attrs.push(attr);
if get_doc_str(attrs.last().unwrap()).is_some() {
let message = "<div class=\"warning\">This API has been marked as <strong>unstable</strong>: it works as advertised, but it may be changed in a future release.</div>";
let note: Attribute = parse_quote!(#[doc = #message]);
attrs.push(note);
break;
}
}
for attr in old_attrs.by_ref() {
if let Some(lit_str) = get_doc_str(&attr) {
if !lit_str.value().trim().is_empty() {
return Err(Error::new_spanned(
attr,
"documentation for `#[unstable]` items must have a blank doc line after the brief description. \
This blank line is critical for proper formatting: it will separate the unstable warning from the content below and ensures that \
markdown links (like [`Type`](path::to::Type)) are correctly processed by rustdoc. \
Add an empty `///` line after your brief description and before detailed explanations.",
));
}
attrs.push(attr);
break;
}
attrs.push(attr);
}
attrs.extend(old_attrs);
Ok(())
}
#[proc_macro_attribute]
pub fn unstable_doc(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut item = match parse_annotable_item!(tokens) {
Ok(item) => item,
Err(err) => return err.into_compile_error().into(),
};
if let Err(err) = add_unstable_warning(&mut item) {
return err.into_compile_error().into();
}
TokenStream::from(item.to_token_stream())
}
#[proc_macro_attribute]
pub fn unstable(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut item = match parse_annotable_item!(tokens) {
Ok(item) => item,
Err(err) => return err.into_compile_error().into(),
};
if let Err(err) = add_unstable_warning(&mut item) {
return err.into_compile_error().into();
}
let attrs = match item.attributes_mut() {
Ok(attrs) => attrs,
Err(err) => return err.into_compile_error().into(),
};
let feature_gate: Attribute = parse_quote!(#[cfg(feature = "unstable")]);
attrs.push(feature_gate);
TokenStream::from(item.to_token_stream())
}
#[proc_macro_attribute]
pub fn internal(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut item = match parse_annotable_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(),
};
let feature_gate: Attribute = parse_quote!(#[cfg(feature = "internal")]);
let hide_doc: Attribute = parse_quote!(#[doc(hidden)]);
attrs.push(feature_gate);
attrs.push(hide_doc);
TokenStream::from(item.to_token_stream())
}
fn get_doc_str(attr: &Attribute) -> Option<&LitStr> {
if attr
.path()
.get_ident()
.is_some_and(|ident| &ident.to_string() == "doc")
{
if let syn::Meta::NameValue(nv) = &attr.meta {
if let syn::Expr::Lit(syn::ExprLit {
lit: syn::Lit::Str(lit_str),
..
}) = &nv.value
{
return Some(lit_str);
}
}
}
None
}
fn keformat_support(source: &str) -> proc_macro2::TokenStream {
let format = match KeFormat::new(&source) {
Ok(format) => format,
Err(e) => panic!("{}", e),
};
let specs = unsafe { macro_support::specs(&format) };
let len = specs.len();
let setters = specs.iter().map(|spec| {
let id = &source[spec.spec_start..(spec.spec_start + spec.id_end as usize)];
let set_id = quote::format_ident!("{}", id);
quote! {
pub fn #set_id <S: ::core::fmt::Display>(&mut self, value: S) -> Result<&mut Self, ::zenoh::key_expr::format::FormatSetError> {
match self.0.set(#id, value) {
Ok(_) => Ok(self),
Err(e) => Err(e)
}
}
}
});
let getters = specs.iter().map(|spec| {
let source = &source[spec.spec_start..spec.spec_end];
let id = &source[..(spec.id_end as usize)];
let get_id = quote::format_ident!("{}", id);
let pattern = unsafe {
keyexpr::from_str_unchecked(if spec.pattern_end != u16::MAX {
&source[(spec.id_end as usize + 1)..(spec.spec_start + spec.pattern_end as usize)]
} else {
&source[(spec.id_end as usize + 1)..]
})
};
let doc = format!("Get the parsed value for `{id}`.\n\nThis value is guaranteed to be a valid key expression intersecting with `{pattern}`");
if pattern.as_bytes() == b"**" {
quote! {
#[doc = #doc]
pub fn #get_id (&self) -> Option<& ::zenoh::key_expr::keyexpr> {
unsafe {
let s =self._0.get(#id).unwrap_unchecked();
(!s.is_empty()).then(|| ::zenoh::key_expr::keyexpr::from_str_unchecked(s))
}
}
}
} else {
quote! {
#[doc = #doc]
pub fn #get_id (&self) -> &::zenoh::key_expr::keyexpr {
unsafe {::zenoh::key_expr::keyexpr::from_str_unchecked(self._0.get(#id).unwrap_unchecked())}
}
}
}
});
let segments = specs.iter().map(|spec| {
let SegmentBuilder {
segment_start,
prefix_end,
spec_start,
id_end,
pattern_end,
spec_end,
segment_end,
} = spec;
quote! {
::zenoh::key_expr::format::macro_support::SegmentBuilder {
segment_start: #segment_start,
prefix_end: #prefix_end,
spec_start: #spec_start,
id_end: #id_end,
pattern_end: #pattern_end,
spec_end: #spec_end,
segment_end: #segment_end,
},
}
});
let format_doc = format!("The `{source}` format, as a zero-sized-type.");
let formatter_doc = format!("And instance of a formatter for `{source}`.");
quote! {
use ::zenoh::Result as ZResult;
const FORMAT_INNER: ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> = unsafe {
::zenoh::key_expr::format::macro_support::const_new(#source, [#(#segments)*])
};
#[doc = #format_doc]
#[derive(Copy, Clone, Hash)]
pub struct Format;
#[doc = #formatter_doc]
#[derive(Clone)]
pub struct Formatter(::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>);
impl ::core::fmt::Debug for Format {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Debug::fmt(&FORMAT_INNER, f)
}
}
impl ::core::fmt::Debug for Formatter {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Debug::fmt(&self.0, f)
}
}
impl ::core::fmt::Display for Format {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Display::fmt(&FORMAT_INNER, f)
}
}
impl ::core::ops::Deref for Format {
type Target = ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>;
fn deref(&self) -> &Self::Target {&FORMAT_INNER}
}
impl ::core::ops::Deref for Formatter {
type Target = ::zenoh::key_expr::format::KeFormatter<'static, [::zenoh::key_expr::format::Segment<'static>; #len]>;
fn deref(&self) -> &Self::Target {&self.0}
}
impl ::core::ops::DerefMut for Formatter {
fn deref_mut(&mut self) -> &mut Self::Target {&mut self.0}
}
impl Formatter {
#(#setters)*
}
pub struct Parsed<'s>{_0: ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>}
impl<'s> ::core::ops::Deref for Parsed<'s> {
type Target = ::zenoh::key_expr::format::Parsed<'s, [::zenoh::key_expr::format::Segment<'s>; #len]>;
fn deref(&self) -> &Self::Target {&self._0}
}
impl Parsed<'_> {
#(#getters)*
}
impl Format {
pub fn formatter() -> Formatter {
Formatter(Format.formatter())
}
pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult<Parsed<'s>> {
Ok(Parsed{_0: Format.parse(target)?})
}
pub fn into_inner(self) -> ::zenoh::key_expr::format::KeFormat<'static, [::zenoh::key_expr::format::Segment<'static>; #len]> {
FORMAT_INNER
}
}
pub fn formatter() -> Formatter {
Format::formatter()
}
pub fn parse<'s>(target: &'s ::zenoh::key_expr::keyexpr) -> ZResult<Parsed<'s>> {
Format::parse(target)
}
}
}
struct FormatDeclaration {
vis: syn::Visibility,
name: syn::Ident,
lit: syn::LitStr,
}
impl syn::parse::Parse for FormatDeclaration {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let vis = input.parse()?;
let name = input.parse()?;
let _: syn::Token!(:) = input.parse()?;
let lit = input.parse()?;
Ok(FormatDeclaration { vis, name, lit })
}
}
struct FormatDeclarations(syn::punctuated::Punctuated<FormatDeclaration, syn::Token!(,)>);
impl syn::parse::Parse for FormatDeclarations {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
Ok(Self(input.parse_terminated(
FormatDeclaration::parse,
syn::Token![,],
)?))
}
}
#[proc_macro]
pub fn kedefine(tokens: TokenStream) -> TokenStream {
let declarations: FormatDeclarations = syn::parse(tokens).unwrap();
let content = declarations.0.into_iter().map(|FormatDeclaration { vis, name, lit }|
{
let source = lit.value();
let docstring = format!(
r"The module associated with the `{source}` format, it contains:
- `Format`, a zero-sized type that represents your format.
- `formatter()`, a function that constructs a `Formatter` specialized for your format:
- for every spec in your format, `Formatter` will have a method named after the spec's `id` that lets you set a value for that field of your format. These methods will return `Result<&mut Formatter, FormatError>`.
- `parse(target: &keyexpr) -> ZResult<Parsed<'_>>` will parse the provided key expression according to your format. Just like `KeFormat::parse`, parsing is lazy: each field will match the smallest subsection of your `target` that is included in its pattern.
- like `Formatter`, `Parsed` will have a method named after each spec's `id` that returns `&keyexpr`; except for specs whose pattern was `**`, these will return an `Option<&keyexpr>`, where `None` signifies that the pattern was matched by an empty list of chunks."
);
let support = keformat_support(&source);
quote! {
#[doc = #docstring]
#vis mod #name{
#support
}
}});
quote!(#(#content)*).into()
}
struct FormatUsage {
id: syn::Expr,
assigns: Vec<(syn::Expr, syn::Expr)>,
}
impl syn::parse::Parse for FormatUsage {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let id = input.parse()?;
let mut assigns = Vec::new();
if !input.is_empty() {
input.parse::<syn::Token!(,)>()?;
}
assigns.extend(
input
.parse_terminated(syn::Expr::parse, syn::Token![,])?
.into_iter()
.map(|a| match a {
syn::Expr::Assign(a) => (*a.left, *a.right),
a => (a.clone(), a),
}),
);
Ok(FormatUsage { id, assigns })
}
}
#[proc_macro]
pub fn kewrite(tokens: TokenStream) -> TokenStream {
let FormatUsage { id, assigns } = syn::parse(tokens).unwrap();
let mut sets = None;
for (l, r) in assigns.iter().rev() {
if let Some(set) = sets {
sets = Some(quote!(.#l(#r).and_then(|x| x #set)));
} else {
sets = Some(quote!(.#l(#r)));
}
}
quote!(#id #sets).into()
}
#[proc_macro]
pub fn keformat(tokens: TokenStream) -> TokenStream {
let formatted: proc_macro2::TokenStream = kewrite(tokens).into();
quote!(match #formatted {
Ok(ok) => ok.build(),
Err(e) => Err(e.into()),
})
.into()
}
#[proc_macro]
pub fn ke(tokens: TokenStream) -> TokenStream {
let value: LitStr = syn::parse(tokens).unwrap();
let ke = value.value();
match zenoh_keyexpr::keyexpr::new(&ke) {
Ok(_) => quote!(unsafe { zenoh::key_expr::keyexpr::from_str_unchecked(#ke)}).into(),
Err(e) => panic!("{}", e),
}
}
#[proc_macro]
pub fn nonwild_ke(tokens: TokenStream) -> TokenStream {
let value: LitStr = syn::parse(tokens).unwrap();
let ke = value.value();
match zenoh_keyexpr::nonwild_keyexpr::new(&ke) {
Ok(_) => quote!(unsafe { zenoh::key_expr::nonwild_keyexpr::from_str_unchecked(#ke)}).into(),
Err(e) => panic!("{}", e),
}
}
mod zenoh_runtime_derive;
use syn::DeriveInput;
use zenoh_runtime_derive::{derive_generic_runtime_param, derive_register_param};
#[proc_macro_derive(GenericRuntimeParam)]
pub fn generic_runtime_param(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse_macro_input!(input);
derive_generic_runtime_param(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_derive(RegisterParam, attributes(alias, param))]
pub fn register_param(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let input: DeriveInput = syn::parse_macro_input!(input);
derive_register_param(input)
.unwrap_or_else(syn::Error::into_compile_error)
.into()
}
#[proc_macro_attribute]
pub fn internal_trait(_attr: TokenStream, item: TokenStream) -> TokenStream {
let mut input = parse_macro_input!(item as ItemImpl);
let trait_path = &input.trait_.as_ref().unwrap().1;
let struct_path = &input.self_ty;
let generics = &input.generics;
let mut struct_methods = quote! {};
for item_fn in input.items.iter_mut() {
if let syn::ImplItem::Fn(method) = item_fn {
let method_name = &method.sig.ident;
let method_generic_params = &method.sig.generics.params;
let method_generic_params = if method_generic_params.is_empty() {
quote! {}
} else {
quote! {<#method_generic_params>}
};
let method_args = &method.sig.inputs;
let method_output = &method.sig.output;
let where_clause = &method.sig.generics.where_clause;
let mut method_call_args = quote! {};
for arg in method_args.iter() {
match arg {
syn::FnArg::Receiver(_) => {
method_call_args.extend(quote! { self, });
}
syn::FnArg::Typed(pat_type) => {
let pat = &pat_type.pat;
method_call_args.extend(quote! { #pat, });
}
}
}
let mut attributes = quote! {};
for attr in &method.attrs {
attributes.extend(quote! {
#attr
});
}
method
.attrs
.retain(|attr| !attr.meta.path().is_ident("deprecated"));
struct_methods.extend(quote! {
#attributes
pub fn #method_name #method_generic_params (#method_args) #method_output #where_clause {
<#struct_path as #trait_path>::#method_name(#method_call_args)
}
});
}
}
let struct_methods_output = quote! {
impl #generics #struct_path {
#struct_methods
}
};
(quote! {
#input
#struct_methods_output
})
.into()
}
#[proc_macro_attribute]
pub fn pub_visibility_if_internal(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut out = TokenStream::new();
let mut item_original: syn::Item = syn::parse(tokens).expect("failed to parse input");
let item_modified;
let not_internal_feature_gate: Attribute = parse_quote!(#[cfg(not(feature = "internal"))]);
let internal_feature_gate: Attribute = parse_quote!(#[cfg(feature = "internal")]);
let hide_doc: Attribute = parse_quote!(#[doc(hidden)]);
let allow_dead_code: Attribute = parse_quote!(#[allow(dead_code)]);
match &mut item_original {
Item::Fn(item_fn) => {
let mut item_fn_modified = item_fn.clone();
item_fn_modified.vis = syn::Visibility::Public(syn::token::Pub(item_fn.span()));
item_fn_modified
.attrs
.splice(0..0, vec![internal_feature_gate, hide_doc, allow_dead_code]);
item_modified = Item::Fn(item_fn_modified);
item_fn.attrs.splice(0..0, vec![not_internal_feature_gate]);
}
Item::Struct(item_struct) => {
let mut item_struct_modified = item_struct.clone();
item_struct_modified
.attrs
.splice(0..0, vec![internal_feature_gate, hide_doc, allow_dead_code]);
item_struct_modified.vis = syn::Visibility::Public(syn::token::Pub(item_struct.span()));
item_modified = Item::Struct(item_struct_modified);
item_struct
.attrs
.splice(0..0, vec![not_internal_feature_gate]);
}
Item::Type(item_type) => {
let mut item_type_modified = item_type.clone();
item_type_modified
.attrs
.splice(0..0, vec![internal_feature_gate, hide_doc, allow_dead_code]);
item_type_modified.vis = syn::Visibility::Public(syn::token::Pub(item_type.span()));
item_modified = Item::Type(item_type_modified);
item_type
.attrs
.splice(0..0, vec![not_internal_feature_gate]);
}
_ => panic!("pub_visibility_if_internal only works with struct, type and fn"),
}
let ts: TokenStream = item_original.into_token_stream().into();
out.extend(ts);
let ts: TokenStream = item_modified.into_token_stream().into();
out.extend(ts);
out
}
#[proc_macro_attribute]
pub fn internal_or_unstable(_attr: TokenStream, tokens: TokenStream) -> TokenStream {
let mut out = TokenStream::new();
let mut item_original: syn::Item = syn::parse(tokens).expect("failed to parse input");
let item_modified;
let non_unstable_feature_gate: Attribute = parse_quote!(#[cfg(not(feature = "unstable"))]);
let internal_feature_gate: Attribute = parse_quote!(#[zenoh_macros::internal]);
let unstable_feature_gate: Attribute = parse_quote!(#[zenoh_macros::unstable]);
match &mut item_original {
Item::Fn(item_fn) => {
let mut item_fn_modified = item_fn.clone();
item_fn_modified.vis = syn::Visibility::Public(syn::token::Pub(item_fn.span()));
item_fn_modified
.attrs
.splice(0..0, vec![non_unstable_feature_gate, internal_feature_gate]);
item_modified = Item::Fn(item_fn_modified);
item_fn.attrs.splice(0..0, vec![unstable_feature_gate]);
}
Item::Struct(item_struct) => {
let mut item_struct_modified = item_struct.clone();
item_struct_modified
.attrs
.splice(0..0, vec![non_unstable_feature_gate, internal_feature_gate]);
item_struct_modified.vis = syn::Visibility::Public(syn::token::Pub(item_struct.span()));
item_modified = Item::Struct(item_struct_modified);
item_struct.attrs.splice(0..0, vec![unstable_feature_gate]);
}
Item::Type(item_type) => {
let mut item_type_modified = item_type.clone();
item_type_modified
.attrs
.splice(0..0, vec![non_unstable_feature_gate, internal_feature_gate]);
item_type_modified.vis = syn::Visibility::Public(syn::token::Pub(item_type.span()));
item_modified = Item::Type(item_type_modified);
item_type.attrs.splice(0..0, vec![unstable_feature_gate]);
}
_ => panic!("internal_or_unstable only works with struct, type and fn"),
}
let ts: TokenStream = item_original.into_token_stream().into();
out.extend(ts);
let ts: TokenStream = item_modified.into_token_stream().into();
out.extend(ts);
out
}