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 test for comparing impl method type with trait bounds #12611

Closed
wants to merge 3 commits into from

Conversation

fhahn
Copy link
Contributor

@fhahn fhahn commented Feb 27, 2014

closes #2687

This patch checks if all type bounds of an argument in an implementation of a trait can be found in the trait definition.

I'm not entirely sure about the wording of the error message though.

@alexcrichton
Copy link
Member

I'm not sure this is quite exhaustive enough just yet, it looks like it would still allow code of the form:

trait A {
    fn foo<T: Eq + Ord>(&self);
}

impl A for int {
    fn foo<T: Ord + Ord>(&self) {}
}


struct Bar;
impl Foo for Bar {
fn test_fn<T:ParamTrait>(&self, _: T) {} //~ ERROR: in method
Copy link
Member

Choose a reason for hiding this comment

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

Can you test the error message a little more exhaustively than just "in method" ?

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've added a couple of more tests and better error message checks.

@flaper87
Copy link
Contributor

I think this case is missing too:

trait A {}

trait B: A {}

trait C {
    fn test<T: A>(&self) {}
}

impl C for  int {
    fn test<T: B>(&self) {}
}

fn main() { }

In the example above the bounds of the method test in the trait C are stronger than the bounds of its implementation. This should be fine, but not the other way around, since A is implied by B.

(unless I'm missing something)

EDIT: I meant to say:

trait A {}

trait B: A {}

trait C {
    fn test<T: B>(&self) {}
}

impl C for  int {
    fn test<T: A>(&self) {}
}

fn main() { }

@fhahn
Copy link
Contributor Author

fhahn commented Feb 28, 2014

I have a question concerning the following trait:

trait A {
    fn foo<T: Eq + Ord>(&self);
}

Are the following two implementations considered equivalent?

impl A for int {
    fn foo<T: Eq + Ord>(&self) {}
}
impl A for int {
    fn foo<T: Ord + Eq>(&self) {}
}

My PR at the moment assumes that the order must be equal as well (the second case would be invalid), but this behavior could be changed easily.

@flaper87
Copy link
Contributor

@fhahn bounds are commutative

@@ -874,6 +872,33 @@ fn compare_impl_method(tcx: ty::ctxt,
trait_param_def.bounds.trait_bounds.len()));
return;
}
for (i, impl_bound) in impl_param_def.bounds.trait_bounds.iter().enumerate() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this for loop could be simplified by using ty::each_bound_trait_and_supertraits. I'm sorry, I don't have much time to come up with a working example but I'd recommend you to take a look there, it iterates over the bounds and supertraits.

@alexcrichton
Copy link
Member

I'm not entirely convinced that this is the right algorithm, and travis seems to agree with me. I'm not sure where supetraits come in to this verification, but I may just not be understanding what's going on.

@fhahn
Copy link
Contributor Author

fhahn commented Mar 2, 2014

@flaper87 I'm not sure if your example from above should be valid.

trait A {
    fn foo();
}

trait B: A {
     fn bar();
}

trait C {
    fn test<T: A>(&self, x: T);
}

impl C for  int {
    fn test<T: B>(&self, x: T) {
         x.bar();
    }
}

As far as I understand, trait bounds should work the following way, but I could easily miss something.
The trait C says the function test can be called with an parameter which implements A. The implementation of C for int breaks this contract, because there the function test cannot be called with a trait just implementing A. Shouldn't arguments in subtypes of traits be contravariant, e.g. the call fn test<T: A>(&self, x: T) should be valid for every implementation of that trait. With contravariant arguments, the following would be possible:

trait A {
    fn foo();
}

trait B: A {
     fn bar();
}

trait C {
    fn test<T: B>(&self, x: T);
}

impl C for  int {
    fn test<T: A>(&self, x: T) {
         x.foo();
    }
}

This implementation of trait C does not break it's contract, because the function test for int could be called with an argument with type B, because the type in the implementation is more general.

@flaper87
Copy link
Contributor

flaper87 commented Mar 3, 2014

