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
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/librustc/middle/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ pub enum Lint {
DeadCode,
VisiblePrivateTypes,
UnnecessaryTypecast,
DuplicatedTypeBound,

MissingDoc,
UnreachableCode,
Expand Down Expand Up @@ -304,6 +305,13 @@ static lint_table: &'static [(&'static str, LintSpec)] = &[
default: allow,
}),

("duplicated_type_bound",
LintSpec {
lint: DuplicatedTypeBound,
desc: "detects duplicated type bound",
default: warn
}),

("unused_mut",
LintSpec {
lint: UnusedMut,
Expand Down
70 changes: 53 additions & 17 deletions src/librustc/middle/typeck/check/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,15 +112,16 @@ use middle::lang_items::TypeIdLangItem;
use util::common::{block_query, indenter, loop_query};
use util::ppaux;
use util::ppaux::{UserString, Repr};
use util::nodemap::{FnvHashMap, NodeMap};
use util::nodemap::{NodeMap, FnvHashMap};
use collections::{HashMap, HashSet};

use std::cell::{Cell, RefCell};
use collections::HashMap;
use std::mem::replace;
use std::result;
use std::vec;
use std::vec_ng::Vec;
use std::vec_ng;
use std::str::StrVector;
use syntax::abi::AbiSet;
use syntax::ast::{Provided, Required};
use syntax::ast;
Expand Down Expand Up @@ -864,25 +865,60 @@ fn compare_impl_method(tcx: ty::ctxt,
return;
}

// FIXME(#2687)---we should be checking that the bounds of the
// trait imply the bounds of the subtype, but it appears we
// are...not checking this.
if impl_param_def.bounds.trait_bounds.len() !=
trait_param_def.bounds.trait_bounds.len()
{
// check that the bounds of the trait imply the bounds of the implementation type
let mut enforced_bounds = HashSet::new();
let mut unspecified_bounds = ~[];
for impl_bound in impl_param_def.bounds.trait_bounds.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Another problem is that we need to be using the substituted form of the trait bounds. If you look below, you'll see we create a substitution from the trait type / region parameters to those of the impl -- you need to apply this substitution to the bounds before doing mk_sub_trait_refs

let mut specified = false;
for trait_bound in trait_param_def.bounds.trait_bounds.iter() {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think that keying on def-id is sufficient.

Imagine a test like this:

trait Getter<T> { }

trait Trait {
    fn method<G:Getter<int>>();
}

impl Trait for uint {
    fn method<G: Getter<uint>>();
    //~^ ERROR requires Getter<uint> but Trait provides Getter<int>
}

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 the routine we need is to invoke mk_sub_trait_refs or some variation thereof. We'll need to apply the substitution of "trait params -> impl params" so as to bring the two sets of bounds into the same namespace. Ping me on IRC if this is too curt of an explanation.

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.

match infer::mk_sub_trait_refs(&infcx,
false,
infer::RelateTraitRefs(impl_m_span),
*impl_bound,
*trait_bound) {
result::Ok(()) => {
enforced_bounds.insert(trait_bound.def_id);
specified = true;
} // Ok.
result::Err(_) => {}
}
}
}
if !specified {
unspecified_bounds.push(format!("`{}`", impl_bound.repr(tcx)));
}
}

let unenforced_bounds: ~[~str] =
trait_param_def.bounds.trait_bounds.iter().filter_map(|&trait_bound| {
if !enforced_bounds.contains(&trait_bound.def_id) {
Some(format!("`{}`", trait_bound.repr(tcx)))
} else {
None
}
}).collect();

if unenforced_bounds.len() > 0 {
tcx.sess.span_err(
impl_m_span,
format!("in method `{method}`, \
{nbounds, plural, =1{trait bound} other{trait bounds}} \
{bounds} not enforced by this implementation",
method = token::get_ident(trait_m.ident),
nbounds = unenforced_bounds.len(),
bounds = unenforced_bounds.connect(", ")));
}
if unspecified_bounds.len() > 0 {
tcx.sess.span_err(
impl_m_span,
format!("in method `{method}`, \
type parameter {typaram} has \
{nimpl, plural, =1{# trait bound} other{# trait bounds}}, \
but the corresponding type parameter in \
the trait declaration has \
{ntrait, plural, =1{# trait bound} other{# trait bounds}}",
implementation {nbounds, plural, =1{bound} other{bounds}} \
{bounds} {nbounds, plural, =1{was} other{were}} \
not specified in trait definition",
method = token::get_ident(trait_m.ident),
typaram = i,
nimpl = impl_param_def.bounds.trait_bounds.len(),
ntrait = trait_param_def.bounds.trait_bounds.len()));
return;
nbounds = unspecified_bounds.len(),
bounds = unspecified_bounds.connect(", ")));
}
}

