1use proc_macro_crate::{FoundCrate, crate_name};
2use proc_macro2::{Span, TokenStream};
3use quote::{ToTokens, TokenStreamExt, format_ident, quote};
4use syn::{
5 Attribute, Field, Ident, LitStr, meta::ParseNestedMeta, punctuated::Punctuated, visit::Visit,
6};
7
8pub(crate) struct RumaCommon(TokenStream);
12
13impl RumaCommon {
14 pub(crate) fn new() -> Self {
16 let inner = if let Ok(FoundCrate::Name(name)) = crate_name("ruma-common") {
17 let import = format_ident!("{name}");
18 quote! { ::#import }
19 } else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
20 let import = format_ident!("{name}");
21 quote! { ::#import }
22 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
23 let import = format_ident!("{name}");
24 quote! { ::#import::ruma }
25 } else {
26 quote! { ::ruma_common }
27 };
28
29 Self(inner)
30 }
31
32 pub(crate) fn reexported(&self, reexport: RumaCommonReexport) -> TokenStream {
34 quote! { #self::exports::#reexport }
35 }
36}
37
38impl ToTokens for RumaCommon {
39 fn to_tokens(&self, tokens: &mut TokenStream) {
40 self.0.to_tokens(tokens);
41 }
42}
43
44pub(crate) enum RumaCommonReexport {
46 RumaMacros,
48
49 Serde,
51
52 SerdeHtmlForm,
54
55 SerdeJson,
57
58 Http,
60
61 Bytes,
63}
64
65impl ToTokens for RumaCommonReexport {
66 fn to_tokens(&self, tokens: &mut TokenStream) {
67 let crate_name = match self {
68 Self::RumaMacros => "ruma_macros",
69 Self::Serde => "serde",
70 Self::SerdeHtmlForm => "serde_html_form",
71 Self::SerdeJson => "serde_json",
72 Self::Http => "http",
73 Self::Bytes => "bytes",
74 };
75
76 tokens.append(Ident::new(crate_name, Span::call_site()));
77 }
78}
79
80pub(crate) struct RumaEvents(TokenStream);
85
86impl RumaEvents {
87 pub(crate) fn new() -> Self {
89 let inner = if let Ok(FoundCrate::Name(name)) = crate_name("ruma-events") {
90 let import = format_ident!("{name}");
91 quote! { ::#import }
92 } else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
93 let import = format_ident!("{name}");
94 quote! { ::#import::events }
95 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
96 let import = format_ident!("{name}");
97 quote! { ::#import::ruma::events }
98 } else {
99 quote! { ::ruma_events }
100 };
101
102 Self(inner)
103 }
104
105 pub(crate) fn reexported(&self, reexport: RumaEventsReexport) -> TokenStream {
107 quote! { #self::exports::#reexport }
108 }
109
110 pub(crate) fn ruma_common(&self) -> RumaCommon {
112 RumaCommon(quote! { #self::exports::ruma_common })
113 }
114}
115
116impl ToTokens for RumaEvents {
117 fn to_tokens(&self, tokens: &mut TokenStream) {
118 self.0.to_tokens(tokens);
119 }
120}
121
122pub(crate) enum RumaEventsReexport {
124 Serde,
126
127 SerdeJson,
129}
130
131impl ToTokens for RumaEventsReexport {
132 fn to_tokens(&self, tokens: &mut TokenStream) {
133 let crate_name = match self {
134 Self::Serde => "serde",
135 Self::SerdeJson => "serde_json",
136 };
137
138 tokens.append(Ident::new(crate_name, Span::call_site()));
139 }
140}
141
142pub(crate) fn to_camel_case(name: &Ident) -> Ident {
144 let span = name.span();
145 let name = name.to_string();
146
147 let s: String = name
148 .split('_')
149 .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
150 .collect();
151 Ident::new(&s, span)
152}
153
154pub(crate) fn m_prefix_name_to_type_name(name: &LitStr) -> syn::Result<Ident> {
157 let span = name.span();
158 let name = name.value();
159
160 let name = name.strip_prefix("m.").ok_or_else(|| {
161 syn::Error::new(
162 span,
163 format!("well-known matrix events have to start with `m.` found `{name}`"),
164 )
165 })?;
166
167 let s: String = name
168 .strip_suffix(".*")
169 .unwrap_or(name)
170 .split(&['.', '_'] as &[char])
171 .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
172 .collect();
173
174 Ok(Ident::new(&s, span))
175}
176
177pub struct PrivateField<'a>(pub &'a Field);
180
181impl ToTokens for PrivateField<'_> {
182 fn to_tokens(&self, tokens: &mut TokenStream) {
183 let Field { attrs, vis: _, mutability, ident, colon_token, ty } = self.0;
184 assert_eq!(*mutability, syn::FieldMutability::None);
185
186 for attr in attrs {
187 attr.to_tokens(tokens);
188 }
189 ident.to_tokens(tokens);
190 colon_token.to_tokens(tokens);
191 ty.to_tokens(tokens);
192 }
193}
194
195#[cfg(feature = "__internal_macro_expand")]
196pub fn cfg_expand_struct(item: &mut syn::ItemStruct) {
197 use std::mem;
198
199 use proc_macro2::TokenTree;
200 use syn::{Fields, LitBool, Meta, visit_mut::VisitMut};
201
202 fn eval_cfg(cfg_expr: TokenStream) -> Option<bool> {
203 let cfg_macro_call = quote! { ::core::cfg!(#cfg_expr) };
204 let expanded = match proc_macro::TokenStream::from(cfg_macro_call).expand_expr() {
205 Ok(t) => t,
206 Err(e) => {
207 eprintln!("Failed to expand cfg! {e}");
208 return None;
209 }
210 };
211
212 let lit: LitBool = syn::parse(expanded).expect("cfg! must expand to a boolean literal");
213 Some(lit.value())
214 }
215
216 fn tokentree_not_comma(tree: &TokenTree) -> bool {
217 match tree {
218 TokenTree::Punct(p) => p.as_char() != ',',
219 _ => true,
220 }
221 }
222
223 struct CfgAttrExpand;
224
225 impl VisitMut for CfgAttrExpand {
226 fn visit_attribute_mut(&mut self, attr: &mut Attribute) {
227 if attr.meta.path().is_ident("cfg_attr") {
228 let Meta::List(list) = &attr.meta else { return };
230 let mut token_iter = list.tokens.clone().into_iter();
231
232 let cfg_expr: TokenStream =
235 token_iter.by_ref().take_while(tokentree_not_comma).collect();
236
237 let Some(cfg_value) = eval_cfg(cfg_expr) else { return };
238 if cfg_value {
239 let attr_tokens: TokenStream =
246 token_iter.by_ref().take_while(tokentree_not_comma).collect();
247
248 if attr_tokens.is_empty() {
249 return;
251 }
252
253 attr.meta = syn::parse2(attr_tokens)
254 .expect("syn must be able to parse cfg-attr arguments as syn::Meta");
255
256 let rest: TokenStream = token_iter.collect();
257 assert!(
258 rest.is_empty(),
259 "cfg_attr's with multiple arguments after the cfg expression are not \
260 currently supported by __internal_macro_expand."
261 );
262 }
263 }
264 }
265 }
266
267 CfgAttrExpand.visit_item_struct_mut(item);
268
269 let Fields::Named(fields) = &mut item.fields else {
270 panic!("only named fields are currently supported by __internal_macro_expand");
271 };
272
273 'fields: for mut field in mem::take(&mut fields.named) {
275 for attr in mem::take(&mut field.attrs) {
277 if !attr.meta.path().is_ident("cfg") {
279 field.attrs.push(attr);
280 continue;
281 }
282
283 let Meta::List(list) = &attr.meta else {
285 field.attrs.push(attr);
286 continue;
287 };
288 let Some(cfg_value) = eval_cfg(list.tokens.clone()) else {
290 field.attrs.push(attr);
291 continue;
292 };
293
294 if !cfg_value {
296 continue 'fields;
297 }
298 }
299
300 fields.named.push(field);
303 }
304}
305
306pub(crate) trait StructFieldExt {
308 fn ident(&self) -> &Ident;
312
313 fn cfg_attrs(&self) -> impl Iterator<Item = &'_ Attribute>;
315
316 fn serde_meta_items(&self) -> impl Iterator<Item = syn::Meta>;
318
319 fn has_serde_meta_item(&self, meta: SerdeMetaItem) -> bool;
321}
322
323impl StructFieldExt for Field {
324 fn ident(&self) -> &Ident {
325 self.ident.as_ref().expect("struct field should be named")
326 }
327
328 fn cfg_attrs(&self) -> impl Iterator<Item = &'_ Attribute> {
329 self.attrs.iter().filter(|a| a.path().is_ident("cfg"))
330 }
331
332 fn serde_meta_items(&self) -> impl Iterator<Item = syn::Meta> {
333 self.attrs.iter().flat_map(AttributeExt::serde_meta_items)
334 }
335
336 fn has_serde_meta_item(&self, meta: SerdeMetaItem) -> bool {
337 self.serde_meta_items().any(|serde_meta| serde_meta == meta)
338 }
339}
340
341#[derive(Clone, Copy)]
343pub(crate) enum SerdeMetaItem {
344 Flatten,
346
347 Default,
349
350 Rename,
352
353 Alias,
355}
356
357impl SerdeMetaItem {
358 fn as_str(self) -> &'static str {
360 match self {
361 Self::Flatten => "flatten",
362 Self::Default => "default",
363 Self::Rename => "rename",
364 Self::Alias => "alias",
365 }
366 }
367}
368
369impl PartialEq<SerdeMetaItem> for syn::Meta {
370 fn eq(&self, other: &SerdeMetaItem) -> bool {
371 self.path().is_ident(other.as_str())
372 }
373}
374
375pub(crate) trait AttributeExt {
377 fn serde_meta_items(&self) -> impl Iterator<Item = syn::Meta>;
379}
380
381impl AttributeExt for Attribute {
382 fn serde_meta_items(&self) -> impl Iterator<Item = syn::Meta> {
383 if self.path().is_ident("serde")
384 && let syn::Meta::List(list) = &self.meta
385 {
386 list.parse_args_with(Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated).ok()
387 } else {
388 None
389 }
390 .into_iter()
391 .flatten()
392 }
393}
394
395pub(crate) trait TypeExt {
397 fn option_inner_type(&self) -> Option<&syn::Type>;
399
400 fn has_lifetime(&self) -> bool;
402}
403
404impl TypeExt for syn::Type {
405 fn option_inner_type(&self) -> Option<&syn::Type> {
406 let syn::Type::Path(syn::TypePath { path: syn::Path { segments, .. }, .. }) = self else {
407 return None;
408 };
409
410 if segments.last().unwrap().ident != "Option" {
411 return None;
412 }
413
414 let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
415 args: option_args,
416 ..
417 }) = &segments.last().unwrap().arguments
418 else {
419 panic!("Option should use angle brackets");
420 };
421 let syn::GenericArgument::Type(inner_type) = option_args.first().unwrap() else {
422 panic!("Option brackets should contain type");
423 };
424
425 Some(inner_type)
426 }
427
428 fn has_lifetime(&self) -> bool {
429 struct Visitor {
430 found_lifetime: bool,
431 }
432
433 impl<'ast> Visit<'ast> for Visitor {
434 fn visit_lifetime(&mut self, _lt: &'ast syn::Lifetime) {
435 self.found_lifetime = true;
436 }
437 }
438
439 let mut vis = Visitor { found_lifetime: false };
440 vis.visit_type(self);
441
442 vis.found_lifetime
443 }
444}
445
446pub(crate) fn expand_fields_as_list<'a>(
457 fields: impl IntoIterator<Item = &'a Field>,
458) -> TokenStream {
459 fields
460 .into_iter()
461 .map(|field| {
462 let ident = field.ident();
463 let cfg_attrs = field.cfg_attrs();
464
465 quote! {
466 #( #cfg_attrs )*
467 #ident,
468 }
469 })
470 .collect()
471}
472
473pub(crate) fn expand_fields_as_variable_declarations(fields: &[Field], src: &Ident) -> TokenStream {
484 fields
485 .iter()
486 .map(|field| {
487 let ident = field.ident();
488 let cfg_attrs = field.cfg_attrs();
489
490 quote! {
491 #( #cfg_attrs )*
492 let #ident = #src.#ident;
493 }
494 })
495 .collect()
496}
497
498pub(crate) trait ParseNestedMetaExt {
500 fn has_value(&self) -> bool;
502}
503
504impl ParseNestedMetaExt for ParseNestedMeta<'_> {
505 fn has_value(&self) -> bool {
506 !self.input.is_empty() && !self.input.peek(syn::Token![,])
507 }
508}