JRRudy1 / transient

Rust crate providing a reimplementation of the `std::any::Any` trait that supports types with non-`'static` lifetimes.
Other
35 stars 1 forks source link

Derive macro rework #4

Closed JRRudy1 closed 3 months ago

JRRudy1 commented 3 months ago

This PR includes another significant rework of the derive macro, building on those from PR #3 and the v0.3.0 tag.

Excluding any implementation/cosmetic changes, the list of changes are summarized below (only the first of which impacts the public API):

Variance declaration attribute syntax (public API)

The "variance" helper attribute has been replaced by separate "covariant" and "contravariant" attributes, which are used a bit differently:

Before:

#[derive(Transient)]
#[variance('a = co)]
struct Co<'a> {
    val: &'a str,
}
#[derive(Transient)]
#[variance('a = co, 'b = co)]
struct CoCo<'a, 'b> {
    val: &'a &'b str,
}
#[derive(Transient)]
#[variance('a = contra, 'b = co)]
struct ContraCo<'a, 'b> {
    val: fn(&'a str) -> &'b str,
}

After:

#[derive(Transient)]
#[covariant(a)]
struct Co1<'a> {
    val: &'a str,
}
#[derive(Transient)]
#[covariant]  // can exclude argument
struct Co2<'a> {
    val: &'a str,
}
#[derive(Transient)]
#[covariant(a, b)]
struct CoCo1<'a, 'b> {
    val: &'a &'b str,
}
#[derive(Transient)]
#[covariant(a)]
#[covariant(b)]
struct CoCo2<'a, 'b> {
    val: &'a &'b str,
}
#[derive(Transient)]
#[covariant] // excluding argument applies it to all
struct CoCo3<'a, 'b> {
    val: &'a &'b str,
}
#[derive(Transient)]
#[contravariant(a)]
#[covariant(b)]  // mixed variance requires separate attributes
struct ContraCo<'a, 'b> {
    val: fn(&'a str) -> &'b str,
}

The new syntax follows a standard MetaListIdent format and renders more nicely in some editors than the custom "'a = co" syntax. The new syntax also makes it easier to declare multiple lifetime parameters with the same variance, at the cost of being slightly harder to declare mixed variances since they need separate attributes.

The processing of these attributes is pretty robust to various edge cases, as discussed in the updated docstring.

Generated Static/Transient impls

The generated impls have been slightly tweaked; the 'static type bounds are now pushed to the where clause instead of the generics list, and it is better at avoiding duplicate 'static bounds.

Before:

#[derive(Transient)]
struct S<'a, T1, T2, T3: 'static>(&'a T) where T2: 'static; // only T1 needs a new bound

unsafe impl<'a, T1: 'static, T2: 'static, T3: 'static + 'static> Transient for S<'a, T1, T2, T3>
where
    T2: 'static
{
    type Static = S<'static, T1, T2, T3>;
    type Transience = Inv<'a>;
}

After:

#[derive(Transient)]
struct S<'a, T1, T2, T3: 'static>(&'a T) where T2: 'static; // only T1 needs a new bound

unsafe impl<'a, T1, T2, T3: 'static> Transient for S<'a, T1, T2, T3>
where
    T1: 'static
    T2: 'static
{
    type Static = S<'static, T1, T2, T3>;
    type Transience = Inv<'a>;
}

Generated variance validation (implementation detail)

The macro now generates a module for the struct that contains a check function for each declared variance, instead of using a nested function. I also renamed to the test lifetime depending on the variance requested to make the error message slightly more helpful, but it still is not great.

Before:

#[derive(Transient)]
#[variance('a = contra, 'b = co)]
struct ContraCo<'a, 'b> {
    val: fn(&'a str) -> &'b str,
}
#[allow(unused)]
#[allow(non_snake_case)]
fn __validate_ContraCo() {
    #[allow(unused)]
    #[allow(non_snake_case)]
    fn __validate_ContraCo_a<'__test_lifetime, 'a: '__test_lifetime, 'b>(
        v: ContraCo<'__test_lifetime, 'b>,
    ) -> ContraCo<'a, 'b> {
        v
    }
    #[allow(unused)]
    #[allow(non_snake_case)]
    fn __validate_ContraCo_b<'__test_lifetime: 'b, 'a, 'b>(
        v: ContraCo<'a, '__test_lifetime>,
    ) -> ContraCo<'a, 'b> {
        v
    }
}

After:

#[derive(Transient)]
#[contravariant(a)]
#[covariant(b)]
struct ContraCo<'a, 'b> {
    val: fn(&'a str) -> &'b str,
}
mod __validate_ContraCo {
    #![allow(non_snake_case, dead_code)]
    fn contravariant_wrt_a<'__short, 'a: '__short, 'b>(
        v: ContraCo<'__short, 'b>,
    ) -> ContraCo<'a, 'b> {
        v
    }
    fn covariant_wrt_b<'__long: 'b, 'a, 'b>(
        v: ContraCo<'a, '__long>,
    ) -> ContraCo<'a, 'b> {
        v
    }
}
JRRudy1 commented 3 months ago

@the10thWiz let me know if you have any feedback, thanks again!

the10thWiz commented 3 months ago

@JRRudy1 sorry I was a bit busy with work. This looks awesome!