1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//! When working with iterators/futures or other hign-generic types sometimes it's useful to assert
//! that type of some expression implements some traits or even cast smt to `impl Trait`.
//!
//! This crate provides macros for both goals — [`assert_bound`] and [`as_opaque`].
//!
//! [`assert_bound`]: ./macro.assert_bound.html
//! [`as_opaque`]: ./macro.as_opaque.html
#![no_std]

/// Assert that expression implements trait(s) at compile-time.
///
/// ## Examples
/// ```
/// use assert_bound::assert_bound;
/// use std::fmt::Debug;
///
/// assert_bound!(() => Debug + Ord + PartialEq<()>);
/// ```
///
/// ```
/// # use assert_bound::assert_bound;
/// trait T {}
/// impl T for () {}
///
/// assert_bound!(() => T);
/// ```
/// **Note**: expression is **not** executed:
/// ```
/// # use assert_bound::assert_bound;
/// let mut var = 0;
/// assert_bound!({ var = 1; } => Eq);
/// assert_eq!(var, 0);
/// ```
/// However you can execute it by calling result of the macro:
/// ```
/// # use assert_bound::assert_bound;
/// let mut var = 0;
/// assert_bound!({ var = 1; } => Eq)();
/// assert_eq!(var, 1);
/// ```
/// ```compile_fail
/// # use assert_bound::assert_bound;
/// // f32/f64 doesn't implement `Eq`,
/// // so rustc will fail to compile that.
/// assert_bound!(0.1; Eq);
/// ```
/// ## Expand
/// Following code:
/// ```
/// # use assert_bound::assert_bound;
/// assert_bound!({ println!("Hewwo?") } => Ord + PartialEq<()>);
/// ```
/// expands to something like this:
/// ```
/// || {
///     fn assert_bound<T>(_: &T)
///     where
///         T: Ord,
///         T: PartialEq<()>,
///     {}
///
///     let expr = { println!("Hewwo?") };
///     assert_bound(&expr);
///     expr
/// };
/// ```
#[macro_export]
macro_rules! assert_bound {
    (
        $e:expr =>
        // One trait bound, e.g. `std::fmt::Debug`, `PartialEq<()>`
        $head:ident $( :: $tail:ident )* $( < $param:ty $(, $p:ty)* > )?
        // Zero or more trait bounds splited by `+`
        $(+ $head2:ident $( :: $tail2:ident )* $( < $param2:ty $(, $p2:ty)* > )?)*
    ) => {
        // Lambda is needed for discarding $e (lambda is returned from macro so
        // it's possible to call it in order to not discard)
        || {
            /// Assert that `T` implements traits these were given to macro
            #[inline(always)]
            fn assert_bound<__EXPR_TYPE>(_: &__EXPR_TYPE)
            where
                __EXPR_TYPE: $head $( :: $tail )* $( < $param $(, $p)* > )?,
                $( __EXPR_TYPE: $head2 $( :: $tail2 )* $( < $param2 $(, $p2)* > )? , )*
            {}

            let expr = $e;
            // Assert that $e implement traits
            assert_bound(&$e);
            // Return $e from lambda
            expr
        }
    };
}

/// Cast type to opaque type that implements trait(s) (`impl Trait + Trait2`)
///
/// ```
/// # use assert_bound::as_opaque;
///
/// println!("{:?}", as_opaque!(() => std::fmt::Debug)); // `as_opaque!` return `impl Debug`
/// ```
///
/// ```
/// # use assert_bound::as_opaque;
/// let t = as_opaque!(() => PartialEq<()> + PartialOrd<()>);
///
/// assert!(t == ());
/// assert_eq!(t.partial_cmp(&()), Some(std::cmp::Ordering::Equal));
/// ```
///
/// ```compile_fail
/// # use assert_bound::{as_opaque, assert_bound};
/// assert_bound!(&(), Eq); // OK
/// assert_bound!(&as_opaque!((); Ord), Ord); // OK
/// assert_bound!(&as_opaque!((); Ord), Eq); // Error
/// ```
///
/// ## Expand
/// Following code:
/// ```
/// # use assert_bound::as_opaque;
/// as_opaque!(() => PartialEq<()> + PartialOrd<()>);
/// ```
/// expands to something like this:
/// ```
/// fn as_opaque<T>(expr: T) -> impl PartialEq<()> + PartialOrd<()> + 'static
/// where
///     T: PartialEq<()>,
///     T: PartialOrd<()>,
///     T: 'static,
/// {
///     expr
/// }
///
/// let expr = ();
/// as_opaque(expr);
/// ```
#[macro_export]
macro_rules! as_opaque {
    (
        $e:expr =>
        // One trait bound, e.g. `std::fmt::Debug`, `PartialEq<()>`
        $head:ident $( :: $tail:ident )* $( < $param:ty $(, $p:ty)* > )?
        // Zero or more trait bounds splited by `+`
        $(+ $head2:ident $( :: $tail2:ident )* $( < $param2:ty $(, $p2:ty)* > )?)*
        // lifetime
        ; $lifetime:tt
    ) => {
            {
                /// Cast type to anonymous type that implements trait(s) these were given to macro
                #[inline(always)]
                fn as_opaque<'lifetime, __EXPR_TYPE>(expr: __EXPR_TYPE)
                    -> impl $head $( :: $tail )* $( < $param $(, $p)* > )?
                    $(+ $head2 $( :: $tail2 )* $( < $param2 $(, $p2)* > )? )*
                    + 'lifetime
                where
                    __EXPR_TYPE: $head $( :: $tail )* $( < $param $(, $p)* > )?,
                    $( __EXPR_TYPE: $head2 $( :: $tail2 )* $( < $param2 $(, $p2)* > )? , )*
                    __EXPR_TYPE: 'lifetime,
                {
                    expr
                }

                let expr = $e;
                let opaque = as_opaque::<$lifetime, _>(expr);
                opaque
            }
    };

    // Variant without lifetime (use default 'static)
    (
        $e:expr =>
        // One trait bound, e.g. `std::fmt::Debug`, `PartialEq<()>`
        $head:ident $( :: $tail:ident )* $( < $param:ty $(, $p:ty)* > )?
        // Zero or more trait bounds splited by `+`
        $(+ $head2:ident $( :: $tail2:ident )* $( < $param2:ty $(, $p2:ty)* > )?)*
    ) => {
        $crate::as_opaque!(
            $e =>
            $head $( :: $tail )* $( < $param $(, $p)* > )?
            $(+ $head2 $( :: $tail2 )* $( < $param2 $(, $p2)* > )?)*
            ; 'static
        )
    };
}