diff --git a/RELEASES.md b/RELEASES.md index f06c6a74..c9c1d81e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -15,6 +15,9 @@ Breaking changes * `EvalAltResult::ErrorLoopBreak` is renamed to `EvalAltResult::LoopBreak`. * `Engine::register_raw_fn` and `FnPtr::call_dynamic` function signatures have changed. * Callback signatures to `Engine::on_var` and `Engine::register_custom_syntax` have changed. +* `EvalAltResult::ErrorRuntime` now wraps a `Dynamic` instead of a string. +* Default call stack depth for `debug` builds is reduced to 8 (from 12) because it keeps overflowing the stack in GitHub CI! +* Keyword `thread` is reserved. New features ------------ @@ -27,6 +30,7 @@ Enhancements ------------ * Calling `eval` or `Fn` in method-call style, which is an error, is now caught during parsing. +* `func!()` call style is valid even under `no_closure` feature. Version 0.19.2 diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index fd406370..f577c1df 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -9,27 +9,27 @@ //! //! #[export_module] //! mod advanced_math { -//! pub const MYSTIC_NUMBER: FLOAT = 42.0 as FLOAT; +//! pub const MYSTIC_NUMBER: FLOAT = 42.0; //! //! pub fn euclidean_distance(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { //! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() //! } //! } //! -//! fn main() -> Result<(), Box> { -//! let mut engine = Engine::new(); -//! let m = rhai::exported_module!(advanced_math); -//! let mut r = StaticModuleResolver::new(); -//! r.insert("Math::Advanced".to_string(), m); -//! engine.set_module_resolver(Some(r)); +//! # fn main() -> Result<(), Box> { +//! let mut engine = Engine::new(); +//! let m = exported_module!(advanced_math); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced", m); +//! engine.set_module_resolver(Some(r)); //! -//! assert_eq!(engine.eval::( -//! r#"import "Math::Advanced" as math; -//! let m = math::MYSTIC_NUMBER; -//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); -//! x"#)?, 41.0); -//! Ok(()) -//! } +//! assert_eq!(engine.eval::( +//! r#" +//! import "Math::Advanced" as math; +//! math::euclidean_distance(0.0, 1.0, 0.0, math::MYSTIC_NUMBER) +//! "#)?, 41.0); +//! # Ok(()) +//! # } //! ``` //! //! # Register a Rust Function with a Rhai `Module` @@ -44,23 +44,22 @@ //! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() //! } //! -//! fn main() -> Result<(), Box> { +//! # fn main() -> Result<(), Box> { +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || 42.0 as FLOAT); +//! let mut m = Module::new(); +//! set_exported_fn!(m, "euclidean_distance", distance_function); +//! let mut r = StaticModuleResolver::new(); +//! r.insert("Math::Advanced", m); +//! engine.set_module_resolver(Some(r)); //! -//! let mut engine = Engine::new(); -//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); -//! let mut m = Module::new(); -//! rhai::set_exported_fn!(m, "euclidean_distance", distance_function); -//! let mut r = StaticModuleResolver::new(); -//! r.insert("Math::Advanced".to_string(), m); -//! engine.set_module_resolver(Some(r)); -//! -//! assert_eq!(engine.eval::( -//! r#"import "Math::Advanced" as math; -//! let m = get_mystic_number(); -//! let x = math::euclidean_distance(0.0, 1.0, 0.0, m); -//! x"#)?, 41.0); -//! Ok(()) -//! } +//! assert_eq!(engine.eval::( +//! r#" +//! import "Math::Advanced" as math; +//! math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()) +//! "#)?, 41.0); +//! # Ok(()) +//! # } //! ``` //! //! # Register a Plugin Function with an `Engine` @@ -70,23 +69,21 @@ //! use rhai::plugin::*; //! use rhai::module_resolvers::*; //! -//! #[rhai::export_fn] +//! #[export_fn] //! pub fn distance_function(x1: FLOAT, y1: FLOAT, x2: FLOAT, y2: FLOAT) -> FLOAT { //! ((y2 - y1).abs().powf(2.0) + (x2 -x1).abs().powf(2.0)).sqrt() //! } //! -//! fn main() -> Result<(), Box> { +//! # fn main() -> Result<(), Box> { +//! let mut engine = Engine::new(); +//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); +//! register_exported_fn!(engine, "euclidean_distance", distance_function); //! -//! let mut engine = Engine::new(); -//! engine.register_fn("get_mystic_number", || { 42 as FLOAT }); -//! rhai::register_exported_fn!(engine, "euclidean_distance", distance_function); -//! -//! assert_eq!(engine.eval::( -//! r#"let m = get_mystic_number(); -//! let x = euclidean_distance(0.0, 1.0, 0.0, m); -//! x"#)?, 41.0); -//! Ok(()) -//! } +//! assert_eq!(engine.eval::( +//! "euclidean_distance(0.0, 1.0, 0.0, get_mystic_number())" +//! )?, 41.0); +//! # Ok(()) +//! # } //! ``` //! diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 2f8a881e..590ac6e9 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -17,13 +17,9 @@ fn raw_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::set_exported_fn!( - m, - "euclidean_distance".to_string(), - raw_fn::distance_function - ); + rhai::set_exported_fn!(m, "euclidean_distance", raw_fn::distance_function); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -53,7 +49,7 @@ fn raw_fn_mut_test() -> Result<(), Box> { let mut m = Module::new(); rhai::set_exported_fn!(m, "add_in_place", raw_fn_mut::add_in_place); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -85,7 +81,7 @@ fn raw_fn_str_test() -> Result<(), Box> { let mut m = Module::new(); rhai::set_exported_fn!(m, "write_out_str", raw_fn_str::write_out_str); let mut r = StaticModuleResolver::new(); - r.insert("Host::IO".to_string(), m); + r.insert("Host::IO", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -143,7 +139,7 @@ fn mut_opaque_ref_test() -> Result<(), Box> { rhai::set_exported_fn!(m, "new_os_message", mut_opaque_ref::new_os_message); rhai::set_exported_fn!(m, "write_out_message", mut_opaque_ref::write_out_message); let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg".to_string(), m); + r.insert("Host::Msg", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -182,13 +178,9 @@ fn raw_returning_fn_test() -> Result<(), Box> { let mut engine = Engine::new(); engine.register_fn("get_mystic_number", || 42 as FLOAT); let mut m = Module::new(); - rhai::set_exported_fn!( - m, - "euclidean_distance".to_string(), - raw_returning_fn::distance_function - ); + rhai::set_exported_fn!(m, "euclidean_distance", raw_returning_fn::distance_function); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( diff --git a/codegen/tests/test_modules.rs b/codegen/tests/test_modules.rs index 014edbeb..2217b384 100644 --- a/codegen/tests/test_modules.rs +++ b/codegen/tests/test_modules.rs @@ -13,7 +13,7 @@ fn empty_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::empty_module::EmptyModule); let mut r = StaticModuleResolver::new(); - r.insert("Module::Empty".to_string(), m); + r.insert("Module::Empty", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -40,7 +40,7 @@ fn one_fn_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module::advanced_math); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -74,7 +74,7 @@ fn one_fn_and_const_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_and_const_module::advanced_math); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -106,7 +106,7 @@ fn raw_fn_str_module_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::raw_fn_str_module::host_io); let mut r = StaticModuleResolver::new(); - r.insert("Host::IO".to_string(), m); + r.insert("Host::IO", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -163,7 +163,7 @@ fn mut_opaque_ref_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::mut_opaque_ref_module::host_msg); let mut r = StaticModuleResolver::new(); - r.insert("Host::Msg".to_string(), m); + r.insert("Host::Msg", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -205,7 +205,7 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { engine.register_fn("get_mystic_number", || 42 as FLOAT); let m = rhai::exported_module!(crate::duplicate_fn_rename::my_adds); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); let output_array = engine.eval::( @@ -329,7 +329,7 @@ fn export_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_by_prefix::my_adds); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); let output_array = engine.eval::( @@ -412,7 +412,7 @@ fn export_all_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_all::my_adds); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); let output_array = engine.eval::( diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index 4d0d39aa..cf2c1a2e 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -21,7 +21,7 @@ fn one_fn_module_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_module_nested_attr::advanced_math); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -57,7 +57,7 @@ fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); assert_eq!( @@ -132,7 +132,7 @@ fn export_nested_by_prefix_test() -> Result<(), Box> { let mut engine = Engine::new(); let m = rhai::exported_module!(crate::export_nested_by_prefix::my_adds); let mut r = StaticModuleResolver::new(); - r.insert("Math::Advanced".to_string(), m); + r.insert("Math::Advanced", m); engine.set_module_resolver(Some(r)); let output_array = engine.eval::( diff --git a/doc/src/appendix/keywords.md b/doc/src/appendix/keywords.md index 94419067..acf603f8 100644 --- a/doc/src/appendix/keywords.md +++ b/doc/src/appendix/keywords.md @@ -61,6 +61,7 @@ Reserved Keywords | `with` | scope | | `module` | module | | `package` | package | +| `thread` | threading | | `spawn` | threading | | `go` | threading | | `await` | async | diff --git a/doc/src/language/fn-capture.md b/doc/src/language/fn-capture.md index e35de979..400241ca 100644 --- a/doc/src/language/fn-capture.md +++ b/doc/src/language/fn-capture.md @@ -16,8 +16,6 @@ it raises an evaluation error. It is possible, through a special syntax, to capture the calling scope - i.e. the scope that makes the function call - and access variables defined there. -Capturing can be disabled via the [`no_closure`] feature. - ```rust fn foo(y) { // function accesses 'x' and 'y', but 'x' is not defined x += y; // 'x' is modified in this function diff --git a/doc/src/language/keywords.md b/doc/src/language/keywords.md index cb18852c..c7cd5f3a 100644 --- a/doc/src/language/keywords.md +++ b/doc/src/language/keywords.md @@ -5,21 +5,21 @@ Keywords The following are reserved keywords in Rhai: -| Active keywords | Reserved keywords | Usage | Inactive under feature | -| ------------------------------------------------- | ------------------------------------------------ | --------------------- | :--------------------: | -| `true`, `false` | | boolean constants | | -| `let`, `const` | `var`, `static` | variable declarations | | -| `is_shared` | | shared values | [`no_closure`] | -| `if`, `else` | `then`, `goto`, `exit` | control flow | | -| | `switch`, `match`, `case` | matching | | -| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | looping | | -| `fn`, `private` | `public`, `new` | functions | [`no_function`] | -| `return` | | return values | | -| `throw` | `try`, `catch` | throw exceptions | | -| `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] | -| `Fn`, `call`, `curry` | | function pointers | | -| | `spawn`, `go`, `sync`, `async`, `await`, `yield` | threading/async | | -| `type_of`, `print`, `debug`, `eval` | | special functions | | -| | `default`, `void`, `null`, `nil` | special values | | +| Active keywords | Reserved keywords | Usage | Inactive under feature | +| ------------------------------------------------- | ---------------------------------------------------------- | ---------------------- | :--------------------: | +| `true`, `false` | | boolean constants | | +| `let`, `const` | `var`, `static` | variable declarations | | +| `is_shared` | | shared values | [`no_closure`] | +| `if`, `else` | `then`, `goto`, `exit` | control flow | | +| | `switch`, `match`, `case` | matching | | +| `while`, `loop`, `for`, `in`, `continue`, `break` | `do`, `each` | looping | | +| `fn`, `private` | `public`, `new` | functions | [`no_function`] | +| `return` | | return values | | +| `throw`, `try`, `catch` | | throw/catch exceptions | | +| `import`, `export`, `as` | `use`, `with`, `module`, `package` | modules/packages | [`no_module`] | +| `Fn`, `call`, `curry` | | function pointers | | +| | `spawn`, `thread`, `go`, `sync`, `async`, `await`, `yield` | threading/async | | +| `type_of`, `print`, `debug`, `eval` | | special functions | | +| | `default`, `void`, `null`, `nil` | special values | | Keywords cannot become the name of a [function] or [variable], even when they are disabled. diff --git a/doc/src/language/try-catch.md b/doc/src/language/try-catch.md index 3ea934fc..5c3b9e1f 100644 --- a/doc/src/language/try-catch.md +++ b/doc/src/language/try-catch.md @@ -4,11 +4,12 @@ Catch Exceptions {{#include ../links.md}} -When an [exception] is thrown via a `throw` statement, evaluation of the script halts +When an [exception] is thrown via a [`throw`] statement, evaluation of the script halts and the [`Engine`] returns with `Err(Box)` containing the exception value that has been thrown. -It is possible, via the `try` ... `catch` statement, to _catch_ exceptions. +It is possible, via the `try` ... `catch` statement, to _catch_ exceptions, optionally +with an _error variable_. ```rust // Catch an exception and capturing its value @@ -26,7 +27,7 @@ try { print(42/0); // deliberate divide-by-zero exception } -catch // no catch variable - exception value is discarded +catch // no error variable - exception value is discarded { print("Ouch!"); } @@ -50,7 +51,7 @@ Re-Throw Exception ------------------ Like the `try` ... `catch` syntax in most languages, it is possible to _re-throw_ -an exception within the `catch` block simply by another `throw` statement without +an exception within the `catch` block simply by another [`throw`] statement without a value. @@ -76,20 +77,23 @@ Catchable Exceptions Many script-oriented exceptions can be caught via `try` ... `catch`: -* Runtime error thrown by a `throw` statement -* Arithmetic error -* Variable not found -* [Function] not found -* [Module] not found -* Unbound [`this`] -* Data type mismatch -* [Array]/[string] indexing out-of-bounds -* Indexing with an inappropriate type -* `for` statement without an iterator -* Error in an `in` expression -* Data race detected -* Assignment to a calculated value/constant value -* Dot expression error +| Error type | Error value | +| --------------------------------------------- | :------------------------: | +| Runtime error thrown by a [`throw`] statement | value in `throw` statement | +| Other runtime error | error message [string] | +| Arithmetic error | error message [string] | +| Variable not found | error message [string] | +| [Function] not found | error message [string] | +| [Module] not found | error message [string] | +| Unbound [`this`] | error message [string] | +| Data type mismatch | error message [string] | +| Assignment to a calculated/constant value | error message [string] | +| [Array]/[string] indexing out-of-bounds | error message [string] | +| Indexing with an inappropriate data type | error message [string] | +| Error in a dot expression | error message [string] | +| `for` statement without an iterator | error message [string] | +| Error in an `in` expression | error message [string] | +| Data race detected | error message [string] | Non-Catchable Exceptions @@ -99,6 +103,6 @@ Some exceptions _cannot_ be caught: * Syntax error during parsing * System error - e.g. script file not found -* Script evaluation over [limits]({{rootUrl}}/safety/index.md) -* [Stack overflow][maximum call stack depth] +* Script evaluation metrics over [safety limits]({{rootUrl}}/safety/index.md) +* Function calls nesting exceeding [maximum call stack depth] * Script evaluation manually terminated diff --git a/doc/src/links.md b/doc/src/links.md index 3ef7d328..5093c412 100644 --- a/doc/src/links.md +++ b/doc/src/links.md @@ -89,6 +89,7 @@ [function overloading]: {{rootUrl}}/rust/functions.md#function-overloading [fallible function]: {{rootUrl}}/rust/fallible.md [fallible functions]: {{rootUrl}}/rust/fallible.md +[`throw`]: {{rootUrl}}/language/throw.md [exception]: {{rootUrl}}/language/throw.md [exceptions]: {{rootUrl}}/language/throw.md [function pointer]: {{rootUrl}}/language/fn-ptr.md diff --git a/doc/src/safety/index.md b/doc/src/safety/index.md index 22ae237d..6e4e8c1c 100644 --- a/doc/src/safety/index.md +++ b/doc/src/safety/index.md @@ -33,3 +33,13 @@ The most important resources to watch out for are: * **Data**: A malicious script may attempt to read from and/or write to data that it does not own. If this happens, it is a severe security breach and may put the entire system at risk. + + +`unchecked` +----------- + +All these safe-guards can be turned off via the [`unchecked`] feature, which disables all +safety checks (even fatal errors such as arithmetic overflows and division-by-zero). + +This will increase script evaluation performance, at the expense of having an erroneous +script able to panic the entire system. diff --git a/src/engine.rs b/src/engine.rs index c95fed5a..e7de12bd 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1695,11 +1695,11 @@ impl Engine { // Normal function call Expr::FnCall(x) if x.1.is_none() => { - let ((name, native, capture, pos), _, hash, args_expr, def_val) = x.as_ref(); + let ((name, native, cap_scope, pos), _, hash, args_expr, def_val) = x.as_ref(); let def_val = def_val.map(Into::::into); self.make_function_call( scope, mods, state, lib, this_ptr, name, args_expr, &def_val, *hash, *native, - false, *capture, level, + false, *cap_scope, level, ) .map_err(|err| err.fill_position(*pos)) } diff --git a/src/fn_call.rs b/src/fn_call.rs index 1844bf5d..fda09ff2 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -490,7 +490,7 @@ impl Engine { is_ref: bool, _is_method: bool, pub_only: bool, - _capture: Option, + _capture_scope: Option, def_val: &Option, _level: usize, ) -> Result<(Dynamic, bool), Box> { @@ -562,7 +562,7 @@ impl Engine { // Move captured variables into scope #[cfg(not(feature = "no_closure"))] - if let Some(captured) = _capture { + if let Some(captured) = _capture_scope { captured .into_iter() .filter(|ScopeEntry { name, .. }| { @@ -868,7 +868,7 @@ impl Engine { mut hash_script: u64, native: bool, pub_only: bool, - capture: bool, + capture_scope: bool, level: usize, ) -> Result> { let args_expr = args_expr.as_ref(); @@ -1043,7 +1043,7 @@ impl Engine { let mut arg_values: StaticVec<_>; let mut args: StaticVec<_>; let mut is_ref = false; - let capture = if cfg!(not(feature = "no_closure")) && capture && !scope.is_empty() { + let capture = if capture_scope && !scope.is_empty() { Some(scope.clone_visible()) } else { None diff --git a/src/module/resolvers/stat.rs b/src/module/resolvers/stat.rs index be556d56..b530202b 100644 --- a/src/module/resolvers/stat.rs +++ b/src/module/resolvers/stat.rs @@ -16,7 +16,7 @@ use crate::stdlib::{boxed::Box, collections::HashMap, ops::AddAssign, string::St /// let mut resolver = StaticModuleResolver::new(); /// /// let module = Module::new(); -/// resolver.insert("hello".to_string(), module); +/// resolver.insert("hello", module); /// /// let mut engine = Engine::new(); /// diff --git a/src/parser.rs b/src/parser.rs index b6a57086..50eb0454 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1998,7 +1998,6 @@ fn parse_primary( root_expr = match (root_expr, token) { // Qualified function call with ! - #[cfg(not(feature = "no_closure"))] (Expr::Variable(x), Token::Bang) if x.1.is_some() => { return Err(if !match_token(input, Token::LeftParen).0 { LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(token_pos) @@ -2008,7 +2007,6 @@ fn parse_primary( }); } // Function call with ! - #[cfg(not(feature = "no_closure"))] (Expr::Variable(x), Token::Bang) => { let (matched, pos) = match_token(input, Token::LeftParen); if !matched { diff --git a/tests/functions.rs b/tests/functions.rs index 062eb1ae..e04f7ae3 100644 --- a/tests/functions.rs +++ b/tests/functions.rs @@ -158,13 +158,13 @@ fn test_function_captures() -> Result<(), Box> { *engine .compile( r#" - fn foo() { this += x; } + fn foo() { this += x; } - let x = 41; - let y = 999; + let x = 41; + let y = 999; - y.foo!(); - "# + y.foo!(); + "# ) .expect_err("should error") .0,