Expand Down
15 changes: 15 additions & 0 deletions src/librustc/middle/typeck/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ are represented as `ty_param()` instances.

use metadata::csearch;
use middle::resolve_lifetime;
use middle::lint::DuplicatedTypeBound;
use middle::ty::{ImplContainer, MethodContainer, TraitContainer, substs};
use middle::ty::{ty_param_bounds_and_ty};
use middle::ty;
Expand All @@ -45,6 +46,8 @@ use middle::typeck::{CrateCtxt, lookup_def_tcx, no_params, write_ty_to_tcx};
use util::ppaux;
use util::ppaux::Repr;

use collections::HashSet;

use std::rc::Rc;
use std::vec_ng::Vec;
use std::vec_ng;
Expand Down Expand Up @@ -1029,11 +1032,23 @@ pub fn ty_generics(ccx: &CrateCtxt,
builtin_bounds: ty::EmptyBuiltinBounds(),
trait_bounds: Vec::new()
};

let mut added_bounds = HashSet::new();
for ast_bound in ast_bounds.iter() {
match *ast_bound {
TraitTyParamBound(ref b) => {
let ty = ty::mk_param(ccx.tcx, param_ty.idx, param_ty.def_id);
let trait_ref = instantiate_trait_ref(ccx, b, ty);
if !added_bounds.insert(trait_ref.def_id) {
ccx.tcx.sess.add_lint(
DuplicatedTypeBound,
0,
b.path.span,
format!("duplicated bound `{}`, ignoring it",
trait_ref.repr(ccx.tcx)));
continue;
}

if !ty::try_add_builtin_trait(
ccx.tcx, trait_ref.def_id,
&mut param_bounds.builtin_bounds)
Expand Down
36 changes: 36 additions & 0 deletions src/test/compile-fail/typeck-duplicated-trait-bounds.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// Tests contravariant type parameters in implementations of traits, see #2687

#[deny(duplicated_type_bound)];

trait A {}

trait Foo {
fn test_duplicated_builtin_bounds_fn<T: Eq+Ord+Eq>(&self);
//~^ ERROR duplicated bound `std::cmp::Eq`, ignoring it

fn test_duplicated_user_bounds_fn<T: A+A>(&self);
//~^ ERROR duplicated bound `A`, ignoring it
}

impl Foo for int {
fn test_duplicated_builtin_bounds_fn<T: Eq+Ord+Eq+Eq>(&self) {}
//~^ ERROR duplicated bound `std::cmp::Eq`, ignoring it
//~^^ ERROR duplicated bound `std::cmp::Eq`, ignoring it

fn test_duplicated_user_bounds_fn<T: A+A+A+A>(&self) {}
//~^ ERROR duplicated bound `A`, ignoring it
//~^^ ERROR duplicated bound `A`, ignoring it
//~^^^ ERROR duplicated bound `A`, ignoring it
}

fn main() {}
81 changes: 81 additions & 0 deletions src/test/compile-fail/typeck-trait-bounds-impl-comparison.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// Make sure rustc checks the type parameter bounds in implementations of traits,
// see #2687

trait A {}

trait B: A {}

trait C: A {}

trait Foo {
fn test_error1_fn<T: Eq>(&self);
fn test_error2_fn<T: Eq + Ord>(&self);
fn test_error3_fn<T: Eq + Ord>(&self);
fn test3_fn<T: Eq + Ord>(&self);
fn test4_fn<T: Eq + Ord>(&self);
fn test_error5_fn<T: A>(&self);
fn test_error6_fn<T: A + Eq>(&self);
fn test_error7_fn<T: A>(&self);
fn test_error8_fn<T: B>(&self);
}

impl Foo for int {
// invalid bound for T, was defined as Eq in trait
fn test_error1_fn<T: Ord>(&self) {}
//~^ ERROR bound `std::cmp::Eq` not enforced by this implementation
//~^^ ERROR implementation bound `std::cmp::Ord` was not specified in trait definition

// invalid bound for T, was defined as Eq + Ord in trait
fn test_error2_fn<T: Eq + B>(&self) {}
//~^ ERROR bound `std::cmp::Ord` not enforced by this implementation
//~^^ ERROR implementation bound `B` was not specified in trait definition

// invalid bound for T, was defined as Eq + Ord in trait
fn test_error3_fn<T: B + Eq>(&self) {}
//~^ ERROR bound `std::cmp::Ord` not enforced by this implementation
//~^^ ERROR implementation bound `B` was not specified in trait definition

// multiple bounds, same order as in trait
fn test3_fn<T: Ord + Eq>(&self) {}

// multiple bounds, different order as in trait
fn test4_fn<T: Eq + Ord>(&self) {}

// parameters in impls must be equal or more general than in the defining trait
fn test_error5_fn<T: B>(&self) {}
//~^ ERROR bound `A` not enforced by this implementation
//~^^ ERROR implementation bound `B` was not specified in trait definition

fn test_error6_fn<T: A>(&self) {}
//~^ ERROR bound `std::cmp::Eq` not enforced by this implementation

fn test_error7_fn<T: A + Eq>(&self) {}
//~^ ERROR implementation bound `std::cmp::Eq` was not specified in trait definition

fn test_error8_fn<T: C>(&self) {}
//~^ ERROR implementation bound `C` was not specified in trait definition
//~^^ ERROR bound `B` not enforced by this implementation
}


trait Getter<T> { }

trait Trait {
fn method<G:Getter<int>>();
}

impl Trait for uint {
fn method<G: Getter<uint>>() {}
//~^ ERROR requires Getter<uint> but Trait provides Getter<int>
}
fn main() {}
23 changes: 23 additions & 0 deletions src/test/compile-fail/typeck-trait-bounds-impl-comparison2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// Make sure rustc checks the type parameter bounds in implementations of traits,
// see #2687
trait Getter<T> { }

trait Trait {
fn method<G:Getter<int>>();
}

impl Trait for uint {
fn method<G: Getter<uint>>() {}
//~^ ERROR requires Getter<uint> but Trait provides Getter<int>
}
fn main() {}
63 changes: 63 additions & 0 deletions src/test/run-pass/typeck-contravariant-trait-bounds-impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
// Copyright 2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// Tests contravariant type parameters in implementations of traits, see #2687

trait A {
fn a(&self) -> int;
}

impl A for u8 {
fn a(&self) -> int {
8
}
}

impl A for u32 {
fn a(&self) -> int {
32
}
}

trait B: A {
fn b(&self) -> int;
}

impl B for u32 {
fn b(&self) -> int {
-1
}
}

trait Foo {
fn test_fn<T: B>(&self, x: T) -> int;
fn test_duplicated_bounds1_fn<T: B+B>(&self) -> int;
fn test_duplicated_bounds2_fn<T: B>(&self) -> int;
}

impl Foo for int {
fn test_fn<T: A>(&self, x: T) -> int {
x.a()
}

fn test_duplicated_bounds1_fn<T: B+B>(&self) -> int {
99
}

fn test_duplicated_bounds2_fn<T: B+B>(&self) -> int {
199
}
}

fn main() {
let x: int = 0;
assert!(8 == x.test_fn(5u8));
assert!(32 == x.test_fn(5u32));
}