@fhahn I'm sorry, it looks like I mistakenly inverted the bounds of the trait and its implementation. I meant to say what you wrote in your last comment. In other words, the idea is to make the bounds on the trait stronger than the bounds on the implementation. See this comment

@flaper87
Copy link
Contributor

flaper87 commented Mar 3, 2014

Also, I'd like @nikomatsakis to double check this PR.

@nikomatsakis
Copy link
Contributor

ok.

@fhahn
Copy link
Contributor Author

fhahn commented Mar 3, 2014

I've update the patch. It now accepts contravariant argument bounds in implementations. I guess this should be documented as well? I've added a simple run-pass testcase with contravariant arguments.

The patch in it's current form requires the bounds to be unique, so the following definition would be problematic:

trait C {
    fn test<T: Eq + Eq>(&self);
}

impl C for  int {
    fn test<T: Eq + Eq >(&self) {}
}

I'm not sure if a bound definition with non-unique traits should be valid. If they are valid, should fn test<T: Eq + Eq >(&self); be equal to fn test<T: Eq >(&self);?

@flaper87
Copy link
Contributor

flaper87 commented Mar 3, 2014

@fhahn we should probably raise a warning for non-unique bounds and drop 1 of them. I don't think that should be valid.

@flaper87
Copy link
Contributor

flaper87 commented Mar 6, 2014

This needs to be rebased. @nikomatsakis any comments on this PR ?

@nikomatsakis
Copy link
Contributor

Sorry, I plan to review, but it will take me some time. This week is very busy.

@fhahn
Copy link
Contributor Author

fhahn commented Mar 7, 2014

I've rebased the PR. It raises errors for the following two cases:

  • a type parameter was specified in the trait definition but not in the implementation
  • a type parameter was specified in the implementation but not in the trait

Type parameters in the implementation can be supertypes of the type parameters in the trait.

The other commit raises a warning for duplicated type parameters and drops the duplicated parameter. One thing that isn't warned about at the moment are duplicated subtypes and supertypes.

Should I document this behaviour?

@@ -1010,11 +1012,23 @@ pub fn ty_generics(ccx: &CrateCtxt,
builtin_bounds: ty::EmptyBuiltinBounds(),
trait_bounds: ~[]
};

let mut added_bounds = HashMap::new();
Copy link
Contributor

Choose a reason for hiding this comment

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

What about using a MutableSet here?

Copy link
Member

Choose a reason for hiding this comment

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

MutableSet is a trait??

You probably mean HashSet.

Copy link
Contributor

Choose a reason for hiding this comment

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

