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

Add associated types support for #[derive(...)] #21237

Merged
merged 2 commits into from
Mar 26, 2015

Conversation

erickt
Copy link
Contributor

@erickt erickt commented Jan 16, 2015

This PR adds support for associated types to the #[derive(...)] syntax extension. In order to do this, it switches over to using where predicates to apply the type constraints. So now this:

type Trait {
    type Type;
}

#[derive(Clone)]
struct Foo<A> where A: Trait {
    a: A,
    b: <A as Trait>::Type,
}

Gets expended into this impl:

impl<A: Clone> Clone for Foo<A> where
    A: Trait,
    <A as Trait>::Type: Clone,
{
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}

@rust-highfive
Copy link
Collaborator

r? @alexcrichton

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

@erickt erickt force-pushed the derive-assoc-types branch from a861370 to 46a221e Compare January 16, 2015 15:14
@erickt
Copy link
Contributor Author

erickt commented Jan 16, 2015

Unfortunately this can't land quite yet. It turns out the visibility checker reports that this is exposing a private type:

struct Foo<T>(T);
impl<T> Clone for Foo<T> where T: Clone { ... }

pub struct Bar<T>(Foo<T>);
impl<T> Clone for Bar<T> where Foo<T>: Clone { ... }

@nikomatsakis said in #21203 (comment) that the privacy rules are being too strict here and might need to be relaxed.

@alexcrichton
Copy link
Member

r? @huonw - you're more familiar with #[derive] internals than I

@rust-highfive rust-highfive assigned huonw and unassigned alexcrichton Jan 16, 2015
@nikomatsakis
Copy link
Contributor

I'm not so sure about the privacy thing anymore.


