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.72.1, the following code does not compile:",
"```compile_fail",
concat!("use core::ops::", stringify!($Op), ";"),
"",
"struct A<T>(T);",
"",
concat!("impl<'a, T, O> ", stringify!($Op), " for &'a A<T>"),
"where",
concat!(" &'a T: ", stringify!($Op), "<Output = O>,"),
"{",
concat!(" type Output = A<O>;"),
"",
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();
}
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
);
}