1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
//! The `#[smart_display::private_metadata]` attribute.
use std::iter;
use proc_macro2::{Ident, Span, TokenStream};
use quote::{format_ident, quote, ToTokens};
use syn::punctuated::Punctuated;
use syn::spanned::Spanned;
use syn::token::{Paren, Pub};
use syn::{
Error, ItemStruct, Path, PathArguments, PathSegment, Result, Token, VisRestricted, Visibility,
};
/// Implementation of the `#[smart_display::private_metadata]` attribute.
pub(crate) fn implementation(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
if !attr.is_empty() {
return Err(Error::new_spanned(
attr,
"`delegate_display` does not take any arguments",
));
}
let mut input = syn::parse2::<ItemStruct>(item)?;
// Public items are not supported by the macro.
if matches!(input.vis, Visibility::Public(_)) {
return Err(Error::new_spanned(
input.vis.into_token_stream(),
"`metadata` can only be used on non-pubic items",
));
}
// Preserve the original visibility for the re-export.
let original_vis = input.vis.clone();
// Change the visibility of the struct to public, avoiding any compiler errors.
input.vis = Visibility::Public(Token![pub](input.vis.span()));
// Change the visibility of fields as necessary.
for field in &mut input.fields {
vis_prepend_super(&mut field.vis)?;
}
let mod_name = format_ident!(
"__powerfmt_private_{}",
input.ident,
span = Span::call_site() // Avoid a warning about the module not being snake case.
);
Ok(quote! {
mod #mod_name {
use super::*;
#[non_exhaustive]
#input
}
#original_vis use #mod_name::*;
})
}
fn vis_prepend_super(vis: &mut Visibility) -> Result<()> {
match vis {
Visibility::Public(_) => {
// If the visibility is public, there is nothing to do.
Ok(())
}
Visibility::Restricted(VisRestricted { path, in_token, .. }) => {
let segments = &path.segments;
let ident = &segments
.first()
.or_else(|| segments.last())
.expect("path should have at least one segment")
.ident;
match ident.to_string().as_ref() {
"crate" => {
// If the path is simply `crate`, there is nothing to do. Otherwise, we need to
// remove the final segment.
if path.get_ident().is_none() {
path.segments.pop();
}
Ok(())
}
"super" => {
*in_token = in_token.or_else(|| Some(Token![in](path.span())));
// Any path starting with `super` needs another `super` prepended.
path.segments = iter::once(PathSegment {
ident: Token![super](path.span()).into(),
arguments: PathArguments::default(),
})
.chain(path.segments.iter().cloned())
.collect();
Ok(())
}
"self" => {
// `pub(self)` -> `pub(super)`
//
// There is absolutely no reason this should ever be written, but it is valid
// syntax.
let super_span = path.get_ident().map_or_else(Span::call_site, Ident::span);
path.segments = Punctuated::from_iter([PathSegment {
ident: Token![super](super_span).into(),
arguments: PathArguments::None,
}]);
Ok(())
}
_ => Err(Error::new_spanned(path, "unexpected visibility path")),
}
}
Visibility::Inherited => {
// inherited visibility -> `pub(super)`
*vis = Visibility::Restricted(VisRestricted {
pub_token: Pub::default(),
paren_token: Paren::default(),
in_token: None,
path: Box::new(Path {
leading_colon: None,
segments: Punctuated::from_iter([PathSegment {
ident: format_ident!("super"),
arguments: PathArguments::None,
}]),
}),
});
Ok(())
}
}
}