diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 210e41df..0d34c266 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -16,7 +16,6 @@ use quote::{quote, quote_spanned}; use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; use crate::attrs::{ExportInfo, ExportScope, ExportedParams}; -use crate::rhai_module::flatten_type_groups; #[derive(Clone, Debug, Eq, PartialEq)] pub enum Index { @@ -48,10 +47,10 @@ impl FnSpecialAccess { match self { FnSpecialAccess::None => None, FnSpecialAccess::Property(Property::Get(ref g)) => { - Some((format!("get${}", g.to_string()), g.to_string(), g.span())) + Some((format!("{}{}", FN_GET, g), g.to_string(), g.span())) } FnSpecialAccess::Property(Property::Set(ref s)) => { - Some((format!("set${}", s.to_string()), s.to_string(), s.span())) + Some((format!("{}{}", FN_SET, s), s.to_string(), s.span())) } FnSpecialAccess::Index(Index::Get) => Some(( FN_IDX_GET.to_string(), @@ -67,6 +66,14 @@ impl FnSpecialAccess { } } +pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type { + match ty { + syn::Type::Group(syn::TypeGroup { ref elem, .. }) + | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()), + _ => ty, + } +} + #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option>, @@ -76,6 +83,8 @@ pub(crate) struct ExportedFnParams { pub special: FnSpecialAccess, } +pub const FN_GET: &str = "get$"; +pub const FN_SET: &str = "set$"; pub const FN_IDX_GET: &str = "index$get$"; pub const FN_IDX_SET: &str = "index$set$"; @@ -130,36 +139,24 @@ impl ExportedParams for ExportedFnParams { "use attribute 'index_set' instead", )) } - ("name", Some(s)) if s.value().starts_with("get$") => { + ("name", Some(s)) if s.value().starts_with(FN_GET) => { return Err(syn::Error::new( item_span, format!( "use attribute 'getter = \"{}\"' instead", - &s.value()["get$".len()..] + &s.value()[FN_GET.len()..] ), )) } - ("name", Some(s)) if s.value().starts_with("set$") => { + ("name", Some(s)) if s.value().starts_with(FN_SET) => { return Err(syn::Error::new( item_span, format!( "use attribute 'setter = \"{}\"' instead", - &s.value()["set$".len()..] + &s.value()[FN_SET.len()..] ), )) } - ("name", Some(s)) if s.value().contains('$') => { - return Err(syn::Error::new( - s.span(), - "Rhai function names may not contain dollar sign", - )) - } - ("name", Some(s)) if s.value().contains('.') => { - return Err(syn::Error::new( - s.span(), - "Rhai function names may not contain dot", - )) - } ("name", Some(s)) => name.push(s.value()), ("set", Some(s)) => { special = match special { @@ -225,6 +222,7 @@ pub(crate) struct ExportedFn { entire_span: proc_macro2::Span, signature: syn::Signature, is_public: bool, + return_dynamic: bool, mut_receiver: bool, params: ExportedFnParams, } @@ -235,6 +233,10 @@ impl Parse for ExportedFn { let entire_span = fn_all.span(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); + let dynamic_type_path1 = syn::parse2::(quote! { Dynamic }).unwrap(); + let dynamic_type_path2 = syn::parse2::(quote! { rhai::Dynamic }).unwrap(); + let mut return_dynamic = false; + // #[cfg] attributes are not allowed on functions due to what is generated for them crate::attrs::deny_cfg_attr(&fn_all.attrs)?; @@ -250,11 +252,11 @@ impl Parse for ExportedFn { }) => true, syn::FnArg::Typed(syn::PatType { ref ty, .. }) => { match flatten_type_groups(ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => true, - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. @@ -285,18 +287,18 @@ impl Parse for ExportedFn { _ => panic!("internal error: receiver argument outside of first position!?"), }; let is_ok = match flatten_type_groups(ty.as_ref()) { - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: Some(_), .. }) => false, - &syn::Type::Reference(syn::TypeReference { + syn::Type::Reference(syn::TypeReference { mutability: None, ref elem, .. }) => { matches!(flatten_type_groups(elem.as_ref()), &syn::Type::Path(ref p) if p.path == str_type_path) } - &syn::Type::Verbatim(_) => false, + syn::Type::Verbatim(_) => false, _ => true, }; if !is_ok { @@ -308,21 +310,26 @@ impl Parse for ExportedFn { } } - // No returning references or pointers. + // Check return type. if let syn::ReturnType::Type(_, ref rtype) = fn_all.sig.output { - match rtype.as_ref() { - &syn::Type::Ptr(_) => { + match flatten_type_groups(rtype.as_ref()) { + syn::Type::Ptr(_) => { return Err(syn::Error::new( fn_all.sig.output.span(), - "cannot return a pointer to Rhai", + "Rhai functions cannot return pointers", )) } - &syn::Type::Reference(_) => { + syn::Type::Reference(_) => { return Err(syn::Error::new( fn_all.sig.output.span(), - "cannot return a reference to Rhai", + "Rhai functions cannot return references", )) } + syn::Type::Path(p) + if p.path == dynamic_type_path1 || p.path == dynamic_type_path2 => + { + return_dynamic = true + } _ => {} } } @@ -330,6 +337,7 @@ impl Parse for ExportedFn { entire_span, signature: fn_all.sig, is_public, + return_dynamic, mut_receiver, params: ExportedFnParams::default(), }) @@ -419,7 +427,7 @@ impl ExportedFn { pub(crate) fn return_type(&self) -> Option<&syn::Type> { if let syn::ReturnType::Type(_, ref rtype) = self.signature.output { - Some(rtype) + Some(flatten_type_groups(rtype)) } else { None } @@ -437,7 +445,7 @@ impl ExportedFn { { return Err(syn::Error::new( self.signature.span(), - "return_raw functions must return Result", + "return_raw functions must return Result>", )); } @@ -467,7 +475,7 @@ impl ExportedFn { FnSpecialAccess::Property(Property::Set(_)) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "property setter must return no value", + "property setter cannot return any value", )) } // 4a. Index getters must take the subject and the accessed "index" as arguments. @@ -495,7 +503,7 @@ impl ExportedFn { FnSpecialAccess::Index(Index::Set) if self.return_type().is_some() => { return Err(syn::Error::new( self.signature.span(), - "index setter must return no value", + "index setter cannot return a value", )) } _ => {} @@ -532,7 +540,7 @@ impl ExportedFn { dynamic_signature.ident = syn::Ident::new("dynamic_result_fn", proc_macro2::Span::call_site()); dynamic_signature.output = syn::parse2::(quote! { - -> Result + -> Result> }) .unwrap(); let arguments: Vec = dynamic_signature @@ -555,18 +563,22 @@ impl ExportedFn { .return_type() .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); - if !self.params.return_raw { + if self.params.return_raw { quote_spanned! { return_span=> - type EvalBox = Box; pub #dynamic_signature { - Ok(Dynamic::from(super::#name(#(#arguments),*))) + super::#name(#(#arguments),*) + } + } + } else if self.return_dynamic { + quote_spanned! { return_span=> + pub #dynamic_signature { + Ok(super::#name(#(#arguments),*)) } } } else { quote_spanned! { return_span=> - type EvalBox = Box; pub #dynamic_signature { - super::#name(#(#arguments),*) + Ok(Dynamic::from(super::#name(#(#arguments),*))) } } } @@ -734,8 +746,14 @@ impl ExportedFn { .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); let return_expr = if !self.params.return_raw { - quote_spanned! { return_span=> - Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) + if self.return_dynamic { + quote_spanned! { return_span=> + Ok(#sig_name(#(#unpack_exprs),*)) + } + } else { + quote_spanned! { return_span=> + Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) + } } } else { quote_spanned! { return_span=> diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 94c0f720..f18e809e 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -3,6 +3,7 @@ use std::collections::HashMap; use quote::{quote, ToTokens}; use crate::attrs::ExportScope; +use crate::function::flatten_type_groups; use crate::function::{ExportedFn, FnSpecialAccess}; use crate::module::Module; @@ -174,14 +175,6 @@ pub(crate) fn generate_body( } } -pub(crate) fn flatten_type_groups(ty: &syn::Type) -> &syn::Type { - match ty { - syn::Type::Group(syn::TypeGroup { ref elem, .. }) - | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => flatten_type_groups(elem.as_ref()), - _ => ty, - } -} - pub(crate) fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { fn make_key(name: impl ToString, itemfn: &ExportedFn) -> String { itemfn diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index f028c0b5..ea790f29 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -88,7 +88,10 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a reference to Rhai"); + assert_eq!( + format!("{}", err), + "Rhai functions cannot return references" + ); } #[test] @@ -98,7 +101,7 @@ mod function_tests { }; let err = syn::parse2::(input_tokens).unwrap_err(); - assert_eq!(format!("{}", err), "cannot return a pointer to Rhai"); + assert_eq!(format!("{}", err), "Rhai functions cannot return pointers"); } #[test] @@ -295,8 +298,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn() -> Result { + pub fn dynamic_result_fn() -> Result > { Ok(Dynamic::from(super::do_nothing())) } } @@ -340,8 +342,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize) -> Result { + pub fn dynamic_result_fn(x: usize) -> Result > { Ok(Dynamic::from(super::do_something(x))) } } @@ -351,6 +352,51 @@ mod generate_tests { assert_streams_eq(item_fn.generate(), expected_tokens); } + #[test] + fn return_dynamic() { + let input_tokens: TokenStream = quote! { + pub fn return_dynamic() -> (((rhai::Dynamic))) { + ().into() + } + }; + + let expected_tokens = quote! { + #[allow(unused)] + pub mod rhai_fn_return_dynamic { + use super::*; + struct Token(); + impl PluginFunction for Token { + fn call(&self, + args: &mut [&mut Dynamic] + ) -> Result> { + debug_assert_eq!(args.len(), 0usize, + "wrong arg count: {} != {}", args.len(), 0usize); + Ok(return_dynamic()) + } + + fn is_method_call(&self) -> bool { false } + fn is_variadic(&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 { + Token().into() + } + pub fn token_input_types() -> Box<[TypeId]> { + Token().input_types() + } + pub fn dynamic_result_fn() -> Result > { + Ok(super::return_dynamic()) + } + } + }; + + 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! { @@ -417,8 +463,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: usize, y: usize) -> Result { + pub fn dynamic_result_fn(x: usize, y: usize) -> Result > { Ok(Dynamic::from(super::add_together(x, y))) } } @@ -464,8 +509,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result { + pub fn dynamic_result_fn(x: &mut usize, y: usize) -> Result > { Ok(Dynamic::from(super::increment(x, y))) } } @@ -510,8 +554,7 @@ mod generate_tests { pub fn token_input_types() -> Box<[TypeId]> { Token().input_types() } - type EvalBox = Box; - pub fn dynamic_result_fn(message: &str) -> Result { + pub fn dynamic_result_fn(message: &str) -> Result > { Ok(Dynamic::from(super::special_print(message))) } } diff --git a/codegen/ui_tests/return_mut_ref.stderr b/codegen/ui_tests/return_mut_ref.stderr index c849a2c7..6e56c276 100644 --- a/codegen/ui_tests/return_mut_ref.stderr +++ b/codegen/ui_tests/return_mut_ref.stderr @@ -1,4 +1,4 @@ -error: cannot return a reference to Rhai +error: Rhai functions cannot return references --> $DIR/return_mut_ref.rs:12:38 | 12 | pub fn test_fn(input: &mut Clonable) -> &mut bool { diff --git a/codegen/ui_tests/return_pointer.stderr b/codegen/ui_tests/return_pointer.stderr index 1b736db6..9b771be6 100644 --- a/codegen/ui_tests/return_pointer.stderr +++ b/codegen/ui_tests/return_pointer.stderr @@ -1,4 +1,4 @@ -error: cannot return a pointer to Rhai +error: Rhai functions cannot return pointers --> $DIR/return_pointer.rs:12:33 | 12 | pub fn test_fn(input: Clonable) -> *const str { diff --git a/codegen/ui_tests/return_shared_ref.stderr b/codegen/ui_tests/return_shared_ref.stderr index 13577531..50d223ef 100644 --- a/codegen/ui_tests/return_shared_ref.stderr +++ b/codegen/ui_tests/return_shared_ref.stderr @@ -1,4 +1,4 @@ -error: cannot return a reference to Rhai +error: Rhai functions cannot return pointers --> $DIR/return_shared_ref.rs:12:33 | 12 | pub fn test_fn(input: Clonable) -> &'static str {