utility_macros_internals/union/
union_impl.rs

1use convert_case::{Case, Casing as _};
2use proc_macro2::{Literal, Span, TokenStream, TokenTree};
3use quote::quote;
4use syn::Ident;
5
6use crate::expect_token::expect_token;
7
8fn is_static_str(literal: &Literal) -> bool {
9    let s = literal.to_string();
10    s.starts_with("\"") && s.ends_with("\"")
11}
12
13pub fn union_impl(item: TokenStream) -> TokenStream {
14    let mut tokens = item.into_iter();
15
16    expect_token!(tokens, ident = "type");
17    let ident = expect_token!(tokens, ident);
18    expect_token!(tokens, punct = '=');
19
20    let mut static_str = true;
21    let mut strings = Vec::new();
22    let mut types = Vec::new();
23
24    loop {
25        let t = tokens.next();
26        match t {
27            Some(TokenTree::Literal(literal)) if is_static_str(&literal) => {
28                strings.push(literal);
29            }
30            Some(TokenTree::Ident(ident)) => {
31                static_str = false;
32                types.push(ident);
33            }
34            _ => {
35                panic!("expected &'static str literal or identifier")
36            }
37        }
38
39        match tokens.next() {
40            Some(TokenTree::Punct(punct)) if punct.as_char() == '|' => {}
41            Some(TokenTree::Punct(punct)) if punct.as_char() == ';' => break,
42            _ => panic!("expected `|` or `;`"),
43        }
44    }
45
46    if static_str {
47        return static_strs_impl(ident, strings);
48    }
49
50    if strings.len() > 0 {
51        panic!("expected either all variants to be &'static str or all variants to be types");
52    }
53
54    types_impls(ident, types)
55}
56
57fn static_strs_impl(ident: Ident, literals: Vec<Literal>) -> TokenStream {
58    let variants = literals
59        .clone()
60        .into_iter()
61        .map(|literal| {
62            let s = literal.to_string();
63            let s = s.trim_matches('"');
64            let s = s.to_case(Case::Pascal).clone();
65            let s = s.replace(" ", "");
66            Ident::new(&s, Span::call_site())
67        })
68        .collect::<Vec<_>>();
69
70    quote! {
71        #[derive(Clone, Debug)]
72        pub enum #ident {
73            #(#variants),*
74        }
75
76        impl ::utility_macros::_um::union::static_str_union::StaticStrUnion for #ident {
77            fn strs() -> Vec<&'static str>
78            where
79                Self: Sized
80            {
81                vec![#(#literals),*]
82            }
83
84            fn as_str(&self) -> &'static str {
85                match self {
86                    #(Self::#variants => #literals),*
87                }
88            }
89
90            fn try_from_str(value: &str) -> ::utility_macros::_um::error::Result<Self>
91            where
92                Self: Sized
93            {
94                match value {
95                    #(#literals => Ok(Self::#variants),)*
96                    _ => Err(::utility_macros::_um::error::Error::InvalidVariant(value.to_string()))
97                }
98            }
99        }
100
101        impl Into<&'static str> for #ident {
102            fn into(self) -> &'static str {
103                ::utility_macros::_um::union::static_str_union::StaticStrUnion::as_str(&self)
104            }
105        }
106
107        impl ::std::str::FromStr for #ident {
108            type Err = ::utility_macros::_um::error::Error;
109
110            fn from_str(s: &str) -> ::utility_macros::_um::error::Result<Self> {
111                ::utility_macros::_um::union::static_str_union::StaticStrUnion::try_from_str(s)
112            }
113        }
114    }
115}
116
117fn types_impls(ident: Ident, types: Vec<Ident>) -> TokenStream {
118    quote! {
119        #[derive(Clone)]
120        pub enum #ident {
121            #(#types(#types)),*
122        }
123
124        impl ::utility_macros::_um::union::union::Union for #ident {
125        }
126
127        #(
128            ::utility_macros::_um::_sa::assert_impl_all!(#types: Clone);
129
130            impl TryInto<#types> for #ident {
131                type Error = ::utility_macros::_um::error::Error;
132
133                fn try_into(self) -> ::utility_macros::_um::error::Result<#types> {
134                    match self {
135                        Self::#types(value) => Ok(value),
136                        _ => Err(::utility_macros::_um::error::Error::InvalidVariant(stringify!(#types).to_string()))
137                    }
138                }
139            }
140
141            impl From<#types> for #ident {
142                fn from(value: #types) -> Self {
143                    Self::#types(value)
144                }
145            }
146        )*
147    }
148}