impl<'a> visit::Visitor<'a> for Visitor<'a> {
fn visit_path(&mut self, path: &'a ast::Path, _id: ast::NodeId) {
match path.segments.first() {
Copy link
Member

Choose a reason for hiding this comment

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

I suspect this should check that path.segments has length 1, or maybe the shadowing rules means it doesn't matter? (i.e. is struct Foo<std> { x: std::option::Option<u8> } legal?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Drat, you're right, this currently compiles:

mod T {
    pub type Type = i32;
}

trait Trait {
    type Type;
}

struct Baz<T: Trait> {
    a: T::Type,
}

I filed #21301 to get that fixed. It'll probably not be that much of an issue in practice though, as you'd have to be crazy to write that :)

@huonw
Copy link
Member

huonw commented Feb 1, 2015

@erickt what's the status of this? Is

Unfortunately this can't land quite yet

still true?

@erickt
Copy link
Contributor Author

erickt commented Feb 3, 2015

@huonw: Ack! I was on a business trip this past week and forgot all about wrapping up this PR. I'll try to get it done this week :)

@erickt
Copy link
Contributor Author

erickt commented Feb 5, 2015

@huonw: Should work now, and it passes locally. re-r?

@erickt
Copy link
Contributor Author

erickt commented Feb 7, 2015

@huonw: updated the PR to also work with phantom types, closing #7671.

@huonw
Copy link
Member

huonw commented Feb 8, 2015

This approach is definitely an improvement, but it doesn't solve #7671 completely, e.g.

#[derive(Debug)] // T: Debug is not inserted
struct Foo<T>;

#[derive(Debug)] // T: Debug is inserted, but T is still phantom
struct Bar<T> {
    x: Foo<T>
}

Also, I think this may "break" the standard library marker types like InvariantType (and Phantom if/when it appears) which are possibly meant to represent that some type behaves like it stores a T, so the relevant traits should only be implemented when T has them implemented. The type parameters to these types are phantom types so #[derive] will no longer insert those bounds after this patch.

That said, I'm not sure that it is necessary to have these restricted implementions.

@goffrie
Copy link
Contributor

goffrie commented Feb 8, 2015

I suspect it's not worth trying to solve the general case of #7671. The privacy thing is a real kicker - if a public type Bar<T> contains a private type Foo<T>, then Bar essentially needs to expose the full rules for Foo<T> being Clone-able - so the derive mechanism would need to see what impls exist for Foo<T>, which might depend on even more private types... As a middle ground, maybe we could emit the where Foo<T>: Clone clause when Foo is public (or Bar is private), but use the approximation where T: Clone otherwise?

@erickt
Copy link
Contributor Author

erickt commented Feb 8, 2015

@goffrie: unfortunately in libsyntax at this point in time we can't see if a type is public or private. I believe we would need typeck in order to have that information.

@huon: good point. Maybe phantom types impls should written by hand.

@erickt
Copy link
Contributor Author

erickt commented Feb 9, 2015

After talking to @huonw, I feel like there isn't a great solution that works for phantom/marker types at the same time as working for an edge case of associated types. Say we extend @huonw's phantom type example from before, this time adding an associated type that's not using the trait type:

#[derive(Debug)]
struct Foo<T>;

#[derive(Debug)]
struct Bar<T>(Foo<T>);

trait Trait { type Type; }

#[derive(Debug)]
struct Baz<T: Trait>(T::Type);

We can either generate an impl that constrains all the type parameters, which is our current approach:

struct Foo<T>;
impl<T> Debug for Foo<T> where T: Debug { ... }

struct Bar<T>(Foo<T>);
impl<T> Debug for Bar<T> where T: Debug { ... }

trait Trait { type Type; }

struct Baz<T: Trait>(T::Type);
impl<T> Debug for Baz<T> where T: Debug, T::Type: Debug { ... }

Notice that the impl for Baz has an unnecessary T: Debug constraint. Or only the type parameters used in the field:

struct Foo<T>;
impl<T> Debug for Foo<T> { ... }

struct Bar<T>(Foo<T>);
impl<T> Debug for Bar<T> where T: Debug { ... }

trait Trait { type Type; }

struct Baz<T: Trait>(T::Type);
impl<T> Debug for Baz<T> where T::Type: Debug { ... }

Notice that for Bar there is an unnecessary T: Debug constraint because Foo<T> doesn't use T.

Does anyone have an opinion on which approach to use, or if there are some good heuristics here to decide what to do here?

None => {}
}
}
ast::TyQPath(_) => {
Copy link
Member

Choose a reason for hiding this comment

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

Hm, this will pick up non-generic QPath's too, right? e.g. struct Foo(<Bar as Iterator>::Item);.
(I guess these come up rarely enough that it probably doesn't matter?)

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'll add a pass through the QPath that checks if it references any of the type parameters. If it does, I'll add it to the types to be constrained. If not, i'll skip it. That should protect against this case.

@erickt erickt force-pushed the derive-assoc-types branch from 6311d3b to edb33f2 Compare March 14, 2015 07:43
@erickt
Copy link
Contributor Author

erickt commented Mar 14, 2015

Took a while, but I finally updated the patch to compile on head and address @goffrie's comment.

@@ -558,6 +652,21 @@ impl<'a> TraitDef<'a> {
enum_def: &EnumDef,
type_ident: Ident,
generics: &Generics) -> P<ast::Item> {
let mut field_tys = Vec::new();

for variant in enum_def.variants.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be written as

        let field_tys = enum_def.variants.flat_map(|variant| {
          match variant.node.kind {
            ast::VariantKind::TupleVariantKind(ref args) => {
              args.iter().map(|arg| arg.ty.clone())
            }
            ast::VariantKind::StructVariantKind(ref args) => {
              args.fields.iter().map(|field| field.node.ty.clone())
            }
          }
        }).collect();

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@tamird: Unfortunately it cannot because those inner iterators are actually two different types.

@erickt
Copy link
Contributor Author

erickt commented Mar 19, 2015

@bors: r+ edb33f2

@bors
Copy link
Contributor

bors commented Mar 19, 2015

⌛ Testing commit edb33f2 with merge e989552...

@bors
Copy link
Contributor

bors commented Mar 19, 2015

💔 Test failed - auto-mac-64-opt

@Manishearth
Copy link
Member



---- [compile-fail] compile-fail/derive-assoc-type-not-impl.rs stdout ----

error: compile-fail test compiled successfully!
status: exit code: 0
command: x86_64-apple-darwin/stage2/bin/rustc /Users/rustbuild/src/rust-buildbot/slave/auto-mac-64-opt/build/src/test/compile-fail/derive-assoc-type-not-impl.rs -L x86_64-apple-darwin/test/compile-fail/ --target=x86_64-apple-darwin -L x86_64-apple-darwin/test/compile-fail/derive-assoc-type-not-impl.stage2-x86_64-apple-darwinlibaux -C prefer-dynamic -o x86_64-apple-darwin/test/compile-fail/derive-assoc-type-not-impl.stage2-x86_64-apple-darwin --cfg rtopt --cfg debug -O -L x86_64-apple-darwin/rt
stdout:
------------------------------------------

------------------------------------------
stderr:
------------------------------------------

------------------------------------------

thread '[compile-fail] compile-fail/derive-assoc-type-not-impl.rs' panicked at 'explicit panic', /Users/rustbuild/src/rust-buildbot/slave/auto-mac-64-opt/build/src/compiletest/runtest.rs:1482



failures:
    [compile-fail] compile-fail/derive-assoc-type-not-impl.rs

@bors
Copy link
Contributor

bors commented Mar 24, 2015

⌛ Testing commit 7971f39 with merge 4c3f968...

@bors
Copy link
Contributor

bors commented Mar 24, 2015

💔 Test failed - auto-linux-64-x-android-t

@erickt erickt force-pushed the derive-assoc-types branch from 7971f39 to 9cabe27 Compare March 24, 2015 21:43
@erickt
Copy link
Contributor Author

erickt commented Mar 24, 2015

@bors: retry

@erickt
Copy link
Contributor Author

erickt commented Mar 25, 2015

@bors: r+ rollup

@bors
Copy link
Contributor

bors commented Mar 25, 2015

📌 Commit 9cabe27 has been approved by erickt

@bors
Copy link
Contributor

bors commented Mar 25, 2015

⌛ Testing commit 9cabe27 with merge 72d0695...

@bors
Copy link
Contributor

bors commented Mar 25, 2015

💔 Test failed - auto-win-32-opt

@erickt
Copy link
Contributor Author

erickt commented Mar 25, 2015

@bors: retry

Manishearth added a commit to Manishearth/rust that referenced this pull request Mar 26, 2015
This PR adds support for associated types to the `#[derive(...)]` syntax extension. In order to do this, it switches over to using where predicates to apply the type constraints. So now this:

```rust
type Trait {
    type Type;
}

#[derive(Clone)]
struct Foo<A> where A: Trait {
    a: A,
    b: <A as Trait>::Type,
}
```

Gets expended into this impl:

```rust
impl<A: Clone> Clone for Foo<A> where
    A: Trait,
    <A as Trait>::Type: Clone,
{
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}
```
erickt added a commit to erickt/rust that referenced this pull request Mar 26, 2015
This PR adds support for associated types to the `#[derive(...)]` syntax extension. In order to do this, it switches over to using where predicates to apply the type constraints. So now this:

```rust
type Trait {
    type Type;
}

#[derive(Clone)]
struct Foo<A> where A: Trait {
    a: A,
    b: <A as Trait>::Type,
}
```

Gets expended into this impl:

```rust
impl<A: Clone> Clone for Foo<A> where
    A: Trait,
    <A as Trait>::Type: Clone,
{
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}
```
erickt added a commit to erickt/rust that referenced this pull request Mar 26, 2015
This PR adds support for associated types to the `#[derive(...)]` syntax extension. In order to do this, it switches over to using where predicates to apply the type constraints. So now this:

```rust
type Trait {
    type Type;
}

#[derive(Clone)]
struct Foo<A> where A: Trait {
    a: A,
    b: <A as Trait>::Type,
}
```

Gets expended into this impl:

```rust
impl<A: Clone> Clone for Foo<A> where
    A: Trait,
    <A as Trait>::Type: Clone,
{
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}
```
@bors
Copy link
Contributor

bors commented Mar 26, 2015

⌛ Testing commit 9cabe27 with merge 557d434...

bors added a commit that referenced this pull request Mar 26, 2015
This PR adds support for associated types to the `#[derive(...)]` syntax extension. In order to do this, it switches over to using where predicates to apply the type constraints. So now this:

```rust
type Trait {
    type Type;
}

#[derive(Clone)]
struct Foo<A> where A: Trait {
    a: A,
    b: <A as Trait>::Type,
}
```

Gets expended into this impl:

```rust
impl<A: Clone> Clone for Foo<A> where
    A: Trait,
    <A as Trait>::Type: Clone,
{
    fn clone(&self) -> Foo<T> {
        Foo {
            a: self.a.clone(),
            b: self.b.clone(),
        }
    }
}
```
@bors bors merged commit 9cabe27 into rust-lang:master Mar 26, 2015
michaelkirk added a commit to michaelkirk/rust-geo that referenced this pull request Apr 1, 2020
derive applies the necessary trait bounds to derived impl.

see rust-lang/rust#21237 for more details
bors bot added a commit to georust/geo that referenced this pull request Apr 2, 2020
435: derive Eq for other core geo types r=frewsxcv a=michaelkirk

Adds `Eq` conformance to other core geo types, building off of #431

> Seems reasonable to me! Yeah +1 to adding the same impl for all the other core geo types

Rather than spelling out the impl, I've taken advantage of how `derive` already bounds the generated impl for us. See rust-lang/rust#21237 for more details

I wrote a few doc-tests while developing, but I reverted them in fc33f9f since they're pretty verbose, and seem superfluous since they're only asserting rust language semantics rather than anything substantial in this lib.

I can un-revert them if you'd prefer. 

Co-authored-by: Michael Kirk <[email protected]>
@jendrikw
Copy link
Contributor

I think this is badly documented. A few sentences at https://doc.rust-lang.org/std/clone/trait.Clone.html#derivable would be nice.

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.

10 participants