From c5b2debf4a7cf0b44db800ffb5f3c268975fa99e Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Mon, 31 Aug 2020 19:03:45 -0500 Subject: [PATCH 01/19] Book updates for Plugins --- doc/src/plugins/function.md | 11 ++- doc/src/plugins/index.md | 10 ++- doc/src/plugins/module.md | 155 +++++++++++++++++++----------------- 3 files changed, 92 insertions(+), 84 deletions(-) diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 43af9e13..888d7220 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -1,5 +1,5 @@ -Create a Plugin Function -======================== +Exporting a Rust Function to Rhai +================================= {{#include ../links.md}} @@ -13,10 +13,9 @@ Macros | Macro | Apply to | Behavior | | ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- | -| `#[export_fn]` | Rust function defined in module | Export the function | -| `#[rhai_fn(return_raw)]` | Rust function returning `Result>` | Specify that this is a [fallible function] | -| `register_exported_fn!` | [`Engine`] instance, register name, function name | Register function into the [`Engine`] under specific name | -| `set_exported_fn!` | [`Module`], register name, function name | Register function into the [`Module`] under specific name | +| `#[export_fn]` | Rust function defined in a Rust module | Export the function | +| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name | `#[export_fn]` and `register_exported_fn!` diff --git a/doc/src/plugins/index.md b/doc/src/plugins/index.md index 353dedd3..04fb1d35 100644 --- a/doc/src/plugins/index.md +++ b/doc/src/plugins/index.md @@ -3,9 +3,11 @@ Plugins {{#include ../links.md}} -Rhai contains a robust _plugin_ system that greatly simplifies registration of custom functions. +Rhai contains a robust _plugin_ system that greatly simplifies registration of custom +functionality. -Instead of the large `Engine::register_XXX` API, and the parallel `Module::set_fn_XXX` API, -a _plugin_ simplifies the work of creating and registering multiple functions into an [`Engine`]. +Instead of using the large `Engine::register_XXX` API or the parallel `Module::set_fn_XXX` API, +a _plugin_ simplifies the work of creating and registering new functionality in an [`Engine`]. -Plugins are processed via a set of procedural macros under the `rhai::plugins` module. +Plugins are processed via a set of procedural macros under the `rhai::plugins` module. These +allow registering Rust functions directly in the Engine, or adding Rust modules as packages. diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index 22380425..b26eff3d 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -1,43 +1,26 @@ -Create a Plugin Module -====================== +Exporting a Rust Module to Rhai +=============================== {{#include ../links.md}} -The core of creating a plugin [module] is the `#[export_module]` attribute. +When applied to a Rust module, the `#[export_module]` attribute will generate the necessary +code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions. This code +is exactly what would need to be written by hand to achieve the same goal, and is custom fit +to each exported item. -When applied on a module definition, `#[export_module]` automatically generates Rhai-acceptable -functions from all `pub` functions defined within. - -The resulting module can then be loaded into an [`Engine`] as a normal [module], -or as a [custom package]. +This Rust module can then either be loaded into an [`Engine`] as a normal [module] or +registered as a [custom package]. This is done by using the `exported_module!` macro. -Macros ------- - -| Macro | Apply to | Behavior | -| --------------------------- | ----------------------------------------------------------------------------- | ----------------------------------------------- | -| `#[export_module]` | Rust module | Export all `pub` functions | -| `#[rhai_fn(skip)]` | Function in Rust module | Do not export this function | -| `#[rhai_fn(return_raw)]` | `pub` function in Rust module returning `Result>` | Specify that this is a [fallible function] | -| `#[rhai_fn(name = "...")]` | `pub` function in Rust module | Register function under specific name | -| `#[rhai_fn(get = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property getter under specific name | -| `#[rhai_fn(set = "...")]` | `pub` function in Rust module (first parameter must be `&mut`) | Register a property setter under specific name | -| `#[rhai_fn(index_get]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index getter | -| `#[rhai_fn(index_set)]` | `pub` function in Rust module (first parameter must be `&mut`) | Register an index setter | -| `#[rhai_mod(name = "...")]` | `pub` sub-module in Rust module | Export the sub-module under specific name | -| `exported_module!` | Rust module name | Create a [module] containing exported functions | - - -`#[export_module]` and `exported_module!` +Using`#[export_module]` and `exported_module!` ---------------------------------------- -Apply `#[export_module]` onto a standard module to convert all `pub` functions -into Rhai plugin functions. +Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin +functions. ```rust -use rhai::plugins::*; // import macros +use rhai::plugins::*; // a "prelude" import for macros #[export_module] mod my_module { @@ -58,22 +41,24 @@ mod my_module { 42 } } +``` +In order to load this into an [`Engine`], use the `load_package` method on the exported module: + +```rust fn main() { let mut engine = Engine::new(); - // 'exported_module!' creates the plugin module. + // The macro call creates the Rhai module. let module = exported_module!(my_module); - // A module can simply be loaded as a custom package. + // A module can simply be loaded, registering all public its contents. engine.load_package(module); } ``` -The above automatically defines a plugin module named `my_module` which can be converted into -a Rhai [module] via `exported_module!`. The functions contained within the module definition -(i.e. `greet`, `get_num` and `increment`) are automatically registered into the [`Engine`] when -`Engine::load_package` is called. +The functions contained within the module definition (i.e. `greet`, `get_num` and `increment`) +are automatically registered into the [`Engine`] when `Engine::load_package` is called. ```rust let x = greet("world"); @@ -89,6 +74,47 @@ increment(x); x == 43; ``` +Registering this as a custom package is almost the same, except that a module resolver must +point to the module, rather than being loaded directly. See the [module] section for more +information. + + +Function Overloading and Operators +--------------------------------- + +Operators and overloaded functions can be specified via applying the `#[rhai_fn(name = "...")]` +attribute to individual functions. + +The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with +the [`Engine`], disregarding the actual name of the function. + +With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai, so long as they have different parameters. + +Operators (which require function names that are not valid for Rust) can also be registered this way. + +```rust +use rhai::plugins::*; // a "prelude" import for macros + +#[export_module] +mod my_module { + // This is the '+' operator for 'MyType'. + #[rhai_fn(name = "+")] + pub fn add(obj: &mut MyType, value: i64) { + obj.prop += value; + } + // This function is 'calc (i64)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_default(num: i64) -> i64 { + ... + } + // This function is 'calc (i64, bool)'. + #[rhai_fn(name = "calc")] + pub fn calc_with_option(num: i64, option: bool) -> i64 { + ... + } +} +``` + Getters, Setters and Indexers ----------------------------- @@ -97,7 +123,7 @@ Functions can be marked as [getters/setters] and [indexers] for [custom types] v attribute, which is applied on a function level. ```rust -use rhai::plugins::*; // import macros +use rhai::plugins::*; // a "prelude" import for macros #[export_module] mod my_module { @@ -129,43 +155,6 @@ mod my_module { ``` -Function Overloading and Operators ---------------------------------- - -Operators and overloaded functions can be specified via `#[rhai_fn(name = "...")]` applied upon -individual functions. - -The text string given as the `name` parameter to `#[rhai_fn]` is used to register the function with -the [`Engine`], disregarding the actual name of the function. - -With `#[rhai_fn(name = "...")]`, multiple functions may be registered under the same name in Rhai. - -Operators (which require function names that are not valid for Rust) can also be registered this way. - -```rust -use rhai::plugins::*; // import macros - -#[export_module] -mod my_module { - // This is the '+' operator for 'MyType'. - #[rhai_fn(name = "+")] - pub fn add(obj: &mut MyType, value: i64) { - obj.prop += value; - } - // This function is 'calc (i64)'. - #[rhai_fn(name = "calc")] - pub fn calc_with_default(num: i64) -> i64 { - ... - } - // This function is 'calc (i64, bool)'. - #[rhai_fn(name = "calc")] - pub fn calc_with_option(num: i64, option: bool) -> i64 { - ... - } -} -``` - - Fallible Functions ------------------ @@ -176,7 +165,7 @@ A syntax error is generated if the function with `#[rhai_fn(return_raw)]` does n have the appropriate return type. ```rust -use rhai::plugins::*; // import macros +use rhai::plugins::*; // a "prelude" import for macros #[export_module] mod my_module { @@ -192,3 +181,21 @@ mod my_module { } } ``` + + +Inner Attributes +------ + +As shown above, inner attributes can be applied to inner items to tweak the export process. `#[rhai_fn]` is applied to functions, and `#[rhai_mod]` is applied to inner modules. + +Here is the complete list of parameters currently supported: + +| Attribute | Use with | Apply to | Behavior | +| ----------------------------- | ------------ | -------------------------------------------------------------------- | ----------------------------------------------- | +| `skip` | `#[rhai_fn]` `#[rhai_mod]` | Function or submodule| Do not export this item | +| `name = "..."` | `#[rhai_fn]` `#[rhai_mod]` | `pub` item | Register under the specified name | +| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | Register a getter for the named property | +| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | Register a setter for the named property | +| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | Register an index getter | +| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | Register an index setter | +| `return_raw` | `#[rhai_fn]` | function returning `Result>` | Mark this as a [fallible function] | From 3af49cec70c444f4291bbc271bc73c71334f78ed Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 1 Sep 2020 23:15:22 -0500 Subject: [PATCH 02/19] Implement export_* attributes for macros --- codegen/src/attrs.rs | 13 +++ codegen/src/function.rs | 26 +++++- codegen/src/lib.rs | 12 ++- codegen/src/module.rs | 91 +++++++++++-------- codegen/src/rhai_module.rs | 8 +- codegen/tests/test_modules.rs | 165 ++++++++++++++++++++++++++++++++++ 6 files changed, 274 insertions(+), 41 deletions(-) diff --git a/codegen/src/attrs.rs b/codegen/src/attrs.rs index 1850d294..5d1e42d7 100644 --- a/codegen/src/attrs.rs +++ b/codegen/src/attrs.rs @@ -1,5 +1,18 @@ use syn::{parse::ParseStream, parse::Parser, spanned::Spanned}; +#[derive(Debug)] +pub enum ExportScope { + PubOnly, + Prefix(String), + All, +} + +impl Default for ExportScope { + fn default() -> ExportScope { + ExportScope::PubOnly + } +} + pub trait ExportedParams: Sized { fn parse_stream(args: ParseStream) -> syn::Result; fn no_attrs() -> Self; diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 337eccab..f5bb6989 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -10,10 +10,12 @@ use alloc::format; #[cfg(not(no_std))] use std::format; +use std::borrow::Cow; + use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; -use crate::attrs::{ExportInfo, ExportedParams}; +use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { @@ -222,10 +224,24 @@ impl ExportedFn { &self.params } + pub(crate) fn update_scope(&mut self, parent_scope: &ExportScope) { + let keep = match (self.params.skip, parent_scope) { + (true, _) => false, + (_, ExportScope::PubOnly) => self.is_public, + (_, ExportScope::Prefix(s)) => self.exported_name().as_ref().starts_with(s), + (_, ExportScope::All) => true, + }; + self.params.skip = !keep; + } + pub(crate) fn skipped(&self) -> bool { self.params.skip } + pub(crate) fn signature(&self) -> &syn::Signature { + &self.signature + } + pub(crate) fn mutable_receiver(&self) -> bool { self.mut_receiver } @@ -242,6 +258,14 @@ impl ExportedFn { &self.signature.ident } + pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> { + if let Some(ref name) = self.params.name { + Cow::Borrowed(name.as_str()) + } else { + Cow::Owned(self.signature.ident.to_string()) + } + } + pub(crate) fn arg_list(&self) -> impl Iterator { self.signature.inputs.iter() } diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 7054cbe5..9e47a831 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -124,10 +124,18 @@ pub fn export_fn( #[proc_macro_attribute] pub fn export_module( - _args: proc_macro::TokenStream, + args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let module_def = parse_macro_input!(input as module::Module); + 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()), + }; + let mut module_def = parse_macro_input!(input as module::Module); + if let Err(e) = module_def.set_params(parsed_params) { + return e.to_compile_error().into(); + } + let tokens = module_def.generate(); proc_macro::TokenStream::from(tokens) } diff --git a/codegen/src/module.rs b/codegen/src/module.rs index b1a76f29..79014b8d 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -16,13 +16,14 @@ use std::mem; use std::borrow::Cow; -use crate::attrs::{AttrItem, ExportInfo, ExportedParams}; -use crate::function::{ExportedFnParams}; +use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams}; +use crate::function::ExportedFnParams; #[derive(Debug, Default)] pub(crate) struct ExportedModParams { pub name: Option, - pub skip: bool, + skip: bool, + pub scope: ExportScope, } impl Parse for ExportedModParams { @@ -50,6 +51,7 @@ impl ExportedParams for ExportedModParams { let ExportInfo { items: attrs, .. } = info; let mut name = None; let mut skip = false; + let mut scope = ExportScope::default(); for attr in attrs { let AttrItem { key, value } = attr; match (key.to_string().as_ref(), value) { @@ -57,6 +59,14 @@ impl ExportedParams for ExportedModParams { ("name", None) => return Err(syn::Error::new(key.span(), "requires value")), ("skip", None) => skip = true, ("skip", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), + ("export_prefix", Some(s)) => scope = ExportScope::Prefix(s.value()), + ("export_prefix", None) => { + return Err(syn::Error::new(key.span(), "requires value")) + } + ("export_all", None) => scope = ExportScope::All, + ("export_all", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } (attr, _) => { return Err(syn::Error::new( key.span(), @@ -69,6 +79,7 @@ impl ExportedParams for ExportedModParams { Ok(ExportedModParams { name, skip, + scope, ..Default::default() }) } @@ -83,6 +94,13 @@ pub(crate) struct Module { params: ExportedModParams, } +impl Module { + pub fn set_params(&mut self, params: ExportedModParams) -> syn::Result<()> { + self.params = params; + Ok(()) + } +} + impl Parse for Module { fn parse(input: ParseStream) -> syn::Result { let mut mod_all: syn::ItemMod = input.parse()?; @@ -101,16 +119,11 @@ impl Parse for Module { // #[cfg] attributes are not allowed on functions crate::attrs::deny_cfg_attr(&itemfn.attrs)?; - let mut params: ExportedFnParams = + let params: ExportedFnParams = match crate::attrs::inner_item_attributes(&mut itemfn.attrs, "rhai_fn") { Ok(p) => p, Err(e) => return Err(e), }; - params.skip = if let syn::Visibility::Public(_) = itemfn.vis { - params.skip - } else { - true - }; syn::parse2::(itemfn.to_token_stream()) .and_then(|mut f| { f.set_params(params)?; @@ -150,20 +163,15 @@ impl Parse for Module { syn::Item::Mod(m) => m, _ => unreachable!(), }; - let mut params: ExportedModParams = + let params: ExportedModParams = match crate::attrs::inner_item_attributes(&mut itemmod.attrs, "rhai_mod") { Ok(p) => p, Err(e) => return Err(e), }; - params.skip = if let syn::Visibility::Public(_) = itemmod.vis { - params.skip - } else { - true - }; let module = - syn::parse2::(itemmod.to_token_stream()).map(|mut f| { - f.params = params; - f + syn::parse2::(itemmod.to_token_stream()).and_then(|mut m| { + m.set_params(params)?; + Ok(m) })?; submodules.push(module); } else { @@ -200,6 +208,28 @@ impl Module { } } + pub fn update_scope(&mut self, parent_scope: &ExportScope) { + let keep = match (self.params.skip, parent_scope) { + (true, _) => false, + (_, ExportScope::PubOnly) => { + if let Some(ref mod_all) = self.mod_all { + matches!(mod_all.vis, syn::Visibility::Public(_)) + } else { + false + } + } + (_, ExportScope::Prefix(s)) => { + if let Some(ref mod_all) = self.mod_all { + mod_all.ident.to_string().starts_with(s) + } else { + false + } + } + (_, ExportScope::All) => true, + }; + self.params.skip = !keep; + } + pub fn skipped(&self) -> bool { self.params.skip } @@ -218,7 +248,7 @@ impl Module { // Extract the current structure of the module. let Module { mod_all, - fns, + mut fns, consts, mut submodules, params, @@ -233,7 +263,12 @@ impl Module { // Generate new module items. // // This is done before inner module recursive generation, because that is destructive. - let mod_gen = crate::rhai_module::generate_body(&fns, &consts, &submodules); + let mod_gen = crate::rhai_module::generate_body( + &mut fns, + &consts, + &mut submodules, + ¶ms.scope, + ); // NB: submodules must have their new items for exporting generated in depth-first order // to avoid issues caused by re-parsing them @@ -454,22 +489,6 @@ mod module_tests { ); } - #[test] - fn one_private_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - fn get_mystic_number() -> INT { - 42 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_eq!(item_mod.fns.len(), 1); - assert!(item_mod.fns[0].skipped()); - assert!(item_mod.consts.is_empty()); - } - #[test] fn one_skipped_fn_module() { let input_tokens: TokenStream = quote! { diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 7e1aa106..40485adb 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -2,15 +2,17 @@ use std::collections::HashMap; use quote::{quote, ToTokens}; +use crate::attrs::ExportScope; use crate::function::ExportedFn; use crate::module::Module; pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) fn generate_body( - fns: &[ExportedFn], + fns: &mut [ExportedFn], consts: &[ExportedConst], - submodules: &[Module], + submodules: &mut [Module], + parent_scope: &ExportScope, ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); @@ -28,6 +30,7 @@ pub(crate) fn generate_body( } for itemmod in submodules { + itemmod.update_scope(&parent_scope); if itemmod.skipped() { continue; } @@ -56,6 +59,7 @@ pub(crate) fn generate_body( // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { + function.update_scope(&parent_scope); if function.skipped() { continue; } diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index dd5acd05..3ce1bf37 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -221,3 +221,168 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { assert_eq!(&output_array[1].as_int().unwrap(), &43); Ok(()) } + +mod export_by_prefix { + use rhai::plugin::*; + #[export_module(export_prefix = "foo_")] + pub mod my_adds { + use rhai::{FLOAT, INT}; + + #[rhai_fn(name = "foo_add_f")] + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + #[rhai_fn(name = "foo_add_i")] + fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + + #[rhai_fn(name = "bar_add")] + pub fn add_float2(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn foo_m(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + fn foo_n(i1: INT, i2: INT) -> INT { + i1 + i2 + } + + pub fn bar_m(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + } +} + +#[test] +fn export_by_prefix_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::export_by_prefix::my_adds); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::foo_add_f(ex, 1.0); + let gx = math::foo_m(41.0, 1.0); + let ei = 41; + let fi = math::foo_add_i(ei, 1); + let gi = math::foo_n(41, 1); + [fx, gx, fi, gi] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &42.0); + assert_eq!(&output_array[1].as_float().unwrap(), &42.0); + assert_eq!(&output_array[2].as_int().unwrap(), &42); + assert_eq!(&output_array[3].as_int().unwrap(), &42); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::bar_add(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::bar_add (f64, f64)" + && p == rhai::Position::new(3, 23))); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::add_float2(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::add_float2 (f64, f64)" + && p == rhai::Position::new(3, 23))); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::bar_m(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::bar_m (f64, f64)" + && p == rhai::Position::new(3, 23))); + + Ok(()) +} + +mod export_all { + use rhai::plugin::*; + #[export_module(export_all)] + pub mod my_adds { + use rhai::{FLOAT, INT}; + + #[rhai_fn(name = "foo_add_f")] + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + #[rhai_fn(name = "foo_add_i")] + fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + + #[rhai_fn(skip)] + pub fn add_float2(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn foo_m(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + fn foo_n(i1: INT, i2: INT) -> INT { + i1 + i2 + } + + #[rhai_fn(skip)] + fn foo_p(i1: INT, i2: INT) -> INT { + i1 * i2 + } + } +} + +#[test] +fn export_all_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::export_all::my_adds); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::foo_add_f(ex, 1.0); + let gx = math::foo_m(41.0, 1.0); + let ei = 41; + let fi = math::foo_add_i(ei, 1); + let gi = math::foo_n(41, 1); + [fx, gx, fi, gi] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &42.0); + assert_eq!(&output_array[1].as_float().unwrap(), &42.0); + assert_eq!(&output_array[2].as_int().unwrap(), &42); + assert_eq!(&output_array[3].as_int().unwrap(), &42); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41; + let fx = math::foo_p(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::foo_p (i64, i64)" + && p == rhai::Position::new(3, 23))); + + Ok(()) +} From 5585405bdc131c40e448a79dfd10eb67adccb0c9 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 2 Sep 2020 20:10:52 -0500 Subject: [PATCH 03/19] Make export_prefix use fn name not export name --- codegen/src/function.rs | 2 +- codegen/tests/test_modules.rs | 24 +++++++----------------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index f5bb6989..9c005554 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -228,7 +228,7 @@ impl ExportedFn { let keep = match (self.params.skip, parent_scope) { (true, _) => false, (_, ExportScope::PubOnly) => self.is_public, - (_, ExportScope::Prefix(s)) => self.exported_name().as_ref().starts_with(s), + (_, ExportScope::Prefix(s)) => self.name().to_string().starts_with(s), (_, ExportScope::All) => true, }; self.params.skip = !keep; diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 3ce1bf37..f873e42b 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -229,16 +229,16 @@ mod export_by_prefix { use rhai::{FLOAT, INT}; #[rhai_fn(name = "foo_add_f")] - pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + pub fn foo_add1(f1: FLOAT, f2: FLOAT) -> FLOAT { f1 + f2 } - #[rhai_fn(name = "foo_add_i")] - fn add_int(i1: INT, i2: INT) -> INT { + #[rhai_fn(name = "bar_add_i")] + fn foo_add_int(i1: INT, i2: INT) -> INT { i1 + i2 } - #[rhai_fn(name = "bar_add")] + #[rhai_fn(name = "foo_add_float2")] pub fn add_float2(f1: FLOAT, f2: FLOAT) -> FLOAT { f1 + f2 } @@ -271,7 +271,7 @@ fn export_by_prefix_test() -> Result<(), Box> { let fx = math::foo_add_f(ex, 1.0); let gx = math::foo_m(41.0, 1.0); let ei = 41; - let fi = math::foo_add_i(ei, 1); + let fi = math::bar_add_i(ei, 1); let gi = math::foo_n(41, 1); [fx, gx, fi, gi] "#, @@ -284,21 +284,11 @@ fn export_by_prefix_test() -> Result<(), Box> { assert!(matches!(*engine.eval::( r#"import "Math::Advanced" as math; let ex = 41.0; - let fx = math::bar_add(ex, 1.0); + let fx = math::foo_add_float2(ex, 1.0); fx "#).unwrap_err(), EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::bar_add (f64, f64)" - && p == rhai::Position::new(3, 23))); - - assert!(matches!(*engine.eval::( - r#"import "Math::Advanced" as math; - let ex = 41.0; - let fx = math::add_float2(ex, 1.0); - fx - "#).unwrap_err(), - EvalAltResult::ErrorFunctionNotFound(s, p) - if s == "math::add_float2 (f64, f64)" + if s == "math::foo_add_float2 (f64, f64)" && p == rhai::Position::new(3, 23))); assert!(matches!(*engine.eval::( From 0b3fef66515f99baf45df6068c9f5343f4e213bd Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Wed, 2 Sep 2020 20:31:16 -0500 Subject: [PATCH 04/19] Add nested module export_prefix test --- codegen/tests/test_nested.rs | 132 ++++++++++++++++++++++++++++++++++- 1 file changed, 131 insertions(+), 1 deletion(-) diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index bf0c0030..f0144990 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -1,5 +1,5 @@ use rhai::module_resolvers::*; -use rhai::{Engine, EvalAltResult, RegisterFn, FLOAT, INT}; +use rhai::{Array, Engine, EvalAltResult, RegisterFn, FLOAT, INT}; pub mod one_fn_module_nested_attr { use rhai::plugin::*; @@ -70,3 +70,133 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { ); Ok(()) } + +mod export_nested_by_prefix { + use rhai::plugin::*; + #[export_module(export_prefix = "foo_")] + + pub mod my_adds { + pub mod foo_first_adders { + use rhai::{FLOAT, INT}; + + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } + + pub mod foo_second_adders { + use rhai::{FLOAT, INT}; + + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } + + #[rhai_mod(name = "foo_third_adders")] + pub mod baz_third_adders { + use rhai::{FLOAT, INT}; + + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } + + pub mod bar_fourth_adders { + use rhai::{FLOAT, INT}; + + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 + } + + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 + } + } + } +} + +#[test] +fn export_nested_by_prefix_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + let output_array = engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::foo_first_adders::add_float(ex, 1.0); + + let ei = 41; + let fi = math::foo_first_adders::add_int(ei, 1); + + let gx = 41.0; + let hx = math::foo_second_adders::add_float(gx, 1.0); + + let gi = 41; + let hi = math::foo_second_adders::add_int(gi, 1); + + [fx, hx, fi, hi] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &42.0); + assert_eq!(&output_array[1].as_float().unwrap(), &42.0); + assert_eq!(&output_array[2].as_int().unwrap(), &42); + assert_eq!(&output_array[3].as_int().unwrap(), &42); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::foo_third_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::foo_third_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 41))); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41; + let fx = math::foo_third_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::foo_third_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 41))); + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41; + let fx = math::bar_fourth_adders::add_int(ex, 1); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::bar_fourth_adders::add_int (i64, i64)" + && p == rhai::Position::new(3, 42))); + + + assert!(matches!(*engine.eval::( + r#"import "Math::Advanced" as math; + let ex = 41.0; + let fx = math::bar_fourth_adders::add_float(ex, 1.0); + fx + "#).unwrap_err(), + EvalAltResult::ErrorFunctionNotFound(s, p) + if s == "math::bar_fourth_adders::add_float (f64, f64)" + && p == rhai::Position::new(3, 42))); + + Ok(()) +} From ae953315fe0835616459c3cf107a0fc10807973b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 4 Sep 2020 15:42:31 +0800 Subject: [PATCH 05/19] Refine plugins doc. --- RELEASES.md | 4 ---- doc/src/SUMMARY.md | 4 ++-- doc/src/plugins/function.md | 14 +++++------ doc/src/plugins/module.md | 46 ++++++++++++++++++++++++------------- tests/plugins.rs | 2 ++ 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index da91ff68..69044fc5 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -20,10 +20,6 @@ Bug fixes * Imported modules now work inside closures. * Closures that capture now work under `no_object`. - -Version 0.18.2 -============== - New features ------------ diff --git a/doc/src/SUMMARY.md b/doc/src/SUMMARY.md index c6457b52..db96844b 100644 --- a/doc/src/SUMMARY.md +++ b/doc/src/SUMMARY.md @@ -46,8 +46,8 @@ The Rhai Scripting Language 2. [Load a Plugin Module as a Package](rust/packages/plugin.md) 3. [Manually Create a Custom Package](rust/packages/create.md) 10. [Plugins](plugins/index.md) - 1. [Create a Plugin Module](plugins/module.md) - 2. [Create a Plugin Function](plugins/function.md) + 1. [Export a Rust Module](plugins/module.md) + 2. [Export a Rust Function](plugins/function.md) 5. [Rhai Language Reference](language/index.md) 1. [Comments](language/comments.md) 2. [Values and Types](language/values-and-types.md) diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 888d7220..391d97fb 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -1,5 +1,5 @@ -Exporting a Rust Function to Rhai -================================= +Export a Rust Function to Rhai +============================= {{#include ../links.md}} @@ -11,11 +11,11 @@ individual functions instead of a full-blown [plugin module]. Macros ------ -| Macro | Apply to | Behavior | -| ------------------------ | ------------------------------------------------------------- | --------------------------------------------------------- | -| `#[export_fn]` | Rust function defined in a Rust module | Export the function | -| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name | -| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name | +| Macro | Apply to | Behavior | +| ----------------------- | --------------------------------------------------------------- | -------------------------------------------------------- | +| `#[export_fn]` | Rust function defined in a Rust module | Export the function | +| `register_exported_fn!` | [`Engine`] instance, register name string, use path to function | Register function into an [`Engine`] under specific name | +| `set_exported_fn!` | [`Module`] instance, register name string, use path to function | Register function into an [`Module`] under specific name | `#[export_fn]` and `register_exported_fn!` diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index b26eff3d..9f510998 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -1,10 +1,10 @@ -Exporting a Rust Module to Rhai -=============================== +Export a Rust Module to Rhai +============================ {{#include ../links.md}} -When applied to a Rust module, the `#[export_module]` attribute will generate the necessary +When applied to a Rust module, the `#[export_module]` attribute generates the necessary code and metadata to allow Rhai access to its public (i.e. marked `pub`) functions. This code is exactly what would need to be written by hand to achieve the same goal, and is custom fit to each exported item. @@ -14,7 +14,7 @@ registered as a [custom package]. This is done by using the `exported_module!` m Using`#[export_module]` and `exported_module!` ----------------------------------------- +--------------------------------------------- Apply `#[export_module]` onto a Rust module to convert all `pub` functions into Rhai plugin functions. @@ -183,19 +183,33 @@ mod my_module { ``` +`#[export_module]` Parameters +---------------------------- + +Parameters can be applied to the `#[export_module]` attribute to override its default behavior. + +| Parameter | Behavior | +| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- | +| _None_ | Export only public (i.e. `pub`) functions | +| `export_all` | Export all functions (including private, non-`pub` functions); use `#[rhai_fn(skip)]` on individual functions to avoid export | +| `export_prefix = "..."` | Export functions (including private, non-`pub` functions) with names starting with a specific prefix | + + Inner Attributes ------- +---------------- -As shown above, inner attributes can be applied to inner items to tweak the export process. `#[rhai_fn]` is applied to functions, and `#[rhai_mod]` is applied to inner modules. +Inner attributes can be applied to the inner items of a module to tweak the export process. -Here is the complete list of parameters currently supported: +`#[rhai_fn]` is applied to functions, while `#[rhai_mod]` is applied to sub-modules. -| Attribute | Use with | Apply to | Behavior | -| ----------------------------- | ------------ | -------------------------------------------------------------------- | ----------------------------------------------- | -| `skip` | `#[rhai_fn]` `#[rhai_mod]` | Function or submodule| Do not export this item | -| `name = "..."` | `#[rhai_fn]` `#[rhai_mod]` | `pub` item | Register under the specified name | -| `get = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | Register a getter for the named property | -| `set = "..."` | `#[rhai_fn]` | function with `&mut` first parameter | Register a setter for the named property | -| `index_get` | `#[rhai_fn]` | function with `&mut` first parameter | Register an index getter | -| `index_set` | `#[rhai_fn]` | function with `&mut` first parameter | Register an index setter | -| `return_raw` | `#[rhai_fn]` | function returning `Result>` | Mark this as a [fallible function] | +Parameters should be set on inner attributes to specify the desired behavior. + +| Attribute Parameter | Use with | Apply to | Behavior | +| ------------------- | --------------------------- | -------------------------------------------------------- | ----------------------------------------------------- | +| `skip` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Do not export this function/sub-module | +| `name = "..."` | `#[rhai_fn]`, `#[rhai_mod]` | Function or sub-module | Register function/sub-module under the specified name | +| `get = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a getter for the named property | +| `set = "..."` | `#[rhai_fn]` | Function with `&mut` first parameter | Register a setter for the named property | +| `index_get` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index getter | +| `index_set` | `#[rhai_fn]` | Function with `&mut` first parameter | Register an index setter | +| `return_raw` | `#[rhai_fn]` | Function returning `Result>` | Mark this as a [fallible function] | diff --git a/tests/plugins.rs b/tests/plugins.rs index dd56ae61..5310c79e 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -12,6 +12,8 @@ mod test { #[cfg(not(feature = "no_object"))] pub mod feature { + use rhai::{Array, Dynamic, EvalAltResult}; + #[rhai_fn(get = "foo", return_raw)] #[inline(always)] pub fn foo(array: &mut Array) -> Result> { From 99b822863093178c378b4ef03d86b8347c66e3a7 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 5 Sep 2020 17:55:35 +0800 Subject: [PATCH 06/19] Use smallvec/union for nightly builds. --- Cargo.toml | 2 +- tests/functions.rs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83a2d021..9d989830 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ internals = [] # expose internal data structures unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. # compiling for no-std -no_std = [ "no_closure", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] +no_std = [ "smallvec/union", "num-traits/libm", "hashbrown", "core-error", "libm", "ahash" ] [profile.release] lto = "fat" diff --git a/tests/functions.rs b/tests/functions.rs index 296f0ddd..34143ab6 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -1,6 +1,5 @@ #![cfg(not(feature = "no_function"))] use rhai::{Engine, EvalAltResult, ParseErrorType, INT}; -use std::io::Read; #[test] fn test_functions() -> Result<(), Box> { From 900c54bc1fcaf3df615cd9540a4117e91a04ed70 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 5 Sep 2020 12:30:12 -0500 Subject: [PATCH 07/19] Fix trybuild tests for latest nightly --- codegen/ui_tests/non_clonable.stderr | 4 ++-- codegen/ui_tests/non_clonable_second.stderr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr index dbae1d47..10a06303 100644 --- a/codegen/ui_tests/non_clonable.stderr +++ b/codegen/ui_tests/non_clonable.stderr @@ -1,5 +1,5 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied --> $DIR/non_clonable.rs:11:23 | 11 | pub fn test_fn(input: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr index 26202d83..07034875 100644 --- a/codegen/ui_tests/non_clonable_second.stderr +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -1,5 +1,5 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied --> $DIR/non_clonable_second.rs:11:27 | 11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` From 2ee2ca52c9fb1b13ffd9562012522f7472bef7cb Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 5 Sep 2020 14:10:46 -0500 Subject: [PATCH 08/19] Split internal tests into their own files --- codegen/src/function.rs | 519 -------------- codegen/src/lib.rs | 3 + codegen/src/module.rs | 1256 +--------------------------------- codegen/src/test/function.rs | 518 ++++++++++++++ codegen/src/test/mod.rs | 2 + codegen/src/test/module.rs | 1243 +++++++++++++++++++++++++++++++++ 6 files changed, 1778 insertions(+), 1763 deletions(-) create mode 100644 codegen/src/test/function.rs create mode 100644 codegen/src/test/mod.rs create mode 100644 codegen/src/test/module.rs diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 9c005554..3470e691 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -553,522 +553,3 @@ impl ExportedFn { } } } - -#[cfg(test)] -mod function_tests { - use super::ExportedFn; - - use proc_macro2::TokenStream; - use quote::quote; - - #[test] - fn minimal_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_nothing() { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_nothing"); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 0); - } - - #[test] - fn one_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_something"); - assert_eq!(item_fn.arg_list().count(), 1); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap() - ); - } - - #[test] - fn two_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize, y: f32) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_something"); - assert_eq!(item_fn.arg_list().count(), 2); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap() - ); - assert_eq!( - item_fn.arg_list().nth(1).unwrap(), - &syn::parse2::(quote! { y: f32 }).unwrap() - ); - } - - #[test] - fn usize_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_number() -> usize { 42 } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "get_magic_number"); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert_eq!(item_fn.arg_list().count(), 0); - assert_eq!( - item_fn.return_type().unwrap(), - &syn::Type::Path(syn::TypePath { - qself: None, - path: syn::parse2::(quote! { usize }).unwrap() - }) - ); - } - - #[test] - fn ref_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_phrase() -> &'static str { "open sesame" } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); - } - - #[test] - fn ptr_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_phrase() -> *const str { "open sesame" } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); - } - - #[test] - fn ref_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn greet(who: &Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "references from Rhai in this position must be mutable" - ); - } - - #[test] - fn ref_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn greet(count: usize, who: &Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "this type in this position passes from Rhai by value" - ); - } - - #[test] - fn mut_ref_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn give(item_name: &str, who: &mut Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "this type in this position passes from Rhai by value" - ); - } - - #[test] - fn str_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn log(message: &str) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "log"); - assert_eq!(item_fn.arg_list().count(), 1); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap() - ); - } - - #[test] - fn str_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn log(level: usize, message: &str) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "log"); - assert_eq!(item_fn.arg_list().count(), 2); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { level: usize }).unwrap() - ); - assert_eq!( - item_fn.arg_list().nth(1).unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap() - ); - } - - #[test] - fn private_fn() { - let input_tokens: TokenStream = quote! { - fn do_nothing() { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_nothing"); - assert!(!item_fn.mutable_receiver()); - assert!(!item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 0); - } - - #[test] - fn receiver_fn() { - let input_tokens: TokenStream = quote! { - pub fn act_upon(&mut self) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "act_upon"); - assert!(item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 1); - } - - #[test] - fn immutable_receiver_fn() { - let input_tokens: TokenStream = quote! { - pub fn act_upon(&self) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "act_upon"); - assert!(item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 1); - } -} - -#[cfg(test)] -mod generate_tests { - use super::ExportedFn; - - use proc_macro2::TokenStream; - use quote::quote; - - fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { - let actual = actual.to_string(); - let expected = expected.to_string(); - if &actual != &expected { - let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); - let (actual_diff, expected_diff) = { - let mut actual_diff = String::new(); - let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { - actual_diff.push(a); - expected_diff.push(e); - } - (actual_diff, expected_diff) - }; - eprintln!("actual != expected, diverge at char {}", counter); - } - assert_eq!(actual, expected); - } - - #[test] - fn minimal_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_nothing() { } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_do_nothing { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(do_nothing())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn() -> Result { - Ok(Dynamic::from(super::do_nothing())) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn one_arg_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_do_something { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(do_something(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize) -> Result { - Ok(Dynamic::from(super::do_something(x))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn one_arg_usize_fn_impl() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let expected_tokens = quote! { - impl PluginFunction for MyType { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(do_something(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(MyType()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); - } - - #[test] - fn two_arg_returning_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn add_together(x: usize, y: usize) -> usize { x + y } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_add_together { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_together(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize, y: usize) -> Result { - Ok(Dynamic::from(super::add_together(x, y))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn mut_arg_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn increment(x: &mut usize, y: usize) { *x += y; } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_increment { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { - Ok(Dynamic::from(super::increment(x, y))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert!(item_fn.mutable_receiver()); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn str_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn special_print(message: &str) { eprintln!("----{}----", message); } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_special_print { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(special_print(&arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(message: &str) -> Result { - Ok(Dynamic::from(super::special_print(message))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert!(!item_fn.mutable_receiver()); - assert_streams_eq(item_fn.generate(), expected_tokens); - } -} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 9e47a831..b9d8880d 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -102,6 +102,9 @@ mod module; mod register; mod rhai_module; +#[cfg(test)] +mod test; + #[proc_macro_attribute] pub fn export_fn( args: proc_macro::TokenStream, diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 79014b8d..07ae54da 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -301,6 +301,18 @@ impl Module { self.mod_all.as_ref().map(|m| &m.ident) } + pub fn consts(&self) -> &[ExportedConst] { + &self.consts + } + + pub fn fns(&self) -> &[ExportedFn] { + &self.fns + } + + pub fn submodules(&self) -> &[Module] { + &self.submodules + } + pub fn content(&self) -> Option<&Vec> { match self.mod_all { Some(syn::ItemMod { @@ -311,1247 +323,3 @@ impl Module { } } } - -#[cfg(test)] -mod module_tests { - use super::Module; - - use proc_macro2::TokenStream; - use quote::quote; - - #[test] - fn empty_module() { - let input_tokens: TokenStream = quote! { - pub mod empty { } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - } - - #[test] - fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "get_mystic_number"); - assert_eq!(item_mod.fns[0].arg_count(), 0); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "add_one_to"); - assert_eq!(item_mod.fns[0].arg_count(), 1); - assert_eq!( - item_mod.fns[0].arg_list().next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap() - ); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - let mut args = item_mod.fns[0].arg_list(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "add_together"); - assert_eq!(item_mod.fns[0].arg_count(), 2); - assert_eq!( - args.next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap() - ); - assert_eq!( - args.next().unwrap(), - &syn::parse2::(quote! { y: INT }).unwrap() - ); - assert!(args.next().is_none()); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert_eq!(&item_mod.submodules[0].consts[0].0, "MYSTIC_NUMBER"); - assert_eq!( - item_mod.submodules[0].consts[0].1, - syn::parse2::(quote! { 42 }).unwrap() - ); - } - - #[test] - fn one_skipped_fn_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub mod skip_this { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert_eq!(item_mod.submodules[0].fns.len(), 1); - assert!(item_mod.submodules[0].fns[0].skipped()); - assert!(item_mod.submodules[0].consts.is_empty()); - assert!(item_mod.submodules[0].submodules.is_empty()); - } - - #[test] - fn one_skipped_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_mod(skip)] - pub mod skip_this { - pub fn get_mystic_number() -> INT { - 42 - } - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert!(item_mod.submodules[0].skipped()); - } - - #[test] - fn one_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert_eq!(item_mod.consts.len(), 1); - assert_eq!(&item_mod.consts[0].0, "MYSTIC_NUMBER"); - assert_eq!( - item_mod.consts[0].1, - syn::parse2::(quote! { 42 }).unwrap() - ); - } - - #[test] - fn one_skipped_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_eq!(item_mod.fns.len(), 1); - assert!(item_mod.fns[0].skipped()); - assert!(item_mod.consts.is_empty()); - } - - #[test] - fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - } -} - -#[cfg(test)] -mod generate_tests { - use super::Module; - - use proc_macro2::TokenStream; - use quote::quote; - - fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { - let actual = actual.to_string(); - let expected = expected.to_string(); - if &actual != &expected { - let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); - let (_actual_diff, _expected_diff) = { - let mut actual_diff = String::new(); - let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { - actual_diff.push(a); - expected_diff.push(e); - } - (actual_diff, expected_diff) - }; - eprintln!("actual != expected, diverge at char {}", counter); - } - assert_eq!(actual, expected); - } - - #[test] - fn empty_module() { - let input_tokens: TokenStream = quote! { - pub mod empty { } - }; - - let expected_tokens = quote! { - pub mod empty { - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("get_mystic_number", FnAccess::Public, &[], - CallableFunction::from_plugin(get_mystic_number_token())); - m - } - #[allow(non_camel_case_types)] - struct get_mystic_number_token(); - impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(get_mystic_number())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(get_mystic_number_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn get_mystic_number_token_callable() -> CallableFunction { - CallableFunction::from_plugin(get_mystic_number_token()) - } - pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { - get_mystic_number_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(add_one_to_token())); - m - } - #[allow(non_camel_case_types)] - struct add_one_to_token(); - impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(add_one_to(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_one_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn add_one_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_one_to_token()) - } - pub fn add_one_to_token_input_types() -> Box<[TypeId]> { - add_one_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn two_fn_overload_module() { - let input_tokens: TokenStream = quote! { - pub mod two_fns { - #[rhai_fn(name = "add_n")] - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - - #[rhai_fn(name = "add_n")] - pub fn add_n_to(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let expected_tokens = quote! { - pub mod two_fns { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - - pub fn add_n_to(x: INT, y: INT) -> INT { - x + y - } - - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(add_one_to_token())); - m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), - core::any::TypeId::of::()], - CallableFunction::from_plugin(add_n_to_token())); - m - } - - #[allow(non_camel_case_types)] - struct add_one_to_token(); - impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(add_one_to(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_one_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn add_one_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_one_to_token()) - } - pub fn add_one_to_token_input_types() -> Box<[TypeId]> { - add_one_to_token().input_types() - } - - #[allow(non_camel_case_types)] - struct add_n_to_token(); - impl PluginFunction for add_n_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_n_to(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_n_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn add_n_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_n_to_token()) - } - pub fn add_n_to_token_input_types() -> Box<[TypeId]> { - add_n_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), - core::any::TypeId::of::()], - CallableFunction::from_plugin(add_together_token())); - m - } - #[allow(non_camel_case_types)] - struct add_together_token(); - impl PluginFunction for add_together_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_together(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_together_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn add_together_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_together_token()) - } - pub fn add_together_token_input_types() -> Box<[TypeId]> { - add_together_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_module_imports_preserved() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub use rhai::INT; - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub use rhai::INT; - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_private_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_skipped_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_skipped_submodule() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[rhai_mod(skip)] - pub mod inner_secrets { - pub const SECRET_NUMBER: INT = 86; - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - pub mod inner_secrets { - pub const SECRET_NUMBER: INT = 86; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("get_mystic_number", FnAccess::Public, &[], - CallableFunction::from_plugin(get_mystic_number_token())); - m - } - #[allow(non_camel_case_types)] - struct get_mystic_number_token(); - impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(get_mystic_number())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(get_mystic_number_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn get_mystic_number_token_callable() -> CallableFunction { - CallableFunction::from_plugin(get_mystic_number_token()) - } - pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { - get_mystic_number_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_str_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod str_fn { - pub fn print_out_to(x: &str) { - x + 1 - } - } - }; - - let expected_tokens = quote! { - pub mod str_fn { - pub fn print_out_to(x: &str) { - x + 1 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("print_out_to", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(print_out_to_token())); - m - } - #[allow(non_camel_case_types)] - struct print_out_to_token(); - impl PluginFunction for print_out_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(print_out_to(&arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(print_out_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn print_out_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(print_out_to_token()) - } - pub fn print_out_to_token_input_types() -> Box<[TypeId]> { - print_out_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_mut_ref_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod ref_fn { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - }; - - let expected_tokens = quote! { - pub mod ref_fn { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_fn_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_fn_with_cfg_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[cfg(not(feature = "no_float"))] - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - #[cfg(not(feature = "no_float"))] - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - #[cfg(not(feature = "no_float"))] { - m.set_sub_module("it_is", self::it_is::rhai_module_generate()); - } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - } - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn dual_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod two_constants { - pub mod first_is { - pub const MYSTIC_NUMBER: INT = 42; - } - pub mod second_is { - pub const SPECIAL_CPU_NUMBER: INT = 68000; - } - } - }; - - let expected_tokens = quote! { - pub mod two_constants { - pub mod first_is { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - pub mod second_is { - pub const SPECIAL_CPU_NUMBER: INT = 68000; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("SPECIAL_CPU_NUMBER", 68000); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } - { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn deep_tree_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod heap_root { - pub const VALUE: INT = 100; - pub mod left { - pub const VALUE: INT = 19; - pub mod left { - pub const VALUE: INT = 17; - pub mod left { - pub const VALUE: INT = 2; - } - pub mod right { - pub const VALUE: INT = 7; - } - } - pub mod right { - pub const VALUE: INT = 3; - } - } - pub mod right { - pub const VALUE: INT = 36; - pub mod left { - pub const VALUE: INT = 25; - } - pub mod right { - pub const VALUE: INT = 1; - } - } - } - }; - - let expected_tokens = quote! { - pub mod heap_root { - pub const VALUE: INT = 100; - pub mod left { - pub const VALUE: INT = 19; - pub mod left { - pub const VALUE: INT = 17; - pub mod left { - pub const VALUE: INT = 2; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 2); - m - } - } - pub mod right { - pub const VALUE: INT = 7; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 7); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 17); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - pub mod right { - pub const VALUE: INT = 3; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 3); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 19); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - pub mod right { - pub const VALUE: INT = 36; - pub mod left { - pub const VALUE: INT = 25; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 25); - m - } - } - pub mod right { - pub const VALUE: INT = 1; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 1); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 36); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 100); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } -} diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs new file mode 100644 index 00000000..5430b311 --- /dev/null +++ b/codegen/src/test/function.rs @@ -0,0 +1,518 @@ +#[cfg(test)] +mod function_tests { + use crate::function::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn one_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + } + + #[test] + fn two_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize, y: f32) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { y: f32 }).unwrap() + ); + } + + #[test] + fn usize_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_number() -> usize { 42 } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "get_magic_number"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert_eq!(item_fn.arg_list().count(), 0); + assert_eq!( + item_fn.return_type().unwrap(), + &syn::Type::Path(syn::TypePath { + qself: None, + path: syn::parse2::(quote! { usize }).unwrap() + }) + ); + } + + #[test] + fn ref_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> &'static str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); + } + + #[test] + fn ptr_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> *const str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); + } + + #[test] + fn ref_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "references from Rhai in this position must be mutable" + ); + } + + #[test] + fn ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(count: usize, who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn mut_ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn give(item_name: &str, who: &mut Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn str_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(level: usize, message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { level: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn private_fn() { + let input_tokens: TokenStream = quote! { + fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(!item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&mut self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } + + #[test] + fn immutable_receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } +} + +#[cfg(test)] +mod generate_tests { + use crate::function::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual + .chars() + .zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (actual_diff, expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_nothing { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(do_nothing())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn() -> Result { + Ok(Dynamic::from(super::do_nothing())) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_something { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize) -> Result { + Ok(Dynamic::from(super::do_something(x))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn_impl() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + impl PluginFunction for MyType { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); + } + + #[test] + fn two_arg_returning_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn add_together(x: usize, y: usize) -> usize { x + y } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_add_together { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + Ok(Dynamic::from(super::add_together(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn mut_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn increment(x: &mut usize, y: usize) { *x += y; } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_increment { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + Ok(Dynamic::from(super::increment(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn special_print(message: &str) { eprintln!("----{}----", message); } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_special_print { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(special_print(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(message: &str) -> Result { + Ok(Dynamic::from(super::special_print(message))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(!item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } +} diff --git a/codegen/src/test/mod.rs b/codegen/src/test/mod.rs new file mode 100644 index 00000000..452c9b21 --- /dev/null +++ b/codegen/src/test/mod.rs @@ -0,0 +1,2 @@ +mod function; +mod module; diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs new file mode 100644 index 00000000..5d4e6186 --- /dev/null +++ b/codegen/src/test/module.rs @@ -0,0 +1,1243 @@ +#[cfg(test)] +mod module_tests { + use crate::module::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number"); + assert_eq!(item_mod.fns()[0].arg_count(), 0); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "add_one_to"); + assert_eq!(item_mod.fns()[0].arg_count(), 1); + assert_eq!( + item_mod.fns()[0].arg_list().next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + let mut args = item_mod.fns()[0].arg_list(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "add_together"); + assert_eq!(item_mod.fns()[0].arg_count(), 2); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { y: INT }).unwrap() + ); + assert!(args.next().is_none()); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert_eq!(&item_mod.submodules()[0].consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.submodules()[0].consts()[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod skip_this { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert_eq!(item_mod.submodules()[0].fns().len(), 1); + assert!(item_mod.submodules()[0].fns()[0].skipped()); + assert!(item_mod.submodules()[0].consts().is_empty()); + assert!(item_mod.submodules()[0].submodules().is_empty()); + } + + #[test] + fn one_skipped_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_mod(skip)] + pub mod skip_this { + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert!(item_mod.submodules()[0].skipped()); + } + + #[test] + fn one_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert_eq!(item_mod.consts().len(), 1); + assert_eq!(&item_mod.consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.consts()[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_eq!(item_mod.fns().len(), 1); + assert!(item_mod.fns()[0].skipped()); + assert!(item_mod.consts().is_empty()); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + } +} + +#[cfg(test)] +mod generate_tests { + use crate::module::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual + .chars() + .zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (_actual_diff, _expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let expected_tokens = quote! { + pub mod empty { + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[allow(non_camel_case_types)] + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m + } + #[allow(non_camel_case_types)] + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn two_fn_overload_module() { + let input_tokens: TokenStream = quote! { + pub mod two_fns { + #[rhai_fn(name = "add_n")] + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + #[rhai_fn(name = "add_n")] + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod two_fns { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_n_to_token())); + m + } + + #[allow(non_camel_case_types)] + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + + #[allow(non_camel_case_types)] + struct add_n_to_token(); + impl PluginFunction for add_n_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_n_to(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_n_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_n_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_n_to_token()) + } + pub fn add_n_to_token_input_types() -> Box<[TypeId]> { + add_n_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_together_token())); + m + } + #[allow(non_camel_case_types)] + struct add_together_token(); + impl PluginFunction for add_together_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_together_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_together_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_together_token()) + } + pub fn add_together_token_input_types() -> Box<[TypeId]> { + add_together_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module_imports_preserved() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_submodule() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[rhai_mod(skip)] + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[allow(non_camel_case_types)] + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_str_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("print_out_to", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(print_out_to_token())); + m + } + #[allow(non_camel_case_types)] + struct print_out_to_token(); + impl PluginFunction for print_out_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(print_out_to(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(print_out_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn print_out_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(print_out_to_token()) + } + pub fn print_out_to_token_input_types() -> Box<[TypeId]> { + print_out_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_mut_ref_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + }; + + let expected_tokens = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_with_cfg_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + #[cfg(not(feature = "no_float"))] { + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn dual_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + } + } + }; + + let expected_tokens = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("SPECIAL_CPU_NUMBER", 68000); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } + { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn deep_tree_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + } + pub mod right { + pub const VALUE: INT = 7; + } + } + pub mod right { + pub const VALUE: INT = 3; + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + } + pub mod right { + pub const VALUE: INT = 1; + } + } + } + }; + + let expected_tokens = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 2); + m + } + } + pub mod right { + pub const VALUE: INT = 7; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 7); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 17); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + pub mod right { + pub const VALUE: INT = 3; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 3); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 19); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 25); + m + } + } + pub mod right { + pub const VALUE: INT = 1; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 1); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 36); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 100); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } +} From 9882b97fca1da3468e7757b386252f5e1da66182 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 5 Sep 2020 12:30:12 -0500 Subject: [PATCH 09/19] Fix trybuild tests for latest nightly --- codegen/ui_tests/non_clonable.stderr | 4 ++-- codegen/ui_tests/non_clonable_second.stderr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codegen/ui_tests/non_clonable.stderr b/codegen/ui_tests/non_clonable.stderr index dbae1d47..10a06303 100644 --- a/codegen/ui_tests/non_clonable.stderr +++ b/codegen/ui_tests/non_clonable.stderr @@ -1,5 +1,5 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied --> $DIR/non_clonable.rs:11:23 | 11 | pub fn test_fn(input: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` diff --git a/codegen/ui_tests/non_clonable_second.stderr b/codegen/ui_tests/non_clonable_second.stderr index 26202d83..07034875 100644 --- a/codegen/ui_tests/non_clonable_second.stderr +++ b/codegen/ui_tests/non_clonable_second.stderr @@ -1,5 +1,5 @@ -error[E0277]: the trait bound `NonClonable: std::clone::Clone` is not satisfied +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied --> $DIR/non_clonable_second.rs:11:27 | 11 | pub fn test_fn(a: u32, b: NonClonable) -> bool { - | ^^^^^^^^^^^ the trait `std::clone::Clone` is not implemented for `NonClonable` + | ^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` From 384152004eb3b6220c2675f9a9677e4c8a27c431 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sat, 5 Sep 2020 14:10:46 -0500 Subject: [PATCH 10/19] Split internal tests into their own files --- codegen/src/function.rs | 519 -------------- codegen/src/lib.rs | 3 + codegen/src/module.rs | 1256 +--------------------------------- codegen/src/test/function.rs | 518 ++++++++++++++ codegen/src/test/mod.rs | 2 + codegen/src/test/module.rs | 1243 +++++++++++++++++++++++++++++++++ 6 files changed, 1778 insertions(+), 1763 deletions(-) create mode 100644 codegen/src/test/function.rs create mode 100644 codegen/src/test/mod.rs create mode 100644 codegen/src/test/module.rs diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 9c005554..3470e691 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -553,522 +553,3 @@ impl ExportedFn { } } } - -#[cfg(test)] -mod function_tests { - use super::ExportedFn; - - use proc_macro2::TokenStream; - use quote::quote; - - #[test] - fn minimal_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_nothing() { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_nothing"); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 0); - } - - #[test] - fn one_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_something"); - assert_eq!(item_fn.arg_list().count(), 1); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap() - ); - } - - #[test] - fn two_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize, y: f32) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_something"); - assert_eq!(item_fn.arg_list().count(), 2); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { x: usize }).unwrap() - ); - assert_eq!( - item_fn.arg_list().nth(1).unwrap(), - &syn::parse2::(quote! { y: f32 }).unwrap() - ); - } - - #[test] - fn usize_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_number() -> usize { 42 } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "get_magic_number"); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert_eq!(item_fn.arg_list().count(), 0); - assert_eq!( - item_fn.return_type().unwrap(), - &syn::Type::Path(syn::TypePath { - qself: None, - path: syn::parse2::(quote! { usize }).unwrap() - }) - ); - } - - #[test] - fn ref_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_phrase() -> &'static str { "open sesame" } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); - } - - #[test] - fn ptr_returning_fn() { - let input_tokens: TokenStream = quote! { - pub fn get_magic_phrase() -> *const str { "open sesame" } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); - } - - #[test] - fn ref_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn greet(who: &Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "references from Rhai in this position must be mutable" - ); - } - - #[test] - fn ref_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn greet(count: usize, who: &Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "this type in this position passes from Rhai by value" - ); - } - - #[test] - fn mut_ref_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn give(item_name: &str, who: &mut Person) { } - }; - - let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!( - format!("{}", err), - "this type in this position passes from Rhai by value" - ); - } - - #[test] - fn str_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn log(message: &str) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "log"); - assert_eq!(item_fn.arg_list().count(), 1); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap() - ); - } - - #[test] - fn str_second_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn log(level: usize, message: &str) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "log"); - assert_eq!(item_fn.arg_list().count(), 2); - assert!(!item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - - assert_eq!( - item_fn.arg_list().next().unwrap(), - &syn::parse2::(quote! { level: usize }).unwrap() - ); - assert_eq!( - item_fn.arg_list().nth(1).unwrap(), - &syn::parse2::(quote! { message: &str }).unwrap() - ); - } - - #[test] - fn private_fn() { - let input_tokens: TokenStream = quote! { - fn do_nothing() { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "do_nothing"); - assert!(!item_fn.mutable_receiver()); - assert!(!item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 0); - } - - #[test] - fn receiver_fn() { - let input_tokens: TokenStream = quote! { - pub fn act_upon(&mut self) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "act_upon"); - assert!(item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 1); - } - - #[test] - fn immutable_receiver_fn() { - let input_tokens: TokenStream = quote! { - pub fn act_upon(&self) { } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_eq!(&item_fn.name().to_string(), "act_upon"); - assert!(item_fn.mutable_receiver()); - assert!(item_fn.is_public()); - assert!(item_fn.return_type().is_none()); - assert_eq!(item_fn.arg_list().count(), 1); - } -} - -#[cfg(test)] -mod generate_tests { - use super::ExportedFn; - - use proc_macro2::TokenStream; - use quote::quote; - - fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { - let actual = actual.to_string(); - let expected = expected.to_string(); - if &actual != &expected { - let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); - let (actual_diff, expected_diff) = { - let mut actual_diff = String::new(); - let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { - actual_diff.push(a); - expected_diff.push(e); - } - (actual_diff, expected_diff) - }; - eprintln!("actual != expected, diverge at char {}", counter); - } - assert_eq!(actual, expected); - } - - #[test] - fn minimal_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_nothing() { } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_do_nothing { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(do_nothing())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn() -> Result { - Ok(Dynamic::from(super::do_nothing())) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn one_arg_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_do_something { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(do_something(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize) -> Result { - Ok(Dynamic::from(super::do_something(x))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn one_arg_usize_fn_impl() { - let input_tokens: TokenStream = quote! { - pub fn do_something(x: usize) { } - }; - - let expected_tokens = quote! { - impl PluginFunction for MyType { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(do_something(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(MyType()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); - } - - #[test] - fn two_arg_returning_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn add_together(x: usize, y: usize) -> usize { x + y } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_add_together { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_together(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize, y: usize) -> Result { - Ok(Dynamic::from(super::add_together(x, y))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn mut_arg_usize_fn() { - let input_tokens: TokenStream = quote! { - pub fn increment(x: &mut usize, y: usize) { *x += y; } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_increment { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg1 = mem::take(args[1usize]).clone().cast::(); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { - Ok(Dynamic::from(super::increment(x, y))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert!(item_fn.mutable_receiver()); - assert_streams_eq(item_fn.generate(), expected_tokens); - } - - #[test] - fn str_arg_fn() { - let input_tokens: TokenStream = quote! { - pub fn special_print(message: &str) { eprintln!("----{}----", message); } - }; - - let expected_tokens = quote! { - #[allow(unused)] - pub mod rhai_fn_special_print { - use super::*; - struct Token(); - impl PluginFunction for Token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(special_print(&arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { Box::new(Token()) } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn token_callable() -> CallableFunction { - CallableFunction::from_plugin(Token()) - } - pub fn token_input_types() -> Box<[TypeId]> { - Token().input_types() - } - type EvalBox = Box; - pub fn dynamic_result_fn(message: &str) -> Result { - Ok(Dynamic::from(super::special_print(message))) - } - } - }; - - let item_fn = syn::parse2::(input_tokens).unwrap(); - assert!(!item_fn.mutable_receiver()); - assert_streams_eq(item_fn.generate(), expected_tokens); - } -} diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 9e47a831..b9d8880d 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -102,6 +102,9 @@ mod module; mod register; mod rhai_module; +#[cfg(test)] +mod test; + #[proc_macro_attribute] pub fn export_fn( args: proc_macro::TokenStream, diff --git a/codegen/src/module.rs b/codegen/src/module.rs index 79014b8d..07ae54da 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -301,6 +301,18 @@ impl Module { self.mod_all.as_ref().map(|m| &m.ident) } + pub fn consts(&self) -> &[ExportedConst] { + &self.consts + } + + pub fn fns(&self) -> &[ExportedFn] { + &self.fns + } + + pub fn submodules(&self) -> &[Module] { + &self.submodules + } + pub fn content(&self) -> Option<&Vec> { match self.mod_all { Some(syn::ItemMod { @@ -311,1247 +323,3 @@ impl Module { } } } - -#[cfg(test)] -mod module_tests { - use super::Module; - - use proc_macro2::TokenStream; - use quote::quote; - - #[test] - fn empty_module() { - let input_tokens: TokenStream = quote! { - pub mod empty { } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - } - - #[test] - fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "get_mystic_number"); - assert_eq!(item_mod.fns[0].arg_count(), 0); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "add_one_to"); - assert_eq!(item_mod.fns[0].arg_count(), 1); - assert_eq!( - item_mod.fns[0].arg_list().next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap() - ); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - let mut args = item_mod.fns[0].arg_list(); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.fns.len(), 1); - assert_eq!(item_mod.fns[0].name().to_string(), "add_together"); - assert_eq!(item_mod.fns[0].arg_count(), 2); - assert_eq!( - args.next().unwrap(), - &syn::parse2::(quote! { x: INT }).unwrap() - ); - assert_eq!( - args.next().unwrap(), - &syn::parse2::(quote! { y: INT }).unwrap() - ); - assert!(args.next().is_none()); - assert_eq!( - item_mod.fns[0].return_type().unwrap(), - &syn::parse2::(quote! { INT }).unwrap() - ); - } - - #[test] - fn one_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert_eq!(&item_mod.submodules[0].consts[0].0, "MYSTIC_NUMBER"); - assert_eq!( - item_mod.submodules[0].consts[0].1, - syn::parse2::(quote! { 42 }).unwrap() - ); - } - - #[test] - fn one_skipped_fn_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub mod skip_this { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert_eq!(item_mod.submodules[0].fns.len(), 1); - assert!(item_mod.submodules[0].fns[0].skipped()); - assert!(item_mod.submodules[0].consts.is_empty()); - assert!(item_mod.submodules[0].submodules.is_empty()); - } - - #[test] - fn one_skipped_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_mod(skip)] - pub mod skip_this { - pub fn get_mystic_number() -> INT { - 42 - } - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - assert_eq!(item_mod.submodules.len(), 1); - assert!(item_mod.submodules[0].skipped()); - } - - #[test] - fn one_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert_eq!(item_mod.consts.len(), 1); - assert_eq!(&item_mod.consts[0].0, "MYSTIC_NUMBER"); - assert_eq!( - item_mod.consts[0].1, - syn::parse2::(quote! { 42 }).unwrap() - ); - } - - #[test] - fn one_skipped_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_eq!(item_mod.fns.len(), 1); - assert!(item_mod.fns[0].skipped()); - assert!(item_mod.consts.is_empty()); - } - - #[test] - fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert!(item_mod.fns.is_empty()); - assert!(item_mod.consts.is_empty()); - } -} - -#[cfg(test)] -mod generate_tests { - use super::Module; - - use proc_macro2::TokenStream; - use quote::quote; - - fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { - let actual = actual.to_string(); - let expected = expected.to_string(); - if &actual != &expected { - let mut counter = 0; - let iter = actual - .chars() - .zip(expected.chars()) - .inspect(|_| counter += 1) - .skip_while(|(a, e)| *a == *e); - let (_actual_diff, _expected_diff) = { - let mut actual_diff = String::new(); - let mut expected_diff = String::new(); - for (a, e) in iter.take(50) { - actual_diff.push(a); - expected_diff.push(e); - } - (actual_diff, expected_diff) - }; - eprintln!("actual != expected, diverge at char {}", counter); - } - assert_eq!(actual, expected); - } - - #[test] - fn empty_module() { - let input_tokens: TokenStream = quote! { - pub mod empty { } - }; - - let expected_tokens = quote! { - pub mod empty { - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_factory_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("get_mystic_number", FnAccess::Public, &[], - CallableFunction::from_plugin(get_mystic_number_token())); - m - } - #[allow(non_camel_case_types)] - struct get_mystic_number_token(); - impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(get_mystic_number())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(get_mystic_number_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn get_mystic_number_token_callable() -> CallableFunction { - CallableFunction::from_plugin(get_mystic_number_token()) - } - pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { - get_mystic_number_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_single_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(add_one_to_token())); - m - } - #[allow(non_camel_case_types)] - struct add_one_to_token(); - impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(add_one_to(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_one_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn add_one_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_one_to_token()) - } - pub fn add_one_to_token_input_types() -> Box<[TypeId]> { - add_one_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn two_fn_overload_module() { - let input_tokens: TokenStream = quote! { - pub mod two_fns { - #[rhai_fn(name = "add_n")] - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - - #[rhai_fn(name = "add_n")] - pub fn add_n_to(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let expected_tokens = quote! { - pub mod two_fns { - pub fn add_one_to(x: INT) -> INT { - x + 1 - } - - pub fn add_n_to(x: INT, y: INT) -> INT { - x + y - } - - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], - CallableFunction::from_plugin(add_one_to_token())); - m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), - core::any::TypeId::of::()], - CallableFunction::from_plugin(add_n_to_token())); - m - } - - #[allow(non_camel_case_types)] - struct add_one_to_token(); - impl PluginFunction for add_one_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(add_one_to(arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_one_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn add_one_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_one_to_token()) - } - pub fn add_one_to_token_input_types() -> Box<[TypeId]> { - add_one_to_token().input_types() - } - - #[allow(non_camel_case_types)] - struct add_n_to_token(); - impl PluginFunction for add_n_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_n_to(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_n_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn add_n_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_n_to_token()) - } - pub fn add_n_to_token_input_types() -> Box<[TypeId]> { - add_n_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_double_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn add_together(x: INT, y: INT) -> INT { - x + y - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), - core::any::TypeId::of::()], - CallableFunction::from_plugin(add_together_token())); - m - } - #[allow(non_camel_case_types)] - struct add_together_token(); - impl PluginFunction for add_together_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 2usize, - "wrong arg count: {} != {}", args.len(), 2usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - let arg1 = mem::take(args[1usize]).clone().cast::(); - Ok(Dynamic::from(add_together(arg0, arg1))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(add_together_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::(), - TypeId::of::()].into_boxed_slice() - } - } - pub fn add_together_token_callable() -> CallableFunction { - CallableFunction::from_plugin(add_together_token()) - } - pub fn add_together_token_input_types() -> Box<[TypeId]> { - add_together_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_module_imports_preserved() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub use rhai::INT; - pub const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub use rhai::INT; - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_private_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_skipped_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[rhai_fn(skip)] - pub fn get_mystic_number() -> INT { - 42 - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_skipped_submodule() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - #[rhai_mod(skip)] - pub mod inner_secrets { - pub const SECRET_NUMBER: INT = 86; - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub fn get_mystic_number() -> INT { - 42 - } - pub mod inner_secrets { - pub const SECRET_NUMBER: INT = 86; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("get_mystic_number", FnAccess::Public, &[], - CallableFunction::from_plugin(get_mystic_number_token())); - m - } - #[allow(non_camel_case_types)] - struct get_mystic_number_token(); - impl PluginFunction for get_mystic_number_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 0usize, - "wrong arg count: {} != {}", args.len(), 0usize); - Ok(Dynamic::from(get_mystic_number())) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(get_mystic_number_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![].into_boxed_slice() - } - } - pub fn get_mystic_number_token_callable() -> CallableFunction { - CallableFunction::from_plugin(get_mystic_number_token()) - } - pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { - get_mystic_number_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_private_constant_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_str_arg_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod str_fn { - pub fn print_out_to(x: &str) { - x + 1 - } - } - }; - - let expected_tokens = quote! { - pub mod str_fn { - pub fn print_out_to(x: &str) { - x + 1 - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("print_out_to", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(print_out_to_token())); - m - } - #[allow(non_camel_case_types)] - struct print_out_to_token(); - impl PluginFunction for print_out_to_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0 = mem::take(args[0usize]).clone().cast::(); - Ok(Dynamic::from(print_out_to(&arg0))) - } - - fn is_method_call(&self) -> bool { false } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(print_out_to_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn print_out_to_token_callable() -> CallableFunction { - CallableFunction::from_plugin(print_out_to_token()) - } - pub fn print_out_to_token_input_types() -> Box<[TypeId]> { - print_out_to_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_mut_ref_fn_module() { - let input_tokens: TokenStream = quote! { - pub mod ref_fn { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - }; - - let expected_tokens = quote! { - pub mod ref_fn { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_fn_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_fn_with_cfg_module() { - let input_tokens: TokenStream = quote! { - pub mod one_fn { - #[cfg(not(feature = "no_float"))] - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - } - } - }; - - let expected_tokens = quote! { - pub mod one_fn { - #[cfg(not(feature = "no_float"))] - pub mod it_is { - pub fn increment(x: &mut FLOAT) { - *x += 1.0 as FLOAT; - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_fn("increment", FnAccess::Public, - &[core::any::TypeId::of::()], - CallableFunction::from_plugin(increment_token())); - m - } - #[allow(non_camel_case_types)] - struct increment_token(); - impl PluginFunction for increment_token { - fn call(&self, - args: &mut [&mut Dynamic], pos: Position - ) -> Result> { - debug_assert_eq!(args.len(), 1usize, - "wrong arg count: {} != {}", args.len(), 1usize); - let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); - Ok(Dynamic::from(increment(arg0))) - } - - fn is_method_call(&self) -> bool { true } - fn is_varadic(&self) -> bool { false } - fn clone_boxed(&self) -> Box { - Box::new(increment_token()) - } - fn input_types(&self) -> Box<[TypeId]> { - new_vec![TypeId::of::()].into_boxed_slice() - } - } - pub fn increment_token_callable() -> CallableFunction { - CallableFunction::from_plugin(increment_token()) - } - pub fn increment_token_input_types() -> Box<[TypeId]> { - increment_token().input_types() - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - #[cfg(not(feature = "no_float"))] { - m.set_sub_module("it_is", self::it_is::rhai_module_generate()); - } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn one_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - } - } - }; - - let expected_tokens = quote! { - pub mod one_constant { - pub mod it_is { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn dual_constant_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod two_constants { - pub mod first_is { - pub const MYSTIC_NUMBER: INT = 42; - } - pub mod second_is { - pub const SPECIAL_CPU_NUMBER: INT = 68000; - } - } - }; - - let expected_tokens = quote! { - pub mod two_constants { - pub mod first_is { - pub const MYSTIC_NUMBER: INT = 42; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("MYSTIC_NUMBER", 42); - m - } - } - pub mod second_is { - pub const SPECIAL_CPU_NUMBER: INT = 68000; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("SPECIAL_CPU_NUMBER", 68000); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } - { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } - - #[test] - fn deep_tree_nested_module() { - let input_tokens: TokenStream = quote! { - pub mod heap_root { - pub const VALUE: INT = 100; - pub mod left { - pub const VALUE: INT = 19; - pub mod left { - pub const VALUE: INT = 17; - pub mod left { - pub const VALUE: INT = 2; - } - pub mod right { - pub const VALUE: INT = 7; - } - } - pub mod right { - pub const VALUE: INT = 3; - } - } - pub mod right { - pub const VALUE: INT = 36; - pub mod left { - pub const VALUE: INT = 25; - } - pub mod right { - pub const VALUE: INT = 1; - } - } - } - }; - - let expected_tokens = quote! { - pub mod heap_root { - pub const VALUE: INT = 100; - pub mod left { - pub const VALUE: INT = 19; - pub mod left { - pub const VALUE: INT = 17; - pub mod left { - pub const VALUE: INT = 2; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 2); - m - } - } - pub mod right { - pub const VALUE: INT = 7; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 7); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 17); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - pub mod right { - pub const VALUE: INT = 3; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 3); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 19); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - pub mod right { - pub const VALUE: INT = 36; - pub mod left { - pub const VALUE: INT = 25; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 25); - m - } - } - pub mod right { - pub const VALUE: INT = 1; - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 1); - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 36); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - #[allow(unused_imports)] - use super::*; - #[allow(unused_mut)] - pub fn rhai_module_generate() -> Module { - let mut m = Module::new(); - m.set_var("VALUE", 100); - { m.set_sub_module("left", self::left::rhai_module_generate()); } - { m.set_sub_module("right", self::right::rhai_module_generate()); } - m - } - } - }; - - let item_mod = syn::parse2::(input_tokens).unwrap(); - assert_streams_eq(item_mod.generate(), expected_tokens); - } -} diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs new file mode 100644 index 00000000..5430b311 --- /dev/null +++ b/codegen/src/test/function.rs @@ -0,0 +1,518 @@ +#[cfg(test)] +mod function_tests { + use crate::function::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn one_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + } + + #[test] + fn two_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize, y: f32) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_something"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { x: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { y: f32 }).unwrap() + ); + } + + #[test] + fn usize_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_number() -> usize { 42 } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "get_magic_number"); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert_eq!(item_fn.arg_list().count(), 0); + assert_eq!( + item_fn.return_type().unwrap(), + &syn::Type::Path(syn::TypePath { + qself: None, + path: syn::parse2::(quote! { usize }).unwrap() + }) + ); + } + + #[test] + fn ref_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> &'static str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); + } + + #[test] + fn ptr_returning_fn() { + let input_tokens: TokenStream = quote! { + pub fn get_magic_phrase() -> *const str { "open sesame" } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); + } + + #[test] + fn ref_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "references from Rhai in this position must be mutable" + ); + } + + #[test] + fn ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn greet(count: usize, who: &Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn mut_ref_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn give(item_name: &str, who: &mut Person) { } + }; + + let err = syn::parse2::(input_tokens).unwrap_err(); + assert_eq!( + format!("{}", err), + "this type in this position passes from Rhai by value" + ); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 1); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn str_second_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn log(level: usize, message: &str) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "log"); + assert_eq!(item_fn.arg_list().count(), 2); + assert!(!item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + + assert_eq!( + item_fn.arg_list().next().unwrap(), + &syn::parse2::(quote! { level: usize }).unwrap() + ); + assert_eq!( + item_fn.arg_list().nth(1).unwrap(), + &syn::parse2::(quote! { message: &str }).unwrap() + ); + } + + #[test] + fn private_fn() { + let input_tokens: TokenStream = quote! { + fn do_nothing() { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "do_nothing"); + assert!(!item_fn.mutable_receiver()); + assert!(!item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 0); + } + + #[test] + fn receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&mut self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } + + #[test] + fn immutable_receiver_fn() { + let input_tokens: TokenStream = quote! { + pub fn act_upon(&self) { } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_eq!(&item_fn.name().to_string(), "act_upon"); + assert!(item_fn.mutable_receiver()); + assert!(item_fn.is_public()); + assert!(item_fn.return_type().is_none()); + assert_eq!(item_fn.arg_list().count(), 1); + } +} + +#[cfg(test)] +mod generate_tests { + use crate::function::ExportedFn; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual + .chars() + .zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (actual_diff, expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn minimal_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_nothing() { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_nothing { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(do_nothing())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn() -> Result { + Ok(Dynamic::from(super::do_nothing())) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_do_something { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize) -> Result { + Ok(Dynamic::from(super::do_something(x))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn one_arg_usize_fn_impl() { + let input_tokens: TokenStream = quote! { + pub fn do_something(x: usize) { } + }; + + let expected_tokens = quote! { + impl PluginFunction for MyType { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(do_something(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(MyType()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate_impl("MyType"), expected_tokens); + } + + #[test] + fn two_arg_returning_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn add_together(x: usize, y: usize) -> usize { x + y } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_add_together { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + Ok(Dynamic::from(super::add_together(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn mut_arg_usize_fn() { + let input_tokens: TokenStream = quote! { + pub fn increment(x: &mut usize, y: usize) { *x += y; } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_increment { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg1 = mem::take(args[1usize]).clone().cast::(); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + Ok(Dynamic::from(super::increment(x, y))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } + + #[test] + fn str_arg_fn() { + let input_tokens: TokenStream = quote! { + pub fn special_print(message: &str) { eprintln!("----{}----", message); } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_special_print { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(special_print(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { Box::new(Token()) } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn token_callable() -> CallableFunction { + CallableFunction::from_plugin(Token()) + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + type EvalBox = Box; + pub fn dynamic_result_fn(message: &str) -> Result { + Ok(Dynamic::from(super::special_print(message))) + } + } + }; + + let item_fn = syn::parse2::(input_tokens).unwrap(); + assert!(!item_fn.mutable_receiver()); + assert_streams_eq(item_fn.generate(), expected_tokens); + } +} diff --git a/codegen/src/test/mod.rs b/codegen/src/test/mod.rs new file mode 100644 index 00000000..452c9b21 --- /dev/null +++ b/codegen/src/test/mod.rs @@ -0,0 +1,2 @@ +mod function; +mod module; diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs new file mode 100644 index 00000000..5d4e6186 --- /dev/null +++ b/codegen/src/test/module.rs @@ -0,0 +1,1243 @@ +#[cfg(test)] +mod module_tests { + use crate::module::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number"); + assert_eq!(item_mod.fns()[0].arg_count(), 0); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "add_one_to"); + assert_eq!(item_mod.fns()[0].arg_count(), 1); + assert_eq!( + item_mod.fns()[0].arg_list().next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + let mut args = item_mod.fns()[0].arg_list(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "add_together"); + assert_eq!(item_mod.fns()[0].arg_count(), 2); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { x: INT }).unwrap() + ); + assert_eq!( + args.next().unwrap(), + &syn::parse2::(quote! { y: INT }).unwrap() + ); + assert!(args.next().is_none()); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert_eq!(&item_mod.submodules()[0].consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.submodules()[0].consts()[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod skip_this { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert_eq!(item_mod.submodules()[0].fns().len(), 1); + assert!(item_mod.submodules()[0].fns()[0].skipped()); + assert!(item_mod.submodules()[0].consts().is_empty()); + assert!(item_mod.submodules()[0].submodules().is_empty()); + } + + #[test] + fn one_skipped_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_mod(skip)] + pub mod skip_this { + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.submodules().len(), 1); + assert!(item_mod.submodules()[0].skipped()); + } + + #[test] + fn one_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert_eq!(item_mod.consts().len(), 1); + assert_eq!(&item_mod.consts()[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.consts()[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_eq!(item_mod.fns().len(), 1); + assert!(item_mod.fns()[0].skipped()); + assert!(item_mod.consts().is_empty()); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns().is_empty()); + assert!(item_mod.consts().is_empty()); + } +} + +#[cfg(test)] +mod generate_tests { + use crate::module::Module; + + use proc_macro2::TokenStream; + use quote::quote; + + fn assert_streams_eq(actual: TokenStream, expected: TokenStream) { + let actual = actual.to_string(); + let expected = expected.to_string(); + if &actual != &expected { + let mut counter = 0; + let iter = actual + .chars() + .zip(expected.chars()) + .inspect(|_| counter += 1) + .skip_while(|(a, e)| *a == *e); + let (_actual_diff, _expected_diff) = { + let mut actual_diff = String::new(); + let mut expected_diff = String::new(); + for (a, e) in iter.take(50) { + actual_diff.push(a); + expected_diff.push(e); + } + (actual_diff, expected_diff) + }; + eprintln!("actual != expected, diverge at char {}", counter); + } + assert_eq!(actual, expected); + } + + #[test] + fn empty_module() { + let input_tokens: TokenStream = quote! { + pub mod empty { } + }; + + let expected_tokens = quote! { + pub mod empty { + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_factory_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[allow(non_camel_case_types)] + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_single_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_one_to", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m + } + #[allow(non_camel_case_types)] + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn two_fn_overload_module() { + let input_tokens: TokenStream = quote! { + pub mod two_fns { + #[rhai_fn(name = "add_n")] + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + #[rhai_fn(name = "add_n")] + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod two_fns { + pub fn add_one_to(x: INT) -> INT { + x + 1 + } + + pub fn add_n_to(x: INT, y: INT) -> INT { + x + y + } + + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::()], + CallableFunction::from_plugin(add_one_to_token())); + m.set_fn("add_n", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_n_to_token())); + m + } + + #[allow(non_camel_case_types)] + struct add_one_to_token(); + impl PluginFunction for add_one_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(add_one_to(arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_one_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn add_one_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_one_to_token()) + } + pub fn add_one_to_token_input_types() -> Box<[TypeId]> { + add_one_to_token().input_types() + } + + #[allow(non_camel_case_types)] + struct add_n_to_token(); + impl PluginFunction for add_n_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_n_to(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_n_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_n_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_n_to_token()) + } + pub fn add_n_to_token_input_types() -> Box<[TypeId]> { + add_n_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_double_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn add_together(x: INT, y: INT) -> INT { + x + y + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("add_together", FnAccess::Public, &[core::any::TypeId::of::(), + core::any::TypeId::of::()], + CallableFunction::from_plugin(add_together_token())); + m + } + #[allow(non_camel_case_types)] + struct add_together_token(); + impl PluginFunction for add_together_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + let arg1 = mem::take(args[1usize]).clone().cast::(); + Ok(Dynamic::from(add_together(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(add_together_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), + TypeId::of::()].into_boxed_slice() + } + } + pub fn add_together_token_callable() -> CallableFunction { + CallableFunction::from_plugin(add_together_token()) + } + pub fn add_together_token_input_types() -> Box<[TypeId]> { + add_together_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_module_imports_preserved() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub use rhai::INT; + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_skipped_submodule() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + #[rhai_mod(skip)] + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub fn get_mystic_number() -> INT { + 42 + } + pub mod inner_secrets { + pub const SECRET_NUMBER: INT = 86; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("get_mystic_number", FnAccess::Public, &[], + CallableFunction::from_plugin(get_mystic_number_token())); + m + } + #[allow(non_camel_case_types)] + struct get_mystic_number_token(); + impl PluginFunction for get_mystic_number_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(Dynamic::from(get_mystic_number())) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(get_mystic_number_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![].into_boxed_slice() + } + } + pub fn get_mystic_number_token_callable() -> CallableFunction { + CallableFunction::from_plugin(get_mystic_number_token()) + } + pub fn get_mystic_number_token_input_types() -> Box<[TypeId]> { + get_mystic_number_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_private_constant_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_str_arg_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + } + }; + + let expected_tokens = quote! { + pub mod str_fn { + pub fn print_out_to(x: &str) { + x + 1 + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("print_out_to", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(print_out_to_token())); + m + } + #[allow(non_camel_case_types)] + struct print_out_to_token(); + impl PluginFunction for print_out_to_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0 = mem::take(args[0usize]).clone().cast::(); + Ok(Dynamic::from(print_out_to(&arg0))) + } + + fn is_method_call(&self) -> bool { false } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(print_out_to_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn print_out_to_token_callable() -> CallableFunction { + CallableFunction::from_plugin(print_out_to_token()) + } + pub fn print_out_to_token_input_types() -> Box<[TypeId]> { + print_out_to_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_mut_ref_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + }; + + let expected_tokens = quote! { + pub mod ref_fn { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_with_cfg_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + #[cfg(not(feature = "no_float"))] + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + #[cfg(not(feature = "no_float"))] { + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("it_is", self::it_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn dual_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + } + } + }; + + let expected_tokens = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("SPECIAL_CPU_NUMBER", 68000); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + { m.set_sub_module("first_is", self::first_is::rhai_module_generate()); } + { m.set_sub_module("second_is", self::second_is::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn deep_tree_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + } + pub mod right { + pub const VALUE: INT = 7; + } + } + pub mod right { + pub const VALUE: INT = 3; + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + } + pub mod right { + pub const VALUE: INT = 1; + } + } + } + }; + + let expected_tokens = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 2); + m + } + } + pub mod right { + pub const VALUE: INT = 7; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 7); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 17); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + pub mod right { + pub const VALUE: INT = 3; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 3); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 19); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 25); + m + } + } + pub mod right { + pub const VALUE: INT = 1; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 1); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 36); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 100); + { m.set_sub_module("left", self::left::rhai_module_generate()); } + { m.set_sub_module("right", self::right::rhai_module_generate()); } + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } +} From dbfd3df810cdfee0a09095f18f93a3e0b4b593f3 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 4 Sep 2020 11:57:40 +0800 Subject: [PATCH 11/19] Support multiple names in rhai_fn. --- codegen/src/function.rs | 44 ++++++++++++----------- codegen/src/rhai_module.rs | 74 +++++++++++++++++++++----------------- tests/plugins.rs | 4 ++- 3 files changed, 68 insertions(+), 54 deletions(-) diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 3470e691..aa4001f5 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -19,7 +19,7 @@ use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { - pub name: Option, + pub name: Option>, pub return_raw: bool, pub skip: bool, pub span: Option, @@ -55,11 +55,12 @@ impl ExportedParams for ExportedFnParams { Default::default() } - fn from_info( - info: crate::attrs::ExportInfo, - ) -> syn::Result { - let ExportInfo { item_span: span, items: attrs } = info; - let mut name = None; + fn from_info(info: crate::attrs::ExportInfo) -> syn::Result { + let ExportInfo { + item_span: span, + items: attrs, + } = info; + let mut name = Vec::new(); let mut return_raw = false; let mut skip = false; for attr in attrs { @@ -73,15 +74,15 @@ impl ExportedParams for ExportedFnParams { "Rhai function names may not contain dot", )); } - name = Some(s.value()) + name.push(s.value()) } - ("get", Some(s)) => name = Some(make_getter(&s.value())), - ("set", Some(s)) => name = Some(make_setter(&s.value())), + ("get", Some(s)) => name.push(make_getter(&s.value())), + ("set", Some(s)) => name.push(make_setter(&s.value())), ("get", None) | ("set", None) | ("name", None) => { return Err(syn::Error::new(key.span(), "requires value")) } - ("index_get", None) => name = Some(FN_IDX_GET.to_string()), - ("index_set", None) => name = Some(FN_IDX_SET.to_string()), + ("index_get", None) => name.push(FN_IDX_GET.to_string()), + ("index_set", None) => name.push(FN_IDX_SET.to_string()), ("return_raw", None) => return_raw = true, ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) @@ -98,7 +99,7 @@ impl ExportedParams for ExportedFnParams { } Ok(ExportedFnParams { - name, + name: if name.is_empty() { None } else { Some(name) }, return_raw, skip, span: Some(span), @@ -260,7 +261,7 @@ impl ExportedFn { pub(crate) fn exported_name<'n>(&'n self) -> Cow<'n, str> { if let Some(ref name) = self.params.name { - Cow::Borrowed(name.as_str()) + Cow::Borrowed(name.last().unwrap().as_str()) } else { Cow::Owned(self.signature.ident.to_string()) } @@ -346,7 +347,9 @@ impl ExportedFn { }) .collect(); - let return_span = self.return_type().map(|r| r.span()) + let return_span = self + .return_type() + .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); if !self.params.return_raw { quote_spanned! { return_span=> @@ -393,11 +396,10 @@ impl ExportedFn { pub fn generate_impl(&self, on_type_name: &str) -> proc_macro2::TokenStream { let sig_name = self.name().clone(); - let name = self - .params - .name - .clone() - .unwrap_or_else(|| self.name().to_string()); + let name = self.params.name.as_ref().map_or_else( + || self.name().to_string(), + |names| names.last().unwrap().clone(), + ); let arg_count = self.arg_count(); let is_method_call = self.mutable_receiver(); @@ -518,7 +520,9 @@ impl ExportedFn { // Handle "raw returns", aka cases where the result is a dynamic or an error. // // This allows skipping the Dynamic::from wrap. - let return_span = self.return_type().map(|r| r.span()) + let return_span = self + .return_type() + .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); let return_expr = if !self.params.return_raw { quote_spanned! { return_span=> diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 40485adb..57ec78a8 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -67,12 +67,12 @@ pub(crate) fn generate_body( &format!("{}_token", function.name().to_string()), function.name().span(), ); - let reg_name = function + let reg_names = function .params() .name .clone() - .unwrap_or_else(|| function.name().to_string()); - let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); + .unwrap_or_else(|| vec![function.name().to_string()]); + let fn_input_types: Vec = function .arg_list() .map(|fnarg| match fnarg { @@ -110,13 +110,17 @@ pub(crate) fn generate_body( }) .collect(); - set_fn_stmts.push( - syn::parse2::(quote! { - m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], - CallableFunction::from_plugin(#fn_token_name())); - }) - .unwrap(), - ); + for reg_name in reg_names { + let fn_literal = syn::LitStr::new(®_name, proc_macro2::Span::call_site()); + + set_fn_stmts.push( + syn::parse2::(quote! { + m.set_fn(#fn_literal, FnAccess::Public, &[#(#fn_input_types),*], + CallableFunction::from_plugin(#fn_token_name())); + }) + .unwrap(), + ); + } gen_fn_tokens.push(quote! { #[allow(non_camel_case_types)] @@ -155,29 +159,33 @@ pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn:: let mut renames = HashMap::::new(); let mut names = HashMap::::new(); for itemfn in fns.iter() { - if let Some(ref name) = itemfn.params().name { - let current_span = itemfn.params().span.as_ref().unwrap(); - let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { - let type_string: String = match fnarg { - syn::FnArg::Receiver(_) => unimplemented!("receiver rhai_fns not implemented"), - syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { - ty.as_ref().to_token_stream().to_string() - } - }; - argstr.push('.'); - argstr.push_str(&type_string); - argstr - }); - if let Some(other_span) = renames.insert(key, *current_span) { - let mut err = syn::Error::new( - *current_span, - format!("duplicate Rhai signature for '{}'", &name), - ); - err.combine(syn::Error::new( - other_span, - format!("duplicated function renamed '{}'", &name), - )); - return Err(err); + if let Some(ref names) = itemfn.params().name { + for name in names { + let current_span = itemfn.params().span.as_ref().unwrap(); + let key = itemfn.arg_list().fold(name.clone(), |mut argstr, fnarg| { + let type_string: String = match fnarg { + syn::FnArg::Receiver(_) => { + unimplemented!("receiver rhai_fns not implemented") + } + syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { + ty.as_ref().to_token_stream().to_string() + } + }; + argstr.push('.'); + argstr.push_str(&type_string); + argstr + }); + if let Some(other_span) = renames.insert(key, *current_span) { + let mut err = syn::Error::new( + *current_span, + format!("duplicate Rhai signature for '{}'", &name), + ); + err.combine(syn::Error::new( + other_span, + format!("duplicated function renamed '{}'", &name), + )); + return Err(err); + } } } else { let ident = itemfn.name(); diff --git a/tests/plugins.rs b/tests/plugins.rs index 5310c79e..cac8718f 100644 --- a/tests/plugins.rs +++ b/tests/plugins.rs @@ -21,7 +21,7 @@ mod test { } } - #[rhai_fn(name = "test")] + #[rhai_fn(name = "test", name = "hi")] #[inline(always)] pub fn len(array: &mut Array, mul: INT) -> INT { (array.len() as INT) * mul @@ -74,6 +74,8 @@ fn test_plugins_package() -> Result<(), Box> { #[cfg(not(feature = "no_object"))] assert_eq!(engine.eval::("let a = [1, 2, 3]; a.foo")?, 1); + assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); + assert_eq!(engine.eval::("let a = [1, 2, 3]; hi(a, 2)")?, 6); assert_eq!(engine.eval::("let a = [1, 2, 3]; test(a, 2)")?, 6); assert_eq!(engine.eval::("2 + 2")?, 5); assert_eq!( From 397acb4fcea8096a4bfae6d6cf654b40d9a40c8b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 5 Sep 2020 10:46:24 +0800 Subject: [PATCH 12/19] Add tests for multiple renames. --- codegen/tests/test_modules.rs | 67 +++++++++++++++++++ ...ai_fn_rename_collision_oneattr_multiple.rs | 30 +++++++++ ...n_rename_collision_oneattr_multiple.stderr | 17 +++++ .../rhai_fn_rename_collision_with_itself.rs | 25 +++++++ ...hai_fn_rename_collision_with_itself.stderr | 17 +++++ 5 files changed, 156 insertions(+) create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs create mode 100644 codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index f873e42b..014edbeb 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -222,6 +222,73 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { Ok(()) } +mod multiple_fn_rename { + use rhai::plugin::*; + #[export_module] + pub mod my_adds { + use rhai::{FLOAT, INT}; + + pub fn get_mystic_number() -> FLOAT { + 42.0 + } + #[rhai_fn(name = "add", name = "+", name = "add_together")] + pub fn add_float(f1: FLOAT, f2: FLOAT) -> FLOAT { + f1 + f2 * 2.0 + } + + #[rhai_fn(name = "add", name = "+", name = "add_together")] + pub fn add_int(i1: INT, i2: INT) -> INT { + i1 + i2 * 2 + } + + #[rhai_fn(name = "prop", get = "prop")] + pub fn get_prop(x: FLOAT) -> FLOAT { + x * 2.0 + } + + #[rhai_fn(name = "idx", index_get)] + pub fn index(x: FLOAT, i: INT) -> FLOAT { + x + (i as FLOAT) + } + } +} + +#[test] +fn multiple_fn_rename_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::multiple_fn_rename::my_adds); + engine.load_package(m); + + let output_array = engine.eval::( + r#" + let fx = get_mystic_number(); + let fy1 = add(fx, 1.0); + let fy2 = add_together(fx, 1.0); + let fy3 = fx + 1.0; + let p1 = fx.prop; + let p2 = prop(fx); + let idx1 = fx[1]; + let idx2 = idx(fx, 1); + let ix = 42; + let iy1 = add(ix, 1); + let iy2 = add_together(ix, 1); + let iy3 = ix + 1; + [fy1, fy2, fy3, iy1, iy2, iy3, p1, p2, idx1, idx2] + "#, + )?; + assert_eq!(&output_array[0].as_float().unwrap(), &44.0); + assert_eq!(&output_array[1].as_float().unwrap(), &44.0); + assert_eq!(&output_array[2].as_float().unwrap(), &44.0); + assert_eq!(&output_array[3].as_int().unwrap(), &44); + assert_eq!(&output_array[4].as_int().unwrap(), &44); + assert_eq!(&output_array[5].as_int().unwrap(), &44); + assert_eq!(&output_array[6].as_float().unwrap(), &84.0); + assert_eq!(&output_array[7].as_float().unwrap(), &84.0); + assert_eq!(&output_array[8].as_float().unwrap(), &43.0); + assert_eq!(&output_array[9].as_float().unwrap(), &43.0); + Ok(()) +} + mod export_by_prefix { use rhai::plugin::*; #[export_module(export_prefix = "foo_")] diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs new file mode 100644 index 00000000..112d9cc0 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.rs @@ -0,0 +1,30 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo", get = "bar")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } + + #[rhai_fn(get = "bar")] + pub fn foo(input: Point) -> bool { + input.x < input.y + } +} + +fn main() { + let n = Point { x: 0.0, y: 10.0 }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr new file mode 100644 index 00000000..2b1da81c --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'get$bar' + --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:17:15 + | +17 | #[rhai_fn(get = "bar")] + | ^^^ + +error: duplicated function renamed 'get$bar' + --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:12:15 + | +12 | #[rhai_fn(name = "foo", get = "bar")] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8 + | +25 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs new file mode 100644 index 00000000..0ba996eb --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.rs @@ -0,0 +1,25 @@ +use rhai::plugin::*; + +#[derive(Clone)] +pub struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { + pub use super::Point; + #[rhai_fn(name = "foo", name = "bar", name = "foo")] + pub fn test_fn(input: Point) -> bool { + input.x > input.y + } +} + +fn main() { + let n = Point { x: 0.0, y: 10.0 }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr new file mode 100644 index 00000000..40f3b830 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr @@ -0,0 +1,17 @@ +error: duplicate Rhai signature for 'foo' + --> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15 + | +12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")] + | ^^^^ + +error: duplicated function renamed 'foo' + --> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15 + | +12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_fn_rename_collision_with_itself.rs:20:8 + | +20 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` From bd30edb8774eb6a5362420caf8fcfc9b9bc0091b Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sun, 6 Sep 2020 11:23:12 +0800 Subject: [PATCH 13/19] Fix test output. --- .../ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr | 4 ++-- codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr index 2b1da81c..091a5893 100644 --- a/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr +++ b/codegen/ui_tests/rhai_fn_rename_collision_oneattr_multiple.stderr @@ -2,13 +2,13 @@ error: duplicate Rhai signature for 'get$bar' --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:17:15 | 17 | #[rhai_fn(get = "bar")] - | ^^^ + | ^^^^^^^^^^^ error: duplicated function renamed 'get$bar' --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:12:15 | 12 | #[rhai_fn(name = "foo", get = "bar")] - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0433]: failed to resolve: use of undeclared type or module `test_module` --> $DIR/rhai_fn_rename_collision_oneattr_multiple.rs:25:8 diff --git a/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr index 40f3b830..ffbca90b 100644 --- a/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr +++ b/codegen/ui_tests/rhai_fn_rename_collision_with_itself.stderr @@ -2,13 +2,13 @@ error: duplicate Rhai signature for 'foo' --> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15 | 12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")] - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: duplicated function renamed 'foo' --> $DIR/rhai_fn_rename_collision_with_itself.rs:12:15 | 12 | #[rhai_fn(name = "foo", name = "bar", name = "foo")] - | ^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0433]: failed to resolve: use of undeclared type or module `test_module` --> $DIR/rhai_fn_rename_collision_with_itself.rs:20:8 From d41fde9c31977fdb8f013b6ae3e44b75dcdff769 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 8 Sep 2020 18:01:34 +0800 Subject: [PATCH 14/19] Use multiple renames to simplify plugins. --- src/packages/array_basic.rs | 12 ++-------- src/packages/eval.rs | 11 +++++---- src/packages/fn_basic.rs | 7 +----- src/packages/map_basic.rs | 14 +++-------- src/packages/math_basic.rs | 48 +++++++------------------------------ src/packages/string_more.rs | 8 ++----- src/packages/time_basic.rs | 9 ++----- 7 files changed, 25 insertions(+), 84 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 66c35924..767c4c5f 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -86,24 +86,16 @@ def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { #[export_module] mod array_functions { + #[rhai_fn(name = "len", get = "len")] #[inline(always)] pub fn len(list: &mut Array) -> INT { list.len() as INT } - #[rhai_fn(get = "len")] - #[inline(always)] - pub fn len_prop(list: &mut Array) -> INT { - len(list) - } + #[rhai_fn(name = "append", name = "+=")] #[inline(always)] pub fn append(x: &mut Array, y: Array) { x.extend(y); } - #[rhai_fn(name = "+=")] - #[inline(always)] - pub fn append_operator(x: &mut Array, y: Array) { - append(x, y) - } #[rhai_fn(name = "+")] #[inline] pub fn concat(mut x: Array, y: Array) -> Array { diff --git a/src/packages/eval.rs b/src/packages/eval.rs index 0e2187da..acebf216 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -5,10 +5,13 @@ use crate::plugin::*; use crate::result::EvalAltResult; def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - set_exported_fn!(lib, "eval", eval_override); + lib.combine_flatten(exported_module!(eval_override)); }); -#[export_fn(return_raw)] -fn eval_override(_script: ImmutableString) -> Result> { - Err("eval is evil!".into()) +#[export_module] +mod eval_override { + #[rhai_fn(return_raw)] + pub fn eval(_script: ImmutableString) -> Result> { + Err("eval is evil!".into()) + } } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index bb3835d7..61d18371 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -8,14 +8,9 @@ def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { #[export_module] mod fn_ptr_functions { + #[rhai_fn(name = "name", get = "name")] #[inline(always)] pub fn name(f: &mut FnPtr) -> ImmutableString { f.get_fn_name().clone() } - - #[rhai_fn(get = "name")] - #[inline(always)] - pub fn name_prop(f: &mut FnPtr) -> ImmutableString { - name(f) - } } diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index d479fcfd..a5983268 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -18,33 +18,25 @@ mod map_functions { pub fn has(map: &mut Map, prop: ImmutableString) -> bool { map.contains_key(&prop) } + #[rhai_fn(name = "len", get = "len")] #[inline(always)] pub fn len(map: &mut Map) -> INT { map.len() as INT } - #[rhai_fn(get = "len")] - #[inline(always)] - pub fn len_prop(map: &mut Map) -> INT { - len(map) - } #[inline(always)] pub fn clear(map: &mut Map) { map.clear(); } - #[inline] + #[inline(always)] pub fn remove(x: &mut Map, name: ImmutableString) -> Dynamic { x.remove(&name).unwrap_or_else(|| ().into()) } + #[rhai_fn(name = "mixin", name = "+=")] pub fn mixin(map1: &mut Map, map2: Map) { map2.into_iter().for_each(|(key, value)| { map1.insert(key, value); }); } - #[rhai_fn(name = "+=")] - #[inline(always)] - pub fn mixin_operator(map1: &mut Map, map2: Map) { - mixin(map1, map2) - } #[rhai_fn(name = "+")] pub fn merge(mut map1: Map, map2: Map) -> Map { map2.into_iter().for_each(|(key, value)| { diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index a7c17e50..ddbb640c 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -157,78 +157,46 @@ mod float_functions { pub fn log10(x: FLOAT) -> FLOAT { x.log10() } + #[rhai_fn(name = "floor", get = "floor")] #[inline(always)] pub fn floor(x: FLOAT) -> FLOAT { x.floor() } - #[rhai_fn(get = "floor")] - #[inline(always)] - pub fn floor_prop(x: FLOAT) -> FLOAT { - floor(x) - } + #[rhai_fn(name = "ceiling", get = "ceiling")] #[inline(always)] pub fn ceiling(x: FLOAT) -> FLOAT { x.ceil() } - #[rhai_fn(get = "ceiling")] - #[inline(always)] - pub fn ceiling_prop(x: FLOAT) -> FLOAT { - ceiling(x) - } + #[rhai_fn(name = "round", get = "round")] #[inline(always)] pub fn round(x: FLOAT) -> FLOAT { x.ceil() } - #[rhai_fn(get = "round")] - #[inline(always)] - pub fn round_prop(x: FLOAT) -> FLOAT { - ceiling(x) - } + #[rhai_fn(name = "int", get = "int")] #[inline(always)] pub fn int(x: FLOAT) -> FLOAT { x.trunc() } - #[rhai_fn(get = "int")] - #[inline(always)] - pub fn int_prop(x: FLOAT) -> FLOAT { - int(x) - } + #[rhai_fn(name = "fraction", get = "fraction")] #[inline(always)] pub fn fraction(x: FLOAT) -> FLOAT { x.fract() } - #[rhai_fn(get = "fraction")] - #[inline(always)] - pub fn fraction_prop(x: FLOAT) -> FLOAT { - fraction(x) - } + #[rhai_fn(name = "is_nan", get = "is_nan")] #[inline(always)] pub fn is_nan(x: FLOAT) -> bool { x.is_nan() } - #[rhai_fn(get = "is_nan")] - #[inline(always)] - pub fn is_nan_prop(x: FLOAT) -> bool { - is_nan(x) - } + #[rhai_fn(name = "is_finite", get = "is_finite")] #[inline(always)] pub fn is_finite(x: FLOAT) -> bool { x.is_finite() } - #[rhai_fn(get = "is_finite")] - #[inline(always)] - pub fn is_finite_prop(x: FLOAT) -> bool { - is_finite(x) - } + #[rhai_fn(name = "is_infinite", get = "is_infinite")] #[inline(always)] pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } - #[rhai_fn(get = "is_infinite")] - #[inline(always)] - pub fn is_infinite_prop(x: FLOAT) -> bool { - is_infinite(x) - } #[rhai_fn(name = "to_int", return_raw)] #[inline] pub fn f32_to_int(x: f32) -> Result> { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 3766d487..5dfd4897 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -140,6 +140,7 @@ mod string_functions { pub fn add_prepend_unit(_x: (), s: ImmutableString) -> ImmutableString { s } + #[rhai_fn(name = "+=")] #[inline(always)] pub fn append_char(s: &mut ImmutableString, ch: char) { @@ -151,17 +152,12 @@ mod string_functions { *s += &add; } + #[rhai_fn(name = "len", get = "len")] #[inline(always)] pub fn len(s: &mut ImmutableString) -> INT { s.chars().count() as INT } - #[rhai_fn(get = "len")] - #[inline(always)] - pub fn len_prop(s: &mut ImmutableString) -> INT { - len(s) - } - #[inline(always)] pub fn clear(s: &mut ImmutableString) { s.make_mut().clear(); diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 3ea2b255..80ba2637 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -30,7 +30,8 @@ mod time_functions { pub fn timestamp() -> Instant { Instant::now() } - #[rhai_fn(return_raw)] + + #[rhai_fn(name = "elapsed", get = "elapsed", return_raw)] pub fn elapsed(timestamp: &mut Instant) -> Result> { #[cfg(not(feature = "no_float"))] { @@ -52,12 +53,6 @@ mod time_functions { } } - #[rhai_fn(get = "elapsed", return_raw)] - #[inline(always)] - pub fn elapsed_prop(timestamp: &mut Instant) -> Result> { - elapsed(timestamp) - } - #[rhai_fn(return_raw, name = "-")] pub fn time_diff(ts1: Instant, ts2: Instant) -> Result> { #[cfg(not(feature = "no_float"))] From 192979ebfdda34150eae5cd2d3b6f46aff32951c Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 8 Sep 2020 16:03:38 -0500 Subject: [PATCH 15/19] Crate workspace to include codegen --- Cargo.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 9d989830..37f7f626 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,3 +1,9 @@ +[workspace] +members = [ + ".", + "codegen" +] + [package] name = "rhai" version = "0.18.3" From 229475caeffa202d59c3c3269c27a2e7af4e941d Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 8 Sep 2020 16:04:04 -0500 Subject: [PATCH 16/19] Add tests fixed by workspace --- .../ui_tests/rhai_fn_non_clonable_return.rs | 27 +++++++++++++++++ .../rhai_fn_non_clonable_return.stderr | 10 +++++++ .../ui_tests/rhai_mod_non_clonable_return.rs | 29 +++++++++++++++++++ .../rhai_mod_non_clonable_return.stderr | 10 +++++++ 4 files changed, 76 insertions(+) create mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.rs create mode 100644 codegen/ui_tests/rhai_fn_non_clonable_return.stderr create mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.rs create mode 100644 codegen/ui_tests/rhai_mod_non_clonable_return.stderr diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.rs b/codegen/ui_tests/rhai_fn_non_clonable_return.rs new file mode 100644 index 00000000..e2e2d788 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.rs @@ -0,0 +1,27 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_fn] +pub fn test_fn(input: f32) -> NonClonable { + NonClonable { + a: input, + b: 10, + c: 'a', + d: true, + } +} + +fn main() { + let n = test_fn(20.0); + if n.c == 'a' { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_fn_non_clonable_return.stderr b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr new file mode 100644 index 00000000..ebe6c264 --- /dev/null +++ b/codegen/ui_tests/rhai_fn_non_clonable_return.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied + --> $DIR/rhai_fn_non_clonable_return.rs:11:8 + | +11 | pub fn test_fn(input: f32) -> NonClonable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + | + ::: $WORKSPACE/src/any.rs + | + | pub fn from(value: T) -> Self { + | ----- required by this bound in `rhai::Dynamic::from` diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.rs b/codegen/ui_tests/rhai_mod_non_clonable_return.rs new file mode 100644 index 00000000..fe8f5fff --- /dev/null +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +struct NonClonable { + a: f32, + b: u32, + c: char, + d: bool, +} + +#[export_module] +pub mod test_mod { + pub fn test_fn(input: f32) -> NonClonable { + NonClonable { + a: input, + b: 10, + c: 'a', + d: true, + } + } +} + +fn main() { + let n = test_mod::test_fn(20.0); + if n.c == 'a' { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_non_clonable_return.stderr b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr new file mode 100644 index 00000000..99a42bb3 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_non_clonable_return.stderr @@ -0,0 +1,10 @@ +error[E0277]: the trait bound `NonClonable: Clone` is not satisfied + --> $DIR/rhai_mod_non_clonable_return.rs:12:12 + | +12 | pub fn test_fn(input: f32) -> NonClonable { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `NonClonable` + | + ::: $WORKSPACE/src/any.rs + | + | pub fn from(value: T) -> Self { + | ----- required by this bound in `rhai::Dynamic::from` From 60ecb87c5dbc877fc051c020420e8b0ca71de01e Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 8 Sep 2020 16:17:58 -0500 Subject: [PATCH 17/19] Put no_std_test into its own empty workspace --- no_std/no_std_test/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/no_std/no_std_test/Cargo.toml b/no_std/no_std_test/Cargo.toml index 56a73c1e..723cbdfc 100644 --- a/no_std/no_std_test/Cargo.toml +++ b/no_std/no_std_test/Cargo.toml @@ -1,5 +1,7 @@ cargo-features = ["named-profiles"] +[workspace] + [package] name = "no_std_test" version = "0.1.0" From e6e1362a610d779debf735215beb41f3993664d5 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Tue, 8 Sep 2020 16:24:08 -0500 Subject: [PATCH 18/19] Do not run all workspace tests --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b5465c05..37d8c233 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -52,7 +52,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --all ${{matrix.flags}} + args: ${{matrix.flags}} # no-std builds are a bit more extensive to test no_std_build: name: NoStdBuild From d527ef7dbfb1cae37090978016a463f32a891864 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Wed, 9 Sep 2020 09:12:28 +0800 Subject: [PATCH 19/19] Remove SC from codegen authors, add jhwgh1968 to Rhai authors. --- Cargo.toml | 2 +- codegen/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 37f7f626..5e529f38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,7 @@ members = [ name = "rhai" version = "0.18.3" edition = "2018" -authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] +authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung", "jhwgh1968"] description = "Embedded scripting for Rust" homepage = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai" diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 89a6f66f..97a62e06 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -2,7 +2,7 @@ name = "rhai_codegen" version = "0.1.0" edition = "2018" -authors = ["jhwgh1968", "Stephen Chung"] +authors = ["jhwgh1968"] description = "Proceducral macro support package for Rhai, a scripting language for Rust" homepage = "https://github.com/jonathandturner/rhai" repository = "https://github.com/jonathandturner/rhai"