ref-ops 0.2.5

An escape hatch for implementing `ops` traits for references to newtypes.
Documentation
use core::ops::{Neg, Not};

macro_rules! doc {
    ($( $x:expr, )* @$item:item) => {
        $( #[doc = $x] )*
        $item
    };
}

macro_rules! def_unary {
    ($Op:ident, $op:ident, $RefOp:ident, $ref_op:ident) => {
        mod $op {
            pub trait Sealed {}
        }

        doc!(
            concat!("`", stringify!($op), "` operation through references."),
            "",
            "As of Rust 1.73.0, the following code does not compile:",
            "```compile_fail",
            concat!("use core::ops::", stringify!($Op), ";"),
            "",
            "struct A<T>(T);",
            "",
            concat!("impl<'a, T> ", stringify!($Op), " for &'a A<T>"),
            "where",
            concat!("    &'a T: ", stringify!($Op), ","),
            "{",
            concat!("    type Output = A<<&'a T as ", stringify!($Op), ">::Output>;"),
            "",
            concat!("    fn ", stringify!($op), "(self) -> Self::Output {"),
            concat!("        A(self.0.", stringify!($op), "())"),
            "    }",
            "}",
            "",
            "fn _f<T>(a: T)",
            "where",
            concat!("    for<'a> &'a T: ", stringify!($Op), ","),
            "{",
            concat!("    let _op_a = (&a).", stringify!($op), "();"),
            "",
            concat!("    // to do something with `a` and `_op_a`"),
            "}",
            "",
            "fn _g<T>(a: T)",
            "where",
            concat!("    for<'a> &'a T: ", stringify!($Op), ","),
            "{",
            "    _f(a);",
            "}",
            "```",
            "but the following code does:",
            "```",
            concat!("use core::ops::", stringify!($Op), ";"),
            concat!("use ref_ops::", stringify!($RefOp),";"),
            "",
            "struct A<T>(T);",
            "",            "",
            concat!("impl<T> ", stringify!($Op), " for &A<T>"),
            "where",
            concat!("    T: ", stringify!($RefOp), ","),
            "{",
            "    type Output = A<T::Output>;",
            "",
            concat!("    fn ", stringify!($op), "(self) -> Self::Output {"),
            concat!("        A(self.0.", stringify!($ref_op), "())"),
            "    }",
            "}",
            "",
            "fn _f<T>(a: T)",
            "where",
            concat!("    for<'a> &'a T: ", stringify!($Op), ","),
            "{",
            concat!("    let _op_a = (&a).", stringify!($op), "();"),
            "",
            concat!("    // to do something with `a` and `_op_a`"),
            "}",
            "",
            "fn _g<T>(a: T)",
            "where",
            concat!("    for<'a> &'a T: ", stringify!($Op), ","),
            "{",
            "    _f(a);",
            "}",
            "```",
            @pub trait $RefOp: $op::Sealed {
                doc!(
                    concat!("The resulting type after applying `", stringify!($op), "` operation."),
                    @type Output;
                );

                doc!(
                    concat!("Performs `", stringify!($op), "` operation."),
                    @fn $ref_op(&self) -> Self::Output;
                );
            }
        );

        impl<T, O> $op::Sealed for T
        where
            T: ?Sized,
            for<'a> &'a T: $Op<Output = O>,
        {
        }

        impl<T, O> $RefOp for T
        where
            T: ?Sized,
            for<'a> &'a T: $Op<Output = O>,
        {
            type Output = O;

            fn $ref_op(&self) -> O {
                self.$op()
            }
        }
    };
}

def_unary!(Neg, neg, RefNeg, ref_neg);
def_unary!(Not, not, RefNot, ref_not);

#[cfg(test)]
mod tests {
    use super::*;

    macro_rules! test_unary {
        ($fn:ident, $Op:ident, $op:ident, $RefOp:ident, $ref_op:ident, $assert:expr, $dummy:literal) => {
            #[test]
            fn $fn() {
                #[derive(PartialEq)]
                struct A<T: ?Sized>(T);

                impl<T> $Op for &A<T>
                where
                    T: ?Sized + $RefOp,
                {
                    type Output = A<T::Output>;

                    fn $op(self) -> Self::Output {
                        A(self.0.$ref_op())
                    }
                }

                fn f<T>(a: T)
                where
                    for<'a> &'a T: $Op,
                {
                    let _op_a = (&a).$op();

                    // to do something with `a` and `_op_a`
                }

                fn g<T>(a: T)
                where
                    for<'a> &'a T: $Op,
                {
                    f(a);
                }

                g($dummy);

                assert!($assert);
            }
        };
    }

    test_unary!(
        test_neg,
        Neg,
        neg,
        RefNeg,
        ref_neg,
        -&A(1.0) == A(-1.0),
        1.0
    );
    test_unary!(
        test_not,
        Not,
        not,
        RefNot,
        ref_not,
        !&A(true) == A(false),
        true
    );
}