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 Version 1.2.0
============= =============
New features
------------
* `#[cfg(...)]` attributes can now be put directly on plugin functions or function defined in a plugin module.
Enhancements Enhancements
------------ ------------

View File

@ -1,6 +1,6 @@
[package] [package]
name = "rhai_codegen" name = "rhai_codegen"
version = "1.1.1" version = "1.2.0"
edition = "2018" edition = "2018"
authors = ["jhwgh1968", "Stephen Chung"] authors = ["jhwgh1968", "Stephen Chung"]
description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" 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<()> { pub fn collect_cfg_attr(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
if let Some(cfg_attr) = attrs attrs
.iter() .iter()
.find(|a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false)) .filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
{ .cloned()
Err(syn::Error::new( .collect()
cfg_attr.span(),
"cfg attributes not allowed on this item",
))
} else {
Ok(())
}
} }

View File

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

View File

@ -88,7 +88,7 @@
//! //!
use quote::quote; use quote::quote;
use syn::parse_macro_input; use syn::{parse_macro_input, spanned::Spanned};
mod attrs; mod attrs;
mod function; mod function;
@ -130,9 +130,19 @@ pub fn export_fn(
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") { let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") {
Ok(args) => args, 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); 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) { if let Err(e) = function_def.set_params(parsed_params) {
return e.to_compile_error().into(); return e.to_compile_error().into();
} }
@ -173,7 +183,7 @@ pub fn export_module(
) -> proc_macro::TokenStream { ) -> proc_macro::TokenStream {
let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_module") { let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_module") {
Ok(args) => args, 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); let mut module_def = parse_macro_input!(input as module::Module);
if let Err(e) = module_def.set_params(parsed_params) { if let Err(e) = module_def.set_params(parsed_params) {

View File

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

View File

@ -1,5 +1,5 @@
use proc_macro2::{Span, TokenStream}; use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::{quote, ToTokens};
use std::collections::BTreeMap; use std::collections::BTreeMap;
@ -10,7 +10,7 @@ use crate::function::{
}; };
use crate::module::Module; 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( pub fn generate_body(
fns: &mut [ExportedFn], fns: &mut [ExportedFn],
@ -25,11 +25,18 @@ pub fn generate_body(
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap(); let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
let string_type_path = syn::parse2::<syn::Path>(quote! { String }).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_literal = syn::LitStr::new(&const_name, Span::call_site());
let const_ref = syn::Ident::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( set_const_statements.push(
syn::parse2::<syn::Stmt>(quote! { syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_var(#const_literal, #const_ref); m.set_var(#const_literal, #const_ref);
}) })
.unwrap(), .unwrap(),
@ -42,13 +49,8 @@ pub fn generate_body(
continue; continue;
} }
let module_name = item_mod.module_name(); let module_name = item_mod.module_name();
let exported_name: syn::LitStr = let exported_name = syn::LitStr::new(item_mod.exported_name().as_ref(), Span::call_site());
syn::LitStr::new(item_mod.exported_name().as_ref(), Span::call_site()); let cfg_attrs = crate::attrs::collect_cfg_attr(item_mod.attrs());
let cfg_attrs: Vec<_> = item_mod
.attrs()
.iter()
.filter(|&a| a.path.get_ident().map(|i| *i == "cfg").unwrap_or(false))
.collect();
add_mod_blocks.push( add_mod_blocks.push(
syn::parse2::<syn::ExprBlock>(quote! { syn::parse2::<syn::ExprBlock>(quote! {
{ {
@ -126,6 +128,12 @@ pub fn generate_body(
}) })
.collect(); .collect();
let cfg_attrs: Vec<_> = function
.cfg_attrs()
.iter()
.map(syn::Attribute::to_token_stream)
.collect();
for fn_literal in reg_names { for fn_literal in reg_names {
let mut namespace = FnNamespaceAccess::Internal; let mut namespace = FnNamespaceAccess::Internal;
@ -166,6 +174,7 @@ pub fn generate_body(
set_fn_statements.push( set_fn_statements.push(
syn::parse2::<syn::Stmt>(quote! { syn::parse2::<syn::Stmt>(quote! {
#(#cfg_attrs)*
m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public, m.set_fn(#fn_literal, FnNamespace::#ns_str, FnAccess::Public,
#param_names, &[#(#fn_input_types),*], #fn_token_name().into()); #param_names, &[#(#fn_input_types),*], #fn_token_name().into());
}) })
@ -174,9 +183,11 @@ pub fn generate_body(
} }
gen_fn_tokens.push(quote! { gen_fn_tokens.push(quote! {
#(#cfg_attrs)*
#[allow(non_camel_case_types)] #[allow(non_camel_case_types)]
pub struct #fn_token_name(); pub struct #fn_token_name();
}); });
gen_fn_tokens.push(function.generate_impl(&fn_token_name.to_string())); 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); 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] #[test]
fn one_index_getter_and_rename_fn_module() { fn one_index_getter_and_rename_fn_module() {
let input_tokens: TokenStream = quote! { let input_tokens: TokenStream = quote! {
@ -1811,6 +1877,7 @@ mod generate_tests {
pub const MYSTIC_NUMBER: INT = 42; pub const MYSTIC_NUMBER: INT = 42;
} }
pub mod second_is { pub mod second_is {
#[cfg(hello)]
pub const SPECIAL_CPU_NUMBER: INT = 68000; pub const SPECIAL_CPU_NUMBER: INT = 68000;
} }
} }
@ -1836,6 +1903,7 @@ mod generate_tests {
} }
} }
pub mod second_is { pub mod second_is {
#[cfg(hello)]
pub const SPECIAL_CPU_NUMBER: INT = 68000; pub const SPECIAL_CPU_NUMBER: INT = 68000;
#[allow(unused_imports)] #[allow(unused_imports)]
use super::*; use super::*;
@ -1848,6 +1916,7 @@ mod generate_tests {
} }
#[allow(unused_mut)] #[allow(unused_mut)]
pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) {
#[cfg(hello)]
m.set_var("SPECIAL_CPU_NUMBER", SPECIAL_CPU_NUMBER); m.set_var("SPECIAL_CPU_NUMBER", SPECIAL_CPU_NUMBER);
if flatten {} else {} if flatten {} else {}
} }