Allow #[cfg(...)] in plugin functions.

This commit is contained in:
Stephen Chung 2021-10-20 15:30:11 +08:00
parent 0265af415d
commit 3f2dd23e6e
8 changed files with 141 additions and 38 deletions

View File

@ -4,6 +4,11 @@ Rhai Release Notes
Version 1.2.0
=============
New features
------------
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
Enhancements
------------

View File

@ -1,6 +1,6 @@
[package]
name = "rhai_codegen"
version = "1.1.1"
version = "1.2.0"
edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust"

View File

@ -129,16 +129,10 @@ pub fn inner_item_attributes<T: ExportedParams>(
}
}
pub fn deny_cfg_attr(attrs: &[syn::Attribute]) -> syn::Result<()> {
if let Some(cfg_attr) = attrs
pub fn collect_cfg_attr(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
attrs
.iter()
.find(|a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
{
Err(syn::Error::new(
cfg_attr.span(),
"cfg attributes not allowed on this item",
))
} else {
Ok(())
}
.filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
.cloned()
.collect()
}

View File

@ -281,6 +281,7 @@ pub struct ExportedFn {
pass_context: bool,
mut_receiver: bool,
params: ExportedFnParams,
cfg_attrs: Vec<syn::Attribute>,
}
impl Parse for ExportedFn {
@ -294,8 +295,7 @@ impl Parse for ExportedFn {
syn::parse2::<syn::Path>(quote! { rhai::NativeCallContext }).unwrap();
let mut pass_context = false;
// #[cfg] attributes are not allowed on functions due to what is generated for them
crate::attrs::deny_cfg_attr(&fn_all.attrs)?;
let cfg_attrs = crate::attrs::collect_cfg_attr(&fn_all.attrs);
let visibility = fn_all.vis;
@ -403,6 +403,7 @@ impl Parse for ExportedFn {
pass_context,
mut_receiver,
params: Default::default(),
cfg_attrs,
})
}
}
@ -414,6 +415,10 @@ impl ExportedFn {
&self.params
}
pub fn cfg_attrs(&self) -> &[syn::Attribute] {
&self.cfg_attrs
}
pub fn update_scope(&mut self, parent_scope: &ExportScope) {
let keep = match (self.params.skip, parent_scope) {
(true, _) => false,
@ -498,6 +503,10 @@ impl ExportedFn {
}
}
pub fn set_cfg_attrs(&mut self, cfg_attrs: Vec<syn::Attribute>) {
self.cfg_attrs = cfg_attrs
}
pub fn set_params(&mut self, mut params: ExportedFnParams) -> syn::Result<()> {
// Several issues are checked here to avoid issues with diagnostics caused by raising them later.
//
@ -831,11 +840,19 @@ impl ExportedFn {
#[cfg(not(feature = "metadata"))]
let param_names = quote! {};
let cfg_attrs: Vec<_> = self
.cfg_attrs()
.iter()
.map(syn::Attribute::to_token_stream)
.collect();
quote! {
#(#cfg_attrs)*
impl #type_name {
#param_names
#[inline(always)] pub fn param_types() -> [TypeId; #arg_count] { [#(#input_type_exprs),*] }
}
#(#cfg_attrs)*
impl PluginFunction for #type_name {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {

View File

@ -88,7 +88,7 @@
//!
use quote::quote;
use syn::parse_macro_input;
use syn::{parse_macro_input, spanned::Spanned};
mod attrs;
mod function;
@ -130,9 +130,19 @@ pub fn export_fn(
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") {
Ok(args) => args,
Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()),
Err(err) => return err.to_compile_error().into(),
};
let mut function_def = parse_macro_input!(input as function::ExportedFn);
if !function_def.cfg_attrs().is_empty() {
return syn::Error::new(
function_def.cfg_attrs()[0].span(),
"`cfg` attributes are not allowed for `export_fn`",
)
.to_compile_error()
.into();
}
if let Err(e) = function_def.set_params(parsed_params) {
return e.to_compile_error().into();
}
@ -173,7 +183,7 @@ pub fn export_module(
) -> proc_macro::TokenStream {
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_module") {
Ok(args) => args,
Err(err) => return proc_macro::TokenStream::from(err.to_compile_error()),
Err(err) => return err.to_compile_error().into(),
};
let mut module_def = parse_macro_input!(input as module::Module);
if let Err(e) = module_def.set_params(parsed_params) {

View File

@ -108,6 +108,7 @@ impl Parse for Module {
let fns: Vec<_>;
let mut consts = Vec::new();
let mut sub_modules = Vec::new();
if let Some((_, ref mut content)) = mod_all.content {
// Gather and parse functions.
fns = content
@ -117,15 +118,13 @@ impl Parse for Module {
_ => None,
})
.try_fold(Vec::new(), |mut vec, item_fn| {
// #[cfg] attributes are not allowed on functions
crate::attrs::deny_cfg_attr(&item_fn.attrs)?;
let params =
crate::attrs::inner_item_attributes(&mut item_fn.attrs, "rhai_fn")?;
syn::parse2::<ExportedFn>(item_fn.to_token_stream())
.and_then(|mut f| {
f.set_params(params)?;
f.set_cfg_attrs(crate::attrs::collect_cfg_attr(&item_fn.attrs));
Ok(f)
})
.map(|f| vec.push(f))
@ -141,14 +140,12 @@ impl Parse for Module {
attrs,
ty,
..
}) => {
// #[cfg] attributes are not allowed on const declarations
crate::attrs::deny_cfg_attr(&attrs)?;
if matches!(vis, syn::Visibility::Public(_)) {
consts.push((ident.to_string(), ty.clone(), expr.as_ref().clone()))
}
}
}) if matches!(vis, syn::Visibility::Public(_)) => consts.push((
ident.to_string(),
ty.clone(),
expr.as_ref().clone(),
crate::attrs::collect_cfg_attr(&attrs),
)),
_ => {}
}
}

View File

@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream};
use quote::quote;
use quote::{quote, ToTokens};
use std::collections::BTreeMap;
@ -10,7 +10,7 @@ use crate::function::{
};
use crate::module::Module;
pub type ExportedConst = (String, Box<syn::Type>, syn::Expr);
pub type ExportedConst = (String, Box<syn::Type>, syn::Expr, Vec<syn::Attribute>);
pub fn generate_body(
fns: &mut [ExportedFn],
@ -25,11 +25,18 @@ pub fn generate_body(
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).unwrap();
for (const_name, _, _) in consts {
for (const_name, _, _, cfg_attrs) in consts {
let const_literal = syn::LitStr::new(&const_name, Span::call_site());
let const_ref = syn::Ident::new(&const_name, Span::call_site());
let cfg_attrs: Vec<_> = cfg_attrs
.iter()
.map(syn::Attribute::to_token_stream)
.collect();
set_const_statements.push(
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_var(#const_literal, #const_ref);
})
.unwrap(),
@ -42,13 +49,8 @@ pub fn generate_body(
continue;
}
let module_name = item_mod.module_name();
let exported_name: syn::LitStr =
syn::LitStr::new(item_mod.exported_name().as_ref(), Span::call_site());
let cfg_attrs: Vec<_> = item_mod
.attrs()
.iter()
.filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
.collect();
let exported_name = syn::LitStr::new(item_mod.exported_name().as_ref(), Span::call_site());
let cfg_attrs = crate::attrs::collect_cfg_attr(item_mod.attrs());
add_mod_blocks.push(
syn::parse2::<syn::ExprBlock>(quote! {
{
@ -126,6 +128,12 @@ pub fn generate_body(
})
.collect();
let cfg_attrs: Vec<_> = function
.cfg_attrs()
.iter()
.map(syn::Attribute::to_token_stream)
.collect();
for fn_literal in reg_names {
let mut namespace = FnNamespaceAccess::Internal;
@ -166,6 +174,7 @@ pub fn generate_body(
set_fn_statements.push(
syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], #fn_token_name().into());
})
@ -174,9 +183,11 @@ pub fn generate_body(
}
gen_fn_tokens.push(quote! {
#(#cfg_attrs)*
#[allow(non_camel_case_types)]
pub struct #fn_token_name();
});
gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string()));
}

View File

@ -1568,6 +1568,72 @@ mod generate_tests {
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_index_getter_fn_with_cfg_attr_module() {
let input_tokens: TokenStream = quote! {
pub mod one_index_fn {
#[cfg(hello)]
#[rhai_fn(index_get)]
#[some_other_attr]
pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT {
x.get(i)
}
}
};
let expected_tokens = quote! {
pub mod one_index_fn {
#[cfg(hello)]
#[some_other_attr]
pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT {
x.get(i)
}
#[allow(unused_imports)]
use super::*;
pub fn rhai_module_generate() -> Module {
let mut m = Module::new();
rhai_generate_into_module(&mut m, false);
m.build_index();
m
}
#[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
#[cfg(hello)]
m.set_fn("index$get$", FnNamespace::Global, FnAccess::Public, Some(get_by_index_token::PARAM_NAMES),
&[TypeId::of::<MyCollection>(), TypeId::of::<u64>()],
get_by_index_token().into());
if flatten {} else {}
}
#[cfg(hello)]
#[allow(non_camel_case_types)]
pub struct get_by_index_token();
#[cfg(hello)]
impl get_by_index_token {
pub const PARAM_NAMES: &'static [&'static str] = &["x: &mut MyCollection", "i: u64", "FLOAT"];
#[inline(always)] pub fn param_types() -> [TypeId; 2usize] { [TypeId::of::<MyCollection>(), TypeId::of::<u64>()] }
}
#[cfg(hello)]
impl PluginFunction for get_by_index_token {
#[inline(always)]
fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult {
if args[0usize].is_read_only() {
return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into());
}
let arg1 = mem::take(args[1usize]).cast::<u64>();
let arg0 = &mut args[0usize].write_lock::<MyCollection>().unwrap();
Ok(Dynamic::from(get_by_index(arg0, arg1)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
}
}
};
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
assert_streams_eq(item_mod.generate(), expected_tokens);
}
#[test]
fn one_index_getter_and_rename_fn_module() {
let input_tokens: TokenStream = quote! {
@ -1811,6 +1877,7 @@ mod generate_tests {
pub const MYSTIC_NUMBER: INT = 42;
}
pub mod second_is {
#[cfg(hello)]
pub const SPECIAL_CPU_NUMBER: INT = 68000;
}
}
@ -1836,6 +1903,7 @@ mod generate_tests {
}
}
pub mod second_is {
#[cfg(hello)]
pub const SPECIAL_CPU_NUMBER: INT = 68000;
#[allow(unused_imports)]
use super::*;
@ -1848,6 +1916,7 @@ mod generate_tests {
}
#[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
#[cfg(hello)]
m.set_var("SPECIAL_CPU_NUMBER", SPECIAL_CPU_NUMBER);
if flatten {} else {}
}