205 lines
7.2 KiB
Rust
205 lines
7.2 KiB
Rust
use proc_macro2::TokenStream;
|
|
use proc_macro_crate::{crate_name, FoundCrate};
|
|
use quote::{format_ident, quote, ToTokens};
|
|
use syn::{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 }
|
|
}
|
|
}
|
|
|
|
/// CamelCase's a field ident like "foo_bar" to "FooBar".
|
|
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)
|
|
}
|
|
|
|
/// Splits the given string on `.` and `_` removing the `m.` then camel casing to give a Rust type
|
|
/// name.
|
|
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))
|
|
}
|
|
|
|
/// Wrapper around [`syn::Field`] that emits the field without its visibility,
|
|
/// thus making it private.
|
|
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") {
|
|
// Ignore invalid cfg attributes
|
|
let Meta::List(list) = &attr.meta else { return };
|
|
let mut token_iter = list.tokens.clone().into_iter();
|
|
|
|
// Take all the tokens until the first toplevel comma.
|
|
// That's the cfg-expression part of cfg_attr.
|
|
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 {
|
|
// If we had the whole attribute list and could emit more
|
|
// than one attribute, we'd split the remaining arguments to
|
|
// cfg_attr by commas and turn them into regular attributes
|
|
//
|
|
// Because we can emit only one, do the first and error if
|
|
// there's any more after it.
|
|
let attr_tokens: TokenStream =
|
|
token_iter.by_ref().take_while(tokentree_not_comma).collect();
|
|
|
|
if attr_tokens.is_empty() {
|
|
// no-op cfg_attr??
|
|
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");
|
|
};
|
|
|
|
// Take out all the fields
|
|
'fields: for mut field in mem::take(&mut fields.named) {
|
|
// Take out all the attributes
|
|
for attr in mem::take(&mut field.attrs) {
|
|
// For non-cfg attrs, put them back
|
|
if !attr.meta.path().is_ident("cfg") {
|
|
field.attrs.push(attr);
|
|
continue;
|
|
}
|
|
|
|
// Also put back / ignore invalid cfg attributes
|
|
let Meta::List(list) = &attr.meta else {
|
|
field.attrs.push(attr);
|
|
continue;
|
|
};
|
|
// Also put back / ignore cfg attributes we can't eval
|
|
let Some(cfg_value) = eval_cfg(list.tokens.clone()) else {
|
|
field.attrs.push(attr);
|
|
continue;
|
|
};
|
|
|
|
// Finally, if the cfg is `false`, skip the part where it's put back
|
|
if !cfg_value {
|
|
continue 'fields;
|
|
}
|
|
}
|
|
|
|
// If `continue 'fields` above wasn't hit, we didn't find a cfg that
|
|
// evals to false, so put the field back
|
|
fields.named.push(field);
|
|
}
|
|
}
|