diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index af0b265d..7bcdb878 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "0.3.2" +version = "0.3.3" edition = "2018" authors = ["jhwgh1968"] description = "Procedural macro support package for Rhai, a scripting language for Rust" diff --git a/codegen/src/function.rs b/codegen/src/function.rs index 0054a7b7..d3a7f792 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -106,6 +106,7 @@ pub(crate) fn print_type(ty: &syn::Type) -> String { pub(crate) struct ExportedFnParams { pub name: Vec, pub return_raw: bool, + pub pure: bool, pub skip: bool, pub special: FnSpecialAccess, pub namespace: FnNamespaceAccess, @@ -144,6 +145,7 @@ impl ExportedParams for ExportedFnParams { } = info; let mut name = Vec::new(); let mut return_raw = false; + let mut pure = false; let mut skip = false; let mut namespace = FnNamespaceAccess::Unset; let mut special = FnSpecialAccess::None; @@ -224,6 +226,8 @@ impl ExportedParams for ExportedFnParams { ("index_get", Some(s)) | ("index_set", Some(s)) | ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) } + ("pure", None) => pure = true, + ("pure", Some(s)) => return Err(syn::Error::new(s.span(), "extraneous value")), ("return_raw", None) => return_raw = true, ("return_raw", Some(s)) => { return Err(syn::Error::new(s.span(), "extraneous value")) @@ -255,6 +259,7 @@ impl ExportedParams for ExportedFnParams { Ok(ExportedFnParams { name, return_raw, + pure, skip, special, namespace, @@ -634,19 +639,19 @@ impl ExportedFn { .map(|r| r.span()) .unwrap_or_else(|| proc_macro2::Span::call_site()); if self.params.return_raw { - quote_spanned! { return_span=> + quote_spanned! { return_span => pub #dynamic_signature { #name(#(#arguments),*) } } } else if self.return_dynamic { - quote_spanned! { return_span=> + quote_spanned! { return_span => pub #dynamic_signature { Ok(#name(#(#arguments),*)) } } } else { - quote_spanned! { return_span=> + quote_spanned! { return_span => pub #dynamic_signature { Ok(Dynamic::from(#name(#(#arguments),*))) } @@ -746,18 +751,33 @@ impl ExportedFn { syn::Type::Reference(syn::TypeReference { ref elem, .. }) => elem.as_ref(), p => p, }; - let downcast_span = quote_spanned!( - arg_type.span()=> &mut args[0usize].write_lock::<#arg_type>().unwrap()); + let downcast_span = quote_spanned!(arg_type.span() => + &mut args[0usize].write_lock::<#arg_type>().unwrap() + ); unpack_statements.push( syn::parse2::(quote! { let #var = #downcast_span; }) .unwrap(), ); + if !self.params().pure { + let arg_lit_str = + syn::LitStr::new(&pat.to_token_stream().to_string(), pat.span()); + unpack_statements.push( + syn::parse2::(quote! { + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant(#arg_lit_str.to_string(), Position::NONE) + )); + } + }) + .unwrap(), + ); + } input_type_names.push(arg_name); input_type_exprs.push( - syn::parse2::(quote_spanned!( - arg_type.span()=> TypeId::of::<#arg_type>() + syn::parse2::(quote_spanned!(arg_type.span() => + TypeId::of::<#arg_type>() )) .unwrap(), ); @@ -792,22 +812,25 @@ impl ExportedFn { syn::Type::Path(ref p) if p.path == str_type_path => { is_string = true; is_ref = true; - quote_spanned!(arg_type.span()=> - mem::take(args[#i]).take_immutable_string().unwrap()) + quote_spanned!(arg_type.span() => + mem::take(args[#i]).take_immutable_string().unwrap() + ) } _ => panic!("internal error: why wasn't this found earlier!?"), }, syn::Type::Path(ref p) if p.path == string_type_path => { is_string = true; is_ref = false; - quote_spanned!(arg_type.span()=> - mem::take(args[#i]).take_string().unwrap()) + quote_spanned!(arg_type.span() => + mem::take(args[#i]).take_string().unwrap() + ) } _ => { is_string = false; is_ref = false; - quote_spanned!(arg_type.span()=> - mem::take(args[#i]).cast::<#arg_type>()) + quote_spanned!(arg_type.span() => + mem::take(args[#i]).cast::<#arg_type>() + ) } }; @@ -820,15 +843,15 @@ impl ExportedFn { input_type_names.push(arg_name); if !is_string { input_type_exprs.push( - syn::parse2::(quote_spanned!( - arg_type.span()=> TypeId::of::<#arg_type>() + syn::parse2::(quote_spanned!(arg_type.span() => + TypeId::of::<#arg_type>() )) .unwrap(), ); } else { input_type_exprs.push( - syn::parse2::(quote_spanned!( - arg_type.span()=> TypeId::of::() + syn::parse2::(quote_spanned!(arg_type.span() => + TypeId::of::() )) .unwrap(), ); @@ -860,16 +883,16 @@ impl ExportedFn { .unwrap_or_else(|| proc_macro2::Span::call_site()); let return_expr = if !self.params.return_raw { if self.return_dynamic { - quote_spanned! { return_span=> + quote_spanned! { return_span => Ok(#sig_name(#(#unpack_exprs),*)) } } else { - quote_spanned! { return_span=> + quote_spanned! { return_span => Ok(Dynamic::from(#sig_name(#(#unpack_exprs),*))) } } } else { - quote_spanned! { return_span=> + quote_spanned! { return_span => #sig_name(#(#unpack_exprs),*) } }; @@ -878,9 +901,7 @@ impl ExportedFn { quote! { impl PluginFunction for #type_name { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { - debug_assert_eq!(args.len(), #arg_count, - "wrong arg count: {} != {}", - args.len(), #arg_count); + debug_assert_eq!(args.len(), #arg_count, "wrong arg count: {} != {}", args.len(), #arg_count); #(#unpack_statements)* #return_expr } diff --git a/codegen/src/register.rs b/codegen/src/register.rs index 748baa41..ecd49d1e 100644 --- a/codegen/src/register.rs +++ b/codegen/src/register.rs @@ -33,7 +33,7 @@ pub fn parse_register_macro( )); } let export_name = match &items[1] { - syn::Expr::Lit(lit_str) => quote_spanned!(items[1].span()=> + syn::Expr::Lit(lit_str) => quote_spanned!(items[1].span() => #lit_str.to_string()), expr => quote! { #expr }, }; diff --git a/codegen/src/test/function.rs b/codegen/src/test/function.rs index 1df6e6a2..34e2d38c 100644 --- a/codegen/src/test/function.rs +++ b/codegen/src/test/function.rs @@ -587,6 +587,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0, arg1))) diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index c83f45f6..79f6f872 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -1218,6 +1218,83 @@ mod generate_tests { assert_streams_eq(item_mod.generate(), expected_tokens); } + #[test] + fn mut_ref_pure_fn_module() { + let input_tokens: TokenStream = quote! { + pub mod ref_fn { + #[rhai_fn(pure)] + pub fn foo(x: &mut FLOAT, y: INT) -> FLOAT { + *x + y as FLOAT + } + } + }; + + let expected_tokens = quote! { + pub mod ref_fn { + pub fn foo(x: &mut FLOAT, y: INT) -> FLOAT { + *x + y as FLOAT + } + #[allow(unused_imports)] + use super::*; + + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + rhai_generate_into_module(&mut m, false); + m.build_index(); + m + } + #[allow(unused_mut)] + pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_fn("foo", FnNamespace::Internal, FnAccess::Public, + Some(&["x: &mut FLOAT", "y: INT", "FLOAT"]), &[core::any::TypeId::of::(), core::any::TypeId::of::()], + foo_token().into()); + if flatten {} else {} + } + #[allow(non_camel_case_types)] + struct foo_token(); + impl PluginFunction for foo_token { + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { + debug_assert_eq!(args.len(), 2usize, + "wrong arg count: {} != {}", args.len(), 2usize); + let arg1 = mem::take(args[1usize]).cast::(); + let arg0 = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(foo(arg0, arg1))) + } + + fn is_method_call(&self) -> bool { true } + fn is_variadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(foo_token()) + } + fn input_names(&self) -> Box<[&'static str]> { + new_vec!["x: &mut FLOAT", "y: INT"].into_boxed_slice() + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::(), TypeId::of::()].into_boxed_slice() + } + fn return_type(&self) -> &'static str { + "FLOAT" + } + } + pub fn foo_token_callable() -> CallableFunction { + foo_token().into() + } + pub fn foo_token_input_names() -> Box<[&'static str]> { + foo_token().input_names() + } + pub fn foo_token_input_types() -> Box<[TypeId]> { + foo_token().input_types() + } + pub fn foo_token_return_type() -> &'static str { + foo_token().return_type() + } + } + }; + + 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! { @@ -1255,6 +1332,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } @@ -1333,6 +1415,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } @@ -1432,6 +1519,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(increment(arg0))) } @@ -1530,6 +1622,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } @@ -1607,6 +1704,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 1usize, "wrong arg count: {} != {}", args.len(), 1usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0))) } @@ -1683,6 +1785,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) @@ -1763,6 +1870,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(int_foo(arg0, arg1))) @@ -1842,6 +1954,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) @@ -1927,6 +2044,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 2usize, "wrong arg count: {} != {}", args.len(), 2usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); Ok(Dynamic::from(get_by_index(arg0, arg1))) @@ -2008,6 +2130,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap(); @@ -2097,6 +2224,11 @@ mod generate_tests { fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> Result> { debug_assert_eq!(args.len(), 3usize, "wrong arg count: {} != {}", args.len(), 3usize); + if args[0usize].is_read_only() { + return Err(Box::new( + EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE) + )); + } let arg1 = mem::take(args[1usize]).cast::(); let arg2 = mem::take(args[2usize]).cast::(); let arg0 = &mut args[0usize].write_lock::().unwrap();