erm yeah, sorry! I meant HashSet (I'm falling asleep right now)

Copy link
Contributor

Choose a reason for hiding this comment

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

Just to make sure there's no confusion. I meant HashSet

Update: Which is implemented using a map anyway, it just removes the thinking of map here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I've changed HashMap to HashSet

@alexcrichton
Copy link
Member

@nikomatsakis re-r? (just to make sure)

@flaper87
Copy link
Contributor

Seems like travis has some legitimate failures.

@fhahn
Copy link
Contributor Author

fhahn commented Mar 19, 2014

I've tried to use infer::mk_sub_trait_refs as @nikomatsakis suggested, because his example did not throw an error with the previous check.

As far as I understand, infer::mk_sub_trait_refs should return result::Ok() if the trait bound is a subtype of the implementation bound (= the implementation bound is a super type of the trait bound), the relevant code is here: fhahn@a1bedb7#diff-31c6a728eb925b9f6b0124e93948d270R875 .

But I guess I'm using infer::mk_sub_trait_refs wrong?

@nikomatsakis
Copy link
Contributor

Sorry, I'm very slow getting to reviews right now. This is on my list of "reviews to do", however.

for impl_bound in impl_param_def.bounds.trait_bounds.iter() {
let mut specified = false;
for trait_bound in trait_param_def.bounds.trait_bounds.iter() {
if !enforced_bounds.contains(&trait_bound.def_id) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This still looks wrong. I don't think we can key on def-id. I think you just need to remove this set and do something simpler. I imagine two nested loops (here I wrote it using filter/any). Basically you want to know: Are there any impl bounds which are not implied by some trait bound? If so, that's an error.

let failed_bounds =
    impl_bounds.filter(|ib| trait_bounds.any(|tb| implies(infcx, tb, ib)));
fn implies(infcx, trait_bound, impl_bound) {
    is impl_bound a sub trait ref of trait_bound?
}

Note that this doesn't take the full generality into account that we could. e.g., If the trait has Ord and the impl has Eq, that should be ok. We should be able to do that too; it's a matter of expanding out the "trait_bounds" vector to account for super bounds (or possibly changing mk_sub_trait_refs, I've been debating that in my head just now). But I think we can leave that aside for now, it'd really only be important for #5236 which is not on the table presently.

@nikomatsakis
Copy link
Contributor

ok, I apologize for the delay, I left some comments, ping me when updated, and find me on IRC if you have any questions! Thanks very much for working on this.

@alexcrichton
Copy link
Member

Closing due to inactivity, but feel free to reopen with a rebase!

pcwalton added a commit to pcwalton/rust that referenced this pull request Jul 2, 2014
with the corresponding trait parameter bounds.

This is a version of the patch in PR rust-lang#12611 by Florian Hahn, modified to
address Niko's feedback.

It does not address the issue of duplicate type parameter bounds, nor
does it address the issue of implementation-defined methods that contain
*fewer* bounds than the trait, because Niko's review indicates that this
should not be necessary (and indeed I believe it is not). A test has
been added to ensure that this works.

This will break code like:

    trait Foo {
        fn bar<T:Baz>();
    }

    impl Foo for Boo {
        fn bar<T:Baz + Quux>() { ... }
        //             ^~~~ ERROR
    }

This will be rejected because the implementation requires *more* bounds
than the trait. It can be fixed by either adding the missing bound to
the trait:

    trait Foo {
        fn bar<T:Baz + Quux>();
        //             ^~~~
    }

    impl Foo for Boo {
        fn bar<T:Baz + Quux>() { ... }  // OK
    }

Or by removing the bound from the impl:

    trait Foo {
        fn bar<T:Baz>();
    }

    impl Foo for Boo {
        fn bar<T:Baz>() { ... }  // OK
        //       ^ remove Quux
    }

This patch imports the relevant tests from rust-lang#2687, as well as the test
case in rust-lang#5886, which is fixed as well by this patch.

Closes rust-lang#2687.
Closes rust-lang#5886.

[breaking-change]
bors added a commit that referenced this pull request Jul 3, 2014
…felix

with the corresponding trait parameter bounds.

This is a version of the patch in PR #12611 by Florian Hahn, modified to
address Niko's feedback.

It does not address the issue of duplicate type parameter bounds, nor
does it address the issue of implementation-defined methods that contain
*fewer* bounds than the trait, because Niko's review indicates that this
should not be necessary (and indeed I believe it is not). A test has
been added to ensure that this works.

This will break code like:

    trait Foo {
        fn bar<T:Baz>();
    }

    impl Foo for Boo {
        fn bar<T:Baz + Quux>() { ... }
        //             ^~~~ ERROR
    }

This will be rejected because the implementation requires *more* bounds
than the trait. It can be fixed by either adding the missing bound to
the trait:

    trait Foo {
        fn bar<T:Baz + Quux>();
        //             ^~~~
    }

    impl Foo for Boo {
        fn bar<T:Baz + Quux>() { ... }  // OK
    }

Or by removing the bound from the impl:

    trait Foo {
        fn bar<T:Baz>();
    }

    impl Foo for Boo {
        fn bar<T:Baz>() { ... }  // OK
        //       ^ remove Quux
    }

This patch imports the relevant tests from #2687, as well as the test
case in #5886, which is fixed as well by this patch.

Closes #2687.
Closes #5886.

[breaking-change]

r? @pnkfelix
@fhahn fhahn deleted the issue-2687-cmp-bounds branch July 7, 2014 17:02
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.

comparing bounds on impl method type parameters with bounds declared on trait is completely bogus
5 participants