//! Implementations of [`fmt`]-like derive macros. //! //! [`fmt`]: std::fmt
#[cfg(feature = "debug")] pub(crate) mod debug; #[cfg(feature = "display")] pub(crate) mod display; mod parsing;
use proc_macro2::TokenStream; use quote::{format_ident, quote, ToTokens}; use syn::{
ext::IdentExt as _,
parse::{Parse, ParseStream},
parse_quote,
punctuated::Punctuated,
spanned::Spanned as _,
token,
};
impl FmtAttribute { /// Checks whether this [`FmtAttribute`] can be replaced with a transparent delegation (calling /// a formatting trait directly instead of interpolation syntax). /// /// If such transparent call is possible, then returns an [`Ident`] of the delegated trait and /// the [`Expr`] to pass into the call, otherwise [`None`]. /// /// [`Ident`]: struct@syn::Ident fn transparent_call(&self) -> Option<(Expr, syn::Ident)> { // `FmtAttribute` is transparent when:
// (1) There is exactly one formatting parameter. let lit = self.lit.value(); let param =
parsing::format(&lit).and_then(|(more, p)| more.is_empty().then_some(p))?;
// (2) And the formatting parameter doesn't contain any modifiers. if param
.spec
.map(|s| {
s.align.is_some()
|| s.sign.is_some()
|| s.alternate.is_some()
|| s.zero_padding.is_some()
|| s.width.is_some()
|| s.precision.is_some()
|| !s.ty.is_trivial()
})
.unwrap_or_default()
{ return None;
}
let expr = match param.arg { // (3) And either exactly one positional argument is specified.
Some(parsing::Argument::Integer(_)) | None => (self.args.len() == 1)
.then(|| self.args.first())
.flatten()
.map(|a| a.expr.clone()),
// (4) Or the formatting parameter's name refers to some outer binding.
Some(parsing::Argument::Identifier(name)) ifself.args.is_empty() => {
Some(format_ident!("{name}").into())
}
// (5) Or exactly one named argument is specified for the formatting parameter's name.
Some(parsing::Argument::Identifier(name)) => (self.args.len() == 1)
.then(|| self.args.first())
.flatten()
.filter(|a| a.alias.as_ref().map(|a| a.0 == name).unwrap_or_default())
.map(|a| a.expr.clone()),
}?;
let trait_name = param
.spec
.map(|s| s.ty)
.unwrap_or(parsing::Type::Display)
.trait_name();
Some((expr, format_ident!("{trait_name}")))
}
/// Same as [`transparent_call()`], but additionally checks the returned [`Expr`] whether it's /// one of the [`fmt_args_idents`] of the provided [`syn::Fields`], and makes it suitable for /// passing directly into the transparent call of the delegated formatting trait. /// /// [`fmt_args_idents`]: FieldsExt::fmt_args_idents /// [`transparent_call()`]: FmtAttribute::transparent_call fn transparent_call_on_fields(
&self,
fields: &syn::Fields,
) -> Option<(Expr, syn::Ident)> { self.transparent_call().map(|(expr, trait_ident)| { let expr = iflet Some(field) = fields
.fmt_args_idents()
.find(|field| expr == *field || expr == field.unraw())
{
field.into()
} else {
parse_quote! { &(#expr) }
};
(expr, trait_ident)
})
}
/// Returns an [`Iterator`] over bounded [`syn::Type`]s (and correspondent trait names) by this /// [`FmtAttribute`]. fn bounded_types<'a>(
&'a self,
fields: &'a syn::Fields,
) -> impl Iterator<Item = (&'a syn::Type, &'static str)> { let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
// We ignore unknown fields, as compiler will produce better error messages.
placeholders.into_iter().filter_map(move |placeholder| { let name = match placeholder.arg {
Parameter::Named(name) => self
.args
.iter()
.find_map(|a| (a.alias()? == &name).then_some(&a.expr))
.map_or(Some(name), |expr| expr.ident().map(ToString::to_string))?,
Parameter::Positional(i) => self
.args
.iter()
.nth(i)
.and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))?
.to_string(),
};
let unnamed = name.strip_prefix('_').and_then(|s| s.parse().ok()); let ty = match (&fields, unnamed) {
(syn::Fields::Unnamed(f), Some(i)) => {
f.unnamed.iter().nth(i).map(|f| &f.ty)
}
(syn::Fields::Named(f), None) => f.named.iter().find_map(|f| {
f.ident
.as_ref()
.filter(|s| s.unraw() == name)
.map(|_| &f.ty)
}),
_ => None,
}?;
Some((ty, placeholder.trait_name))
})
}
#[cfg(feature = "display")] /// Checks whether this [`FmtAttribute`] contains an argument with the provided `name` (either /// in its direct [`FmtArgument`]s or inside [`Placeholder`]s). fn contains_arg(&self, name: &str) -> bool { self.placeholders_by_arg(name).next().is_some()
}
#[cfg(feature = "display")] /// Returns an [`Iterator`] over [`Placeholder`]s using an argument with the provided `name` /// (either in its direct [`FmtArgument`]s of this [`FmtAttribute`] or inside the /// [`Placeholder`] itself). fn placeholders_by_arg<'a>(
&'a self,
name: &'a str,
) -> impl Iterator<Item = Placeholder> + 'a { let placeholders = Placeholder::parse_fmt_string(&self.lit.value());
/// Representation of a formatting placeholder. #[derive(Debug, Eq, PartialEq)] struct Placeholder { /// Formatting argument (either named or positional) to be used by this [`Placeholder`].
arg: Parameter,
/// Indicator whether this [`Placeholder`] has any formatting modifiers.
has_modifiers: bool,
/// Name of [`std::fmt`] trait to be used for rendering this [`Placeholder`].
trait_name: &'static str,
}
impl Placeholder { /// Parses [`Placeholder`]s from the provided formatting string. fn parse_fmt_string(s: &str) -> Vec<Self> { letmut n = 0;
parsing::format_string(s)
.into_iter()
.flat_map(|f| f.formats)
.map(|format| { let (maybe_arg, ty) = (
format.arg,
format.spec.map(|s| s.ty).unwrap_or(parsing::Type::Display),
); let position = maybe_arg.map(Into::into).unwrap_or_else(|| { // Assign "the next argument". // https://doc.rust-lang.org/stable/std/fmt/index.html#positional-parameters
n += 1;
Parameter::Positional(n - 1)
});
impl Parse for ContainerAttributes { fn parse(input: ParseStream<'_>) -> syn::Result<Self> { // We do check `FmtAttribute::check_legacy_fmt` eagerly here, because `Either` will swallow // any error of the `Either::Left` if the `Either::Right` succeeds.
FmtAttribute::check_legacy_fmt(input)?;
<Either<FmtAttribute, BoundsAttribute>>::parse(input).map(|v| match v {
Either::Left(fmt) => Self {
bounds: BoundsAttribute::default(),
fmt: Some(fmt),
},
Either::Right(bounds) => Self { bounds, fmt: None },
})
}
}
/// Matches the provided `trait_name` to appropriate [`FmtAttribute`]'s argument name. fn trait_name_to_attribute_name<T>(trait_name: T) -> &'static str where
T: for<'a> PartialEq<&'a str>,
{ match () {
_ if trait_name == "Binary" => "binary",
_ if trait_name == "Debug" => "debug",
_ if trait_name == "Display" => "display",
_ if trait_name == "LowerExp" => "lower_exp",
_ if trait_name == "LowerHex" => "lower_hex",
_ if trait_name == "Octal" => "octal",
_ if trait_name == "Pointer" => "pointer",
_ if trait_name == "UpperExp" => "upper_exp",
_ if trait_name == "UpperHex" => "upper_hex",
_ => unimplemented!(),
}
}
/// Extension of a [`syn::Type`] and a [`syn::Path`] allowing to travers its type parameters. trait ContainsGenericsExt { /// Checks whether this definition contains any of the provided `type_params`. fn contains_generics(&self, type_params: &[&syn::Ident]) -> bool;
}
/// Extension of [`syn::Fields`] providing helpers for a [`FmtAttribute`]. trait FieldsExt { /// Returns an [`Iterator`] over [`syn::Ident`]s representing these [`syn::Fields`] in a /// [`FmtAttribute`] as [`FmtArgument`]s or named [`Placeholder`]s. /// /// [`syn::Ident`]: struct@syn::Ident fn fmt_args_idents(&self) -> impl Iterator<Item = syn::Ident> + '_;
}
#[cfg(test)] mod fmt_attribute_spec { use itertools::Itertools as _; use quote::ToTokens;
usesuper::FmtAttribute;
fn assert<'a>(input: &'a str, parsed: impl AsRef<[&'a str]>) { let parsed = parsed.as_ref(); let attr = syn::parse_str::<FmtAttribute>(&format!("\"\", {}", input)).unwrap(); let fmt_args = attr
.args
.into_iter()
.map(|arg| arg.into_token_stream().to_string())
.collect::<Vec<String>>();
fmt_args.iter().zip_eq(parsed).enumerate().for_each(
|(i, (found, expected))| {
assert_eq!(
*expected, found, "Mismatch at index {i}\n\
Expected: {parsed:?}\n\
Found: {fmt_args:?}",
);
},
);
}
#[test] fn cases() { let cases = [ "ident", "alias = ident", "[a , b , c , d]", "counter += 1", "async { fut . await }", "a < b", "a > b", "{ let x = (a , b) ; }", "invoke (a , b)", "foo as f64", "| a , b | a + b", "obj . k", "for pat in expr { break pat ; }", "if expr { true } else { false }", "vector [2]", "1", "\"foo\"", "loop { break i ; }", "format ! (\"{}\" , q)", "match n { Some (n) => { } , None => { } }", "x . foo ::< T > (a , b)", "x . foo ::< T < [T < T >; if a < b { 1 } else { 2 }] >, { a < b } > (a , b)", "(a + b)", "i32 :: MAX", "1 .. 2", "& a", "[0u8 ; N]", "(a , b , c , d)", "< Ty as Trait > :: T", "< Ty < Ty < T >, { a < b } > as Trait < T > > :: T",
];
assert("", []); for i in1..4 { for permutations in cases.into_iter().permutations(i) { letmut input = permutations.clone().join(",");
assert(&input, &permutations);
input.push(',');
assert(&input, &permutations);
}
}
}
}
#[cfg(test)] mod placeholder_parse_fmt_string_spec { usesuper::{Parameter, Placeholder};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.