1use proc_macro::TokenStream;
96use proc_macro2::{Span, TokenStream as Tokens};
97use quote::{format_ident, quote};
98use syn::{Data, DeriveInput, Fields, LitStr, parse_macro_input, spanned::Spanned};
99
100#[proc_macro_derive(Message, attributes(message))]
103pub fn derive_message(input: TokenStream) -> TokenStream {
104 let input = parse_macro_input!(input as DeriveInput);
105 match expand(&input) {
106 Ok(t) => t.into(),
107 Err(e) => e.to_compile_error().into(),
108 }
109}
110
111#[derive(Default)]
113struct Attrs {
114 kind: Option<String>,
115 krate: Option<syn::Path>,
116 string: bool,
117}
118
119fn attrs(list: &[syn::Attribute]) -> syn::Result<Attrs> {
120 let mut out = Attrs::default();
121 for a in list.iter().filter(|a| a.path().is_ident("message")) {
122 a.parse_nested_meta(|m| {
123 if m.path.is_ident("kind") {
124 out.kind = Some(m.value()?.parse::<LitStr>()?.value());
125 } else if m.path.is_ident("crate") {
126 out.krate = Some(m.value()?.parse::<LitStr>()?.parse()?);
127 } else if m.path.is_ident("string") {
128 out.string = true;
129 } else {
130 return Err(m.error("expected `kind = \"…\"`, `crate = \"…\"` or `string`"));
131 }
132 Ok(())
133 })?;
134 }
135 Ok(out)
136}
137
138fn snake(name: &str) -> String {
140 let chars: Vec<char> = name.chars().collect();
141 let mut out = String::new();
142 for (i, &c) in chars.iter().enumerate() {
143 if c.is_uppercase() {
144 let prev_lower = i > 0 && !chars[i - 1].is_uppercase() && chars[i - 1] != '_';
145 let next_lower = chars.get(i + 1).is_some_and(|n| n.is_lowercase());
146 let prev_upper = i > 0 && chars[i - 1].is_uppercase();
147 if i > 0 && (prev_lower || (prev_upper && next_lower)) {
148 out.push('_');
149 }
150 out.extend(c.to_lowercase());
151 } else {
152 out.push(c);
153 }
154 }
155 out
156}
157
158struct Shape {
161 kind: String,
162 path: Tokens,
163 fields: Vec<(String, syn::Ident, syn::Type)>,
164 named: Option<bool>,
165}
166
167fn shape(kind: String, path: Tokens, fields: &Fields) -> Shape {
168 let (named, fields) = match fields {
169 Fields::Unit => (None, Vec::new()),
170 Fields::Named(f) => (
171 Some(true),
172 f.named
173 .iter()
174 .map(|f| {
175 let id = f.ident.clone().expect("named");
176 (id.to_string(), id, f.ty.clone())
177 })
178 .collect(),
179 ),
180 Fields::Unnamed(f) => (
181 Some(false),
182 f.unnamed
183 .iter()
184 .enumerate()
185 .map(|(i, f)| (i.to_string(), format_ident!("f{i}"), f.ty.clone()))
186 .collect(),
187 ),
188 };
189 Shape {
190 kind,
191 path,
192 fields,
193 named,
194 }
195}
196
197impl Shape {
198 fn pattern(&self) -> Tokens {
200 let path = &self.path;
201 let binds = self.fields.iter().map(|(_, b, _)| b);
202 match self.named {
203 None => quote!(#path),
204 Some(true) => quote!(#path { #(#binds),* }),
205 Some(false) => quote!(#path ( #(#binds),* )),
206 }
207 }
208
209 fn encode(&self, k: &Tokens) -> Tokens {
211 let kind = &self.kind;
212 let entries = self
213 .fields
214 .iter()
215 .map(|(key, b, _)| quote!((#key, #k::MessageField::to_value(#b))));
216 quote!(#k::Value::map([("kind", #k::Value::str(#kind)), #(#entries),*]))
217 }
218
219 fn decode(&self, k: &Tokens) -> Tokens {
221 let path = &self.path;
222 let kind = &self.kind;
223 let reads = self
224 .fields
225 .iter()
226 .map(|(key, _, ty)| quote!(#k::message::field::<#ty>(v, #kind, #key)?));
227 match self.named {
228 None => quote!(#path),
229 Some(true) => {
230 let names = self.fields.iter().map(|(_, b, _)| b);
231 quote!(#path { #(#names: #reads),* })
232 }
233 Some(false) => quote!(#path ( #(#reads),* )),
234 }
235 }
236}
237
238fn expand(input: &DeriveInput) -> syn::Result<Tokens> {
239 let top = attrs(&input.attrs)?;
240 let k: Tokens = match &top.krate {
241 Some(p) => quote!(#p),
242 None => quote!(::kui_native),
243 };
244 let name = &input.ident;
245 let (imp, ty, wh) = input.generics.split_for_impl();
246
247 let shapes: Vec<Shape> = match &input.data {
248 Data::Enum(e) => {
249 let mut out = Vec::new();
250 for v in &e.variants {
251 let a = attrs(&v.attrs)?;
252 if a.krate.is_some() || a.string {
253 return Err(syn::Error::new(
254 v.span(),
255 "`crate` and `string` go on the type, not on a variant",
256 ));
257 }
258 let id = &v.ident;
259 out.push(shape(
260 a.kind.unwrap_or_else(|| snake(&id.to_string())),
261 quote!(#name::#id),
262 &v.fields,
263 ));
264 }
265 out
266 }
267 Data::Struct(s) => vec![shape(
268 top.kind.clone().unwrap_or_else(|| snake(&name.to_string())),
269 quote!(#name),
270 &s.fields,
271 )],
272 Data::Union(u) => {
273 return Err(syn::Error::new(
274 u.union_token.span,
275 "a message is an enum or a struct",
276 ));
277 }
278 };
279 if top.kind.is_some() && matches!(input.data, Data::Enum(_)) {
280 return Err(syn::Error::new(
281 Span::call_site(),
282 "`kind` on an enum goes on each variant",
283 ));
284 }
285 {
286 let mut seen = std::collections::HashSet::new();
287 for s in &shapes {
288 if !seen.insert(s.kind.as_str()) {
289 return Err(syn::Error::new(
290 Span::call_site(),
291 format!("two shapes of this message are both of kind {:?}", s.kind),
292 ));
293 }
294 }
295 }
296
297 let (encode, decode) = if top.string {
298 if shapes.iter().any(|s| s.named.is_some()) || !matches!(input.data, Data::Enum(_)) {
299 return Err(syn::Error::new(
300 Span::call_site(),
301 "`string` is for an enum whose variants are all units",
302 ));
303 }
304 let pats = shapes.iter().map(Shape::pattern);
305 let kinds: Vec<&String> = shapes.iter().map(|s| &s.kind).collect();
306 let paths = shapes.iter().map(|s| &s.path);
307 (
308 quote!(match m { #(#pats => #k::Value::str(#kinds)),* }),
309 quote!(match v.as_str().ok_or(#k::MessageError::NoKind)? {
310 #(#kinds => Ok(#paths),)*
311 other => Err(#k::MessageError::UnknownKind(other.to_string())),
312 }),
313 )
314 } else {
315 let pats = shapes.iter().map(Shape::pattern);
316 let encodes = shapes.iter().map(|s| s.encode(&k));
317 let kinds = shapes.iter().map(|s| &s.kind);
318 let decodes = shapes.iter().map(|s| s.decode(&k));
319 (
320 quote!(match m { #(#pats => #encodes),* }),
321 quote!(match #k::message::kind_of(v)? {
322 #(#kinds => Ok(#decodes),)*
323 other => Err(#k::MessageError::UnknownKind(other.to_string())),
324 }),
325 )
326 };
327
328 Ok(quote! {
329 impl #imp ::core::convert::From<#name #ty> for #k::Value #wh {
330 fn from(m: #name #ty) -> #k::Value {
331 #encode
332 }
333 }
334
335 impl #imp ::core::convert::TryFrom<&#k::Value> for #name #ty #wh {
336 type Error = #k::MessageError;
337 fn try_from(v: &#k::Value) -> ::core::result::Result<Self, #k::MessageError> {
338 #decode
339 }
340 }
341
342 impl #imp ::core::convert::TryFrom<#k::Value> for #name #ty #wh {
343 type Error = #k::MessageError;
344 fn try_from(v: #k::Value) -> ::core::result::Result<Self, #k::MessageError> {
345 <Self as ::core::convert::TryFrom<&#k::Value>>::try_from(&v)
346 }
347 }
348
349 impl #imp #k::MessageField for #name #ty #wh {
350 fn to_value(self) -> #k::Value {
351 self.into()
352 }
353 fn from_value(v: &#k::Value) -> ::core::option::Option<Self> {
354 <Self as ::core::convert::TryFrom<&#k::Value>>::try_from(v).ok()
355 }
356 }
357 })
358}
359
360#[cfg(test)]
361mod tests {
362 use super::snake;
363
364 #[test]
365 fn variant_names_become_snake_case_kinds() {
366 assert_eq!(snake("Inc"), "inc");
367 assert_eq!(snake("TabNew"), "tab_new");
368 assert_eq!(snake("PaneDrag"), "pane_drag");
369 assert_eq!(snake("HTTPGet"), "http_get");
370 assert_eq!(snake("Add10"), "add10");
371 assert_eq!(snake("H"), "h");
372 }
373}