utility_macros_internals/union/
union_impl.rs1use 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}