ct_utils/
ct_if.rs

1//! This module defines a types to encode an IF test within the type system.
2//!
3//! The idea behind this concept is to match the condition, which is a type,
4//! with the structure the test is implemented for.
5//! This allows for a basic test similar to the following
6//! ```rust
7//! let result = std::any::TypeId::of::<i32>() == std::any::TypeId::of::<i32>();
8//! if result {
9//!     // Condition matched
10//! } else {
11//!     // Condition failed to match
12//! }
13//! ```
14//!
15//! # Example
16//! ```rust,no_run
17//! # extern crate ct_utils;
18//! use ct_utils::prelude::*;
19//!
20//! // Trait to negate the signed-ness of a specific integer.
21//! trait DeSignature<Target> {
22//!     type Result: ?Sized;
23//! }
24//!
25//! impl<Target> DeSignature<Target> for i32 {
26//!     // Depending on Target, CTIf::Path will become signed or unsigned.
27//!     // Result will receive the type contained by CTIf::Path.
28//!     type Result = <IfCheck<i32> as CTIf<Target, u32, i32>>::Path;
29//! }
30//! ```
31//!
32//! # Notes
33//! -   If the receiving type parameter is constrained, the generic [`CTIf`] causes
34//!     compilation errors due to missing constraints.
35//!     You have to create your own specialized implementation of [`CTIf`], which is
36//!     made easy by utilizing the macro [`ctif_specialize`].
37//!
38
39use std::marker::PhantomData;
40
41/// Represents a boolean value encoded within the typesystem.
42///
43/// See also [`CTFalse`] and [`CTTrue`].
44pub trait CTBool {
45    /// Returns a boolean value corresponding to the implemented type.
46    fn to_bool() -> bool;
47}
48
49/// Generic version of an IF test encoded within the type system.
50///
51/// Condition   is the type which must match.
52/// OptionTrue  will be passed into [`CTIf::Path`] if the condition match is TRUE.
53/// OptionFalse will be passed into [`CTIf::Path`] if the condition match is FALSE.
54///
55/// Specialized versions of this trait are necessary when the receiving type parameter
56/// is constrained. See [`ctif_specialize`] to create such a specialized version.
57pub trait CTIf<Condition, OptionTrue, OptionFalse>
58where
59    OptionTrue: 'static + ?Sized,
60    OptionFalse: 'static + ?Sized,
61{
62    /// Type representing how the condition on the implemented type matches.
63    type Result: CTBool;
64    /// Type which holds either of the two generic option arguments, depending on how the
65    /// condition matches.
66    type Path: 'static + ?Sized;
67}
68
69/// Type representing a FALSE value.
70pub enum CTFalse {}
71/// Type representing a TRUE value.
72pub enum CTTrue {}
73
74/// Actual type used to perform an IF check through the type system.
75///
76/// Performing an IF test through the type system requires both this structure as well
77/// as the [`CTIf`] trait (or a specialized one). The typed that needs to be matched
78/// is passed into this struct as generic argument for X.
79/// The resulting type must then be cast as [`CTIf`] trait, which includes the desired type.
80/// [`CTIf::Path`] will then hold the match relevant type.
81/// eg
82/// ```rust,ignore
83/// <IfCheck<Subject> as CTIf<Desired, TypeTrue, TypeFalse>>::Path;
84/// ```
85pub struct IfCheck<X> {
86    _marker: PhantomData<X>,
87}
88
89impl CTBool for CTFalse {
90    fn to_bool() -> bool {
91        false
92    }
93}
94
95impl CTBool for CTTrue {
96    fn to_bool() -> bool {
97        true
98    }
99}
100
101impl<X, CondFail, OptionTrue, OptionFalse> CTIf<CondFail, OptionTrue, OptionFalse> for IfCheck<X>
102where
103    OptionTrue: 'static + ?Sized,
104    OptionFalse: 'static + ?Sized,
105{
106    default type Result = CTFalse;
107    default type Path = OptionFalse;
108}
109
110impl<X, OptionTrue, OptionFalse> CTIf<X, OptionTrue, OptionFalse> for IfCheck<X>
111where
112    OptionTrue: 'static + ?Sized,
113    OptionFalse: 'static + ?Sized,
114{
115    type Result = CTTrue;
116    type Path = OptionTrue;
117}
118
119/// Macro for specializing [`CTIf`].
120///
121/// If receiving associated types are constrained, the generic [`CTIf`] is not usable any more
122/// because there is no syntax carrying over these constraints accross the IF test.
123/// Solving this requires building a new trait similar to [`CTIf`] with constraints which are
124/// specific to your use case. These constraints will be applied to [`CTIf::Path`].
125///
126/// This macro helps in building that specialization. It creates a specialized trait and implements
127/// it for [`IfCheck`], the latter can be re-used accross specializations.
128/// Implementers only need to provide a new name for the trait and the required constraints.
129/// Provided outer attributes are applied to the newly created trait as well.
130///
131/// Using this specialized trait is analogue to using [`CTIf`] and [`IfCheck`].
132///
133/// # Example
134/// ```rust,ignore
135/// ctif_specialize {
136///     /// Optional doc-attribute explaining things about the special IF test.
137///     trait_name = "MySpecialIf",
138///     conditions = [Add<u32>, Mul<u32>]
139/// }
140///  ```
141///
142/// That macro call will expand to the following code.
143/// ```rust, ignore
144/// /// Optional doc-attribute explaining things about the special IF test.
145/// pub trait MySpecialIf<Condition, OptionTrue, OptionFalse> {
146///     type Result: CTBool;
147///     type Path: Add<u32> + Mul<u32>;
148/// }
149/// ```
150#[macro_export]
151macro_rules! ctif_specialize {
152    (
153        $( #[$meta_links:meta] )*
154        trait_name = $trait:ident,
155        conditions = [$($cond:path),+]
156    ) => {
157        $( #[$meta_links] )*
158    	pub trait $trait<Condition, OptionTrue, OptionFalse> {
159            /// Auto generated
160    		type Result: $crate::ct_if::CTBool;
161    		/// Auto generated
162            type Path: $( $cond +)+;
163    	}
164
165    	impl<X, CondFail, OptionTrue, OptionFalse> $trait<CondFail, OptionTrue, OptionFalse> for $crate::ct_if::IfCheck<X>
166    	where
167    		OptionTrue: $( $cond +)+,
168    		OptionFalse: $( $cond +)+,
169    	{
170		    default type Result = $crate::ct_if::CTFalse;
171		    default type Path = OptionFalse;
172		}
173
174		impl<X, OptionTrue, OptionFalse> $trait<X, OptionTrue, OptionFalse> for $crate::ct_if::IfCheck<X>
175		where
176    		OptionTrue: $( $cond +)+,
177    		OptionFalse: $( $cond +)+,
178		{
179		    type Result = $crate::ct_if::CTTrue;
180		    type Path = OptionTrue;
181		}
182    };
183}
184
185#[cfg(test)]
186mod test {
187    use super::*;
188    use std::any::TypeId;
189
190    fn match_on<Subject: CTBool>() -> TypeId {
191        TypeId::of::<<IfCheck<Subject> as CTIf<CTTrue, CTTrue, CTFalse>>::Path>()
192    }
193
194    #[test]
195    fn test_true() {
196        assert_eq!(match_on::<CTTrue>(), TypeId::of::<CTTrue>());
197    }
198
199    #[test]
200    fn test_false() {
201        assert_eq!(match_on::<CTFalse>(), TypeId::of::<CTFalse>());
202    }
203}