-
Notifications
You must be signed in to change notification settings - Fork 124
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
'overflow evaluating the requirement' when deriving debug for item that contains itself #363
Comments
expanding the derive with
i don't think |
the linked rust issue is rust-lang/rust#48214. interestingly, with the feature |
@sornas doesn't this code introduce an unbounded recursion? |
I think @sornas oversimplified the example a bit too much, because indeed you wouldn't be able to ever construct such a struct afaict. But something like this is completely valid as a kind of linked list and has the same issue where #[derive(Debug)]
pub struct Item {
pub next: Option<Box<Item>>,
}
fn main() {
println!("{:?}", Item{next: None})
} So this seems quite a problematic about the bounds the we add with our current implementation of the Debug derive. A few options on how to address this:
I think 1 is not really an option, given how little attention that issue has gotten. 2 seems fairly easy to do, but then the question becomes what do we do instead. A few options for those again:
I'm not sure which of these is best. I kinda like 2, but I worry there are some cases that won't work with this. 3 seems a little safer, because it will also be Sized. But it seems annoying to have to rely on 1 at least won't regress with regards to std, but we might get similar errors to std when generating code because it doesn't add correct bounds. Still that seems a lot better than what we have now, because now we throw errors for things that work fine doesn't throw errors for. For reference this is the code I was playing around with when trying what bounds do work correctly: use std::fmt;
use std::fmt::Debug;
pub struct Item {
pub next: Option<Box<Item>>,
}
impl fmt::Debug for Item
where Option<Box<dyn Debug>>: Debug{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Item")
.field("next", &self.next)
.finish()
}
}
fn main() {
println!("{:?}", Item{next: Some(Box::new(Item {next: None}))})
} |
Yes, we cannot reliably detect such cases by the type name only, because there are aliases and import renames: #[derive(Debug)]
pub struct Item {
// No way we can detect this is the same type while expanding a macro.
pub next: Option<Box<Another>>,
}
pub type Another = Item; However, instead of messing with types, maybe it's worth to add user the ability to overwrite the generated bounds. Recalling the context of the question, we made #[derive(Debug)]
#[debug(replace(bound()))] // hypotetical syntax
pub struct Item {
pub next: Option<Box<Item>>,
} expands to impl fmt::Debug for Item { // all the bounds replaced with nothing
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Item")
.field("next", &self.next)
.finish()
}
} Another possible application of #[derive(Debug)]
struct Private<T>(T);
#[derive(Debug)]
pub struct Public<T>(Private<T>); At the moment, the expansion looks like: impl<T> fmt::Debug for Public<T>
where Private<T>: fmt::Debug // oops private type is leaked into public impl, it may not compile!
{} But having an ability to replace the bounds we could just use: #[derive(Debug)]
#[debug(replace(bound(T: Debug)))]
pub struct Public<T>(Private<T>); and the expansion will work: impl<T> fmt::Debug for Public<T>
where T: Debug
{} |
I mainly meant that I think removing is good enough, because indeed users can add any necessary bounds manually, if a bound is still needed for successful compilation. I think the only bounds we should add by default are ones that we know are necessary.
I totally agree that this makes sense as a feature. But I wouldn't consider it a 1.0.0, so let's make that a separate issue. |
Maybe we could do even better. I think we can omit generating trait bounds at all, unless some type parameter participates in the field type. And, we can do it fairly well, considering the fact that, unlike an arbitrary type, a type parameter cannot be aliased or renamed anyhow. |
Yeah I think that would be even better |
## Synopsis The problem, as reported in the issue, is that code like the following ```rust #[derive(derive_more::Debug)] struct Item { next: Option<Box<Item>>, } ``` expands into something like ```rust impl std::fmt::Debug for Item where Item: Debug { /* ... */ } ``` which does not compile. This PR changes the Debug derive so it does not emit those bounds. ## Solution My understanding of the current code is that we iterate over all fields of the struct/enum and add either a specific format bound (e.g. `: fmt::Binary`), a default `: fmt::Debug` bound or skip it if either it is marked as `#[debug(skip)]` or the entire container has a format attribute. The suggested solution in the issue (if I understood it correctly) was to only add bounds if the type is a type variable, since rustc already knows if a concrete type is, say, `: fmt::Debug`. So, instead of adding the bound for every type, we first check that the type contains one of the container's type variables. Since types can be nested, it is an unfortunately long recursive function handling the different types of types. This part of Rust syntax is probably not going to change, so perhaps it is feasible to shorten some of the branches into `_ => false`. One drawback of this implementation is that we iterate over the list of type variables every time we find a "leaf type". I chose `Vec` over `HashSet` because in my experience there are only a handful of type variables per container. Co-authored-by: Jelte Fennema-Nio <github-tech@jeltef.nl> Co-authored-by: Kai Ren <tyranron@gmail.com>
Resolves #363 Requires #377 Follows #371 ## Synopsis The problem is that the `Display` derive adds bounds for all types that are used in the format string. But this is not necessary for types that don't contain a type variable. And adding those bounds can result in errors like for the following code: ```rust #[derive(Display, Debug)] #[display("{inner:?}")] #[display(bounds(T: Display))] struct OptionalBox<T> { inner: Option<Box<T>>, } #[derive(Display, Debug)] #[display("{next}")] struct ItemStruct { next: OptionalBox<ItemStruct>, } ``` That code would generate the following error: ```text error[E0275]: overflow evaluating the requirement `ItemStruct: derive_more::Display` ``` ## Solution This makes sure we don't add unnecessary bounds for Display-like derives. It does so in the same way as #371 did for the Debug derive: By only adding bounds when the type contains a type variable. Co-authored-by: Kai Ren <tyranron@gmail.com>
the following code fails to compile
the same error is reported with stuff like
Vec<Message>
and with enum instead of struct.The text was updated successfully, but these errors were encountered: