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/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..091a5893 --- /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..ffbca90b --- /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` 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!(