diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/displaydoc/src | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/displaydoc/src')
-rw-r--r-- | third_party/rust/displaydoc/src/attr.rs | 134 | ||||
-rw-r--r-- | third_party/rust/displaydoc/src/expand.rs | 145 | ||||
-rw-r--r-- | third_party/rust/displaydoc/src/fmt.rs | 159 | ||||
-rw-r--r-- | third_party/rust/displaydoc/src/lib.rs | 122 |
4 files changed, 560 insertions, 0 deletions
diff --git a/third_party/rust/displaydoc/src/attr.rs b/third_party/rust/displaydoc/src/attr.rs new file mode 100644 index 0000000000..e14593556c --- /dev/null +++ b/third_party/rust/displaydoc/src/attr.rs @@ -0,0 +1,134 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; +use syn::{Attribute, LitStr, Meta, Result}; + +#[derive(Clone)] +pub(crate) struct Display { + pub(crate) fmt: LitStr, + pub(crate) args: TokenStream, +} + +pub(crate) struct VariantDisplay { + pub(crate) r#enum: Option<Display>, + pub(crate) variant: Display, +} + +impl ToTokens for Display { + fn to_tokens(&self, tokens: &mut TokenStream) { + let fmt = &self.fmt; + let args = &self.args; + tokens.extend(quote! { + write!(formatter, #fmt #args) + }); + } +} + +impl ToTokens for VariantDisplay { + fn to_tokens(&self, tokens: &mut TokenStream) { + if let Some(ref r#enum) = self.r#enum { + r#enum.to_tokens(tokens); + tokens.extend(quote! { ?; write!(formatter, ": ")?; }); + } + self.variant.to_tokens(tokens); + } +} + +pub(crate) struct AttrsHelper { + ignore_extra_doc_attributes: bool, + prefix_enum_doc_attributes: bool, +} + +impl AttrsHelper { + pub(crate) fn new(attrs: &[Attribute]) -> Self { + let ignore_extra_doc_attributes = attrs + .iter() + .any(|attr| attr.path.is_ident("ignore_extra_doc_attributes")); + let prefix_enum_doc_attributes = attrs + .iter() + .any(|attr| attr.path.is_ident("prefix_enum_doc_attributes")); + + Self { + ignore_extra_doc_attributes, + prefix_enum_doc_attributes, + } + } + + pub(crate) fn display(&self, attrs: &[Attribute]) -> Result<Option<Display>> { + let displaydoc_attr = attrs.iter().find(|attr| attr.path.is_ident("displaydoc")); + + if let Some(displaydoc_attr) = displaydoc_attr { + let lit = displaydoc_attr + .parse_args() + .expect("#[displaydoc(\"foo\")] must contain string arguments"); + let mut display = Display { + fmt: lit, + args: TokenStream::new(), + }; + + display.expand_shorthand(); + return Ok(Some(display)); + } + + let num_doc_attrs = attrs + .iter() + .filter(|attr| attr.path.is_ident("doc")) + .count(); + + if !self.ignore_extra_doc_attributes && num_doc_attrs > 1 { + panic!("Multi-line comments are disabled by default by displaydoc. Please consider using block doc comments (/** */) or adding the #[ignore_extra_doc_attributes] attribute to your type next to the derive."); + } + + for attr in attrs { + if attr.path.is_ident("doc") { + let meta = attr.parse_meta()?; + let lit = match meta { + Meta::NameValue(syn::MetaNameValue { + lit: syn::Lit::Str(lit), + .. + }) => lit, + _ => unimplemented!(), + }; + + // Make an attempt and cleaning up multiline doc comments + let doc_str = lit + .value() + .lines() + .map(|line| line.trim().trim_start_matches('*').trim()) + .collect::<Vec<&str>>() + .join("\n"); + + let lit = LitStr::new(doc_str.trim(), lit.span()); + + let mut display = Display { + fmt: lit, + args: TokenStream::new(), + }; + + display.expand_shorthand(); + return Ok(Some(display)); + } + } + + Ok(None) + } + + pub(crate) fn display_with_input( + &self, + r#enum: &[Attribute], + variant: &[Attribute], + ) -> Result<Option<VariantDisplay>> { + let r#enum = if self.prefix_enum_doc_attributes { + let result = self + .display(r#enum)? + .expect("Missing doc comment on enum with #[prefix_enum_doc_attributes]. Please remove the attribute or add a doc comment to the enum itself."); + + Some(result) + } else { + None + }; + + Ok(self + .display(variant)? + .map(|variant| VariantDisplay { r#enum, variant })) + } +} diff --git a/third_party/rust/displaydoc/src/expand.rs b/third_party/rust/displaydoc/src/expand.rs new file mode 100644 index 0000000000..6904cbc8c7 --- /dev/null +++ b/third_party/rust/displaydoc/src/expand.rs @@ -0,0 +1,145 @@ +use super::attr::AttrsHelper; +use proc_macro2::TokenStream; +use quote::{format_ident, quote}; +use syn::{Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Result}; + +pub(crate) fn derive(input: &DeriveInput) -> Result<TokenStream> { + let impls = match &input.data { + Data::Struct(data) => impl_struct(input, data), + Data::Enum(data) => impl_enum(input, data), + Data::Union(_) => Err(Error::new_spanned(input, "Unions are not supported")), + }?; + + let helpers = specialization(); + let dummy_const = format_ident!("_DERIVE_Display_FOR_{}", input.ident); + Ok(quote! { + #[allow(non_upper_case_globals, unused_attributes, unused_qualifications)] + const #dummy_const: () = { + #helpers + #impls + }; + }) +} + +#[cfg(feature = "std")] +fn specialization() -> TokenStream { + quote! { + trait DisplayToDisplayDoc { + fn __displaydoc_display(&self) -> Self; + } + + impl<T: core::fmt::Display> DisplayToDisplayDoc for &T { + fn __displaydoc_display(&self) -> Self { + self + } + } + + // If the `std` feature gets enabled we want to ensure that any crate + // using displaydoc can still reference the std crate, which is already + // being compiled in by whoever enabled the `std` feature in + // `displaydoc`, even if the crates using displaydoc are no_std. + extern crate std; + + trait PathToDisplayDoc { + fn __displaydoc_display(&self) -> std::path::Display<'_>; + } + + impl PathToDisplayDoc for std::path::Path { + fn __displaydoc_display(&self) -> std::path::Display<'_> { + self.display() + } + } + + impl PathToDisplayDoc for std::path::PathBuf { + fn __displaydoc_display(&self) -> std::path::Display<'_> { + self.display() + } + } + } +} + +#[cfg(not(feature = "std"))] +fn specialization() -> TokenStream { + quote! {} +} + +fn impl_struct(input: &DeriveInput, data: &DataStruct) -> Result<TokenStream> { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let helper = AttrsHelper::new(&input.attrs); + + let display = helper.display(&input.attrs)?.map(|display| { + let pat = match &data.fields { + Fields::Named(fields) => { + let var = fields.named.iter().map(|field| &field.ident); + quote!(Self { #(#var),* }) + } + Fields::Unnamed(fields) => { + let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i)); + quote!(Self(#(#var),*)) + } + Fields::Unit => quote!(_), + }; + quote! { + impl #impl_generics core::fmt::Display for #ty #ty_generics #where_clause { + fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + #[allow(unused_variables)] + let #pat = self; + #display + } + } + } + }); + + Ok(quote! { #display }) +} + +fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result<TokenStream> { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let helper = AttrsHelper::new(&input.attrs); + + let displays = data + .variants + .iter() + .map(|variant| helper.display_with_input(&input.attrs, &variant.attrs)) + .collect::<Result<Vec<_>>>()?; + + if displays.iter().any(Option::is_some) { + let arms = data + .variants + .iter() + .zip(displays) + .map(|(variant, display)| { + let display = + display.ok_or_else(|| Error::new_spanned(variant, "missing doc comment"))?; + let ident = &variant.ident; + Ok(match &variant.fields { + Fields::Named(fields) => { + let var = fields.named.iter().map(|field| &field.ident); + quote!(#ty::#ident { #(#var),* } => { #display }) + } + Fields::Unnamed(fields) => { + let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i)); + quote!(#ty::#ident(#(#var),*) => { #display }) + } + Fields::Unit => quote!(#ty::#ident => { #display }), + }) + }) + .collect::<Result<Vec<_>>>()?; + Ok(quote! { + impl #impl_generics core::fmt::Display for #ty #ty_generics #where_clause { + fn fmt(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + #[allow(unused_variables)] + match self { + #(#arms,)* + } + } + } + }) + } else { + Err(Error::new_spanned(input, "Missing doc comments")) + } +} diff --git a/third_party/rust/displaydoc/src/fmt.rs b/third_party/rust/displaydoc/src/fmt.rs new file mode 100644 index 0000000000..5848557ea4 --- /dev/null +++ b/third_party/rust/displaydoc/src/fmt.rs @@ -0,0 +1,159 @@ +use crate::attr::Display; +use proc_macro2::TokenStream; +use quote::quote_spanned; +use syn::{Ident, LitStr}; + +macro_rules! peek_next { + ($read:ident) => { + match $read.chars().next() { + Some(next) => next, + None => return, + } + }; +} + +impl Display { + // Transform `"error {var}"` to `"error {}", var`. + pub(crate) fn expand_shorthand(&mut self) { + let span = self.fmt.span(); + let fmt = self.fmt.value(); + let mut read = fmt.as_str(); + let mut out = String::new(); + let mut args = TokenStream::new(); + + while let Some(brace) = read.find('{') { + out += &read[..=brace]; + read = &read[brace + 1..]; + + // skip cases where we find a {{ + if read.starts_with('{') { + out.push('{'); + read = &read[1..]; + continue; + } + + let next = peek_next!(read); + + let var = match next { + '0'..='9' => take_int(&mut read), + 'a'..='z' | 'A'..='Z' | '_' => take_ident(&mut read), + _ => return, + }; + + let ident = Ident::new(&var, span); + + let next = peek_next!(read); + + let arg = if cfg!(feature = "std") && next == '}' { + quote_spanned!(span=> , #ident.__displaydoc_display()) + } else { + quote_spanned!(span=> , #ident) + }; + + args.extend(arg); + } + + out += read; + self.fmt = LitStr::new(&out, self.fmt.span()); + self.args = args; + } +} + +fn take_int(read: &mut &str) -> String { + let mut int = String::new(); + int.push('_'); + for (i, ch) in read.char_indices() { + match ch { + '0'..='9' => int.push(ch), + _ => { + *read = &read[i..]; + break; + } + } + } + int +} + +fn take_ident(read: &mut &str) -> String { + let mut ident = String::new(); + for (i, ch) in read.char_indices() { + match ch { + 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch), + _ => { + *read = &read[i..]; + break; + } + } + } + ident +} + +#[cfg(test)] +mod tests { + use super::*; + use pretty_assertions::assert_eq; + use proc_macro2::Span; + + fn assert(input: &str, fmt: &str, args: &str) { + let mut display = Display { + fmt: LitStr::new(input, Span::call_site()), + args: TokenStream::new(), + }; + display.expand_shorthand(); + assert_eq!(fmt, display.fmt.value()); + assert_eq!(args, display.args.to_string()); + } + + #[test] + fn test_expand() { + assert("fn main() {{ }}", "fn main() {{ }}", ""); + } + + #[test] + #[cfg_attr(not(feature = "std"), ignore)] + fn test_std_expand() { + assert( + "{v} {v:?} {0} {0:?}", + "{} {:?} {} {:?}", + ", v . __displaydoc_display () , v , _0 . __displaydoc_display () , _0", + ); + assert("error {var}", "error {}", ", var . __displaydoc_display ()"); + + assert( + "error {var1}", + "error {}", + ", var1 . __displaydoc_display ()", + ); + + assert( + "error {var1var}", + "error {}", + ", var1var . __displaydoc_display ()", + ); + + assert( + "The path {0}", + "The path {}", + ", _0 . __displaydoc_display ()", + ); + assert("The path {0:?}", "The path {:?}", ", _0"); + } + + #[test] + #[cfg_attr(feature = "std", ignore)] + fn test_nostd_expand() { + assert( + "{v} {v:?} {0} {0:?}", + "{} {:?} {} {:?}", + ", v , v , _0 , _0", + ); + assert("error {var}", "error {}", ", var"); + + assert("The path {0}", "The path {}", ", _0"); + assert("The path {0:?}", "The path {:?}", ", _0"); + + assert("error {var1}", "error {}", ", var1"); + + assert("error {var1var}", "error {}", ", var1var"); + } +} diff --git a/third_party/rust/displaydoc/src/lib.rs b/third_party/rust/displaydoc/src/lib.rs new file mode 100644 index 0000000000..083c613845 --- /dev/null +++ b/third_party/rust/displaydoc/src/lib.rs @@ -0,0 +1,122 @@ +//! This library provides a convenient derive macro for the standard library's +//! [`core::fmt::Display`] trait. +//! +//! [`core::fmt::Display`]: https://doc.rust-lang.org/std/fmt/trait.Display.html +//! +//! ```toml +//! [dependencies] +//! displaydoc = "0.2" +//! ``` +//! +//! *Compiler support: requires rustc 1.31+* +//! +//! <br> +//! +//! ## Example +//! +//! ```rust +//! use std::io; +//! use displaydoc::Display; +//! use thiserror::Error; +//! +//! #[derive(Display, Error, Debug)] +//! pub enum DataStoreError { +//! /// data store disconnected +//! Disconnect(#[source] io::Error), +//! /// the data for key `{0}` is not available +//! Redaction(String), +//! /// invalid header (expected {expected:?}, found {found:?}) +//! InvalidHeader { +//! expected: String, +//! found: String, +//! }, +//! /// unknown data store error +//! Unknown, +//! } +//! ``` +//! +//! <br> +//! +//! ## Details +//! +//! - A `Display` impl is generated for your type if you provide doc comment +//! messages on the struct or each variant of your enum, as shown above in the +//! example. +//! +//! The messages support a shorthand for interpolating fields from the error. +//! +//! - `/// {var}` ⟶ `write!("{}", self.var)` +//! - `/// {0}` ⟶ `write!("{}", self.0)` +//! - `/// {var:?}` ⟶ `write!("{:?}", self.var)` +//! - `/// {0:?}` ⟶ `write!("{:?}", self.0)` +//! +//! - Two optional attributes can be added to your types next to the derive: +//! +//! - `#[ignore_extra_doc_attributes]` makes the macro ignore any doc +//! comment attributes (or `///` lines) after the first. Multi-line +//! comments using `///` are otherwise treated as an error, so use this +//! attribute or consider switching to block doc comments (`/** */`). +//! +//! - `#[prefix_enum_doc_attributes]` combines the doc comment message on +//! your enum itself with the messages for each variant, in the format +//! “enum: variant”. When added to an enum, the doc comment on the enum +//! becomes mandatory. When added to any other type, it has no effect. +//! +//! - In case you want to have an independent doc comment, the +//! `#[displaydoc("...")` atrribute may be used on the variant or struct to +//! override it. +//! +//! <br> +//! +//! ## FAQ +//! +//! 1. **Is this crate `no_std` compatible?** +//! * Yes! This crate implements the `core::fmt::Display` trait not the `std::fmt::Display` trait so it should work in `std` and `no_std` environments. Just add `default-features = false`. +//! +//! 2. **Does this crate work with `Path` and `PathBuf` via the `Display` trait?** +//! * Yuuup. This crate uses @dtolnay's [autoref specialization technique](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) to add a special trait for types to get the display impl, it then specializes for `Path` and `PathBuf` and when either of these types are found it calls `self.display()` to get a `std::path::Display<'_>` type which can be used with the Display format specifier! +#![doc(html_root_url = "https://docs.rs/displaydoc/0.2.3")] +#![cfg_attr(docsrs, feature(doc_cfg))] +#![warn( + rust_2018_idioms, + unreachable_pub, + bad_style, + const_err, + dead_code, + improper_ctypes, + non_shorthand_field_patterns, + no_mangle_generic_items, + overflowing_literals, + path_statements, + patterns_in_fns_without_body, + private_in_public, + unconditional_recursion, + unused, + unused_allocation, + unused_comparisons, + unused_parens, + while_true +)] +#![allow(clippy::try_err)] + +#[allow(unused_extern_crates)] +extern crate proc_macro; + +mod attr; +mod expand; +mod fmt; + +use proc_macro::TokenStream; +use syn::{parse_macro_input, DeriveInput}; + +/// Derive macro for implementing `Display` via doc comment attributes +#[proc_macro_derive( + Display, + attributes(ignore_extra_doc_attributes, prefix_enum_doc_attributes, displaydoc) +)] +pub fn derive_error(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand::derive(&input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} |