1use proc_macro::TokenStream;
27use proc_macro2::{Span, TokenStream as Tokens};
28use quote::{format_ident, quote};
29use syn::{Data, DeriveInput, Fields, LitStr, parse_macro_input, spanned::Spanned};
30
31#[proc_macro_derive(Message, attributes(message))]
32pub fn derive_message(input: TokenStream) -> TokenStream {
33 let input = parse_macro_input!(input as DeriveInput);
34 match expand(&input) {
35 Ok(t) => t.into(),
36 Err(e) => e.to_compile_error().into(),
37 }
38}
39
40#[derive(Default)]
42struct Attrs {
43 kind: Option<String>,
44 krate: Option<syn::Path>,
45 string: bool,
46}
47
48fn attrs(list: &[syn::Attribute]) -> syn::Result<Attrs> {
49 let mut out = Attrs::default();
50 for a in list.iter().filter(|a| a.path().is_ident("message")) {
51 a.parse_nested_meta(|m| {
52 if m.path.is_ident("kind") {
53 out.kind = Some(m.value()?.parse::<LitStr>()?.value());
54 } else if m.path.is_ident("crate") {
55 out.krate = Some(m.value()?.parse::<LitStr>()?.parse()?);
56 } else if m.path.is_ident("string") {
57 out.string = true;
58 } else {
59 return Err(m.error("expected `kind = \"…\"`, `crate = \"…\"` or `string`"));
60 }
61 Ok(())
62 })?;
63 }
64 Ok(out)
65}
66
67fn snake(name: &str) -> String {
69 let chars: Vec<char> = name.chars().collect();
70 let mut out = String::new();
71 for (i, &c) in chars.iter().enumerate() {
72 if c.is_uppercase() {
73 let prev_lower = i > 0 && !chars[i - 1].is_uppercase() && chars[i - 1] != '_';
74 let next_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase());
75 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
76 if i > 0 && (prev_lower || (prev_upper && next_lower)) {
77 out.push('_');
78 }
79 out.extend(c.to_lowercase());
80 } else {
81 out.push(c);
82 }
83 }
84 out
85}
86
87struct Shape {
90 kind: String,
91 path: Tokens,
92 fields: Vec<(String, syn::Ident, syn::Type)>,
93 named: Option<bool>,
94}
95
96fn shape(kind: String, path: Tokens, fields: &Fields) -> Shape {
97 let (named, fields) = match fields {
98 Fields::Unit => (None, Vec::new()),
99 Fields::Named(f) => (
100 Some(true),
101 f.named
102 .iter()
103 .map(|f| {
104 let id = f.ident.clone().expect("named");
105 (id.to_string(), id, f.ty.clone())
106 })
107 .collect(),
108 ),
109 Fields::Unnamed(f) => (
110 Some(false),
111 f.unnamed
112 .iter()
113 .enumerate()
114 .map(|(i, f)| (i.to_string(), format_ident!("f{i}"), f.ty.clone()))
115 .collect(),
116 ),
117 };
118 Shape {
119 kind,
120 path,
121 fields,
122 named,
123 }
124}
125
126impl Shape {
127 fn pattern(&self) -> Tokens {
129 let path = &self.path;
130 let binds = self.fields.iter().map(|(_, b, _)| b);
131 match self.named {
132 None => quote!(#path),
133 Some(true) => quote!(#path { #(#binds),* }),
134 Some(false) => quote!(#path ( #(#binds),* )),
135 }
136 }
137
138 fn encode(&self, k: &Tokens) -> Tokens {
140 let kind = &self.kind;
141 let entries = self
142 .fields
143 .iter()
144 .map(|(key, b, _)| quote!((#key, #k::MessageField::to_value(#b))));
145 quote!(#k::Value::map([("kind", #k::Value::str(#kind)), #(#entries),*]))
146 }
147
148 fn decode(&self, k: &Tokens) -> Tokens {
150 let path = &self.path;
151 let kind = &self.kind;
152 let reads = self
153 .fields
154 .iter()
155 .map(|(key, _, ty)| quote!(#k::message::field::<#ty>(v, #kind, #key)?));
156 match self.named {
157 None => quote!(#path),
158 Some(true) => {
159 let names = self.fields.iter().map(|(_, b, _)| b);
160 quote!(#path { #(#names: #reads),* })
161 }
162 Some(false) => quote!(#path ( #(#reads),* )),
163 }
164 }
165}
166
167fn expand(input: &DeriveInput) -> syn::Result<Tokens> {
168 let top = attrs(&input.attrs)?;
169 let k: Tokens = match &top.krate {
170 Some(p) => quote!(#p),
171 None => quote!(::kui_native),
172 };
173 let name = &input.ident;
174 let (imp, ty, wh) = input.generics.split_for_impl();
175
176 let shapes: Vec<Shape> = match &input.data {
177 Data::Enum(e) => {
178 let mut out = Vec::new();
179 for v in &e.variants {
180 let a = attrs(&v.attrs)?;
181 if a.krate.is_some() || a.string {
182 return Err(syn::Error::new(
183 v.span(),
184 "`crate` and `string` go on the type, not on a variant",
185 ));
186 }
187 let id = &v.ident;
188 out.push(shape(
189 a.kind.unwrap_or_else(|| snake(&id.to_string())),
190 quote!(#name::#id),
191 &v.fields,
192 ));
193 }
194 out
195 }
196 Data::Struct(s) => vec![shape(
197 top.kind.clone().unwrap_or_else(|| snake(&name.to_string())),
198 quote!(#name),
199 &s.fields,
200 )],
201 Data::Union(u) => {
202 return Err(syn::Error::new(
203 u.union_token.span,
204 "a message is an enum or a struct",
205 ));
206 }
207 };
208 if top.kind.is_some() && matches!(input.data, Data::Enum(_)) {
209 return Err(syn::Error::new(
210 Span::call_site(),
211 "`kind` on an enum goes on each variant",
212 ));
213 }
214 {
215 let mut seen = std::collections::HashSet::new();
216 for s in &shapes {
217 if !seen.insert(s.kind.as_str()) {
218 return Err(syn::Error::new(
219 Span::call_site(),
220 format!("two shapes of this message are both of kind {:?}", s.kind),
221 ));
222 }
223 }
224 }
225
226 let (encode, decode) = if top.string {
227 if shapes.iter().any(|s| s.named.is_some()) || !matches!(input.data, Data::Enum(_)) {
228 return Err(syn::Error::new(
229 Span::call_site(),
230 "`string` is for an enum whose variants are all units",
231 ));
232 }
233 let pats = shapes.iter().map(Shape::pattern);
234 let kinds: Vec<&String> = shapes.iter().map(|s| &s.kind).collect();
235 let paths = shapes.iter().map(|s| &s.path);
236 (
237 quote!(match m { #(#pats => #k::Value::str(#kinds)),* }),
238 quote!(match v.as_str().ok_or(#k::MessageError::NoKind)? {
239 #(#kinds => Ok(#paths),)*
240 other => Err(#k::MessageError::UnknownKind(other.to_string())),
241 }),
242 )
243 } else {
244 let pats = shapes.iter().map(Shape::pattern);
245 let encodes = shapes.iter().map(|s| s.encode(&k));
246 let kinds = shapes.iter().map(|s| &s.kind);
247 let decodes = shapes.iter().map(|s| s.decode(&k));
248 (
249 quote!(match m { #(#pats => #encodes),* }),
250 quote!(match #k::message::kind_of(v)? {
251 #(#kinds => Ok(#decodes),)*
252 other => Err(#k::MessageError::UnknownKind(other.to_string())),
253 }),
254 )
255 };
256
257 Ok(quote! {
258 impl #imp ::core::convert::From<#name #ty> for #k::Value #wh {
259 fn from(m: #name #ty) -> #k::Value {
260 #encode
261 }
262 }
263
264 impl #imp ::core::convert::TryFrom<&#k::Value> for #name #ty #wh {
265 type Error = #k::MessageError;
266 fn try_from(v: &#k::Value) -> ::core::result::Result<Self, #k::MessageError> {
267 #decode
268 }
269 }
270
271 impl #imp ::core::convert::TryFrom<#k::Value> for #name #ty #wh {
272 type Error = #k::MessageError;
273 fn try_from(v: #k::Value) -> ::core::result::Result<Self, #k::MessageError> {
274 <Self as ::core::convert::TryFrom<&#k::Value>>::try_from(&v)
275 }
276 }
277
278 impl #imp #k::MessageField for #name #ty #wh {
279 fn to_value(self) -> #k::Value {
280 self.into()
281 }
282 fn from_value(v: &#k::Value) -> ::core::option::Option<Self> {
283 <Self as ::core::convert::TryFrom<&#k::Value>>::try_from(v).ok()
284 }
285 }
286 })
287}
288
289#[cfg(test)]
290mod tests {
291 use super::snake;
292
293 #[test]
294 fn variant_names_become_snake_case_kinds() {
295 assert_eq!(snake("Inc"), "inc");
296 assert_eq!(snake("TabNew"), "tab_new");
297 assert_eq!(snake("PaneDrag"), "pane_drag");
298 assert_eq!(snake("HTTPGet"), "http_get");
299 assert_eq!(snake("Add10"), "add10");
300 assert_eq!(snake("H"), "h");
301 }
302}