rust-lang/rust

Method resolution fails to find inherent method on custom Deref type

Closed this issue · 9 comments

This cannot find the poll method on Pin:

use std::ops::Deref;

pub struct Pin<P>(P);

impl<P, T> Deref for Pin<P> where
    P: Deref<Target = T>,
{
    type Target = T;
    fn deref(&self) -> &T {
        &*self.0
    }
}

impl<'a, F> Pin<&'a mut F> {
    fn poll(self) {}
}

fn test(pin: Pin<&mut ()>) {
    pin.poll()
}

https://play.rust-lang.org/?gist=6e3b3f6f3aa4e8a49cb45480f4dd7e7a&version=stable&mode=debug&edition=2015

This clearly should compile.

This bug may be a blocker on changing the Pin API to a composeable form.

cc @nikomatsakis @eddyb @arielb1 @cramertj

It's specifically the transitive dereferencing that causes the bug, if the deref impl is changed to this it compiles fine:

impl<P> Deref for Pin<P> where {
    type Target = P;
    fn deref(&self) -> &P {
        &self.0
    }
}

I managed to reduce it a bit more:

use std::{
    ops::Deref,
};

pub struct Pin<P>(P);

impl<P, T> Deref for Pin<P>
where
    P: Deref<Target=T>,
{
    type Target = T;

    fn deref(&self) -> &T {
        &*self.0
    }
}

impl<P> Pin<P> {
    fn poll(self) {}
}

fn main() {
    let mut unit = ();
    let pin = Pin(&mut unit);
    pin.poll();
}

The error goes away if you remove the Deref impl for Pin altogether. What happens is the final type produced by autoderef is an inference variable (https://github.com/rust-lang/rust/blob/master/src/librustc_typeck/check/method/probe.rs#L349), causing an error to be raised when structurally_resolved_type is called, because that type could have another method named poll that might conflict. Without the Deref impl for Pin, the error goes away, because the final type for autoderef is just Pin<&mut ()>.

Of course, the final type of autoderef in this case should actually be <Pin<&mut ()> as Deref>::Target, which is (). The fact that it is an inference variable seems like a bug.

My last comment is only for the "type annotations needed" error. I thought the "no method named poll found for type Pin<&mut ()>" error was also being caused by that, but it looks like it might be separate. Investigating...

Never mind, looks like the ambiguity error is triggering the "method not found" error. The reason is the return None here (https://github.com/rust-lang/rust/blob/master/src/librustc_typeck/check/method/probe.rs#L351), which makes the caller think that no methods by that name were found. If we remove that early return it should avoid the double error.

The problem is that autoderef is not eagerly executing nested obligations. The original reason for that is that we can't use the "outer" fulfillment context, while creating a nested fulfillment context felt like it would cause problems (or maybe it did cause a problem back then?).

I'm not sure there's a good reason not to create a fulfillment context and drain it there, only passing the remaining obligations downwards. cc @nikomatsakis

Now that we have canonoicalization, I'm think a good idea would be to do the autoderef chain in a "canonicalization context", and only apply a deref when we are considering it.

I've assigned @nikomatsakis and @eddyb with the hopes that we can make some progress here :-)

Note that this version does compile:

use std::ops::Deref;

pub struct Pin<P>(P);

impl<P> Deref for Pin<P> where
    P: Deref,
{
    type Target = P::Target;
    fn deref(&self) -> &P::Target {
        &*self.0
    }
}

impl<'a, F> Pin<&'a mut F> {
    fn poll(self) {}
}

fn test(pin: Pin<&mut ()>) {
    pin.poll()
}

The only difference is that I am using P::Target and not T

Sounds from @cramertj that this should be enough to unblock the pin API work -- thanks @nikomatsakis!