diff --git a/CHANGELOG.md b/CHANGELOG.md index 9fad9dcf..36936da7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -28,6 +28,7 @@ Enhancements ------------ * `Engine::is_symbol_disabled` is added to test whether a particular keyword/symbol is disabled. +* Support is added to deserialize a `Dynamic` value containing custom types or shared values back into another `Dynamic` (essentially a straight cloned copy). Version 1.13.0 diff --git a/src/func/call.rs b/src/func/call.rs index 6112b0d4..f52fb915 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -576,39 +576,32 @@ impl Engine { ) -> RhaiResultOf<(Dynamic, bool)> { // These may be redirected from method style calls. if hashes.is_native_only() { - let error = match fn_name { - // Handle type_of() - KEYWORD_TYPE_OF => { - if args.len() == 1 { + loop { + match fn_name { + // Handle type_of() + KEYWORD_TYPE_OF if args.len() == 1 => { let typ = self.get_interned_string(self.map_type_name(args[0].type_name())); return Ok((typ.into(), false)); } - true - } - #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED => { - if args.len() == 1 { - return Ok((args[0].is_shared().into(), false)); + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED if args.len() == 1 => { + return Ok((args[0].is_shared().into(), false)) } - true + #[cfg(not(feature = "no_closure"))] + crate::engine::KEYWORD_IS_SHARED => (), + + #[cfg(not(feature = "no_function"))] + crate::engine::KEYWORD_IS_DEF_FN => (), + + KEYWORD_TYPE_OF | KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR + | KEYWORD_FN_PTR_CALL | KEYWORD_FN_PTR_CURRY => (), + + _ => break, } - #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN => true, - - KEYWORD_FN_PTR | KEYWORD_EVAL | KEYWORD_IS_DEF_VAR | KEYWORD_FN_PTR_CALL - | KEYWORD_FN_PTR_CURRY => true, - - _ => false, - }; - - if error { - return Err(ERR::ErrorFunctionNotFound( - self.gen_fn_call_signature(fn_name, args), - pos, - ) - .into()); + let sig = self.gen_fn_call_signature(fn_name, args); + return Err(ERR::ErrorFunctionNotFound(sig, pos).into()); } } @@ -624,31 +617,27 @@ impl Engine { let hash = hashes.script(); let local_entry = &mut None; + let mut resolved = None; #[cfg(not(feature = "no_object"))] - let resolved = if _is_method_call && !args.is_empty() { + if _is_method_call && !args.is_empty() { let typed_hash = crate::calc_typed_method_hash(hash, self.map_type_name(args[0].type_name())); - self.resolve_fn(global, caches, local_entry, None, typed_hash, None, false) - } else { - None - }; - #[cfg(feature = "no_object")] - let resolved = None; + resolved = + self.resolve_fn(global, caches, local_entry, None, typed_hash, None, false); + } - let resolved = if resolved.is_none() { - self.resolve_fn(global, caches, local_entry, None, hash, None, false) - } else { - resolved - }; + if resolved.is_none() { + resolved = self.resolve_fn(global, caches, local_entry, None, hash, None, false); + } - if let Some(FnResolutionCacheEntry { func, ref source }) = resolved.cloned() { + if let Some(FnResolutionCacheEntry { func, source }) = resolved.cloned() { // Script function call debug_assert!(func.is_script()); - let f = func.get_script_fn_def().expect("script-defined function"); let environ = func.get_encapsulated_environ(); + let func = func.get_script_fn_def().expect("script-defined function"); - if f.body.is_empty() { + if func.body.is_empty() { return Ok((Dynamic::UNIT, false)); } @@ -660,23 +649,17 @@ impl Engine { &mut empty_scope }; - let orig_source = mem::replace(&mut global.source, source.clone()); + let orig_source = mem::replace(&mut global.source, source); defer! { global => move |g| g.source = orig_source } return if _is_method_call { - // Method call of script function - map first argument to `this` - let (first_arg, rest_args) = args.split_first_mut().unwrap(); + use std::ops::DerefMut; + // Method call of script function - map first argument to `this` + let (first_arg, args) = args.split_first_mut().unwrap(); + let this_ptr = Some(first_arg.deref_mut()); self.call_script_fn( - global, - caches, - scope, - Some(first_arg), - environ, - f, - rest_args, - true, - pos, + global, caches, scope, this_ptr, environ, func, args, true, pos, ) } else { // Normal call of script function @@ -691,7 +674,7 @@ impl Engine { defer! { args = (args) if swap => move |a| backup.restore_first_arg(a) } - self.call_script_fn(global, caches, scope, None, environ, f, args, true, pos) + self.call_script_fn(global, caches, scope, None, environ, func, args, true, pos) } .map(|r| (r, false)); } @@ -748,55 +731,47 @@ impl Engine { fn_name: &str, mut hash: FnCallHashes, target: &mut crate::eval::Target, - mut call_args: &mut [Dynamic], + call_args: &mut [Dynamic], first_arg_pos: Position, - fn_call_pos: Position, + pos: Position, ) -> RhaiResultOf<(Dynamic, bool)> { - let is_ref_mut = target.is_ref(); - let (result, updated) = match fn_name { // Handle fn_ptr.call(...) KEYWORD_FN_PTR_CALL if target.is_fnptr() => { let fn_ptr = target.read_lock::().expect("`FnPtr`"); // Arguments are passed as-is, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); - curry.extend(fn_ptr.curry().iter().cloned()); - let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len()); - args.extend(curry.iter_mut()); - args.extend(call_args.iter_mut()); + let mut curry = fn_ptr.curry().iter().cloned().collect::>(); + let args = &mut curry + .iter_mut() + .chain(call_args.iter_mut()) + .collect::>(); - // Linked to scripted function? + let _fn_def = (); #[cfg(not(feature = "no_function"))] - let fn_def = fn_ptr.fn_def(); - #[cfg(feature = "no_function")] - let fn_def = (); + let _fn_def = fn_ptr.fn_def(); - match fn_def { + match _fn_def { + // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] - Some(fn_def) if fn_def.params.len() == args.len() => self - .call_script_fn( - global, - caches, - &mut Scope::new(), - None, - fn_ptr.encapsulated_environ().map(|r| r.as_ref()), - fn_def, - args, - true, - fn_call_pos, + Some(fn_def) if fn_def.params.len() == args.len() => { + let scope = &mut Scope::new(); + let environ = fn_ptr.encapsulated_environ().map(|r| r.as_ref()); + + self.call_script_fn( + global, caches, scope, None, environ, fn_def, args, true, pos, ) - .map(|v| (v, false)), + .map(|v| (v, false)) + } _ => { + let _is_anon = false; #[cfg(not(feature = "no_function"))] - let is_anon = fn_ptr.is_anonymous(); - #[cfg(feature = "no_function")] - let is_anon = false; + let _is_anon = fn_ptr.is_anonymous(); // Redirect function name let fn_name = fn_ptr.fn_name(); // Recalculate hashes - let new_hash = if !is_anon && !is_valid_function_name(fn_name) { + let new_hash = if !_is_anon && !is_valid_function_name(fn_name) { FnCallHashes::from_native_only(calc_fn_hash(None, fn_name, args.len())) } else { FnCallHashes::from_hash(calc_fn_hash(None, fn_name, args.len())) @@ -804,153 +779,105 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - global, - caches, - None, - fn_name, - None, - new_hash, - args, - false, - false, - fn_call_pos, + global, caches, None, fn_name, None, new_hash, args, false, false, pos, ) } } } + // Handle obj.call() + KEYWORD_FN_PTR_CALL if call_args.is_empty() => { + return Err(self + .make_type_mismatch_err::(self.map_type_name(target.type_name()), pos)) + } + // Handle obj.call(fn_ptr, ...) KEYWORD_FN_PTR_CALL => { - if call_args.is_empty() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(target.type_name()), - fn_call_pos, - )); - } - if !call_args[0].is_fnptr() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(call_args[0].type_name()), - first_arg_pos, - )); - } + debug_assert!(!call_args.is_empty()); // FnPtr call on object - let fn_ptr = call_args[0].take().cast::(); + let typ = call_args[0].type_name(); + let fn_ptr = call_args[0].take().try_cast::().ok_or_else(|| { + self.make_type_mismatch_err::(self.map_type_name(typ), first_arg_pos) + })?; - let (fn_name, is_anon, fn_curry, _environ, fn_def) = { - #[cfg(not(feature = "no_function"))] - let is_anon = fn_ptr.is_anonymous(); - #[cfg(feature = "no_function")] - let is_anon = false; + #[cfg(not(feature = "no_function"))] + let (is_anon, (fn_name, fn_curry, environ, fn_def)) = + (fn_ptr.is_anonymous(), fn_ptr.take_data()); + #[cfg(feature = "no_function")] + let (is_anon, (fn_name, fn_curry, _), fn_def) = (false, fn_ptr.take_data(), ()); - #[cfg(not(feature = "no_function"))] - let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data(); - #[cfg(feature = "no_function")] - let (fn_name, fn_curry, environ) = fn_ptr.take_data(); - - ( - fn_name, - is_anon, - fn_curry, - environ, - #[cfg(not(feature = "no_function"))] - fn_def, - #[cfg(feature = "no_function")] - (), - ) - }; - - // Replace the first argument with the object pointer, adding the curried arguments - call_args = &mut call_args[1..]; - - let mut curry = FnArgsVec::with_capacity(fn_curry.len()); - curry.extend(fn_curry.into_iter()); - let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); + // Adding the curried arguments and the remaining arguments + let mut curry = fn_curry.into_iter().collect::>(); + let args = &mut FnArgsVec::with_capacity(curry.len() + call_args.len()); args.extend(curry.iter_mut()); - args.extend(call_args.iter_mut()); + args.extend(call_args.iter_mut().skip(1)); - // Linked to scripted function? match fn_def { + // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] Some(fn_def) if fn_def.params.len() == args.len() => { // Check for data race. #[cfg(not(feature = "no_closure"))] ensure_no_data_race(&fn_def.name, args, false)?; + let scope = &mut Scope::new(); + let this_ptr = Some(target.as_mut()); + let environ = environ.as_deref(); + self.call_script_fn( - global, - caches, - &mut Scope::new(), - Some(target), - _environ.as_deref(), - &fn_def, - args, - true, - fn_call_pos, + global, caches, scope, this_ptr, environ, &fn_def, args, true, pos, ) .map(|v| (v, false)) } _ => { + let name = fn_name.as_str(); + let is_ref_mut = target.is_ref(); + // Add the first argument with the object pointer args.insert(0, target.as_mut()); // Recalculate hash + let num_args = args.len(); + let new_hash = match is_anon { - false if !is_valid_function_name(&fn_name) => { - FnCallHashes::from_native_only(calc_fn_hash( - None, - &fn_name, - args.len(), - )) + false if !is_valid_function_name(name) => { + FnCallHashes::from_native_only(calc_fn_hash(None, name, num_args)) } #[cfg(not(feature = "no_function"))] _ => FnCallHashes::from_script_and_native( - calc_fn_hash(None, &fn_name, args.len() - 1), - calc_fn_hash(None, &fn_name, args.len()), + calc_fn_hash(None, name, num_args - 1), + calc_fn_hash(None, name, num_args), ), #[cfg(feature = "no_function")] - _ => FnCallHashes::from_native_only(calc_fn_hash( - None, - &fn_name, - args.len(), - )), + _ => FnCallHashes::from_native_only(calc_fn_hash(None, name, num_args)), }; // Map it to name(args) in function-call style self.exec_fn_call( - global, - caches, - None, - &fn_name, - None, - new_hash, - args, - is_ref_mut, - true, - fn_call_pos, + global, caches, None, name, None, new_hash, args, is_ref_mut, true, pos, ) } } } - KEYWORD_FN_PTR_CURRY => { - if !target.is_fnptr() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(target.type_name()), - fn_call_pos, - )); - } - let mut fn_ptr = target.read_lock::().expect("`FnPtr`").clone(); + // Handle fn_ptr.curry(...) + KEYWORD_FN_PTR_CURRY => { + let typ = target.type_name(); + let mut fn_ptr = target + .read_lock::() + .ok_or_else(|| { + self.make_type_mismatch_err::(self.map_type_name(typ), pos) + })? + .clone(); // Append the new curried arguments to the existing list. - call_args.iter_mut().map(mem::take).for_each(|value| { - fn_ptr.add_curry(value); - }); + fn_ptr.extend(call_args.iter_mut().map(mem::take)); Ok((fn_ptr.into(), false)) } - // Handle is_shared() + // Handle var.is_shared() #[cfg(not(feature = "no_closure"))] crate::engine::KEYWORD_IS_SHARED if call_args.is_empty() => { return Ok((target.is_shared().into(), false)); @@ -960,7 +887,7 @@ impl Engine { let mut fn_name = fn_name; let _redirected; let mut _linked = None; - let mut _arg_values: FnArgsVec<_>; + let mut _arg_values; let mut call_args = call_args; // Check if it is a map method call in OOP style @@ -979,17 +906,16 @@ impl Engine { .iter() .cloned() .chain(call_args.iter_mut().map(mem::take)) - .collect(); + .collect::>(); call_args = &mut _arg_values; } - // Linked to scripted function? + let _fn_def = (); #[cfg(not(feature = "no_function"))] - let fn_def = fn_ptr.fn_def(); - #[cfg(feature = "no_function")] - let fn_def = (); + let _fn_def = fn_ptr.fn_def(); - match fn_def { + match _fn_def { + // Linked to scripted function #[cfg(not(feature = "no_function"))] Some(fn_def) if fn_def.params.len() == call_args.len() => { _linked = Some(( @@ -998,27 +924,27 @@ impl Engine { )) } _ => { + let _is_anon = false; #[cfg(not(feature = "no_function"))] - let is_anon = fn_ptr.is_anonymous(); - #[cfg(feature = "no_function")] - let is_anon = false; + let _is_anon = fn_ptr.is_anonymous(); // Recalculate the hash based on the new function name and new arguments - let args_len = call_args.len() + 1; - hash = match is_anon { + let num_args = call_args.len() + 1; + + hash = match _is_anon { false if !is_valid_function_name(fn_name) => { FnCallHashes::from_native_only(calc_fn_hash( - None, fn_name, args_len, + None, fn_name, num_args, )) } #[cfg(not(feature = "no_function"))] _ => FnCallHashes::from_script_and_native( - calc_fn_hash(None, fn_name, args_len - 1), - calc_fn_hash(None, fn_name, args_len), + calc_fn_hash(None, fn_name, num_args - 1), + calc_fn_hash(None, fn_name, num_args), ), #[cfg(feature = "no_function")] _ => FnCallHashes::from_native_only(calc_fn_hash( - None, fn_name, args_len, + None, fn_name, num_args, )), }; } @@ -1030,39 +956,29 @@ impl Engine { match _linked { #[cfg(not(feature = "no_function"))] Some((fn_def, environ)) => { - // Linked to scripted function + // Linked to scripted function - short-circuit + let scope = &mut Scope::new(); + let environ = environ.as_deref(); + let this_ptr = Some(target.as_mut()); + let args = &mut call_args.iter_mut().collect::>(); + self.call_script_fn( - global, - caches, - &mut Scope::new(), - Some(target), - environ.as_deref(), - &*fn_def, - &mut call_args.iter_mut().collect::>(), - true, - fn_call_pos, + global, caches, scope, this_ptr, environ, &*fn_def, args, true, pos, ) .map(|v| (v, false)) } #[cfg(feature = "no_function")] Some(()) => unreachable!(), None => { + let is_ref_mut = target.is_ref(); + // Attached object pointer in front of the arguments - let mut args = FnArgsVec::with_capacity(call_args.len() + 1); - args.push(target.as_mut()); - args.extend(call_args.iter_mut()); + let args = &mut std::iter::once(target.as_mut()) + .chain(call_args.iter_mut()) + .collect::>(); self.exec_fn_call( - global, - caches, - None, - fn_name, - None, - hash, - &mut args, - is_ref_mut, - true, - fn_call_pos, + global, caches, None, fn_name, None, hash, args, is_ref_mut, true, pos, ) } } @@ -1071,7 +987,7 @@ impl Engine { // Propagate the changed value back to the source if necessary if updated { - target.propagate_changed_value(fn_call_pos)?; + target.propagate_changed_value(pos)?; } Ok((result, updated)) @@ -1093,8 +1009,8 @@ impl Engine { pos: Position, ) -> RhaiResult { let mut first_arg = first_arg; - let mut a_expr = args_expr; - let mut total_args = usize::from(first_arg.is_some()) + a_expr.len(); + let mut args_expr = args_expr; + let mut num_args = usize::from(first_arg.is_some()) + args_expr.len(); let mut curry = FnArgsVec::new_const(); let mut name = fn_name; let mut hashes = hashes; @@ -1104,48 +1020,38 @@ impl Engine { _ if op_token.is_some() => (), // Handle call(fn_ptr, ...) - KEYWORD_FN_PTR_CALL if total_args >= 1 => { + KEYWORD_FN_PTR_CALL if num_args >= 1 => { let arg = first_arg.unwrap(); - let (arg_value, arg_pos) = + let (first_arg_value, first_arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; - if !arg_value.is_fnptr() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), - arg_pos, - )); - } - - let fn_ptr = arg_value.cast::(); + let typ = first_arg_value.type_name(); + let fn_ptr = first_arg_value.try_cast::().ok_or_else(|| { + self.make_type_mismatch_err::(self.map_type_name(typ), first_arg_pos) + })?; #[cfg(not(feature = "no_function"))] - let (fn_name, is_anon, fn_curry, _environ, fn_def) = { - let is_anon = fn_ptr.is_anonymous(); - let (fn_name, fn_curry, environ, fn_def) = fn_ptr.take_data(); - (fn_name, is_anon, fn_curry, environ, fn_def) - }; + let (is_anon, (fn_name, fn_curry, _environ, fn_def)) = + (fn_ptr.is_anonymous(), fn_ptr.take_data()); #[cfg(feature = "no_function")] - let (fn_name, is_anon, fn_curry, _environ) = { - let (fn_name, fn_curry, environ) = fn_ptr.take_data(); - (fn_name, false, fn_curry, environ) - }; + let (is_anon, (fn_name, fn_curry, _environ)) = (false, fn_ptr.take_data()); curry.extend(fn_curry.into_iter()); - // Linked to scripted function? + // Linked to scripted function - short-circuit #[cfg(not(feature = "no_function"))] if let Some(fn_def) = fn_def { - if fn_def.params.len() == curry.len() + a_expr.len() { + if fn_def.params.len() == curry.len() + args_expr.len() { // Evaluate arguments - let mut arg_values = curry - .into_iter() - .map(Ok) - .chain(a_expr.iter().map(|expr| -> Result<_, crate::RhaiError> { - let this_ptr = this_ptr.as_deref_mut(); - self.get_arg_value(global, caches, scope, this_ptr, expr) - .map(|(v, ..)| v) - })) - .collect::>>()?; + let mut arg_values = + FnArgsVec::with_capacity(curry.len() + args_expr.len()); + arg_values.extend(curry); + for expr in args_expr { + let this_ptr = this_ptr.as_deref_mut(); + let (value, _) = + self.get_arg_value(global, caches, scope, this_ptr, expr)?; + arg_values.push(value); + } let args = &mut arg_values.iter_mut().collect::>(); let scope = &mut Scope::new(); let environ = _environ.as_deref(); @@ -1161,14 +1067,14 @@ impl Engine { name = &redirected; // Shift the arguments - first_arg = a_expr.get(0); - if !a_expr.is_empty() { - a_expr = &a_expr[1..]; + first_arg = args_expr.get(0); + if !args_expr.is_empty() { + args_expr = &args_expr[1..]; } - total_args -= 1; + num_args -= 1; // Recalculate hash - let args_len = total_args + curry.len(); + let args_len = num_args + curry.len(); hashes = if !is_anon && !is_valid_function_name(name) { FnCallHashes::from_native_only(calc_fn_hash(None, name, args_len)) @@ -1176,8 +1082,9 @@ impl Engine { FnCallHashes::from_hash(calc_fn_hash(None, name, args_len)) }; } + // Handle Fn(fn_name) - KEYWORD_FN_PTR if total_args == 1 => { + KEYWORD_FN_PTR if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, arg)?; @@ -1192,22 +1099,18 @@ impl Engine { } // Handle curry(x, ...) - KEYWORD_FN_PTR_CURRY if total_args > 1 => { + KEYWORD_FN_PTR_CURRY if num_args > 1 => { let first = first_arg.unwrap(); - let (arg_value, arg_pos) = + let (first_arg_value, first_arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; - if !arg_value.is_fnptr() { - return Err(self.make_type_mismatch_err::( - self.map_type_name(arg_value.type_name()), - arg_pos, - )); - } - - let mut fn_ptr = arg_value.cast::(); + let typ = first_arg_value.type_name(); + let mut fn_ptr = first_arg_value.try_cast::().ok_or_else(|| { + self.make_type_mismatch_err::(self.map_type_name(typ), first_arg_pos) + })?; // Append the new curried arguments to the existing list. - for expr in a_expr { + for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; fn_ptr.add_curry(value); @@ -1218,7 +1121,7 @@ impl Engine { // Handle is_shared(var) #[cfg(not(feature = "no_closure"))] - crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { + crate::engine::KEYWORD_IS_SHARED if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), arg)?; @@ -1227,7 +1130,7 @@ impl Engine { // Handle is_def_fn(fn_name, arity) #[cfg(not(feature = "no_function"))] - crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { + crate::engine::KEYWORD_IS_DEF_FN if num_args == 2 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; @@ -1237,7 +1140,7 @@ impl Engine { .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = - self.get_arg_value(global, caches, scope, this_ptr, &a_expr[0])?; + self.get_arg_value(global, caches, scope, this_ptr, &args_expr[0])?; let num_params = arg_value .as_int() @@ -1255,7 +1158,7 @@ impl Engine { // Handle is_def_fn(this_type, fn_name, arity) #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_object"))] - crate::engine::KEYWORD_IS_DEF_FN if total_args == 3 => { + crate::engine::KEYWORD_IS_DEF_FN if num_args == 3 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), first)?; @@ -1264,15 +1167,20 @@ impl Engine { .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; - let (arg_value, arg_pos) = - self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), &a_expr[0])?; + let (arg_value, arg_pos) = self.get_arg_value( + global, + caches, + scope, + this_ptr.as_deref_mut(), + &args_expr[0], + )?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = - self.get_arg_value(global, caches, scope, this_ptr, &a_expr[1])?; + self.get_arg_value(global, caches, scope, this_ptr, &args_expr[1])?; let num_params = arg_value .as_int() @@ -1291,7 +1199,7 @@ impl Engine { } // Handle is_def_var(fn_name) - KEYWORD_IS_DEF_VAR if total_args == 1 => { + KEYWORD_IS_DEF_VAR if num_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = self.get_arg_value(global, caches, scope, this_ptr, arg)?; @@ -1302,7 +1210,7 @@ impl Engine { } // Handle eval(script) - KEYWORD_EVAL if total_args == 1 => { + KEYWORD_EVAL if num_args == 1 => { // eval - only in function call style let orig_scope_len = scope.len(); #[cfg(not(feature = "no_module"))] @@ -1345,8 +1253,8 @@ impl Engine { } // Normal function call - except for Fn, curry, call and eval (handled above) - let mut arg_values = FnArgsVec::with_capacity(total_args); - let mut args = FnArgsVec::with_capacity(total_args + curry.len()); + let mut arg_values = FnArgsVec::with_capacity(num_args); + let mut args = FnArgsVec::with_capacity(num_args + curry.len()); let mut is_ref_mut = false; // Capture parent scope? @@ -1354,7 +1262,7 @@ impl Engine { // If so, do it separately because we cannot convert the first argument (if it is a simple // variable access) to &mut because `scope` is needed. if capture_scope && !scope.is_empty() { - for expr in first_arg.iter().copied().chain(a_expr.iter()) { + for expr in first_arg.iter().copied().chain(args_expr.iter()) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); @@ -1374,18 +1282,20 @@ impl Engine { } // Call with blank scope - if total_args > 0 || !curry.is_empty() { + if num_args > 0 || !curry.is_empty() { // If the first argument is a variable, and there is no curried arguments, // convert to method-call style in order to leverage potential &mut first argument and // avoid cloning the value if curry.is_empty() && first_arg.map_or(false, |expr| expr.is_variable_access(false)) { let first_expr = first_arg.unwrap(); + self.track_operation(global, first_expr.position())?; + #[cfg(feature = "debugging")] self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), first_expr)?; // func(x, ...) -> x.func(...) - for expr in a_expr { + for expr in args_expr { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); @@ -1398,8 +1308,6 @@ impl Engine { target = target.into_owned(); } - self.track_operation(global, first_expr.position())?; - if target.is_shared() || target.is_temp_value() { arg_values.insert(0, target.take_or_clone().flatten()); } else { @@ -1410,7 +1318,7 @@ impl Engine { } } else { // func(..., ...) - for expr in first_arg.into_iter().chain(a_expr.iter()) { + for expr in first_arg.into_iter().chain(args_expr.iter()) { let (value, ..) = self.get_arg_value(global, caches, scope, this_ptr.as_deref_mut(), expr)?; arg_values.push(value.flatten()); @@ -1450,14 +1358,13 @@ impl Engine { // If so, convert to method-call style in order to leverage potential &mut first argument // and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { + // Get target reference to first argument + let first_arg = &args_expr[0]; + + self.track_operation(global, first_arg.position())?; + #[cfg(feature = "debugging")] - self.run_debugger( - global, - caches, - scope, - this_ptr.as_deref_mut(), - &args_expr[0], - )?; + self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), first_arg)?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); @@ -1468,11 +1375,6 @@ impl Engine { arg_values.push(value.flatten()); } - // Get target reference to first argument - let first_arg = &args_expr[0]; - - self.track_operation(global, first_arg.position())?; - let target = self.search_scope_only(global, caches, scope, this_ptr, first_arg)?; #[cfg(not(feature = "no_closure"))] diff --git a/src/func/native.rs b/src/func/native.rs index 409e3948..300daaef 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -7,8 +7,8 @@ use crate::plugin::PluginFunction; use crate::tokenizer::{is_valid_function_name, Token, TokenizeState}; use crate::types::dynamic::Variant; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalContext, FuncArgs, Position, RhaiResult, RhaiResultOf, - StaticVec, VarDefInfo, ERR, + calc_fn_hash, Dynamic, Engine, EvalContext, FnArgsVec, FuncArgs, Position, RhaiResult, + RhaiResultOf, StaticVec, VarDefInfo, ERR, }; use std::any::{type_name, TypeId}; #[cfg(feature = "no_std")] @@ -309,9 +309,9 @@ impl<'a> NativeCallContext<'a> { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + let args = &mut arg_values.iter_mut().collect::>(); - self._call_fn_raw(fn_name, &mut args, false, false, false) + self._call_fn_raw(fn_name, args, false, false, false) .and_then(|result| { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { @@ -340,9 +340,9 @@ impl<'a> NativeCallContext<'a> { let mut arg_values = StaticVec::new_const(); args.parse(&mut arg_values); - let mut args: StaticVec<_> = arg_values.iter_mut().collect(); + let args = &mut arg_values.iter_mut().collect::>(); - self._call_fn_raw(fn_name, &mut args, true, false, false) + self._call_fn_raw(fn_name, args, true, false, false) .and_then(|result| { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { diff --git a/src/func/register.rs b/src/func/register.rs index 5e410dd7..9f5901ec 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -57,7 +57,7 @@ pub fn by_value(data: &mut Dynamic) -> T { } if TypeId::of::() == TypeId::of::() { // If T is `String`, data must be `ImmutableString`, so map directly to it - return reify! { data.take().into_string().expect("`ImmutableString`") => T }; + return reify! { data.take().into_string().expect("`ImmutableString`") => !!! T }; } // We consume the argument and then replace it with () - the argument is not supposed to be used again. diff --git a/src/module/mod.rs b/src/module/mod.rs index d00cf0b2..201527cb 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -118,7 +118,7 @@ impl FuncInfo { } } } else { - let params: FnArgsVec<_> = self + let params = self .metadata .params_info .iter() @@ -134,7 +134,7 @@ impl FuncInfo { }; result }) - .collect(); + .collect::>(); signature.push_str(¶ms.join(", ")); } signature.push(')'); @@ -932,7 +932,10 @@ impl Module { hash_fn: u64, arg_names: impl IntoIterator, ) -> &mut Self { - let mut param_names: FnArgsVec<_> = arg_names.into_iter().map(Into::into).collect(); + let mut param_names = arg_names + .into_iter() + .map(Into::into) + .collect::>(); if let Some(f) = self.functions.as_mut().and_then(|m| m.get_mut(&hash_fn)) { let (param_names, return_type_name) = if param_names.len() > f.metadata.num_params { @@ -1053,13 +1056,12 @@ impl Module { let _arg_names = arg_names; let is_method = func.is_method(); - let mut param_types: FnArgsVec<_> = arg_types + let param_types = arg_types .as_ref() .iter() .enumerate() .map(|(i, &type_id)| Self::map_type(!is_method || i > 0, type_id)) - .collect(); - param_types.shrink_to_fit(); + .collect::>(); let is_dynamic = param_types .iter() diff --git a/src/optimizer.rs b/src/optimizer.rs index 2a6e7fd0..876702c2 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -1195,12 +1195,11 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { && x.constant_args() // all arguments are constants => { // First search for script-defined functions (can override built-in) + let _has_script_fn = false; #[cfg(not(feature = "no_function"))] - let has_script_fn = !x.hashes.is_native_only() && state.global.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some(); - #[cfg(feature = "no_function")] - let has_script_fn = false; + let _has_script_fn = !x.hashes.is_native_only() && state.global.lib.iter().find_map(|m| m.get_script_fn(&x.name, x.args.len())).is_some(); - if !has_script_fn { + if !_has_script_fn { let arg_values = &mut x.args.iter().map(Expr::get_literal_value).collect::>>().unwrap(); let result = match x.name.as_str() { diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 664fa15b..7de4fecd 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -265,7 +265,7 @@ mod string_functions { /// /// print(text); // prints "hello, world!" /// - /// x.truncate(10); + /// text.truncate(10); /// /// print(text); // prints "hello, world!" /// ``` @@ -273,10 +273,10 @@ mod string_functions { if len > 0 { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; - let chars: StaticVec<_> = string.chars().collect(); - let copy = string.make_mut(); - copy.clear(); - copy.extend(chars.into_iter().take(len)); + if let Some((index, _)) = string.char_indices().nth(len) { + let copy = string.make_mut(); + copy.truncate(index); + } } else { clear(string); } @@ -1109,7 +1109,7 @@ mod string_functions { copy.clear(); copy.extend(chars.iter().skip(offset).take(len)); } - /// Remove all characters from the string except until the `start` position. + /// Remove all characters from the string up to the `start` position. /// /// * If `start` < 0, position counts from the end of the string (`-1` is the last character). /// * If `start` < -length of string, the string is not modified. diff --git a/src/reify.rs b/src/reify.rs index cc8b6647..4c9506a8 100644 --- a/src/reify.rs +++ b/src/reify.rs @@ -8,6 +8,8 @@ /// * `reify! { `_variable_ or _expression_` => |`_temp-variable_`: `_type_`|` _code_ `)` /// * `reify! { `_variable_ or _expression_ `=>` `Option<`_type_`>` `)` /// * `reify! { `_variable_ or _expression_ `=>` _type_ `)` +/// +/// * `reify! { `_expression_ `=> !!!` _type_ `)` (unsafe, no type checks!) macro_rules! reify { ($old:ident => |$new:ident : $t:ty| $code:expr, || $fallback:expr) => {{ #[allow(clippy::redundant_else)] @@ -45,4 +47,11 @@ macro_rules! reify { ($old:expr => $t:ty) => { reify! { $old => |v: $t| v, || unreachable!() } }; + + ($old:expr => !!! $t:ty) => {{ + let old_value = $old; + let new_value: $t = + unsafe { std::mem::transmute_copy(&std::mem::ManuallyDrop::new(old_value)) }; + new_value + }}; } diff --git a/src/serde/de.rs b/src/serde/de.rs index 30a1de6f..681a6817 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -124,6 +124,10 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { type Error = RhaiError; fn deserialize_any>(self, visitor: V) -> RhaiResultOf { + if type_name::() == type_name::() { + return Ok(reify! { self.0.clone() => !!! V::Value }); + } + match self.0 .0 { Union::Unit(..) => self.deserialize_unit(visitor), Union::Bool(..) => self.deserialize_bool(visitor), diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 4eec9053..ec936f67 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -156,7 +156,7 @@ impl ModuleMetadata<'_> { impl<'a> From<&'a crate::Module> for ModuleMetadata<'a> { fn from(module: &'a crate::Module) -> Self { - let mut functions: StaticVec<_> = module.iter_fn().map(Into::into).collect(); + let mut functions = module.iter_fn().map(Into::into).collect::>(); functions.sort(); Self { diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index f1bf1d2b..c3887d86 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1184,89 +1184,89 @@ impl Dynamic { self.flatten_in_place(); if TypeId::of::() == TypeId::of::() { - return Some(reify! { self => T }); + return Some(reify! { self => !!! T }); } if TypeId::of::() == TypeId::of::<()>() { return match self.0 { - Union::Unit(..) => Some(reify! { () => T }), + Union::Unit(..) => Some(reify! { () => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Int(n, ..) => Some(reify! { n => T }), + Union::Int(n, ..) => Some(reify! { n => !!! T }), _ => None, }; } #[cfg(not(feature = "no_float"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Float(v, ..) => Some(reify! { *v => T }), + Union::Float(v, ..) => Some(reify! { *v => !!! T }), _ => None, }; } #[cfg(feature = "decimal")] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Decimal(v, ..) => Some(reify! { *v => T }), + Union::Decimal(v, ..) => Some(reify! { *v => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Bool(b, ..) => Some(reify! { b => T }), + Union::Bool(b, ..) => Some(reify! { b => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(s, ..) => Some(reify! { s => T }), + Union::Str(s, ..) => Some(reify! { s => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Str(s, ..) => Some(reify! { s.to_string() => T }), + Union::Str(s, ..) => Some(reify! { s.to_string() => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Char(c, ..) => Some(reify! { c => T }), + Union::Char(c, ..) => Some(reify! { c => !!! T }), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Array(a, ..) => Some(reify! { *a => T }), + Union::Array(a, ..) => Some(reify! { *a => !!! T }), _ => None, }; } #[cfg(not(feature = "no_index"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Blob(b, ..) => Some(reify! { *b => T }), + Union::Blob(b, ..) => Some(reify! { *b => !!! T }), _ => None, }; } #[cfg(not(feature = "no_object"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::Map(m, ..) => Some(reify! { *m => T }), + Union::Map(m, ..) => Some(reify! { *m => !!! T }), _ => None, }; } if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::FnPtr(f, ..) => Some(reify! { *f => T }), + Union::FnPtr(f, ..) => Some(reify! { *f => !!! T }), _ => None, }; } #[cfg(not(feature = "no_time"))] if TypeId::of::() == TypeId::of::() { return match self.0 { - Union::TimeStamp(t, ..) => Some(reify! { *t => T }), + Union::TimeStamp(t, ..) => Some(reify! { *t => !!! T }), _ => None, }; } @@ -1306,7 +1306,7 @@ impl Dynamic { pub fn cast(self) -> T { // Bail out early if the return type needs no cast if TypeId::of::() == TypeId::of::() { - return reify! { self => T }; + return reify! { self => !!! T }; } #[cfg(not(feature = "no_closure"))] @@ -2038,7 +2038,7 @@ impl Dynamic { }) .collect(), Union::Blob(b, ..) if TypeId::of::() == TypeId::of::() => { - Ok(reify! { *b => Vec }) + Ok(reify! { *b => !!! Vec }) } #[cfg(not(feature = "no_closure"))] Union::Shared(ref cell, ..) => { @@ -2061,7 +2061,7 @@ impl Dynamic { .collect() } Union::Blob(ref b, ..) if TypeId::of::() == TypeId::of::() => { - Ok(reify! { b.clone() => Vec }) + Ok(reify! { b.clone() => !!! Vec }) } _ => Err(cell.type_name()), } diff --git a/tests/serde.rs b/tests/serde.rs index fb0ce51e..c8309cef 100644 --- a/tests/serde.rs +++ b/tests/serde.rs @@ -4,8 +4,9 @@ use rhai::{ serde::{from_dynamic, to_dynamic}, Dynamic, Engine, EvalAltResult, ImmutableString, Scope, INT, }; -use serde::{Deserialize, Serialize}; +use serde::{Deserialize, Deserializer, Serialize}; use serde_json::json; +use std::sync::Arc; #[cfg(not(feature = "no_index"))] use rhai::Array; @@ -383,6 +384,37 @@ fn test_serde_de_primary_types() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_object"))] +#[test] +fn test_serde_de_variants() -> Result<(), Box> { + #[derive(Debug)] + struct Foo; + + #[derive(Debug, Deserialize)] + struct Bar { + #[serde(deserialize_with = "deserialize_foo")] + value: Arc, + } + + fn deserialize_foo<'de, D: Deserializer<'de>>(deserializer: D) -> Result, D::Error> { + let value = ::deserialize(deserializer)?; + + value + .try_cast::>() + .ok_or_else(|| serde::de::Error::custom("type error")) + } + + let value = Arc::new(Foo); + let mut map = Map::new(); + map.insert("value".into(), Dynamic::from(value.clone())); + let x = Dynamic::from(map); + let bar = from_dynamic::(&x)?; + + assert!(Arc::ptr_eq(&bar.value, &value)); + + Ok(()) +} + #[test] fn test_serde_de_integer_types() -> Result<(), Box> { assert_eq!(42, from_dynamic::(&Dynamic::from(42 as INT))?);