ruma_macros/
util.rs
1use proc_macro2::TokenStream;
2use proc_macro_crate::{crate_name, FoundCrate};
3use quote::{format_ident, quote, ToTokens};
4use syn::{Attribute, Field, Ident, LitStr};
5
6pub(crate) fn import_ruma_common() -> TokenStream {
7 if let Ok(FoundCrate::Name(name)) = crate_name("ruma-common") {
8 let import = format_ident!("{name}");
9 quote! { ::#import }
10 } else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
11 let import = format_ident!("{name}");
12 quote! { ::#import }
13 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
14 let import = format_ident!("{name}");
15 quote! { ::#import::ruma }
16 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk-appservice") {
17 let import = format_ident!("{name}");
18 quote! { ::#import::ruma }
19 } else {
20 quote! { ::ruma_common }
21 }
22}
23
24pub(crate) fn import_ruma_events() -> TokenStream {
25 if let Ok(FoundCrate::Name(name)) = crate_name("ruma-events") {
26 let import = format_ident!("{name}");
27 quote! { ::#import }
28 } else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
29 let import = format_ident!("{name}");
30 quote! { ::#import::events }
31 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
32 let import = format_ident!("{name}");
33 quote! { ::#import::ruma::events }
34 } else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk-appservice") {
35 let import = format_ident!("{name}");
36 quote! { ::#import::ruma::events }
37 } else {
38 quote! { ::ruma_events }
39 }
40}
41
42pub(crate) fn to_camel_case(name: &Ident) -> Ident {
44 let span = name.span();
45 let name = name.to_string();
46
47 let s: String = name
48 .split('_')
49 .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
50 .collect();
51 Ident::new(&s, span)
52}
53
54pub(crate) fn m_prefix_name_to_type_name(name: &LitStr) -> syn::Result<Ident> {
57 let span = name.span();
58 let name = name.value();
59
60 let name = name.strip_prefix("m.").ok_or_else(|| {
61 syn::Error::new(
62 span,
63 format!("well-known matrix events have to start with `m.` found `{name}`"),
64 )
65 })?;
66
67 let s: String = name
68 .strip_suffix(".*")
69 .unwrap_or(name)
70 .split(&['.', '_'] as &[char])
71 .map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
72 .collect();
73
74 Ok(Ident::new(&s, span))
75}
76
77pub struct PrivateField<'a>(pub &'a Field);
80
81impl ToTokens for PrivateField<'_> {
82 fn to_tokens(&self, tokens: &mut TokenStream) {
83 let Field { attrs, vis: _, mutability, ident, colon_token, ty } = self.0;
84 assert_eq!(*mutability, syn::FieldMutability::None);
85
86 for attr in attrs {
87 attr.to_tokens(tokens);
88 }
89 ident.to_tokens(tokens);
90 colon_token.to_tokens(tokens);
91 ty.to_tokens(tokens);
92 }
93}
94
95#[cfg(feature = "__internal_macro_expand")]
96pub fn cfg_expand_struct(item: &mut syn::ItemStruct) {
97 use std::mem;
98
99 use proc_macro2::TokenTree;
100 use syn::{visit_mut::VisitMut, Fields, LitBool, Meta};
101
102 fn eval_cfg(cfg_expr: TokenStream) -> Option<bool> {
103 let cfg_macro_call = quote! { ::core::cfg!(#cfg_expr) };
104 let expanded = match proc_macro::TokenStream::from(cfg_macro_call).expand_expr() {
105 Ok(t) => t,
106 Err(e) => {
107 eprintln!("Failed to expand cfg! {e}");
108 return None;
109 }
110 };
111
112 let lit: LitBool = syn::parse(expanded).expect("cfg! must expand to a boolean literal");
113 Some(lit.value())
114 }
115
116 fn tokentree_not_comma(tree: &TokenTree) -> bool {
117 match tree {
118 TokenTree::Punct(p) => p.as_char() != ',',
119 _ => true,
120 }
121 }
122
123 struct CfgAttrExpand;
124
125 impl VisitMut for CfgAttrExpand {
126 fn visit_attribute_mut(&mut self, attr: &mut syn::Attribute) {
127 if attr.meta.path().is_ident("cfg_attr") {
128 let Meta::List(list) = &attr.meta else { return };
130 let mut token_iter = list.tokens.clone().into_iter();
131
132 let cfg_expr: TokenStream =
135 token_iter.by_ref().take_while(tokentree_not_comma).collect();
136
137 let Some(cfg_value) = eval_cfg(cfg_expr) else { return };
138 if cfg_value {
139 let attr_tokens: TokenStream =
146 token_iter.by_ref().take_while(tokentree_not_comma).collect();
147
148 if attr_tokens.is_empty() {
149 return;
151 }
152
153 attr.meta = syn::parse2(attr_tokens)
154 .expect("syn must be able to parse cfg-attr arguments as syn::Meta");
155
156 let rest: TokenStream = token_iter.collect();
157 assert!(
158 rest.is_empty(),
159 "cfg_attr's with multiple arguments after the cfg expression are not \
160 currently supported by __internal_macro_expand."
161 );
162 }
163 }
164 }
165 }
166
167 CfgAttrExpand.visit_item_struct_mut(item);
168
169 let Fields::Named(fields) = &mut item.fields else {
170 panic!("only named fields are currently supported by __internal_macro_expand");
171 };
172
173 'fields: for mut field in mem::take(&mut fields.named) {
175 for attr in mem::take(&mut field.attrs) {
177 if !attr.meta.path().is_ident("cfg") {
179 field.attrs.push(attr);
180 continue;
181 }
182
183 let Meta::List(list) = &attr.meta else {
185 field.attrs.push(attr);
186 continue;
187 };
188 let Some(cfg_value) = eval_cfg(list.tokens.clone()) else {
190 field.attrs.push(attr);
191 continue;
192 };
193
194 if !cfg_value {
196 continue 'fields;
197 }
198 }
199
200 fields.named.push(field);
203 }
204}
205
206pub fn field_has_serde_flatten_attribute(field: &Field) -> bool {
208 field.attrs.iter().any(is_serde_flatten_attribute)
209}
210
211fn is_serde_flatten_attribute(attr: &Attribute) -> bool {
213 if !attr.path().is_ident("serde") {
214 return false;
215 }
216
217 let mut contains_flatten = false;
218 let _ = attr.parse_nested_meta(|meta| {
219 if meta.path.is_ident("flatten") {
220 contains_flatten = true;
221 return Err(meta.error("found"));
223 }
224
225 Ok(())
226 });
227
228 contains_flatten
229}