diff --git a/RELEASES.md b/RELEASES.md index 012706d0..bbdbc1ba 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,8 +10,9 @@ Each function defined in an `AST` can optionally attach _doc-comments_ (which, a are comments prefixed by either `///` or `/**`). Doc-comments allow third-party tools to automatically generate documentation for functions defined in a Rhai script. -A new API, `Engine::gen_fn_metadata_to_json`, paired with the new `metadata` feature, -exports the full list of functions metadata (including those in an `AST`) as a JSON document. +A new API, `Engine::gen_fn_metadata_to_json` and `Engine::gen_fn_metadata_with_ast_to_json`, +paired with the new `metadata` feature, exports the full list of functions metadata +(including those in an `AST`) as a JSON document. Bug fixes --------- @@ -36,7 +37,9 @@ New features * `AST::iter_functions` now returns `ScriptFnMetadata` which includes, among others, _doc-comments_ for functions prefixed by `///` or `/**`. * _Doc-comments_ can be enabled/disabled with the new `Engine::set_doc_comments` method. -* A new feature `metadata` is added that pulls in `serde_json` and enables `Engine::gen_fn_metadata_to_json` which exports the full list of functions metadata (including those inside an `AST`) in JSON format. +* A new feature `metadata` is added that pulls in `serde_json` and enables `Engine::gen_fn_metadata_to_json` and ``Engine::gen_fn_metadata_with_ast_to_json` which exports the full list of functions metadata (including those inside an `AST`) in JSON format. +* `Engine::on_debug` provides two additional parameters: `source: Option<&str>` and `pos: Position`. +* `NativeCallContext` and `EvalContext` both expose `source()` which returns the current source, if any. Enhancements ------------ diff --git a/doc/src/engine/custom-syntax.md b/doc/src/engine/custom-syntax.md index 856392b0..99e02cf5 100644 --- a/doc/src/engine/custom-syntax.md +++ b/doc/src/engine/custom-syntax.md @@ -122,6 +122,7 @@ where: | • `scope()` | `&Scope` | reference to the current [`Scope`] | | • `scope_mut()` | `&mut Scope` | mutable reference to the current [`Scope`]; variables can be added to/removed from it | | • `engine()` | `&Engine` | reference to the current [`Engine`] | +| • `source()` | `Option<&str>` | reference to the current source, if any | | • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | | • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | | • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | diff --git a/doc/src/engine/var.md b/doc/src/engine/var.md index 86226b49..5fe79881 100644 --- a/doc/src/engine/var.md +++ b/doc/src/engine/var.md @@ -74,6 +74,7 @@ where: | `context` | `&EvalContext` | reference to the current evaluation _context_ | | • `scope()` | `&Scope` | reference to the current [`Scope`] | | • `engine()` | `&Engine` | reference to the current [`Engine`] | +| • `source()` | `Option<&str>` | reference to the current source, if any | | • `imports()` | `&Imports` | reference to the current stack of [modules] imported via `import` statements | | • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | | • `this_ptr()` | `Option<&Dynamic>` | reference to the current bound [`this`] pointer, if any | diff --git a/doc/src/language/fn-ptr.md b/doc/src/language/fn-ptr.md index 11ca1f4d..16c37e08 100644 --- a/doc/src/language/fn-ptr.md +++ b/doc/src/language/fn-ptr.md @@ -228,6 +228,7 @@ of the particular call to a registered Rust function. It is a type that exposes | Field | Type | Description | | ------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | +| `source()` | `Option<&str>` | reference to the current source, if any | | `imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | | `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | @@ -254,11 +255,11 @@ let fn_ptr = engine.eval_ast::(&ast)?; // Get rid of the script, retaining only functions ast.retain_functions(|_, _, _| true); +// Create function namespace from the 'AST' +let lib = [ast.as_ref()]; + // Create native call context -let context = NativeCallContext::new( - &engine, // the 'Engine' - &[ast.as_ref()] // function namespace from the 'AST' -); +let context = NativeCallContext::new(&engine, &lib); // 'f' captures: the engine, the AST, and the closure let f = move |x: i64| fn_ptr.call_dynamic(context, None, [x.into()]); diff --git a/doc/src/plugins/function.md b/doc/src/plugins/function.md index 03f8361d..bab38bc5 100644 --- a/doc/src/plugins/function.md +++ b/doc/src/plugins/function.md @@ -89,6 +89,7 @@ specially by the plugins system. | Field | Type | Description | | ------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | +| `source()` | `Option<&str>` | reference to the current source, if any | | `imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | | `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | diff --git a/doc/src/plugins/module.md b/doc/src/plugins/module.md index f2edc3d2..a8343220 100644 --- a/doc/src/plugins/module.md +++ b/doc/src/plugins/module.md @@ -400,6 +400,7 @@ specially by the plugins system. | Field | Type | Description | | ------------------- | :-----------------------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | +| `source()` | `Option<&str>` | reference to the current source, if any | | `imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | | `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | diff --git a/doc/src/rust/register-raw.md b/doc/src/rust/register-raw.md index 78137c30..d51e129d 100644 --- a/doc/src/rust/register-raw.md +++ b/doc/src/rust/register-raw.md @@ -70,6 +70,7 @@ where: | `T` | `impl Clone` | return type of the function | | `context` | `NativeCallContext` | the current _native call context_ | | • `engine()` | `&Engine` | the current [`Engine`], with all configurations and settings.
This is sometimes useful for calling a script-defined function within the same evaluation context using [`Engine::call_fn`][`call_fn`], or calling a [function pointer]. | +| • `source()` | `Option<&str>` | reference to the current source, if any | | • `imports()` | `Option<&Imports>` | reference to the current stack of [modules] imported via `import` statements (if any) | | • `iter_namespaces()` | `impl Iterator` | iterator of the namespaces (as [modules]) containing all script-defined functions | | `args` | `&mut [&mut Dynamic]` | a slice containing `&mut` references to [`Dynamic`] values.
The slice is guaranteed to contain enough arguments _of the correct types_. | diff --git a/src/engine.rs b/src/engine.rs index 17937537..9e578753 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -560,6 +560,11 @@ impl<'e, 'x, 'px, 'a, 's, 'm, 'pm, 't, 'pt> EvalContext<'e, 'x, 'px, 'a, 's, 'm, pub fn engine(&self) -> &'e Engine { self.engine } + /// The current source. + #[inline(always)] + pub fn source<'z: 's>(&'z self) -> Option<&'s str> { + self.state.source.as_ref().map(|s| s.as_str()) + } /// The current [`Scope`]. #[inline(always)] pub fn scope(&self) -> &Scope<'px> { @@ -2002,9 +2007,12 @@ impl Engine { // Overriding exact implementation if func.is_plugin_fn() { func.get_plugin_fn() - .call((self, &*mods, lib).into(), args)?; + .call((self, &state.source, &*mods, lib).into(), args)?; } else { - func.get_native_fn()((self, &*mods, lib).into(), args)?; + func.get_native_fn()( + (self, &state.source, &*mods, lib).into(), + args, + )?; } } // Built-in op-assignment function diff --git a/src/fn_call.rs b/src/fn_call.rs index 70258f03..d0b320d2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -178,7 +178,9 @@ impl Engine { // Search for the native function // First search registered functions (can override packages) // Then search packages - // lib.get_fn(hash_fn, pub_only) + // Finally search modules + + //lib.get_fn(hash_fn, pub_only) let f = self .global_namespace .get_fn(hash_fn, pub_only) @@ -198,9 +200,10 @@ impl Engine { // Run external function let result = if func.is_plugin_fn() { - func.get_plugin_fn().call((self, mods, lib).into(), args) + func.get_plugin_fn() + .call((self, &state.source, mods, lib).into(), args) } else { - func.get_native_fn()((self, mods, lib).into(), args) + func.get_native_fn()((self, &state.source, mods, lib).into(), args) }; // Restore the original reference @@ -210,17 +213,16 @@ impl Engine { // See if the function match print/debug (which requires special processing) return Ok(match fn_name { - KEYWORD_PRINT => ( - (self.print)(result.as_str().map_err(|typ| { + KEYWORD_PRINT => { + let text = result.as_str().map_err(|typ| { EvalAltResult::ErrorMismatchOutputType( self.map_type_name(type_name::()).into(), typ.into(), pos, ) - })?) - .into(), - false, - ), + })?; + ((self.print)(text).into(), false) + } KEYWORD_DEBUG => { let text = result.as_str().map_err(|typ| { EvalAltResult::ErrorMismatchOutputType( @@ -534,10 +536,13 @@ impl Engine { // Get function let (func, mut source) = lib .iter() - .find_map(|&m| m.get_fn(hash_script, pub_only).map(|f| (f, m.clone_id()))) + .find_map(|&m| { + m.get_fn(hash_script, pub_only) + .map(|f| (f, m.id_raw().clone())) + }) //.or_else(|| self.global_namespace.get_fn(hash_script, pub_only)) .or_else(|| self.packages.get_fn(hash_script).map(|f| (f, None))) - //.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.clone_id())))) + //.or_else(|| mods.iter().find_map(|(_, m)| m.get_qualified_fn(hash_script).map(|f| (f, m.id_raw().clone())))) .unwrap(); if func.is_script() { @@ -1176,7 +1181,7 @@ impl Engine { let new_scope = &mut Default::default(); let fn_def = f.get_fn_def().clone(); - let mut source = module.clone_id(); + let mut source = module.id_raw().clone(); mem::swap(&mut state.source, &mut source); let result = self.call_script_fn( @@ -1190,7 +1195,7 @@ impl Engine { Some(f) if f.is_plugin_fn() => f .get_plugin_fn() .clone() - .call((self, &*mods, lib).into(), args.as_mut()), + .call((self, module.id_raw(), &*mods, lib).into(), args.as_mut()), Some(f) if f.is_native() => { if !f.is_method() { // Clone first argument @@ -1201,7 +1206,7 @@ impl Engine { } } - f.get_native_fn()((self, &*mods, lib).into(), args.as_mut()) + f.get_native_fn()((self, module.id_raw(), &*mods, lib).into(), args.as_mut()) } Some(_) => unreachable!(), None if def_val.is_some() => Ok(def_val.unwrap().clone()), diff --git a/src/fn_native.rs b/src/fn_native.rs index 6cbe7542..71a3a775 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -47,42 +47,47 @@ pub type Locked = crate::stdlib::sync::RwLock; /// Context of a native Rust function call. #[derive(Debug, Copy, Clone)] -pub struct NativeCallContext<'e, 'a, 'm, 'pm: 'm> { +pub struct NativeCallContext<'e, 's, 'a, 'm, 'pm: 'm> { engine: &'e Engine, + source: Option<&'s str>, pub(crate) mods: Option<&'a Imports>, pub(crate) lib: &'m [&'pm Module], } -impl<'e, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'a Imports, &'m M)> - for NativeCallContext<'e, 'a, 'm, 'pm> +impl<'e, 's, 'a, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> + From<(&'e Engine, &'s Option, &'a Imports, &'m M)> + for NativeCallContext<'e, 's, 'a, 'm, 'pm> { - fn from(value: (&'e Engine, &'a Imports, &'m M)) -> Self { + fn from(value: (&'e Engine, &'s Option, &'a Imports, &'m M)) -> Self { Self { engine: value.0, - mods: Some(value.1), - lib: value.2.as_ref(), + source: value.1.as_ref().map(|s| s.as_str()), + mods: Some(value.2), + lib: value.3.as_ref(), } } } impl<'e, 'm, 'pm: 'm, M: AsRef<[&'pm Module]> + ?Sized> From<(&'e Engine, &'m M)> - for NativeCallContext<'e, '_, 'm, 'pm> + for NativeCallContext<'e, '_, '_, 'm, 'pm> { fn from(value: (&'e Engine, &'m M)) -> Self { Self { engine: value.0, + source: None, mods: None, lib: value.1.as_ref(), } } } -impl<'e, 'a, 'm, 'pm> NativeCallContext<'e, 'a, 'm, 'pm> { +impl<'e, 's, 'a, 'm, 'pm> NativeCallContext<'e, 's, 'a, 'm, 'pm> { /// Create a new [`NativeCallContext`]. #[inline(always)] pub fn new(engine: &'e Engine, lib: &'m impl AsRef<[&'pm Module]>) -> Self { Self { engine, + source: None, mods: None, lib: lib.as_ref(), } @@ -92,13 +97,15 @@ impl<'e, 'a, 'm, 'pm> NativeCallContext<'e, 'a, 'm, 'pm> { #[cfg(feature = "internals")] #[cfg(not(feature = "no_module"))] #[inline(always)] - pub fn new_with_imports( + pub fn new_with_all_fields( engine: &'e Engine, + source: &'s Option, mods: &'a mut Imports, lib: &'m impl AsRef<[&'pm Module]>, ) -> Self { Self { engine, + source: source.as_ref().map(|s| s.as_str()), mods: Some(mods), lib: lib.as_ref(), } @@ -108,6 +115,11 @@ impl<'e, 'a, 'm, 'pm> NativeCallContext<'e, 'a, 'm, 'pm> { pub fn engine(&self) -> &'e Engine { self.engine } + /// The current source. + #[inline(always)] + pub fn source<'z: 's>(&'z self) -> Option<&'s str> { + self.source + } /// _(INTERNALS)_ The current set of modules imported via `import` statements. /// Available under the `internals` feature only. #[cfg(feature = "internals")] diff --git a/src/module/mod.rs b/src/module/mod.rs index e94d3297..e5d34f9a 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -244,8 +244,8 @@ impl Module { } /// Get the ID of the module, if any. - pub(crate) fn clone_id(&self) -> Option { - self.id.clone() + pub(crate) fn id_raw(&self) -> &Option { + &self.id } /// Set the ID of the module. diff --git a/src/parser.rs b/src/parser.rs index 6dfe48c3..a8f72932 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1005,7 +1005,7 @@ fn parse_primary( Token::Reserved(s) if s == KEYWORD_THIS && *next_token != Token::LeftParen => { if !settings.is_function_scope { let msg = format!("'{}' can only be used in functions", s); - return Err(PERR::BadInput(LexError::ImproperSymbol(s, msg)).into_err(settings.pos)); + return Err(LexError::ImproperSymbol(s, msg).into_err(settings.pos)); } else { let var_name_def = IdentX::new(state.get_interned_string(s), settings.pos); Expr::Variable(Box::new((None, None, 0, var_name_def))) @@ -1027,8 +1027,7 @@ fn parse_primary( _ => { return Err( - PERR::BadInput(LexError::UnexpectedInput(token.syntax().to_string())) - .into_err(settings.pos), + LexError::UnexpectedInput(token.syntax().to_string()).into_err(settings.pos) ); } }; @@ -1050,10 +1049,10 @@ fn parse_primary( return Err(if !match_token(input, Token::LeftParen).0 { LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos) } else { - PERR::BadInput(LexError::ImproperSymbol( + LexError::ImproperSymbol( "!".to_string(), "'!' cannot be used to call module functions".to_string(), - )) + ) .into_err(token_pos) }); } @@ -1375,10 +1374,10 @@ fn make_assignment_stmt<'a>( Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } // ??? && ??? = rhs, ??? || ??? = rhs - Expr::And(_, _) | Expr::Or(_, _) => Err(PERR::BadInput(LexError::ImproperSymbol( + Expr::And(_, _) | Expr::Or(_, _) => Err(LexError::ImproperSymbol( "=".to_string(), "Possibly a typo of '=='?".to_string(), - )) + ) .into_err(pos)), // expr = rhs _ => Err(PERR::AssignmentToInvalidLHS("".to_string()).into_err(lhs.position())), @@ -1482,13 +1481,13 @@ fn make_dot_expr( && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] .contains(&x.name.as_ref()) => { - return Err(PERR::BadInput(LexError::ImproperSymbol( + return Err(LexError::ImproperSymbol( x.name.to_string(), format!( "'{}' should not be called in method style. Try {}(...);", x.name, x.name ), - )) + ) .into_err(pos)); } // lhs.func!(...) @@ -1806,7 +1805,6 @@ fn parse_binary_op( make_in_expr(current_lhs, rhs, pos)? } - // This is needed to parse closure followed by a dot. #[cfg(not(feature = "no_object"))] Token::Period => { let rhs = args.pop().unwrap(); @@ -1991,10 +1989,10 @@ fn ensure_not_statement_expr(input: &mut TokenStream, type_name: &str) -> Result /// Make sure that the expression is not a mis-typed assignment (i.e. `a = b` instead of `a == b`). fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { match input.peek().unwrap() { - (Token::Equals, pos) => Err(PERR::BadInput(LexError::ImproperSymbol( + (Token::Equals, pos) => Err(LexError::ImproperSymbol( "=".to_string(), "Possibly a typo of '=='?".to_string(), - )) + ) .into_err(*pos)), (token @ Token::PlusAssign, pos) | (token @ Token::MinusAssign, pos) @@ -2006,10 +2004,10 @@ fn ensure_not_assignment(input: &mut TokenStream) -> Result<(), ParseError> { | (token @ Token::PowerOfAssign, pos) | (token @ Token::AndAssign, pos) | (token @ Token::OrAssign, pos) - | (token @ Token::XOrAssign, pos) => Err(PERR::BadInput(LexError::ImproperSymbol( + | (token @ Token::XOrAssign, pos) => Err(LexError::ImproperSymbol( token.syntax().to_string(), "Expecting a boolean expression, not an assignment".to_string(), - )) + ) .into_err(*pos)), _ => Ok(()), @@ -2985,10 +2983,7 @@ impl Engine { (Token::EOF, _) => (), // Return error if the expression doesn't end (token, pos) => { - return Err( - PERR::BadInput(LexError::UnexpectedInput(token.syntax().to_string())) - .into_err(*pos), - ) + return Err(LexError::UnexpectedInput(token.syntax().to_string()).into_err(*pos)) } } diff --git a/src/serde_impl/de.rs b/src/serde_impl/de.rs index 0aa00a6e..e17d177c 100644 --- a/src/serde_impl/de.rs +++ b/src/serde_impl/de.rs @@ -3,7 +3,7 @@ use super::str::ImmutableStringDeserializer; use crate::dynamic::Union; use crate::stdlib::{any::type_name, boxed::Box, fmt, string::ToString}; -use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, ParseErrorType, Position}; +use crate::{Dynamic, EvalAltResult, ImmutableString, LexError, Position}; use serde::de::{ DeserializeSeed, Deserializer, Error, IntoDeserializer, MapAccess, SeqAccess, Visitor, }; @@ -119,11 +119,9 @@ pub fn from_dynamic<'de, T: Deserialize<'de>>( impl Error for Box { fn custom(err: T) -> Self { - EvalAltResult::ErrorParsing( - ParseErrorType::BadInput(LexError::ImproperSymbol("".to_string(), err.to_string())), - Position::NONE, - ) - .into() + LexError::ImproperSymbol("".to_string(), err.to_string()) + .into_err(Position::NONE) + .into() } } diff --git a/src/serde_impl/metadata.rs b/src/serde_impl/metadata.rs index ef291705..da0e6ab1 100644 --- a/src/serde_impl/metadata.rs +++ b/src/serde_impl/metadata.rs @@ -213,17 +213,17 @@ impl From<&crate::Module> for ModuleMetadata { #[cfg(feature = "serde")] impl Engine { - /// Generate a list of all functions (including those defined in an [`AST`][crate::AST], if provided) + /// Generate a list of all functions (including those defined in an [`AST`][crate::AST]) /// in JSON format. Available only under the `metadata` feature. /// /// Functions from the following sources are included: - /// 1) Functions defined in an [`AST`][crate::AST] (if provided) + /// 1) Functions defined in an [`AST`][crate::AST] /// 2) Functions registered into the global namespace /// 3) Functions in registered sub-modules /// 4) Functions in packages (optional) - pub fn gen_fn_metadata_to_json( + pub fn gen_fn_metadata_with_ast_to_json( &self, - ast: Option<&AST>, + ast: &AST, include_packages: bool, ) -> serde_json::Result { let mut global: ModuleMetadata = Default::default(); @@ -254,4 +254,15 @@ impl Engine { serde_json::to_string_pretty(&global) } + + /// Generate a list of all functions in JSON format. + /// Available only under the `metadata` feature. + /// + /// Functions from the following sources are included: + /// 1) Functions registered into the global namespace + /// 2) Functions in registered sub-modules + /// 3) Functions in packages (optional) + pub fn gen_fn_metadata_to_json(&self, include_packages: bool) -> serde_json::Result { + self.gen_fn_metadata_with_ast_to_json(&Default::default(), include_packages) + } } diff --git a/tests/closures.rs b/tests/closures.rs index aa3db7f4..7c74e9bd 100644 --- a/tests/closures.rs +++ b/tests/closures.rs @@ -1,5 +1,7 @@ #![cfg(not(feature = "no_function"))] -use rhai::{Engine, EvalAltResult, FnPtr, ParseErrorType, RegisterFn, Scope, INT}; +use rhai::{ + Engine, EvalAltResult, FnPtr, NativeCallContext, ParseErrorType, RegisterFn, Scope, INT, +}; use std::any::TypeId; use std::cell::RefCell; use std::mem::take; @@ -269,8 +271,14 @@ fn test_closures_external() -> Result<(), Box> { // Get rid of the script, retaining only functions ast.retain_functions(|_, _, _, _| true); - // Closure 'f' captures: the engine, the AST, and the curried function pointer - let f = move |x: INT| fn_ptr.call_dynamic((&engine, &[ast.as_ref()]).into(), None, [x.into()]); + // Create function namespace from the 'AST' + let lib = [ast.as_ref()]; + + // Create native call context + let context = NativeCallContext::new(&engine, &lib); + + // Closure 'f' captures: the engine, the AST, and the curried function pointer + let f = move |x: INT| fn_ptr.call_dynamic(context, None, [x.into()]); assert_eq!(f(42)?.as_str(), Ok("hello42"));