macros: Add __internal_macro_expand feature for better RA macro expansion
This commit is contained in:
parent
79025dfca4
commit
0e8388abab
@ -14,7 +14,14 @@ rust-version = { workspace = true }
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
# Make the request and response macros expand internal derives they would
|
||||
# usually emit in the `#[derive()]` list directly, such that Rust Analyzer's
|
||||
# expand macro helper can render their output. Requires a nightly toolchain.
|
||||
__internal_macro_expand = ["syn/visit-mut"]
|
||||
|
||||
[dependencies]
|
||||
cfg-if = "1.0.0"
|
||||
once_cell = "1.13.0"
|
||||
proc-macro-crate = "3.1.0"
|
||||
proc-macro2 = "1.0.24"
|
||||
|
@ -1,3 +1,4 @@
|
||||
use cfg_if::cfg_if;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
@ -26,13 +27,34 @@ pub fn expand_request(attr: RequestAttr, item: ItemStruct) -> TokenStream {
|
||||
|DeriveRequestMeta::Error(ty)| quote! { #ty },
|
||||
);
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "__internal_macro_expand")] {
|
||||
use syn::parse_quote;
|
||||
|
||||
let mut derive_input = item.clone();
|
||||
derive_input.attrs.push(parse_quote! { #[ruma_api(error = #error_ty)] });
|
||||
crate::util::cfg_expand_struct(&mut derive_input);
|
||||
|
||||
let extra_derive = quote! { #ruma_macros::_FakeDeriveRumaApi };
|
||||
let ruma_api_attribute = quote! {};
|
||||
let request_impls =
|
||||
expand_derive_request(derive_input).unwrap_or_else(syn::Error::into_compile_error);
|
||||
} else {
|
||||
let extra_derive = quote! { #ruma_macros::Request };
|
||||
let ruma_api_attribute = quote! { #[ruma_api(error = #error_ty)] };
|
||||
let request_impls = quote! {};
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
#maybe_feature_error
|
||||
|
||||
#[derive(Clone, Debug, #ruma_macros::Request, #ruma_common::serde::_FakeDeriveSerde)]
|
||||
#[derive(Clone, Debug, #ruma_common::serde::_FakeDeriveSerde, #extra_derive)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_api(error = #error_ty)]
|
||||
#ruma_api_attribute
|
||||
#item
|
||||
|
||||
#request_impls
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use std::ops::Not;
|
||||
|
||||
use cfg_if::cfg_if;
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{quote, ToTokens};
|
||||
use syn::{
|
||||
@ -41,13 +42,38 @@ pub fn expand_response(attr: ResponseAttr, item: ItemStruct) -> TokenStream {
|
||||
})
|
||||
.unwrap_or_else(|| quote! { OK });
|
||||
|
||||
cfg_if! {
|
||||
if #[cfg(feature = "__internal_macro_expand")] {
|
||||
use syn::parse_quote;
|
||||
|
||||
let mut derive_input = item.clone();
|
||||
derive_input.attrs.push(parse_quote! {
|
||||
#[ruma_api(error = #error_ty, status = #status_ident)]
|
||||
});
|
||||
crate::util::cfg_expand_struct(&mut derive_input);
|
||||
|
||||
let extra_derive = quote! { #ruma_macros::_FakeDeriveRumaApi };
|
||||
let ruma_api_attribute = quote! {};
|
||||
let response_impls =
|
||||
expand_derive_response(derive_input).unwrap_or_else(syn::Error::into_compile_error);
|
||||
} else {
|
||||
let extra_derive = quote! { #ruma_macros::Response };
|
||||
let ruma_api_attribute = quote! {
|
||||
#[ruma_api(error = #error_ty, status = #status_ident)]
|
||||
};
|
||||
let response_impls = quote! {};
|
||||
}
|
||||
}
|
||||
|
||||
quote! {
|
||||
#maybe_feature_error
|
||||
|
||||
#[derive(Clone, Debug, #ruma_macros::Response, #ruma_common::serde::_FakeDeriveSerde)]
|
||||
#[derive(Clone, Debug, #ruma_common::serde::_FakeDeriveSerde, #extra_derive)]
|
||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||
#[ruma_api(error = #error_ty, status = #status_ident)]
|
||||
#ruma_api_attribute
|
||||
#item
|
||||
|
||||
#response_impls
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@
|
||||
//!
|
||||
//! See the documentation for the individual macros for usage details.
|
||||
|
||||
#![cfg_attr(feature = "__internal_macro_expand", feature(proc_macro_expand))]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(unreachable_pub)]
|
||||
// https://github.com/rust-lang/rust-clippy/issues/9029
|
||||
|
@ -91,3 +91,114 @@ impl ToTokens for PrivateField<'_> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user