hecatia-elegua / bilge

Use bitsized types as if they were a feature of rust.
Apache License 2.0
171 stars 17 forks source link

Fix errors when a generic type comes up during bitsize macro processing #77

Closed kitlith closed 1 year ago

kitlith commented 1 year ago

Fixes #76, supersedes #64.

Adding angle brackets around a type forces it into type context, which lets us avoid having to thread things through syn's type hierarchy to try to get it to generate a turbofish. (This is kind of like a turbofish, but pointing the opposite direction.)

Trying to derive bitsize on structs that are themselves generic still fails, though I have another branch where I'm working on that: https://github.com/kitlith/bilge/tree/generic_derive

I figure I'll throw it up as a draft PR if/when this one is merged.

hecatia-elegua commented 1 year ago

Thank you :) I don't mind the test failing for now, but could clear it like this:

#[cfg(not(feature = "nightly"))]
mod generic {
    use super::*;
    //...
}

Also thanks for the generic derive exploration. Looking at it, I'm not sure how generics are supposed to work in bilge... field: T would mean it could be e.g. u8 or u16, so you can't specify the bitsize directly, in which case we would kinda need to have a way to specify a calculated bitsize and have to possibly change a lot more. We could add just the PhantomData part?

kitlith commented 1 year ago

I just need to impl const on nightly, shouldn't be too difficult to pull off.

with generic const exprs we could provide a way to elide the user provided size and just use the computed one everywhere, but for now, I was planning to just get structs similar to phantom data functional and allow for post monomorphization errors for size mismatches.

kitlith commented 1 year ago

For context: my example use case here is for a wrapper struct for (de)serializing the least significant bits of a fixed point number

use az::{WrappingAs, WrappingCast, WrappingCastFrom};
use bilge::prelude::*;
use fixed::traits::Fixed;
use std::marker::PhantomData;
use std::ops::BitAnd;

#[derive(Copy, Clone, Debug)]
pub struct Fix<Repr, N>(pub N, PhantomData<Repr>);

impl<Repr: Number, N> Bitsized for Fix<Repr, N> {
    type ArbitraryInt = Repr;
    const BITS: usize = Repr::BITS;
    const MAX: Self::ArbitraryInt = Self::ArbitraryInt::MAX;
}

// NOTE: i'm mostly working on stable. on nightly you'd need to impl const these
impl<Repr: Number, N: Fixed> From<Repr> for Fix<Repr, N>
where
    Repr::UnderlyingType: WrappingCast<N::Bits>,
{
    fn from(repr: Repr) -> Self {
        let shift: u32 = N::Bits::BITS - Repr::BITS as u32;
        let val: N::Bits = repr.value().wrapping_as();
        // sign extend
        let val = val << shift >> shift;
        Fix(N::from_bits(val), PhantomData)
    }
}

// TODO: should really also do impls for the primitives: u8 .. u128
// for now i'm using workarounds of UInt<u8, 8>, etc
impl<N: Fixed, T: Copy + Clone + BitAnd<Output = T>, const BITS: usize> From<Fix<UInt<T, BITS>, N>> for UInt<T, BITS>
where
    UInt<T, BITS>: Number<UnderlyingType = T>,
    N::Bits: WrappingCast<<UInt<T, BITS> as Number>::UnderlyingType>,
{
    fn from(val: Fix<UInt<T, BITS>, N>) -> UInt<T, BITS> {
        let raw_value: <UInt<T, BITS> as Number>::UnderlyingType = val.0.to_bits().wrapping_as();
        // it'll be nice to have signed arbitrary integers whenever that happens -- i'm not actually checking if it's in-range here.
        UInt::<T, BITS>::new(raw_value & UInt::<T, BITS>::MASK)
    }
}

i.e.: it's a single uX in disguise and with type conversion glue. Might be something to put in a derive macro at some point, but right now i'm okay with manual impls.