Skip to content
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

Avoid repeated trait bounds in derived impls #27134

Merged
merged 1 commit into from
Aug 3, 2015
Merged

Conversation

fhartwig
Copy link
Contributor

Fixes #25022

This adapts the deriving mechanism to not repeat bounds for the same type parameter. To give an example: for the following code:

#[derive(Clone)]
pub struct FlatMap<I, U: IntoIterator, F> {
    iter: I,
    f: F,
    frontiter: Option<U::IntoIter>,
    backiter: Option<U::IntoIter>,
}

the latest nightly generates the following impl signature:

impl <I: ::std::clone::Clone,
      U: ::std::clone::Clone + IntoIterator,
      F: ::std::clone::Clone>
::std::clone::Clone for FlatMap<I, U, F> where
    I: ::std::clone::Clone,
    F: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone

With these changes, the signature changes to this:

impl <I, U: IntoIterator, F> ::std::clone::Clone for FlatMap<I, U, F> where
    I: ::std::clone::Clone,
    F: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone

(Nothing in the body of the impl changes)
Note that the second impl is more permissive, as it doesn't have a Clone bound on U at all. There was a compile-fail test that failed due to this. I don't understand why we would want the old behaviour (and nobody on IRC could tell me either), so please tell me if there is a good reason that I missed.

@rust-highfive
Copy link
Collaborator

r? @huonw

(rust_highfive has picked a reviewer for you, use r? to override)


for field_ty in field_tys {
let tys = find_type_parameters(&*field_ty, &ty_param_names);

for ty in tys {
// if we have already handled this type, skip it
if let ast::TyPath(_, ref p) = ty.node {
if processed_types.contains(&p.segments) { continue }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be if !processed_types.insert(&p.segments) { continue }.

@huonw
Copy link
Member

huonw commented Jul 20, 2015

The old behaviour is because it was a very simple rule (#[derive] puts the trait as a bound on each param) and is somewhat more consistent (at least, can be interpreted as more consistent): structs like struct Foo<T>(Rc<T>); or struct Foo<T>(PhantomData<T>); don't need T: Clone, but #[derive(Clone)] will add it anyway, even though they're "unused".

I don't really know where to go... maybe we can preserve the old behaviour while still removing the double trait bounds? (E.g. check if type parameters are in the processed_types set and add bounds if they're not?)

@huonw
Copy link
Member

huonw commented Jul 20, 2015

(I'd r+ in a heartbeat if the patch just deduplicates, I'm unsure about the other changes... I want it, but it'd be nice to do it "properly", so that the examples I mention above are also handled but it's unclear how we can do this best.)

@fhartwig
Copy link
Contributor Author

Yeah, I can see the consistency argument. Fixing the bounds "properly" would be a much bigger change, and I'm not actually sure it can be done with derive operating at a purely syntactic level.
I'll change this to keep the old behaviour.
I've also considered not touching the deriving mechanism at all, and just cleaning up redundant bounds in rustdoc. Avoiding redundant bounds in the first place seemed like the cleaner way to handle this, but now I'm not sure anymore. What do you think?

@huonw
Copy link
Member

huonw commented Jul 20, 2015

I'm not actually sure it can be done with derive operating at a purely syntactic level.

It can sort-of be done by instead inserting where clauses for each field, e.g. the example you give would just have where F: Clone, I: Clone, Option<U::IntoIter>: Clone. However, this reveals implementation details in an undesirable way. Other than that, yes, it'd need to be more than syntactic.

What do you think?

It's something that probably makes sense to handle in rustdoc too but this is a more general problem: it could be much better at converting source code into a "canonical" representation.

@fhartwig
Copy link
Contributor Author

@huonw Ok, I have amended my changes to keep the bounds on all type parameters. The example signature now looks as follows:

impl <I: ::std::clone::Clone,
      U: ::std::clone::Clone + IntoIterator,
      F: ::std::clone::Clone>
    ::std::clone::Clone for FlatMap<I, U, F>
    where U::IntoIter: ::std::clone::Clone {

// if we have already handled this type, skip it
if let ast::TyPath(_, ref p) = ty.node {
if p.segments.len() == 1
&& ty_param_names.contains(&p.segments[0].identifier.name)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could just add these to the process_fields_types HashSet to begin with; processed_field_types.extend(ty_param_names.iter().cloned()).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so, unless I'm misunderstanding your comment. ty_param_names are ast::Names, while the elements of processed_field_types are Vec<ast::PathSegment>. It's not clear to me that we can somehow convert one into the other for comparison.

@huonw
Copy link
Member

huonw commented Aug 3, 2015

@bors r+

@bors
Copy link
Contributor

bors commented Aug 3, 2015

📌 Commit 93e5a74 has been approved by huonw

@bors
Copy link
Contributor

bors commented Aug 3, 2015

⌛ Testing commit 93e5a74 with merge cde83e1...

@bors
Copy link
Contributor

bors commented Aug 3, 2015

💔 Test failed - auto-mac-64-opt

@alexcrichton
Copy link
Member

@bors: retry

On Mon, Aug 3, 2015 at 10:35 AM, bors [email protected] wrote:

[image: 💔] Test failed - auto-mac-64-opt
http://buildbot.rust-lang.org/builders/auto-mac-64-opt/builds/5956


Reply to this email directly or view it on GitHub
#27134 (comment).

bors added a commit that referenced this pull request Aug 3, 2015
Fixes #25022

This adapts the deriving mechanism to not repeat bounds for the same type parameter. To give an example: for the following code:

```rust
#[derive(Clone)]
pub struct FlatMap<I, U: IntoIterator, F> {
    iter: I,
    f: F,
    frontiter: Option<U::IntoIter>,
    backiter: Option<U::IntoIter>,
}
```
the latest nightly generates the following impl signature:

```rust
impl <I: ::std::clone::Clone,
      U: ::std::clone::Clone + IntoIterator,
      F: ::std::clone::Clone>
::std::clone::Clone for FlatMap<I, U, F> where
    I: ::std::clone::Clone,
    F: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone
```

With these changes, the signature changes to this:
```rust
impl <I, U: IntoIterator, F> ::std::clone::Clone for FlatMap<I, U, F> where
    I: ::std::clone::Clone,
    F: ::std::clone::Clone,
    U::IntoIter: ::std::clone::Clone
```
(Nothing in the body of the impl changes)
Note that the second impl is more permissive, as it doesn't have a `Clone` bound on `U` at all. There was a compile-fail test that failed due to this. I don't understand why we would want the old behaviour (and nobody on IRC could tell me either), so please tell me if there is a good reason that I missed.
@bors
Copy link
Contributor

bors commented Aug 3, 2015

⌛ Testing commit 93e5a74 with merge d877e65...

@bors bors merged commit 93e5a74 into rust-lang:master Aug 3, 2015
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants