rust icon indicating copy to clipboard operation
rust copied to clipboard

rustc should first suggest adding a trait bound on generic impl block when inherent method name bound by that trait collides with methods from unintended traits in scope

Open zjp-CN opened this issue 1 year ago • 1 comments

Code

pub struct Scroll<L>(L);

pub trait Bound {}

impl<L: Bound> Scroll<L> {
    // happens to be the same name with methods in ATraitSomewhere
    pub fn f(&self) -> usize {
        0
    }
}

impl<L> Scroll<L> { // Line12
    pub fn call(&self) {
        self.f(); // meant to call an inherent method
    }
}

// Say an accidental trait is brought into scope via asterisk wildcard
// import syntax or std's prelude or somthing, bad error occurs with
// the following code beacuse it doesn't give the obvious fix by
// patching `L: Bound` on Line12 at all.
#[allow(unused_imports)] use __::ATraitSomewhere;

mod __ {
    pub trait ATraitSomewhere {
        fn f(&self) {}
    }
    impl<T: B> ATraitSomewhere for &T {}
    pub trait B {}
}

Current output

error[E0599]: the method `f` exists for reference `&Scroll<L>`, but its trait bounds were not satisfied
  --> src/lib.rs:14:14
   |
1  | pub struct Scroll<L>(L);
   | -------------------- doesn't satisfy `Scroll<L>: B`
...
14 |         self.f(); // meant to call an inherent method
   |              ^ method cannot be called on `&Scroll<L>` due to unsatisfied trait bounds
   |
note: trait bound `L: Bound` was not satisfied
  --> src/lib.rs:5:9
   |
