Bizarre trait resolution issue with `typenum`

I am attempting to write a trait that represents types which are infallibly (and injectively) constructable from a buffer of bytes of a statically known size (types needn't be plain-old-data, so bytemuck::Pod/zerocopy::FromBytes are too restrictive, but Deserialize is too weak). Ideally this would be defined with const generics, but I can't use associated constants in array lengths, so I am using the hybrid_array and typenum crates for this. The current trait looks like:

// i don't think this can be a crate versioning issue
// since I'm using the version of  typenum re-exported by hybrid_array
use hybrid_array::{typenum, Array, ArraySize};
trait FromByteRepr {
    type Size: ArraySize;
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self;
}

This mostly seems to work fine, however when speccing out a derive macro for this trait, I'm running into some strange issues on structs that contain both generic and concrete types (solely generic or solely concrete seems to work fine). As an example:

// dummy implementation
impl FromByteRepr for i32 {
    type Size = typenum::U4;
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self {
        i32::from_le_bytes(bytes.0)
    }
}

struct Mixed<T> {
    generic: T,
    concrete: i32,
}

use std::ops::Add;
impl<T> FromByteRepr for Mixed<T>
where 
    T: FromByteRepr,
    T::Size: Add<<i32 as FromByteRepr>::Size>,
    // the exact details of the ArraySize trait aren't important
    // _any_ trait bound here causes the error 
    <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize, 
{
    // just a placeholder to make the example minimal
    // in reality this would be typenum::Sum<T::Size, i32::Size>
    type Size = typenum::U1; 
    fn from_bytes(bytes: &Array<u8, Self::Size>) -> Self {
        todo!()
    }
}

This causes the following error:

error[E0277]: cannot add `UInt<UInt<UInt<UTerm, B1>, B0>, B0>` to `<T as foo::FromByteRepr>::Size`
  --> src/lib.rs:79:5
   |
79 | /     impl<T> FromByteRepr for Mixed<T>
80 | |     where
81 | |         T: FromByteRepr,
82 | |         T::Size: Add<<i32 as FromByteRepr>::Size>,
83 | |         <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize,
   | |_________________________________________________________________________^ no implementation for `<T as foo::FromByteRepr>::Size + UInt<UInt<UInt<UTerm, B1>, B0>, B0>`
   |
   = help: the trait `Add<UInt<UInt<UInt<UTerm, B1>, B0>, B0>>` is not implemented for `<T as foo::FromByteRepr>::Size`
help: consider further restricting the associated type
   |
83 |         <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize, <T as foo::FromByteRepr>::Size: Add<UInt<UInt<UInt<UTerm, B1>, B0>, B0>>
   |                                                                           ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

It seems to be telling me that I need to ensure that T::Size implements Add<typenum::U4> but thats what the constraint on line 82 says. Rust even knows that <i32 as FromByteRepr>::Size> == typenum::U4 because if I add the silly constraint i32: FromByteRepr<Size = typenum::U4> (and remove the offending constraint on line 83), compilation is successful.

Bizarrely, seemingly more complicated implementations that involve many generic parameters seem to work fine, but this one does not. Am I doing something obviously wrong, or is this a limitation of the compiler/typenum?

It's some sort of compiler limitation where the associated type doesn't normalize or some-such. There are a number of similar open issues, but I didn't find one that matches this exactly. (But I'm pretty sure I've seen it before.)

E.g. this may fix it (untested):

impl<T> FromByteRepr for Mixed<T>
where 
    T: FromByteRepr,
-   T::Size: Add<<i32 as FromByteRepr>::Size>,
+   T::Size: Add<typenum::U8>,
    // the exact details of the ArraySize trait aren't important
    // _any_ trait bound here causes the error 
    <T::Size as Add<<i32 as FromByteRepr>::Size>>::Output: ArraySize, 
{

Or perhaps

impl<T, Size, I32Size> FromByteRepr for Mixed<T>
where 
    i32: FromByteRepr<Size = I32Size>,
    T: FromByteRepr<Size = Size>,
    Size: Add<I32Size, Output: ArraySize>,

(which avoids having to "manually normalize" <i32 as FromByteRepr>::Size.)

Thanks for the reply! Yes hardcoding the actual value of <i32 as FromByteRepr>::Size works, but the purpose of this example was to spec out a derive macro for this trait, so i32 is just meant to represent some arbitrary concrete type (which the macro would not know the actual instantiation of).

I will try your second example, thanks. Is this a known compiler bug? Is there somewhere I should report it?

These issues may be relevant:

This seems to be exactly the same issue, thanks! I tried to search for similar issues, but getting the exact keywords right was tough.

Looks like the solution is “wait until the next trait solver lands” alas. I’ll try enabling it on nightly and see if that works.

Update: the manual normalization works, enabling the next trait solver on nightly does not seem to currently, so I'll mark the former as resolving my issue.