From 3f2dd23e6eb51746ff4cab53370ccfe55f3bfcf4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 20 Oct 2021 15:30:11 +0800 Subject: [PATCH] Allow #[cfg(...)] in plugin functions. --- CHANGELOG.md | 5 +++ codegen/Cargo.toml | 2 +- codegen/src/attrs.rs | 16 +++------ codegen/src/function.rs | 21 ++++++++++-- codegen/src/lib.rs | 16 +++++++-- codegen/src/module.rs | 19 +++++------ codegen/src/rhai_module.rs | 31 +++++++++++------ codegen/src/test/module.rs | 69 ++++++++++++++++++++++++++++++++++++++ 8 files changed, 141 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2999ebc6..f9b992fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 ------------ diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index ac70efee..472c5ca1 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -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" diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index 57d18c96..977e73de 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -129,16 +129,10 @@ pub fn inner_item_attributes( } } -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 { + 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() } diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 73bda67a..5e70c8c8 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -281,6 +281,7 @@ pub struct ExportedFn { pass_context: bool, mut_receiver: bool, params: ExportedFnParams, + cfg_attrs: Vec, } impl Parse for ExportedFn { @@ -294,8 +295,7 @@ impl Parse for ExportedFn { syn::parse2::(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) { + 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 { diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index d985757b..e2ae077b 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -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) { diff --git a/codegen/src/module.rs b/codegen/src/module.rs index bb791ef6..75099218 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -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::(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), + )), _ => {} } } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 29366301..68691285 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -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::Expr); +pub type ExportedConst = (String, Box, syn::Expr, Vec); pub fn generate_body( fns: &mut [ExportedFn], @@ -25,11 +25,18 @@ pub fn generate_body( let str_type_path = syn::parse2::(quote! { str }).unwrap(); let string_type_path = syn::parse2::(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::(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::(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::(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())); } diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index 9075818c..3ada9401 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -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::(), TypeId::of::()], + 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::(), TypeId::of::()] } + } + #[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::(); + let arg0 = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(get_by_index(arg0, arg1))) + } + + #[inline(always)] fn is_method_call(&self) -> bool { true } + } + } + }; + + let item_mod = syn::parse2::(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 {} }