Skip to content

Commit

Permalink
remove workaround
Browse files Browse the repository at this point in the history
  • Loading branch information
cosmicexplorer committed Jun 16, 2021
1 parent b30c878 commit efa8f4d
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 95 deletions.
28 changes: 2 additions & 26 deletions src/expand.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,12 +288,7 @@ fn ensure_display_in_where_clause_for_type(where_clause: &mut WhereClause, ident
Some(WherePredicate::Type(ref mut pred_ty)) => {
add_display_constraint_to_type_predicate(pred_ty)
}
x => {
unreachable!(
"we ensured this by pushing a type predicate just above, but got: {:?}",
x
)
}
_ => unreachable!("we ensured this by pushing a type predicate just above"),
}
})
}
Expand All @@ -307,15 +302,7 @@ fn ensure_where_clause_has_display_for_all_members(
let param_constraint_mapping = extract_trait_constraints_from_source(where_clause, type_params);

for (ident, known_bounds) in param_constraint_mapping.into_iter() {
// NB: if the type param implements *anything* named "Display", we will
// assume that means they implement core::fmt::Display! This is not
// a perfect heuristic!
let probably_impls_display: bool = known_bounds.iter().any(|trait_bound| {
assert!(!trait_bound.path.segments.is_empty());
let final_segment_ident: &Ident = &trait_bound.path.segments.last().unwrap().ident;
&format!("{}", final_segment_ident) == "Display"
});
if !probably_impls_display {
if known_bounds.is_empty() {
ensure_display_in_where_clause_for_type(where_clause, ident);
}
}
Expand All @@ -334,17 +321,6 @@ fn ensure_where_clause_has_display_for_all_members(
/// parameter may not be used in a way that affects whether the enum cases impl Display. It
/// would be nice to be able to ask rustc "does this type argument impl (e.g.)
/// core::fmt::Display?".
/// However, the solution in this method also allows the user to bypass this check in the case they
/// are already impling `Display` for that type param somehow, by adding something like:
/// mod inner { trait Display = core::any::Any; }
/// #[derive(Display)]
/// pub enum Error<T: core::fmt::Debug+inner::Display> {
/// // We will allow the normal compiler failure to occur
/// // if T ends up not impling Display. Here, we demonstrate
/// // the use of Debug.
/// /// failure of type T: {0:?}
/// ParameterizedFailure(T),
/// }
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();
Expand Down
74 changes: 5 additions & 69 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -135,83 +135,19 @@ use syn::{parse_macro_input, DeriveInput};
/// 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. This can become a problem when writing
/// generic code:
/// ```compile_fail
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// // This docstring uses the `Debug` format specifier ":?":
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: Debug>(pub E);
///
/// // Error: the trait `std::fmt::Display` is not implemented for `MyType`.
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// ```
/// If this is a problem for you, it is recommended to instead:
/// 1. require [`fmt::Display`][core::fmt::Display] for any desired
/// parameterized type,
/// 2. use the `{}` format specifier for the struct or enum field, and
/// 3. make use of the workaround described in
/// [Generic Type Parameters][crate#generic-type-parameters] to avoid pinning
/// specifically the `fmt::Display` trait at the definition site.
///
/// Consumers of your generic API should be able to use `displaydoc::Display` to
/// implement `fmt::Display` for the concrete types they provide to your
/// parameterized errors without much boilerplate.
///
/// #### Trick the Macro to Avoid Requiring Everything to `Display` to `Display`
/// However, there may be cases where some type parameter can't be made to
/// implement `fmt::Display` or can't use the `displaydoc::Display` derive macro
/// for some reason. For example, this won't work immediately:
/// ```compile_fail
/// parameter it's instantiated with:
/// ```rust
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: Debug>(pub E);
///
/// // Still fails to compile because `displaydoc`'s `fmt::Display` implementation
/// // assumes all type parameters need to require `fmt::Display` themselves:
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// ```
/// We would like to be able to implement our own `Display` for any struct or
/// enum without requiring the type parameter to itself implement `Display`. In
/// such cases, the macro can be misled away from adding a `where E: Display`
/// requirement to its generated `fmt::Display` implementation by creating
/// a local trait *named* "Display". This allows the above example to
/// successfully compile:
/// ```rust
/// use core::fmt::Debug;
/// use displaydoc::Display;
///
/// #[derive(Debug)]
/// pub struct MyType;
///
/// mod inner {
/// use core::fmt::Debug;
/// // `#[derive(Display)]` checks for any constraint named "Display" and will
/// // avoid placing a `where E: core::fmt::Display` on that type param if it
/// // sees one.
/// pub trait Display: Debug {}
/// // This means the type is basically `Error<E: Debug>` though.
/// impl<E> Display for E where E: Debug {}
/// }
///
/// /// oh no, an error: {0:?}
/// #[derive(Display)]
/// pub struct Error<E: inner::Display>(pub E);
/// // `E: Debug` now has to propagate to callers.
/// fn generate_error<E: Debug>(e: E) -> Error<E> { Error(e) }
///
/// // MyType still does not itself need to impl `Display` at all!
/// assert!("oh no, an error: MyType" == &format!("{}", Error(MyType)));
/// assert!("oh no, an error: \"cool\"" == &format!("{}", generate_error("cool")));
/// ```
#[proc_macro_derive(Display, attributes(ignore_extra_doc_attributes))]
pub fn derive_error(input: TokenStream) -> TokenStream {
Expand Down

0 comments on commit efa8f4d

Please sign in to comment.