rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
97.35k stars 12.58k forks source link

Very strange error when calling methods with multi-parameter bounds #126905

Open arximboldi opened 3 months ago

arximboldi commented 3 months ago

Code

use std::collections::HashSet;

pub struct AABB {}

pub trait Intersects<T, C> {
    fn intersects(&self, x: &T, ctx: &C) -> bool;
}

impl<C> Intersects<AABB, C> for AABB {
    fn intersects(&self, _x: &AABB, _ctx: &C) -> bool {
        true
    }
}

pub struct QuadTree<T> {
    pub x: Option<T>,
}

impl<T> QuadTree<T> {
    pub fn find_intersects<U, C>(&self, _search: &U, _ctx: &C) -> HashSet<T>
    where
        AABB: Intersects<U, C>,
        U: Intersects<T, C>,
    {
        // ...
        HashSet::new()
    }

    pub fn find_closest<C>(&self, _to: (f64, f64), ctx: &C) -> Option<T>
    where
        AABB: Intersects<T, C> 
              // UNCOMMENT THIS TO FIX
              // + Intersects<AABB, C>
        ,
    {
        let rect = AABB {};
        self.find_intersects(&rect, ctx).into_iter().next()
    }
}

fn main() {
    println!("Hello world");
}

Current output

error[E0277]: the trait bound `T: Intersects<T, C>` is not satisfied
  --> bug.rs:29:30
   |
29 |         self.find_intersects(&rect, ctx).into_iter().next()
   |              --------------- ^^^^^ the trait `Intersects<T, C>` is not implemented for `T`
   |              |
   |              required by a bound introduced by this call
   |
note: required by a bound in `QuadTree::<T>::find_intersects`
  --> bug.rs:17:12
   |
14 |     pub fn find_intersects<U, C>(&self, _search: &U, _ctx: &C) -> HashSet<T>
   |            --------------- required by a bound in this associated function
...
17 |         U: Intersects<T, C>,
   |            ^^^^^^^^^^^^^^^^ required by this bound in `QuadTree::<T>::find_intersects`
help: consider restricting type parameter `T`
   |
13 | impl<T: Intersects<T, C>> QuadTree<T> {
   |       ++++++++++++++++++

error[E0308]: mismatched types
  --> bug.rs:29:30
   |
13 | impl<T> QuadTree<T> {
   |      - expected this type parameter
...
29 |         self.find_intersects(&rect, ctx).into_iter().next()
   |              --------------- ^^^^^ expected `&T`, found `&AABB`
   |              |
   |              arguments to this method are incorrect
   |
   = note: expected reference `&T`
              found reference `&AABB`
note: method defined here
  --> bug.rs:14:12
   |
14 |     pub fn find_intersects<U, C>(&self, _search: &U, _ctx: &C) -> HashSet<T>
   |            ^^^^^^^^^^^^^^^              -----------

error: aborting due to 2 previous errors

Desired output

Not sure exactly. See rationale.

Rationale and extra context

  1. Why is this even an error? There is an implementation that satisfies the bound already visible, shouldn't the line that fixes the error be redundant? (See comment: UNCOMMENT THIS TO FIX)

  2. Even in that case, am I wrong to assume that the deduction that U == T does not make sense? Where is that coming from? Shouldn't the error tell you that you can't pass an AABB because Intersects<AABB, C> is not satisfied and therefore U can't be AABB, and suggest to make the correct change that fixes it straight away, instead of the very misleading suggestion which, in fact, does not fix the issue?

Other cases

No response

Rust Version

rustc 1.77.2 (25ef9e3d8 2024-04-09) (built from a source tarball)
binary: rustc
commit-hash: 25ef9e3d85d934b27d9dada2f9dd52b1dc63bb04
commit-date: 2024-04-09
host: x86_64-unknown-linux-gnu
release: 1.77.2
LLVM version: 17.0.6

Anything else?

Run rustc bug.rs to try.

theemathas commented 3 months ago

Minimized:

pub struct Dummy;
pub struct Thing;

pub trait SomeTrait<T> {}
impl SomeTrait<Thing> for Dummy {}

pub fn foo<T>(_x: T)
where
    Dummy: SomeTrait<T>,
{
}

pub fn what<T>()
where
    // Comment below to fix
    Dummy: SomeTrait<T>,
    // Uncomment below to fix
    // Dummy: SomeTrait<Thing>,
{
    foo(Thing);
    // Comment above and uncomment below to fix
    // foo::<Thing>(Thing);
}
Error output ```rust Compiling playground v0.0.1 (/playground) error[E0308]: mismatched types --> src/lib.rs:20:9 | 13 | pub fn what() | - expected this type parameter ... 20 | foo(Thing); | --- ^^^^^ expected type parameter `T`, found `Thing` | | | arguments to this function are incorrect | = note: expected type parameter `T` found struct `Thing` note: function defined here --> src/lib.rs:7:8 | 7 | pub fn foo(_x: T) | ^^^ ----- For more information about this error, try `rustc --explain E0308`. error: could not compile `playground` (lib) due to 1 previous error ```

It seems that the irrelevant Dummy: SomeTrait<T> is causing rust to incorrectly infer that the only possible foo call is foo::<T>(), missing the alternative possibility of foo::<Thing>().

Explicitly annotating the type of the call makes the code compile. (In the original code, this would be self.find_intersects::<AABB, C>(&rect, ctx).into_iter().next() )

Noratrieb commented 3 months ago

I think this happens because when proving the bounds of the function, for the Dummy: SomeTrait<?0> bound, it will solve it using the candidate from the where clause Dummy: SomeTrait<T>, which is then selected as where clause candidates take priority over the impl, causing the generic args to be unified, constraining ?0 (the generic arg inference variable) to T.

arximboldi commented 3 months ago

It seems that the irrelevant Dummy: SomeTrait<T> is causing rust to incorrectly infer that the only possible foo call is foo::<T>(), missing the alternative possibility of foo::<Thing>().

Note that in the original code that trait is not irrelevant. The usage was omitted to make the submission simpler. In any case thanks for the minimization!

I think @Nilstrieb is pointing in the right direction as to why it happens.