use proc_macro2::TokenStream;
use proc_macro_crate::{crate_name, FoundCrate};
use quote::{format_ident, quote, ToTokens};
use syn::{Attribute, Field, Ident, LitStr};
pub(crate) fn import_ruma_common() -> TokenStream {
if let Ok(FoundCrate::Name(name)) = crate_name("ruma-common") {
let import = format_ident!("{name}");
quote! { ::#import }
} else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
let import = format_ident!("{name}");
quote! { ::#import }
} else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
let import = format_ident!("{name}");
quote! { ::#import::ruma }
} else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk-appservice") {
let import = format_ident!("{name}");
quote! { ::#import::ruma }
} else {
quote! { ::ruma_common }
}
}
pub(crate) fn import_ruma_events() -> TokenStream {
if let Ok(FoundCrate::Name(name)) = crate_name("ruma-events") {
let import = format_ident!("{name}");
quote! { ::#import }
} else if let Ok(FoundCrate::Name(name)) = crate_name("ruma") {
let import = format_ident!("{name}");
quote! { ::#import::events }
} else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk") {
let import = format_ident!("{name}");
quote! { ::#import::ruma::events }
} else if let Ok(FoundCrate::Name(name)) = crate_name("matrix-sdk-appservice") {
let import = format_ident!("{name}");
quote! { ::#import::ruma::events }
} else {
quote! { ::ruma_events }
}
}
pub(crate) fn to_camel_case(name: &Ident) -> Ident {
let span = name.span();
let name = name.to_string();
let s: String = name
.split('_')
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
.collect();
Ident::new(&s, span)
}
pub(crate) fn m_prefix_name_to_type_name(name: &LitStr) -> syn::Result<Ident> {
let span = name.span();
let name = name.value();
let name = name.strip_prefix("m.").ok_or_else(|| {
syn::Error::new(
span,
format!("well-known matrix events have to start with `m.` found `{name}`"),
)
})?;
let s: String = name
.strip_suffix(".*")
.unwrap_or(name)
.split(&['.', '_'] as &[char])
.map(|s| s.chars().next().unwrap().to_uppercase().to_string() + &s[1..])
.collect();
Ok(Ident::new(&s, span))
}
pub struct PrivateField<'a>(pub &'a Field);
impl ToTokens for PrivateField<'_> {
fn to_tokens(&self, tokens: &mut TokenStream) {
let Field { attrs, vis: _, mutability, ident, colon_token, ty } = self.0;
assert_eq!(*mutability, syn::FieldMutability::None);
for attr in attrs {
attr.to_tokens(tokens);
}
ident.to_tokens(tokens);
colon_token.to_tokens(tokens);
ty.to_tokens(tokens);
}
}
#[cfg(feature = "__internal_macro_expand")]
pub fn cfg_expand_struct(item: &mut syn::ItemStruct) {
use std::mem;
use proc_macro2::TokenTree;
use syn::{visit_mut::VisitMut, Fields, LitBool, Meta};
fn eval_cfg(cfg_expr: TokenStream) -> Option<bool> {
let cfg_macro_call = quote! { ::core::cfg!(#cfg_expr) };
let expanded = match proc_macro::TokenStream::from(cfg_macro_call).expand_expr() {
Ok(t) => t,
Err(e) => {
eprintln!("Failed to expand cfg! {e}");
return None;
}
};
let lit: LitBool = syn::parse(expanded).expect("cfg! must expand to a boolean literal");
Some(lit.value())
}
fn tokentree_not_comma(tree: &TokenTree) -> bool {
match tree {
TokenTree::Punct(p) => p.as_char() != ',',
_ => true,
}
}
struct CfgAttrExpand;
impl VisitMut for CfgAttrExpand {
fn visit_attribute_mut(&mut self, attr: &mut syn::Attribute) {
if attr.meta.path().is_ident("cfg_attr") {
let Meta::List(list) = &attr.meta else { return };
let mut token_iter = list.tokens.clone().into_iter();
let cfg_expr: TokenStream =
token_iter.by_ref().take_while(tokentree_not_comma).collect();
let Some(cfg_value) = eval_cfg(cfg_expr) else { return };
if cfg_value {
let attr_tokens: TokenStream =
token_iter.by_ref().take_while(tokentree_not_comma).collect();
if attr_tokens.is_empty() {
return;
}
attr.meta = syn::parse2(attr_tokens)
.expect("syn must be able to parse cfg-attr arguments as syn::Meta");
let rest: TokenStream = token_iter.collect();
assert!(
rest.is_empty(),
"cfg_attr's with multiple arguments after the cfg expression are not \
currently supported by __internal_macro_expand."
);
}
}
}
}
CfgAttrExpand.visit_item_struct_mut(item);
let Fields::Named(fields) = &mut item.fields else {
panic!("only named fields are currently supported by __internal_macro_expand");
};
'fields: for mut field in mem::take(&mut fields.named) {
for attr in mem::take(&mut field.attrs) {
if !attr.meta.path().is_ident("cfg") {
field.attrs.push(attr);
continue;
}
let Meta::List(list) = &attr.meta else {
field.attrs.push(attr);
continue;
};
let Some(cfg_value) = eval_cfg(list.tokens.clone()) else {
field.attrs.push(attr);
continue;
};
if !cfg_value {
continue 'fields;
}
}
fields.named.push(field);
}
}
pub fn field_has_serde_flatten_attribute(field: &Field) -> bool {
field.attrs.iter().any(is_serde_flatten_attribute)
}
fn is_serde_flatten_attribute(attr: &Attribute) -> bool {
if !attr.path().is_ident("serde") {
return false;
}
let mut contains_flatten = false;
let _ = attr.parse_nested_meta(|meta| {
if meta.path.is_ident("flatten") {
contains_flatten = true;
return Err(meta.error("found"));
}
Ok(())
});
contains_flatten
}