Associated type bounds only solved correctly if extracted to separate trait

I ran into some strange behavior with the trait solver while trying to implement codegen for my Rust library. I have the following trait and struct definitions in my library:

trait HasKind {
    type Kind;
}

trait HasView<V> {
    type View;
}

pub trait HasLength {}

struct Node;

struct Signal;

impl HasView<Node> for Signal {
    type View = Node;
}

struct Input;

impl HasKind for Input {
    type Kind = Signal;
}

The input to my codegen is something like this:

#[derive(Interface)]
struct Interface
{
    a: Input
}

I then generate a struct like this and attempt to implement the HasLength trait on it if all of its fields implement HasLength:

#[automatically_derived]
struct InterfaceView<V>
where
    Input: HasKind<Kind: HasView<V>>,
{
    a: <<Input as HasKind>::Kind as HasView<V>>::View,
}

// DOESN'T COMPILE
impl<V> HasLength for InterfaceView<V> where Input: HasKind<Kind: HasView<V, View: HasKind>> {}

This doesn't compile with the following error:

error[E0277]: the trait bound `Signal: HasView<V>` is not satisfied
  --> src/lib.rs:39:1
   |
39 | impl<V> HasLength for InterfaceView<V> where Input: HasKind<Kind: HasView<V, View: HasLength>> {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `HasView<V>` is not implemented for `Signal`
   |
help: consider extending the `where` clause, but there might be an alternative better way to express this requirement
   |
39 | impl<V> HasLength for InterfaceView<V> where Input: HasKind<Kind: HasView<V, View: HasLength>>, Signal: HasView<V> {}

Strangely, I can get it to compile just by removing the HasLength bound, even though it has nothing to do with HasView<V> being implemented on Signal:

// DOES COMPILE
impl<V> HasLength for InterfaceView<V> where Input: HasKind<Kind: HasView<V>> {}

To actually get the HasLength bound to be asserted (which is needed for my codegen), I have to create a new trait which has the exact same trait bounds:

// DOES COMPILE
trait HasViewWithKind<V>: HasKind<Kind: HasView<V, View = <Self as HasViewWithKind<V>>::View>> {
    type View: HasLength;
}

impl<V> HasLength for InterfaceView<V> where Input: HasViewWithKind<V> {}

This achieves what I want, but I am curious why my original attempt does not compile. It seems like the trait solver is unable to understand the implications of the trait bounds on associated types. Is this a issue with the compiler, or is this expected?

You are saying it as if these two are incompatible things.

Looks like famous #20671 which means it's expected (even if not desirable).

I can't explain why, but it looks like the trait solver normalizes Input::Kind incorrectly or in some way that doesn't unify with the HasView<V> bound or something. (Maybe related but that's a guess. The behavior goes back as far as I bothered to check. It works with the new (unstable) trait solver.)

I thought it might be due to the relatively new associated type bound syntax, but this doesn't work either:

where
    Input: HasKind,
    <Input as HasKind>::Kind: HasView<V>,
    <<Input as HasKind>::Kind as HasView<V>>::View: HasLength,

This works though.

impl<V, Kind, View> HasLength for InterfaceView<V>
where
    Input: HasKind<Kind = Kind>,
    Kind: HasView<V, View = View>,
    View: HasLength,
{}

Incidentally, in your playground, there is no V that satisfies the bounds (because Node does not implement HasLength). But that doesn't seem to be a factor in the compiler error.


They did include the Input: HasKind<Kind: HasView<V>> bound, so weren't trying to rely on non-existent elaboration.

1 Like

Unfortunately, I can't use the last option you provided because this code needs to be generated by a proc macro. I think my workaround works for now, but it's great that the new trait solver fixes it :).