diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /third_party/rust/derive_more-impl/src/fmt/mod.rs | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/derive_more-impl/src/fmt/mod.rs')
-rw-r--r-- | third_party/rust/derive_more-impl/src/fmt/mod.rs | 444 |
1 files changed, 444 insertions, 0 deletions
diff --git a/third_party/rust/derive_more-impl/src/fmt/mod.rs b/third_party/rust/derive_more-impl/src/fmt/mod.rs new file mode 100644 index 0000000000..5727d671f5 --- /dev/null +++ b/third_party/rust/derive_more-impl/src/fmt/mod.rs @@ -0,0 +1,444 @@ +//! 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::ToTokens; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned as _, + token, Ident, +}; + +use crate::parsing::Expr; + +/// Representation of a macro attribute expressing additional trait bounds. +#[derive(Debug, Default)] +struct BoundsAttribute(Punctuated<syn::WherePredicate, syn::token::Comma>); + +impl BoundsAttribute { + /// Errors in case legacy syntax is encountered: `bound = "..."`. + fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { + let fork = input.fork(); + + let path = fork + .parse::<syn::Path>() + .and_then(|path| fork.parse::<syn::token::Eq>().map(|_| path)); + match path { + Ok(path) if path.is_ident("bound") => fork + .parse::<syn::Lit>() + .ok() + .and_then(|lit| match lit { + syn::Lit::Str(s) => Some(s.value()), + _ => None, + }) + .map_or(Ok(()), |bound| { + Err(syn::Error::new( + input.span(), + format!("legacy syntax, use `bound({bound})` instead"), + )) + }), + Ok(_) | Err(_) => Ok(()), + } + } +} + +impl Parse for BoundsAttribute { + fn parse(input: ParseStream<'_>) -> syn::Result<Self> { + let _ = input.parse::<syn::Path>().and_then(|p| { + if ["bound", "bounds", "where"] + .into_iter() + .any(|i| p.is_ident(i)) + { + Ok(p) + } else { + Err(syn::Error::new( + p.span(), + "unknown attribute, expected `bound(...)`", + )) + } + })?; + + let content; + syn::parenthesized!(content in input); + + content + .parse_terminated(syn::WherePredicate::parse, token::Comma) + .map(Self) + } +} + +/// Representation of a [`fmt`]-like attribute. +/// +/// [`fmt`]: std::fmt +#[derive(Debug)] +struct FmtAttribute { + /// Interpolation [`syn::LitStr`]. + /// + /// [`syn::LitStr`]: struct@syn::LitStr + lit: syn::LitStr, + + /// Optional [`token::Comma`]. + comma: Option<token::Comma>, + + /// Interpolation arguments. + args: Punctuated<FmtArgument, token::Comma>, +} + +impl FmtAttribute { + /// Returns an [`Iterator`] over bounded [`syn::Type`]s and trait names. + 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 == name).map(|_| &f.ty) + }), + _ => None, + }?; + + Some((ty, placeholder.trait_name)) + }) + } + + /// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`. + fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { + let fork = input.fork(); + + let path = fork + .parse::<syn::Path>() + .and_then(|path| fork.parse::<syn::token::Eq>().map(|_| path)); + match path { + Ok(path) if path.is_ident("fmt") => (|| { + let args = fork + .parse_terminated(syn::Lit::parse, token::Comma) + .ok()? + .into_iter() + .enumerate() + .filter_map(|(i, lit)| match lit { + syn::Lit::Str(str) => Some(if i == 0 { + format!("\"{}\"", str.value()) + } else { + str.value() + }), + _ => None, + }) + .collect::<Vec<_>>(); + (!args.is_empty()).then_some(args) + })() + .map_or(Ok(()), |fmt| { + Err(syn::Error::new( + input.span(), + format!( + "legacy syntax, remove `fmt =` and use `{}` instead", + fmt.join(", "), + ), + )) + }), + Ok(_) | Err(_) => Ok(()), + } + } +} + +impl Parse for FmtAttribute { + fn parse(input: ParseStream) -> syn::Result<Self> { + Ok(Self { + lit: input.parse()?, + comma: input + .peek(syn::token::Comma) + .then(|| input.parse()) + .transpose()?, + args: input.parse_terminated(FmtArgument::parse, token::Comma)?, + }) + } +} + +impl ToTokens for FmtAttribute { + fn to_tokens(&self, tokens: &mut TokenStream) { + self.lit.to_tokens(tokens); + self.comma.to_tokens(tokens); + self.args.to_tokens(tokens); + } +} + +/// Representation of a [named parameter][1] (`identifier '=' expression`) in +/// in a [`FmtAttribute`]. +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters +#[derive(Debug)] +struct FmtArgument { + /// `identifier =` [`Ident`]. + alias: Option<(Ident, token::Eq)>, + + /// `expression` [`Expr`]. + expr: Expr, +} + +impl FmtArgument { + /// Returns an `identifier` of the [named parameter][1]. + /// + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters + fn alias(&self) -> Option<&Ident> { + self.alias.as_ref().map(|(ident, _)| ident) + } +} + +impl Parse for FmtArgument { + fn parse(input: ParseStream) -> syn::Result<Self> { + Ok(Self { + alias: (input.peek(Ident) && input.peek2(token::Eq)) + .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) + .transpose()?, + expr: input.parse()?, + }) + } +} + +impl ToTokens for FmtArgument { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some((ident, eq)) = &self.alias { + ident.to_tokens(tokens); + eq.to_tokens(tokens); + } + self.expr.to_tokens(tokens); + } +} + +/// Representation of a [parameter][1] used in a [`Placeholder`]. +/// +/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters +#[derive(Debug, Eq, PartialEq)] +enum Parameter { + /// [Positional parameter][1]. + /// + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#positional-parameters + Positional(usize), + + /// [Named parameter][1]. + /// + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters + Named(String), +} + +impl<'a> From<parsing::Argument<'a>> for Parameter { + fn from(arg: parsing::Argument<'a>) -> Self { + match arg { + parsing::Argument::Integer(i) => Self::Positional(i), + parsing::Argument::Identifier(i) => Self::Named(i.to_owned()), + } + } +} + +/// Representation of a formatting placeholder. +#[derive(Debug, PartialEq, Eq)] +struct Placeholder { + /// Formatting argument (either named or positional) to be used by this placeholder. + arg: Parameter, + + /// [Width parameter][1], if present. + /// + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#width + width: Option<Parameter>, + + /// [Precision parameter][1], if present. + /// + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#precision + precision: Option<Parameter>, + + /// 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> { + let mut 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) + }); + + Self { + arg: position, + width: format.spec.and_then(|s| match s.width { + Some(parsing::Count::Parameter(arg)) => Some(arg.into()), + _ => None, + }), + precision: format.spec.and_then(|s| match s.precision { + Some(parsing::Precision::Count(parsing::Count::Parameter( + arg, + ))) => Some(arg.into()), + _ => None, + }), + trait_name: ty.trait_name(), + } + }) + .collect() + } +} + +#[cfg(test)] +mod fmt_attribute_spec { + use itertools::Itertools as _; + use quote::ToTokens; + use syn; + + use super::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 in 1..4 { + for permutations in cases.into_iter().permutations(i) { + let mut input = permutations.clone().join(","); + assert(&input, &permutations); + input.push(','); + assert(&input, &permutations); + } + } + } +} + +#[cfg(test)] +mod placeholder_parse_fmt_string_spec { + use super::{Parameter, Placeholder}; + + #[test] + fn indicates_position_and_trait_name_for_each_fmt_placeholder() { + let fmt_string = "{},{:?},{{}},{{{1:0$}}}-{2:.1$x}{par:#?}{:width$}"; + assert_eq!( + Placeholder::parse_fmt_string(&fmt_string), + vec![ + Placeholder { + arg: Parameter::Positional(0), + width: None, + precision: None, + trait_name: "Display", + }, + Placeholder { + arg: Parameter::Positional(1), + width: None, + precision: None, + trait_name: "Debug", + }, + Placeholder { + arg: Parameter::Positional(1), + width: Some(Parameter::Positional(0)), + precision: None, + trait_name: "Display", + }, + Placeholder { + arg: Parameter::Positional(2), + width: None, + precision: Some(Parameter::Positional(1)), + trait_name: "LowerHex", + }, + Placeholder { + arg: Parameter::Named("par".to_owned()), + width: None, + precision: None, + trait_name: "Debug", + }, + Placeholder { + arg: Parameter::Positional(2), + width: Some(Parameter::Named("width".to_owned())), + precision: None, + trait_name: "Display", + }, + ], + ); + } +} |