From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- third_party/rust/displaydoc/src/attr.rs | 137 ++++++++++ third_party/rust/displaydoc/src/expand.rs | 410 ++++++++++++++++++++++++++++++ third_party/rust/displaydoc/src/fmt.rs | 159 ++++++++++++ third_party/rust/displaydoc/src/lib.rs | 187 ++++++++++++++ 4 files changed, 893 insertions(+) create mode 100644 third_party/rust/displaydoc/src/attr.rs create mode 100644 third_party/rust/displaydoc/src/expand.rs create mode 100644 third_party/rust/displaydoc/src/fmt.rs create mode 100644 third_party/rust/displaydoc/src/lib.rs (limited to 'third_party/rust/displaydoc/src') diff --git a/third_party/rust/displaydoc/src/attr.rs b/third_party/rust/displaydoc/src/attr.rs new file mode 100644 index 0000000000..a965d04deb --- /dev/null +++ b/third_party/rust/displaydoc/src/attr.rs @@ -0,0 +1,137 @@ +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, + 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> { + 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 lit = match &attr.meta { + Meta::NameValue(syn::MetaNameValue { + value: + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(lit), + .. + }), + .. + }) => lit, + _ => unimplemented!(), + }; + + // Make an attempt at cleaning up multiline doc comments. + let doc_str = lit + .value() + .lines() + .map(|line| line.trim().trim_start_matches('*').trim()) + .collect::>() + .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> { + 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..8a84390be2 --- /dev/null +++ b/third_party/rust/displaydoc/src/expand.rs @@ -0,0 +1,410 @@ +use super::attr::AttrsHelper; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote}; +use syn::{ + punctuated::Punctuated, + token::{Colon, Comma, PathSep, Plus, Where}, + Data, DataEnum, DataStruct, DeriveInput, Error, Fields, Generics, Ident, Path, PathArguments, + PathSegment, PredicateType, Result, TraitBound, TraitBoundModifier, Type, TypeParam, + TypeParamBound, TypePath, WhereClause, WherePredicate, +}; + +use std::collections::HashMap; + +pub(crate) fn derive(input: &DeriveInput) -> Result { + 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 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 { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let where_clause = generate_where_clause(&input.generics, where_clause); + + 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 { + // NB: This destructures the fields of `self` into named variables (for unnamed + // fields, it uses _0, _1, etc as above). The `#[allow(unused_variables)]` + // section means it doesn't have to parse the individual field references out of + // the docstring. + #[allow(unused_variables)] + let #pat = self; + #display + } + } + } + }); + + Ok(quote! { #display }) +} + +/// Create a `where` predicate for `ident`, without any [bound][TypeParamBound]s yet. +fn new_empty_where_type_predicate(ident: Ident) -> PredicateType { + let mut path_segments = Punctuated::::new(); + path_segments.push_value(PathSegment { + ident, + arguments: PathArguments::None, + }); + PredicateType { + lifetimes: None, + bounded_ty: Type::Path(TypePath { + qself: None, + path: Path { + leading_colon: None, + segments: path_segments, + }, + }), + colon_token: Colon { + spans: [Span::call_site()], + }, + bounds: Punctuated::::new(), + } +} + +/// Create a `where` clause that we can add [WherePredicate]s to. +fn new_empty_where_clause() -> WhereClause { + WhereClause { + where_token: Where { + span: Span::call_site(), + }, + predicates: Punctuated::::new(), + } +} + +enum UseGlobalPrefix { + LeadingColon, + #[allow(dead_code)] + NoLeadingColon, +} + +/// Create a path with segments composed of [Idents] *without* any [PathArguments]. +fn join_paths(name_segments: &[&str], use_global_prefix: UseGlobalPrefix) -> Path { + let mut segments = Punctuated::::new(); + assert!(!name_segments.is_empty()); + segments.push_value(PathSegment { + ident: Ident::new(name_segments[0], Span::call_site()), + arguments: PathArguments::None, + }); + for name in name_segments[1..].iter() { + segments.push_punct(PathSep { + spans: [Span::call_site(), Span::mixed_site()], + }); + segments.push_value(PathSegment { + ident: Ident::new(name, Span::call_site()), + arguments: PathArguments::None, + }); + } + Path { + leading_colon: match use_global_prefix { + UseGlobalPrefix::LeadingColon => Some(PathSep { + spans: [Span::call_site(), Span::mixed_site()], + }), + UseGlobalPrefix::NoLeadingColon => None, + }, + segments, + } +} + +/// Push `new_type_predicate` onto the end of `where_clause`. +fn append_where_clause_type_predicate( + where_clause: &mut WhereClause, + new_type_predicate: PredicateType, +) { + // Push a comma at the end if there are already any `where` predicates. + if !where_clause.predicates.is_empty() { + where_clause.predicates.push_punct(Comma { + spans: [Span::call_site()], + }); + } + where_clause + .predicates + .push_value(WherePredicate::Type(new_type_predicate)); +} + +/// Add a requirement for [core::fmt::Display] to a `where` predicate for some type. +fn add_display_constraint_to_type_predicate( + predicate_that_needs_a_display_impl: &mut PredicateType, +) { + // Create a `Path` of `::core::fmt::Display`. + let display_path = join_paths(&["core", "fmt", "Display"], UseGlobalPrefix::LeadingColon); + + let display_bound = TypeParamBound::Trait(TraitBound { + paren_token: None, + modifier: TraitBoundModifier::None, + lifetimes: None, + path: display_path, + }); + if !predicate_that_needs_a_display_impl.bounds.is_empty() { + predicate_that_needs_a_display_impl.bounds.push_punct(Plus { + spans: [Span::call_site()], + }); + } + + predicate_that_needs_a_display_impl + .bounds + .push_value(display_bound); +} + +/// Map each declared generic type parameter to the set of all trait boundaries declared on it. +/// +/// These boundaries may come from the declaration site: +/// pub enum E { ... } +/// or a `where` clause after the parameter declarations: +/// pub enum E where T: MyTrait { ... } +/// This method will return the boundaries from both of those cases. +fn extract_trait_constraints_from_source( + where_clause: &WhereClause, + type_params: &[&TypeParam], +) -> HashMap> { + // Add trait bounds provided at the declaration site of type parameters for the struct/enum. + let mut param_constraint_mapping: HashMap> = type_params + .iter() + .map(|type_param| { + let trait_bounds: Vec = type_param + .bounds + .iter() + .flat_map(|bound| match bound { + TypeParamBound::Trait(trait_bound) => Some(trait_bound), + _ => None, + }) + .cloned() + .collect(); + (type_param.ident.clone(), trait_bounds) + }) + .collect(); + + // Add trait bounds from `where` clauses, which may be type parameters or types containing + // those parameters. + for predicate in where_clause.predicates.iter() { + // We only care about type and not lifetime constraints here. + if let WherePredicate::Type(ref pred_ty) = predicate { + let ident = match &pred_ty.bounded_ty { + Type::Path(TypePath { path, qself: None }) => match path.get_ident() { + None => continue, + Some(ident) => ident, + }, + _ => continue, + }; + // We ignore any type constraints that aren't direct references to type + // parameters of the current enum of struct definition. No types can be + // constrained in a `where` clause unless they are a type parameter or a generic + // type instantiated with one of the type parameters, so by only allowing single + // identifiers, we can be sure that the constrained type is a type parameter + // that is contained in `param_constraint_mapping`. + if let Some((_, ref mut known_bounds)) = param_constraint_mapping + .iter_mut() + .find(|(id, _)| *id == ident) + { + for bound in pred_ty.bounds.iter() { + // We only care about trait bounds here. + if let TypeParamBound::Trait(ref bound) = bound { + known_bounds.push(bound.clone()); + } + } + } + } + } + + param_constraint_mapping +} + +/// Hygienically add `where _: Display` to the set of [TypeParamBound]s for `ident`, creating such +/// a set if necessary. +fn ensure_display_in_where_clause_for_type(where_clause: &mut WhereClause, ident: Ident) { + for pred_ty in where_clause + .predicates + .iter_mut() + // Find the `where` predicate constraining the current type param, if it exists. + .flat_map(|predicate| match predicate { + WherePredicate::Type(pred_ty) => Some(pred_ty), + // We're looking through type constraints, not lifetime constraints. + _ => None, + }) + { + // Do a complicated destructuring in order to check if the type being constrained in this + // `where` clause is the type we're looking for, so we can use the mutable reference to + // `pred_ty` if so. + let matches_desired_type = matches!( + &pred_ty.bounded_ty, + Type::Path(TypePath { path, .. }) if Some(&ident) == path.get_ident()); + if matches_desired_type { + add_display_constraint_to_type_predicate(pred_ty); + return; + } + } + + // If there is no `where` predicate for the current type param, we will construct one. + let mut new_type_predicate = new_empty_where_type_predicate(ident); + add_display_constraint_to_type_predicate(&mut new_type_predicate); + append_where_clause_type_predicate(where_clause, new_type_predicate); +} + +/// For all declared type parameters, add a [core::fmt::Display] constraint, unless the type +/// parameter already has any type constraint. +fn ensure_where_clause_has_display_for_all_unconstrained_members( + where_clause: &mut WhereClause, + type_params: &[&TypeParam], +) { + let param_constraint_mapping = extract_trait_constraints_from_source(where_clause, type_params); + + for (ident, known_bounds) in param_constraint_mapping.into_iter() { + // If the type parameter has any constraints already, we don't want to touch it, to avoid + // breaking use cases where a type parameter only needs to impl `Debug`, for example. + if known_bounds.is_empty() { + ensure_display_in_where_clause_for_type(where_clause, ident); + } + } +} + +/// Generate a `where` clause that ensures all generic type parameters `impl` +/// [core::fmt::Display] unless already constrained. +/// +/// This approach allows struct/enum definitions deriving [crate::Display] to avoid hardcoding +/// a [core::fmt::Display] constraint into every type parameter. +/// +/// If the type parameter isn't already constrained, we add a `where _: Display` clause to our +/// display implementation to expect to be able to format every enum case or struct member. +/// +/// In fact, we would preferably only require `where _: Display` or `where _: Debug` where the +/// format string actually requires it. However, while [`std::fmt` defines a formal syntax for +/// `format!()`][format syntax], it *doesn't* expose the actual logic to parse the format string, +/// which appears to live in [`rustc_parse_format`]. While we use the [`syn`] crate to parse rust +/// syntax, it also doesn't currently provide any method to introspect a `format!()` string. It +/// would be nice to contribute this upstream in [`syn`]. +/// +/// [format syntax]: std::fmt#syntax +/// [`rustc_parse_format`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_parse_format/index.html +fn generate_where_clause(generics: &Generics, where_clause: Option<&WhereClause>) -> WhereClause { + let mut where_clause = where_clause.cloned().unwrap_or_else(new_empty_where_clause); + let type_params: Vec<&TypeParam> = generics.type_params().collect(); + ensure_where_clause_has_display_for_all_unconstrained_members(&mut where_clause, &type_params); + where_clause +} + +fn impl_enum(input: &DeriveInput, data: &DataEnum) -> Result { + let ty = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + let where_clause = generate_where_clause(&input.generics, where_clause); + + let helper = AttrsHelper::new(&input.attrs); + + let displays = data + .variants + .iter() + .map(|variant| helper.display_with_input(&input.attrs, &variant.attrs)) + .collect::>>()?; + + if data.variants.is_empty() { + 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 { + unreachable!("empty enums cannot be instantiated and thus cannot be printed") + } + } + }) + } else 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!(Self::#ident { #(#var),* } => { #display }) + } + Fields::Unnamed(fields) => { + let var = (0..fields.unnamed.len()).map(|i| format_ident!("_{}", i)); + quote!(Self::#ident(#(#var),*) => { #display }) + } + Fields::Unit => quote!(Self::#ident => { #display }), + }) + }) + .collect::>>()?; + 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..3334a82ff2 --- /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..e4653d6755 --- /dev/null +++ b/third_party/rust/displaydoc/src/lib.rs @@ -0,0 +1,187 @@ +//! 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.56+* +//! +//!
+//! +//! ## Example +//! +//! *Demonstration alongside the [`Error`][std::error::Error] derive macro from [`thiserror`](https://docs.rs/thiserror/1.0.25/thiserror/index.html), +//! to propagate source locations from [`io::Error`][std::io::Error] with the `#[source]` attribute:* +//! ```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, +//! } +//! +//! let error = DataStoreError::Redaction("CLASSIFIED CONTENT".to_string()); +//! assert!("the data for key `CLASSIFIED CONTENT` is not available" == &format!("{}", error)); +//! ``` +//! *Note that although [`io::Error`][std::io::Error] implements `Display`, we do not add it to the +//! generated message for `DataStoreError::Disconnect`, since it is already made available via +//! `#[source]`. See further context on avoiding duplication in error reports at the rust blog +//! [here](https://github.com/yaahc/blog.rust-lang.org/blob/master/posts/inside-rust/2021-05-15-What-the-error-handling-project-group-is-working-towards.md#duplicate-information-issue).* +//! +//!
+//! +//! ## Details +//! +//! - A `fmt::Display` impl is generated for your enum if you provide +//! a docstring comment on each variant as shown above in the example. The +//! `Display` derive macro supports a shorthand for interpolating fields from +//! the error: +//! - `/// {var}` ⟶ `write!("{}", self.var)` +//! - `/// {0}` ⟶ `write!("{}", self.0)` +//! - `/// {var:?}` ⟶ `write!("{:?}", self.var)` +//! - `/// {0:?}` ⟶ `write!("{:?}", self.0)` +//! - This also works with structs and [generic types][crate::Display#generic-type-parameters]: +//! ```rust +//! # use displaydoc::Display; +//! /// oh no, an error: {0} +//! #[derive(Display)] +//! pub struct Error(pub E); +//! +//! let error: Error<&str> = Error("muahaha i am an error"); +//! assert!("oh no, an error: muahaha i am an error" == &format!("{}", error)); +//! ``` +//! +//! - 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. +//! +//!
+//! +//! ## 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, + 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}; + +/// [Custom `#[derive(...)]` macro](https://doc.rust-lang.org/edition-guide/rust-2018/macros/custom-derive.html) +/// for implementing [`fmt::Display`][core::fmt::Display] via doc comment attributes. +/// +/// ### Generic Type Parameters +/// +/// Type parameters to an enum or struct using this macro should *not* need to +/// have an explicit `Display` constraint at the struct or enum definition +/// site. A `Display` implementation for the `derive`d struct or enum is +/// generated assuming each type parameter implements `Display`, but that should +/// be possible without adding the constraint to the struct definition itself: +/// ```rust +/// use displaydoc::Display; +/// +/// /// oh no, an error: {0} +/// #[derive(Display)] +/// pub struct Error(pub E); +/// +/// // No need to require `E: Display`, since `displaydoc::Display` adds that implicitly. +/// fn generate_error(e: E) -> Error { Error(e) } +/// +/// assert!("oh no, an error: muahaha" == &format!("{}", generate_error("muahaha"))); +/// ``` +/// +/// ### Using [`Debug`][core::fmt::Debug] Implementations with Type Parameters +/// However, if a type parameter must instead be constrained with the +/// [`Debug`][core::fmt::Debug] trait so that some field may be printed with +/// `{:?}`, that constraint must currently still also be specified redundantly +/// at the struct or enum definition site. If a struct or enum field is being +/// formatted with `{:?}` via [`displaydoc`][crate], and a generic type +/// parameter must implement `Debug` to do that, then that struct or enum +/// definition will need to propagate the `Debug` constraint to every type +/// parameter it's instantiated with: +/// ```rust +/// use core::fmt::Debug; +/// use displaydoc::Display; +/// +/// /// oh no, an error: {0:?} +/// #[derive(Display)] +/// pub struct Error(pub E); +/// +/// // `E: Debug` now has to propagate to callers. +/// fn generate_error(e: E) -> Error { Error(e) } +/// +/// assert!("oh no, an error: \"cool\"" == &format!("{}", generate_error("cool"))); +/// +/// // Try this with a struct that doesn't impl `Display` at all, unlike `str`. +/// #[derive(Debug)] +/// pub struct Oh; +/// assert!("oh no, an error: Oh" == &format!("{}", generate_error(Oh))); +/// ``` +#[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() +} -- cgit v1.2.3