5  | impl<L: Bound> Scroll<L> {
   |         ^^^^^  ---------
   |         |
   |         unsatisfied trait bound introduced here
note: trait bound `Scroll<L>: B` was not satisfied
  --> src/lib.rs:29:13
   |
29 |     impl<T: B> ATraitSomewhere for &T {}
   |             ^  ---------------     --
   |             |
   |             unsatisfied trait bound introduced here
note: the trait `B` must be implemented
  --> src/lib.rs:30:5
   |
30 |     pub trait B {}
   |     ^^^^^^^^^^^

Desired output

error[E0599]: the method `f` exists for reference `&Scroll<L>`, but its trait bounds were not satisfied
  --> f100.rs:14:14
   |
1  | struct Scroll<L>(L);
   | ---------------- doesn't satisfy `Scroll<L>: B`
...
14 |         self.f(); // meant to call an inherent method
   |              ^ method cannot be called on `&Scroll<L>` due to unsatisfied trait bounds

   |
note: trait bound `L: Bound` was not satisfied
  --> f100.rs:5:9
   |
5  | impl<L: Bound> Scroll<L> {
   |         ^^^^^  ---------
   |         |
   |         unsatisfied trait bound introduced here
note: the method `f` can be called with trait bound `L: Bound` satisfied
  --> f100.rs:12:6
   |
12 |     impl<L> Scroll<L> { // Line12
   |          ^  ---------
   |          |
   |          = help: consider adding `: Bound`
note: trait bound `Scroll<L>: B` was not satisfied
  --> f100.rs:29:13
   |
29 |     impl<T: B> ATraitSomewhere for &T {}
   |             ^  ---------------     --
   |             |
   |             unsatisfied trait bound introduced here
note: the trait `B` must be implemented
  --> f100.rs:30:5
   |
30 |     pub trait B {}
   |     ^^^^^^^^^^^
   = help: items from traits can only be used if the trait is implemented and in scope
note: `ATraitSomewhere` defines an item `f`, perhaps you need to implement it
  --> f100.rs:26:5
   |
26 |     pub trait ATraitSomewhere {
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^

Rationale and extra context

The code closer to what I met is the following as I posted in URLO

struct Scroll<L>(L);
struct Line;

impl<L: AsRef<[Line]>> Scroll<L> {
    fn len(&self) -> usize { self.0.as_ref().len() }
}

impl<L: AsMut<[Line]>> Scroll<L> {
    fn f1(&mut self) { self.len(); }
}
// current error
error[E0599]: the method `len` exists for mutable reference `&mut Scroll<L>`, but its trait bounds were not satisfied
  --> src/lib.rs:9:29
   |
1  | struct Scroll<L>(L);
   | ---------------- doesn't satisfy `Scroll<L>: ExactSizeIterator`
...
9  |     fn f1(&mut self) { self.len(); }
   |                             ^^^ method cannot be called on `&mut Scroll<L>` due to unsatisfied trait bounds
   |
note: trait bound `L: AsRef<[Line]>` was not satisfied
  --> src/lib.rs:4:9
   |
4  | impl<L: AsRef<[Line]>> Scroll<L> {
   |         ^^^^^^^^^^^^^  ---------
   |         |
   |         unsatisfied trait bound introduced here
   = note: the following trait bounds were not satisfied:
           `Scroll<L>: ExactSizeIterator`
           which is required by `&mut Scroll<L>: ExactSizeIterator`
note: the trait `ExactSizeIterator` must be implemented
  --> /playground/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/iter/traits/exact_size.rs:86:1
   |
86 | pub trait ExactSizeIterator: Iterator {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Rustc doesn't report the obvious fix by add L: AsRef<[Line]> bound on the call site block, so it'd be great if we have it

note: trait bound `L: AsRef<[Line]>` was not satisfied
  --> src/lib.rs:4:9
   |
4  | impl<L: AsRef<[Line]>> Scroll<L> {
   |         ^^^^^^^^^^^^^  ---------
   |         |
   |         unsatisfied trait bound introduced here
+note: the method `len` can be called with trait bound `L: AsRef<[Line]>` satisfied
+8  | impl<L: AsMut<[Line]>> Scroll<L> {
+   |      ^
+   |      |
+   |      help: consider adding `L: AsRef<[Line]>`
+   |
note: the following trait bounds were not satisfied:
      `Scroll<L>: ExactSizeIterator`
      which is required by `&mut Scroll<L>: ExactSizeIterator`

Other cases

No response

Rust Version

rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-unknown-linux-gnu
release: 1.75.0
LLVM version: 17.0.6

Anything else?

Some similar issues but the lack of trait bounds happens on functions, not on generic impl blocks.

  • https://github.com/rust-lang/rust/issues/120186
  • https://github.com/rust-lang/rust/issues/108428
  • an ongoing PR to solve the two linked issues above: https://github.com/rust-lang/rust/pull/120507

So I think this issue would be a different case.

zjp-CN avatar Feb 02 '24 00:02 zjp-CN

The method name collision might not cause trait bound error though. Consider the recursive code:

pub struct Scroll<L>(L);

pub trait Bound {}

impl<L: Bound> Scroll<L> {
    // happens to be the same name with methods in ATraitSomewhere
    pub fn f(&self) -> usize {
        0
    }
}

pub trait Trait {
    fn method(&self);
}

impl<L> ATraitSomewhere for Scroll<L> {
    fn f(&self) {
        self.f(); // meant to call an inherent method
    }
}

use __::ATraitSomewhere;

mod __ {
    pub trait ATraitSomewhere {
        fn f(&self) {}
    }
    impl<T: B> ATraitSomewhere for &T {}
    pub trait B {}
}

it emits

warning: function cannot return without recursing
  --> src/lib.rs:17:5
   |
17 |     fn f(&self) {
   |     ^^^^^^^^^^^ cannot return without recursing
18 |         self.f(); // meant to call an inherent method
   |         -------- recursive call site
   |
   = help: a `loop` may express intention better if this is on purpose
   = note: `#[warn(unconditional_recursion)]` on by default

if we replace self.f() with qualified syntax Scroll::<L>::f(self); or <Scroll<L> as ATraitSomewhere>::f(self);, the same warning.

This indicates rustc eagerly treats self.f() as trait method instead of inherent method here? Maybe this will be a different issue too...


Note the inherent vs trait method intentionally returns distinct type here.

When you specify the return type from inherent method (playground), maybe rustc should suggest adding the L: Bound bound the same as this issue suggests?

error[E0308]: mismatched types
  --> src/lib.rs:18:24
   |
18 |         let _: usize = self.f(); // meant to call an inherent method
   |                -----   ^^^^^^^^ expected `usize`, found `()`
   |                |
   |                expected due to this
+note: the method `f` can be called with trait bound `L: Bound` satisfied
+16 | impl<L> ATraitSomewhere for Scroll<L> {
+   |      ^
+   |      |
+   |      help: consider adding `L: Bound`
+   |

zjp-CN avatar Feb 02 '24 01:02 zjp-CN

For the unconditional_recursion warning, here's an example that involves auto-(de)ref in method call from this post.

Simplified as follows: playground

trait Trait<V> {
    fn contains(&self, x: &V) -> bool;
}

#[cfg(fail0)] // comment out to see unconditional_recursion warning
impl<V> Trait<V> for Vec<V> {
    fn contains(&self, x: &V) -> bool {
        self.contains(x)
    }
}

#[cfg(fail1)] // comment out to see unconditional_recursion warning
impl<V: PartialEq> Trait<V> for Vec<V> {
    fn contains(&self, x: &V) -> bool {
        self.contains(x)
    }
}

#[cfg(fail2)] // comment out to see unconditional_recursion warning
impl<V: PartialEq> Trait<V> for Vec<V> {
    fn contains(&self, x: &V) -> bool {
        self.contains(x)
    }
}

#[cfg(ok)] // this is the right case
impl<V: PartialEq> Trait<V> for Vec<V> {
    fn contains(&self, x: &V) -> bool {
        // <[V]>::contains(self, x) // ok
        (**self).contains(x) // method call for slice instead of Vec or &Vec receiver
    }
}

So it's good to see the hint as follows

warning: function cannot return without recursing
 --> src/lib.rs:6:5
  |
6 |     fn contains(&self, x: &V) -> bool {
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ cannot return without recursing
7 |         self.contains(x)
  |         ---------------- recursive call site
  |
  = help: a `loop` may express intention better if this is on purpose
  = note: `#[warn(unconditional_recursion)]` on by default

+note: the method `contains` can be called with trait bound `V: PartialEq` satisfied on `<[V]>`
+ 5 | impl<V> Trait<T> for Vec<V> {
+   |      ^
+   |      |
+   |      help: consider adding `V: PartialEq`
+   |
+ 7 |      self.contains(x)
+   |      ^
+   |      |
+   |      help: `self.contains(x)` then should be `<[V]>::contains(self, x)`

zjp-CN avatar Mar 04 '24 07:03 zjp-CN