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
//! Traits for generic composition.

/// Trait to get part of composed type.
pub trait AsPart<T> {
    /// Borrow part of composed type.
    fn as_part(&self) -> &T;

    /// Mutably borrow part of composed type.
    fn as_part_mut(&mut self) -> &mut T;
}

/// Trait to try to get part of composed type.
pub trait TryAsPart<T> {
    /// Try to borrow part of composed type.
    fn try_as_part(&self) -> Option<&T>;

    /// Try to mutably borrow part of composed type.
    fn try_as_part_mut(&mut self) -> Option<&mut T>;
}

/// Types that implement `AsPart<T>` automatically implement `TryAsPart<T>`.
impl<T, X> TryAsPart<X> for T
where
    T: AsPart<X>,
{
    fn try_as_part(&self) -> Option<&X> {
        Some(self.as_part())
    }

    fn try_as_part_mut(&mut self) -> Option<&mut X> {
        Some(self.as_part_mut())
    }
}

/// Helper macro.
#[doc(hidden)]
#[macro_export]
macro_rules! enable_if_part {
    {(part) {$($b:tt)*}} => {$($b)*};
    {() {$($b:tt)*}} => {};
}

/// Create type composed of other types.
///
/// For structs, this macro is used in the following way.
/// When the `#[part]` attribute is applied to a field, that field will be available using
/// `AsPart<T>`. Only one field can have a `#[part]` of a particular type.
///
/// ```
/// # use sketchbook::compose;
/// # use sketchbook::compose::AsPart;
/// compose! {
///     struct Test {
///         a: i32,
///         
///         #[part]
///         b: i32,
///                   
///         #[part]
///         c: f32,
///     }
/// }
///
/// let x = Test { a: 123, b: 456, c: 1.23 };
/// let y: &f32 = x.as_part();
/// assert_eq!(y, &1.23);
/// ```
///
/// For enums, this macro is used in the following way.
/// When the `#[part]` attribute is applied to a variant, that variant will be available using
/// `TryAsPart<T>`. Only one variant can have a `#[part]` of a particular type. Additionally, the
/// variant must be a tuple of a single value.
/// ```
/// # use sketchbook::compose;
/// # use sketchbook::compose::TryAsPart;
/// compose! {
///     enum Test {
///         A,
///         
///         #[part]
///         B(i32),
///                                            
///         #[part]
///         C(f32),
///     }
/// }
///                                            
/// let x = Test::B(123);
/// let y: Option<&i32> = x.try_as_part();
/// assert_eq!(y, Some(&123));
/// ```
#[macro_export]
macro_rules! compose {
    {
        $(#[$($meta_code:tt)*])?
        $v:vis struct $name:ident {
            $(
                $(#[$attr:ident])? $vi:vis $field:ident: $field_type:ty
            ),*$(,)?
        }
    } => {
        $(#[$($meta_code)*])?
        $v struct $name {
            $($vi $field: $field_type),*
        }

        $($crate::enable_if_part!{($($attr)?) {
            impl $crate::compose::AsPart<$field_type> for $name {
                fn as_part(&self) -> &$field_type {
                    &self.$field
                }

                fn as_part_mut(&mut self) -> &mut $field_type {
                    &mut self.$field
                }
            }
        }})*
    };
    {
        $(#[$($meta_code:tt)*])?
        $v:vis enum $name:ident {
            $($(#[$attr:ident])? $variant:ident$(($variant_type:ty))?),*$(,)?
        }
    } => {
        $(#[$($meta_code)*])?
        $v enum $name {
            $($variant$(($variant_type))?),*
        }

        $($crate::enable_if_part!{($($attr)?) {
            impl $crate::compose::TryAsPart<$($variant_type)?> for $name {
                fn try_as_part(&self) -> Option<&$($variant_type)?> {
                    match self {
                        Self::$variant(value) => Some(value),
                        #[allow(unreachable_patterns)]
                        _ => None
                    }
                }

                fn try_as_part_mut(&mut self) -> Option<&mut $($variant_type)?> {
                    match self {
                        Self::$variant(value) => Some(value),
                        #[allow(unreachable_patterns)]
                        _ => None
                    }
                }
            }
        }})*
    };
}