diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a9aa87d..72776dd3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,11 @@ Net features * A compact script compresses better than one with liberal whitespaces and comments. * Unlike some uglifiers or minifiers, `Engine::compact_script` does not optimize the script in any way, nor does it rename variables. +### Enhanced array API + +* Array methods that take a function pointer, usually a closure (e.g. `map`, `filter`, `index_of` etc.), can now provide a closure with one few parameter but binds the first parameter to `this`. +* This vastly improves performance when working with arrays of object maps by avoiding unnecessary cloning of large types. + Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index d56384f9..7cedfea8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,45 +35,87 @@ unicode-xid = { version = "0.2", default-features = false, optional = true } rust_decimal = { version = "1.16", default-features = false, features = ["maths"], optional = true } getrandom = { version = "0.2", optional = true } rustyline = { version = "10", optional = true } +document-features = { version = "0.2", optional = true } [dev-dependencies] rmp-serde = "1.1" serde_json = { version = "1.0", default-features = false, features = ["alloc"] } [features] + +## Default features: `std`, uses runtime random numbers for hashing. default = ["std", "ahash/runtime-rng"] # ahash/runtime-rng trumps ahash/compile-time-rng +## Standard features: uses compile-time random number for hashing. std = ["ahash/std", "num-traits/std", "smartstring/std"] -unchecked = [] # disable safety checks -sync = [] # restrict to only types that implement Send + Sync -no_position = [] # do not track position in the parser -no_optimize = [] # no script optimizer -no_float = [] # no floating-point -f32_float = [] # set FLOAT=f32 -only_i32 = [] # set INT=i32 (useful for 32-bit systems) -only_i64 = [] # set INT=i64 (default) and disable support for all other integer types -decimal = ["rust_decimal"] # add the Decimal number type -no_index = [] # no arrays and indexing -no_object = [] # no custom objects -no_time = [] # no timestamps -no_function = ["no_closure"] # no script-defined functions (meaning no closures) -no_closure = [] # no automatic sharing and capture of anonymous functions to external variables -no_module = [] # no modules -no_custom_syntax = [] # no custom syntax or custom operators -unicode-xid-ident = ["unicode-xid"] # allow Unicode Standard Annex #31 for identifiers. -metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] # enable exporting functions metadata -internals = [] # expose internal data structures -debugging = ["internals"] # enable debugging -serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types -# compiling for no-std -no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"] +#! ### Enable Special Functionalities -# compiling for WASM -wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] -stdweb = ["getrandom/js", "instant/stdweb"] + ## Require that all data types implement `Send + Sync` (for multi-threaded usage). + sync = [] + ## Add support for the [`Decimal`](https://crates.io/crates/rust_decimal) data type (acts as the system floating-point type under `no_float`). + decimal = ["rust_decimal"] + ## Enable serialization/deserialization of Rhai data types via [`serde`](https://crates.io/crates/serde). + serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] + ## Allow [Unicode Standard Annex #31](https://unicode.org/reports/tr31/) for identifiers. + unicode-xid-ident = ["unicode-xid"] + ## Enable functions metadata (including doc-comments); implies [`serde`](#feature-serde). + metadata = ["serde", "serde_json", "rhai_codegen/metadata", "smartstring/serde"] + ## Expose internal data structures (e.g. `AST` nodes). + internals = [] + ## Enable the debugging interface (implies [`internals`](#feature-internals)). + debugging = ["internals"] + ## Features and dependencies required by `bin` tools: `decimal`, `metadata`, `serde`, `debugging` and [`rustyline`](https://crates.io/crates/rustyline). + bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"] -# compiling bin tools -bin-features = ["decimal", "metadata", "serde", "debugging", "rustyline"] +#! ### System Configuration Features + + ## Use `f32` instead of `f64` as the system floating-point number type. + f32_float = [] + ## Use `i32` instead of `i64` for the system integer number type (useful for 32-bit architectures). + ## All other integer types (e.g. `u8`) are disabled. + only_i32 = [] + ## Disable all integer types (e.g. `u8`) other than `i64`. + only_i64 = [] + +#! ### Disable Language Features + + ## Remove support for floating-point numbers. + no_float = [] + ## Remove support for arrays and indexing. + no_index = [] + ## Remove support for custom types, properties, method-style calls and object maps. + no_object = [] + ## Remove support for time-stamps. + no_time = [] + ## Remove support for script-defined functions (implies [`no_closure`](#feature-no_closure)). + no_function = ["no_closure"] + ## Remove support for capturing external variables in anonymous functions (i.e. closures). + no_closure = [] + ## Remove support for loading external modules. + no_module = [] + ## Remove support for custom syntax. + no_custom_syntax = [] + +#! ### Performance-Related Features + + ## Disable all safety checks. + unchecked = [] + ## Do not track position when parsing. + no_position = [] + ## Disable the script optimizer. + no_optimize = [] + +#! ### Compiling for `no-std` + + ## Turn on `no-std` compilation (nightly only). + no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "hashbrown", "no_time"] + +#! ### JavaScript Interface for WASM + + ## Use [`wasm-bindgen`](https://crates.io/crates/wasm-bindgen) as JavaScript interface. + wasm-bindgen = ["getrandom/js", "instant/wasm-bindgen"] + ## Use [`stdweb`](https://crates.io/crates/stdweb) as JavaScript interface. + stdweb = ["getrandom/js", "instant/stdweb"] [[bin]] name = "rhai-repl" @@ -104,7 +146,7 @@ codegen-units = 1 instant = { version = "0.1.10" } # WASM implementation of std::time::Instant [package.metadata.docs.rs] -features = ["metadata", "serde", "internals", "decimal", "debugging"] +features = ["document-features", "metadata", "serde", "internals", "decimal", "debugging"] [patch.crates-io] # Notice that a custom modified version of `rustyline` is used which supports bracketed paste on Windows. diff --git a/codegen/src/lib.rs b/codegen/src/lib.rs index 22291c4f..aa4af270 100644 --- a/codegen/src/lib.rs +++ b/codegen/src/lib.rs @@ -126,7 +126,10 @@ pub fn export_fn( args: proc_macro::TokenStream, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { - let mut output = proc_macro2::TokenStream::from(input.clone()); + let mut output = quote! { + #[allow(clippy::needless_pass_by_value)] + }; + output.extend(proc_macro2::TokenStream::from(input.clone())); let parsed_params = match crate::attrs::outer_item_attributes(args.into(), "export_fn") { Ok(args) => args, diff --git a/codegen/src/module.rs b/codegen/src/module.rs index ea577f5f..32ac8f3b 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -300,6 +300,7 @@ impl Module { // Regenerate the module with the new content added. Ok(quote! { #(#mod_attrs)* + #[allow(clippy::needless_pass_by_value)] #mod_vis mod #mod_name { #(#orig_content)* #(#inner_modules)* @@ -310,6 +311,7 @@ impl Module { // Regenerate the original module as-is. Ok(quote! { #(#mod_attrs)* + #[allow(clippy::needless_pass_by_value)] #mod_vis mod #mod_name { #(#orig_content)* } diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index 42b18d2b..e0060c50 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -328,7 +328,7 @@ pub fn check_rename_collisions(fns: &[ExportedFn]) -> Result<(), syn::Error> { for (name, fn_name) in names { let current_span = item_fn.params().span.unwrap(); - let key = make_key(&name, item_fn); + let key = make_key(name, item_fn); if let Some(other_span) = renames.insert(key, current_span) { let mut err = syn::Error::new( current_span, diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index f333e23b..0f0bc9dd 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -320,6 +320,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod empty { #[allow(unused_imports)] use super::*; @@ -354,6 +355,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn get_mystic_number() -> INT { 42 @@ -432,6 +434,7 @@ mod generate_tests { */ /// Another line! /// Final line! + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { /// This is a doc-comment. /// Another line. @@ -502,6 +505,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_global_fn { pub fn add_one_to(x: INT) -> INT { x + 1 @@ -561,6 +565,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn add_one_to(x: INT) -> INT { x + 1 @@ -626,6 +631,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod two_fns { pub fn add_one_to(x: INT) -> INT { x + 1 @@ -714,6 +720,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn add_together(x: INT, y: INT) -> INT { x + y @@ -775,6 +782,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn add_together(x: INT, y: INT) -> INT { x + y @@ -848,6 +856,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_constant { #[derive(Debug, Clone)] pub struct Foo(pub INT); @@ -915,6 +924,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_constant { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] @@ -950,6 +960,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_constant { pub use rhai::INT; pub const MYSTIC_NUMBER: INT = 42; @@ -987,6 +998,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { fn get_mystic_number() -> INT { 42 @@ -1025,6 +1037,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn get_mystic_number() -> INT { 42 @@ -1066,10 +1079,12 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn get_mystic_number() -> INT { 42 } + #[allow(clippy::needless_pass_by_value)] pub mod inner_secrets { pub const SECRET_NUMBER: INT = 86; } @@ -1125,6 +1140,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_constant { const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] @@ -1160,6 +1176,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod str_fn { pub fn print_out_to(x: &str) { x + 1 @@ -1219,6 +1236,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod str_fn { pub fn print_out_to(x: String) { x + 1 @@ -1279,6 +1297,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod ref_fn { pub fn foo(x: &mut FLOAT, y: INT) -> FLOAT { *x + y as FLOAT @@ -1339,6 +1358,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod ref_fn { pub fn increment(x: &mut FLOAT) { *x += 1.0 as FLOAT; @@ -1400,7 +1420,9 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { + #[allow(clippy::needless_pass_by_value)] pub mod it_is { pub fn increment(x: &mut FLOAT) { *x += 1.0 as FLOAT; @@ -1483,9 +1505,10 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { #[cfg(not(feature = "no_float"))] - + #[allow(clippy::needless_pass_by_value)] pub mod it_is { pub fn increment(x: &mut FLOAT) { *x += 1.0 as FLOAT; @@ -1572,6 +1595,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn int_foo(x: &mut u64) -> u64 { (*x) * (*x) @@ -1632,6 +1656,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn int_foo(x: &mut u64) -> u64 { (*x) * (*x) @@ -1695,6 +1720,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn int_foo(x: &mut u64, y: u64) { *x = y * y @@ -1756,6 +1782,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_fn { pub fn int_foo(x: &mut u64, y: u64) { *x = y * y @@ -1820,6 +1847,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_index_fn { pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT { x.get(i) @@ -1883,6 +1911,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_index_fn { #[cfg(hello)] #[some_other_attr] @@ -1950,6 +1979,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_index_fn { pub fn get_by_index(x: &mut MyCollection, i: u64) -> FLOAT { x.get(i) @@ -2014,6 +2044,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_index_fn { pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) { x.entry(i).set(item) @@ -2076,6 +2107,7 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_index_fn { pub fn set_by_index(x: &mut MyCollection, i: u64, item: FLOAT) { x.entry(i).set(item) @@ -2140,7 +2172,9 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod one_constant { + #[allow(clippy::needless_pass_by_value)] pub mod it_is { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] @@ -2201,7 +2235,9 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod two_constants { + #[allow(clippy::needless_pass_by_value)] pub mod first_is { pub const MYSTIC_NUMBER: INT = 42; #[allow(unused_imports)] @@ -2221,6 +2257,7 @@ mod generate_tests { if flatten {} else {} } } + #[allow(clippy::needless_pass_by_value)] pub mod second_is { #[cfg(hello)] pub const SPECIAL_CPU_NUMBER: INT = 68000; @@ -2303,12 +2340,16 @@ mod generate_tests { }; let expected_tokens = quote! { + #[allow(clippy::needless_pass_by_value)] pub mod heap_root { pub const VALUE: INT = 100; + #[allow(clippy::needless_pass_by_value)] pub mod left { pub const VALUE: INT = 19; + #[allow(clippy::needless_pass_by_value)] pub mod left { pub const VALUE: INT = 17; + #[allow(clippy::needless_pass_by_value)] pub mod left { pub const VALUE: INT = 2; #[allow(unused_imports)] @@ -2328,6 +2369,7 @@ mod generate_tests { if flatten {} else {} } } + #[allow(clippy::needless_pass_by_value)] pub mod right { pub const VALUE: INT = 7; #[allow(unused_imports)] @@ -2371,6 +2413,7 @@ mod generate_tests { } } } + #[allow(clippy::needless_pass_by_value)] pub mod right { pub const VALUE: INT = 3; #[allow(unused_imports)] @@ -2414,8 +2457,10 @@ mod generate_tests { } } } + #[allow(clippy::needless_pass_by_value)] pub mod right { pub const VALUE: INT = 36; + #[allow(clippy::needless_pass_by_value)] pub mod left { pub const VALUE: INT = 25; #[allow(unused_imports)] @@ -2435,6 +2480,7 @@ mod generate_tests { if flatten {} else {} } } + #[allow(clippy::needless_pass_by_value)] pub mod right { pub const VALUE: INT = 1; #[allow(unused_imports)] diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index 28f0a405..07ea0c99 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -273,7 +273,7 @@ impl Engine { if self.is_debugger_registered() { global.debugger_mut().status = crate::eval::DebuggerStatus::Terminate; let node = &crate::ast::Stmt::Noop(Position::NONE); - self.run_debugger(global, caches, scope, this_ptr.as_deref_mut(), node)?; + self.run_debugger(global, caches, scope, this_ptr, node)?; } #[cfg(not(feature = "no_module"))] diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index e4370ca0..4dd7d703 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -674,7 +674,7 @@ pub mod deprecated_array_functions { #[rhai_fn(name = "map", return_raw)] pub fn map_by_fn_name( ctx: NativeCallContext, - array: Array, + array: &mut Array, mapper: &str, ) -> RhaiResultOf { map(ctx, array, FnPtr::new(mapper)?) @@ -712,7 +712,7 @@ pub mod deprecated_array_functions { #[rhai_fn(name = "filter", return_raw)] pub fn filter_by_fn_name( ctx: NativeCallContext, - array: Array, + array: &mut Array, filter_func: &str, ) -> RhaiResultOf { filter(ctx, array, FnPtr::new(filter_func)?) diff --git a/src/api/files.rs b/src/api/files.rs index b2a2ffd1..2025b412 100644 --- a/src/api/files.rs +++ b/src/api/files.rs @@ -107,7 +107,7 @@ impl Engine { #[inline] pub fn compile_file_with_scope(&self, scope: &Scope, path: PathBuf) -> RhaiResultOf { Self::read_file(&path).and_then(|contents| { - let mut ast = self.compile_with_scope(scope, &contents)?; + let mut ast = self.compile_with_scope(scope, contents)?; ast.set_source(path.to_string_lossy().as_ref()); Ok(ast) }) diff --git a/src/api/json.rs b/src/api/json.rs index dd6d1f42..b62c1fb9 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -162,7 +162,7 @@ pub fn format_map_as_json(map: &Map) -> String { result.push(','); } - write!(result, "{:?}", key).unwrap(); + write!(result, "{key:?}").unwrap(); result.push(':'); if let Some(val) = value.read_lock::() { @@ -170,7 +170,7 @@ pub fn format_map_as_json(map: &Map) -> String { } else if value.is_unit() { result.push_str("null"); } else { - write!(result, "{:?}", value).unwrap(); + write!(result, "{value:?}").unwrap(); } } diff --git a/src/api/mod.rs b/src/api/mod.rs index e59d7757..e9cc9c8a 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -207,13 +207,11 @@ impl Engine { /// Get the default value of the custom state for each evaluation run. #[inline(always)] - #[must_use] pub const fn default_tag(&self) -> &Dynamic { &self.def_tag } /// Get a mutable reference to the default value of the custom state for each evaluation run. #[inline(always)] - #[must_use] pub fn default_tag_mut(&mut self) -> &mut Dynamic { &mut self.def_tag } diff --git a/src/ast/expr.rs b/src/ast/expr.rs index a8376bd6..36299d83 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -117,7 +117,7 @@ impl fmt::Debug for FnCallHashes { return if script == self.native { fmt::Debug::fmt(&self.native, f) } else { - write!(f, "({}, {})", script, self.native) + write!(f, "({script}, {})", self.native) }; } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1e63aa4b..cfa3a943 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -22,8 +22,8 @@ pub use namespace_none::Namespace; #[cfg(not(feature = "no_function"))] pub use script_fn::{ScriptFnDef, ScriptFnMetadata}; pub use stmt::{ - CaseBlocksList, ConditionalExpr, OpAssignment, RangeCase, Stmt, StmtBlock, StmtBlockContainer, - SwitchCasesCollection, TryCatchBlock, + CaseBlocksList, ConditionalExpr, FlowControl, OpAssignment, RangeCase, Stmt, StmtBlock, + StmtBlockContainer, SwitchCasesCollection, }; /// _(internals)_ Placeholder for a script-defined function. diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index d3f11a46..af4ff064 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -71,16 +71,16 @@ impl OpAssignment { /// /// Panics if the token is not an op-assignment operator. #[must_use] - pub fn new_op_assignment_from_token(op: Token, pos: Position) -> Self { - let op_raw = op + pub fn new_op_assignment_from_token(op_assign: Token, pos: Position) -> Self { + let op = op_assign .get_base_op_from_assignment() .expect("op-assignment operator"); Self { - hash_op_assign: calc_fn_hash(None, op.literal_syntax(), 2), - hash_op: calc_fn_hash(None, op_raw.literal_syntax(), 2), - op_assign: op.clone(), - op: op_raw, + hash_op_assign: calc_fn_hash(None, op_assign.literal_syntax(), 2), + hash_op: calc_fn_hash(None, op.literal_syntax(), 2), + op_assign, + op, pos, } } @@ -292,18 +292,6 @@ pub struct SwitchCasesCollection { pub def_case: Option, } -/// _(internals)_ A `try-catch` block. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone, Hash)] -pub struct TryCatchBlock { - /// `try` block. - pub try_block: StmtBlock, - /// `catch` variable, if any. - pub catch_var: Ident, - /// `catch` block. - pub catch_block: StmtBlock, -} - /// Number of items to keep inline for [`StmtBlockContainer`]. #[cfg(not(feature = "no_std"))] const STMT_BLOCK_INLINE_SIZE: usize = 8; @@ -532,6 +520,22 @@ impl Extend for StmtBlock { } } +/// _(internals)_ A flow control block containing: +/// * an expression, +/// * a statements body +/// * an alternate statements body +/// +/// Exported under the `internals` feature only. +#[derive(Debug, Clone, Hash)] +pub struct FlowControl { + /// Flow control expression. + pub expr: Expr, + /// Main body. + pub body: StmtBlock, + /// Branch body. + pub branch: StmtBlock, +} + /// _(internals)_ A statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] @@ -540,7 +544,7 @@ pub enum Stmt { /// No-op. Noop(Position), /// `if` expr `{` stmt `}` `else` `{` stmt `}` - If(Box<(Expr, StmtBlock, StmtBlock)>, Position), + If(Box, Position), /// `switch` expr `{` literal or range or _ `if` condition `=>` stmt `,` ... `}` /// /// ### Data Structure @@ -552,16 +556,16 @@ pub enum Stmt { /// `while` expr `{` stmt `}` | `loop` `{` stmt `}` /// /// If the guard expression is [`UNIT`][Expr::Unit], then it is a `loop` statement. - While(Box<(Expr, StmtBlock)>, Position), + While(Box, Position), /// `do` `{` stmt `}` `while`|`until` expr /// /// ### Flags /// /// * [`NONE`][ASTFlags::NONE] = `while` /// * [`NEGATED`][ASTFlags::NEGATED] = `until` - Do(Box<(Expr, StmtBlock)>, ASTFlags, Position), + Do(Box, ASTFlags, Position), /// `for` `(` id `,` counter `)` `in` expr `{` stmt `}` - For(Box<(Ident, Ident, Expr, StmtBlock)>, Position), + For(Box<(Ident, Ident, FlowControl)>, Position), /// \[`export`\] `let`|`const` id `=` expr /// /// ### Flags @@ -579,7 +583,7 @@ pub enum Stmt { /// `{` stmt`;` ... `}` Block(Box), /// `try` `{` stmt; ... `}` `catch` `(` var `)` `{` stmt; ... `}` - TryCatch(Box, Position), + TryCatch(Box, Position), /// [expression][Expr] Expr(Box), /// `continue`/`break` expr @@ -815,7 +819,9 @@ impl Stmt { Self::Noop(..) => true, Self::Expr(expr) => expr.is_pure(), Self::If(x, ..) => { - x.0.is_pure() && x.1.iter().all(Self::is_pure) && x.2.iter().all(Self::is_pure) + x.expr.is_pure() + && x.body.iter().all(Self::is_pure) + && x.branch.iter().all(Self::is_pure) } Self::Switch(x, ..) => { let (expr, sw) = &**x; @@ -833,10 +839,10 @@ impl Stmt { } // Loops that exit can be pure because it can never be infinite. - Self::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => true, - Self::Do(x, options, ..) if matches!(x.0, Expr::BoolConstant(..)) => match x.0 { + Self::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => true, + Self::Do(x, options, ..) if matches!(x.expr, Expr::BoolConstant(..)) => match x.expr { Expr::BoolConstant(cond, ..) if cond == options.contains(ASTFlags::NEGATED) => { - x.1.iter().all(Self::is_pure) + x.body.iter().all(Self::is_pure) } _ => false, }, @@ -846,13 +852,15 @@ impl Stmt { // For loops can be pure because if the iterable is pure, it is finite, // so infinite loops can never occur. - Self::For(x, ..) => x.2.is_pure() && x.3.iter().all(Self::is_pure), + Self::For(x, ..) => x.2.expr.is_pure() && x.2.body.iter().all(Self::is_pure), Self::Var(..) | Self::Assignment(..) | Self::FnCall(..) => false, Self::Block(block, ..) => block.iter().all(Self::is_pure), Self::BreakLoop(..) | Self::Return(..) => false, Self::TryCatch(x, ..) => { - x.try_block.iter().all(Self::is_pure) && x.catch_block.iter().all(Self::is_pure) + x.expr.is_pure() + && x.body.iter().all(Self::is_pure) + && x.branch.iter().all(Self::is_pure) } #[cfg(not(feature = "no_module"))] @@ -947,15 +955,15 @@ impl Stmt { } } Self::If(x, ..) => { - if !x.0.walk(path, on_node) { + if !x.expr.walk(path, on_node) { return false; } - for s in &x.1 { + for s in &x.body { if !s.walk(path, on_node) { return false; } } - for s in &x.2 { + for s in &x.branch { if !s.walk(path, on_node) { return false; } @@ -996,20 +1004,20 @@ impl Stmt { } } Self::While(x, ..) | Self::Do(x, ..) => { - if !x.0.walk(path, on_node) { + if !x.expr.walk(path, on_node) { return false; } - for s in x.1.statements() { + for s in x.body.statements() { if !s.walk(path, on_node) { return false; } } } Self::For(x, ..) => { - if !x.2.walk(path, on_node) { + if !x.2.expr.walk(path, on_node) { return false; } - for s in &x.3 { + for s in &x.2.body { if !s.walk(path, on_node) { return false; } @@ -1038,12 +1046,12 @@ impl Stmt { } } Self::TryCatch(x, ..) => { - for s in &x.try_block { + for s in &x.body { if !s.walk(path, on_node) { return false; } } - for s in &x.catch_block { + for s in &x.branch { if !s.walk(path, on_node) { return false; } diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index c69ba77b..71d39a43 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -254,12 +254,13 @@ impl Engine { })?; let bit_value = (*value & (1 << bit)) != 0; + #[allow(clippy::cast_possible_truncation)] + let bit = bit as u8; Ok(Target::Bit { source: target, value: bit_value.into(), - #[allow(clippy::cast_possible_truncation)] - bit: bit as u8, + bit, }) } @@ -470,13 +471,8 @@ impl Engine { if chain_type == ChainType::Dotting && !x.is_qualified() => { for expr in &x.args { - let arg_value = self.get_arg_value( - global, - caches, - scope, - this_ptr.as_deref_mut(), - expr, - )?; + let tp = this_ptr.as_deref_mut(); + let arg_value = self.get_arg_value(global, caches, scope, tp, expr)?; _arg_values.push(arg_value.0.flatten()); } } @@ -528,7 +524,7 @@ impl Engine { &self, global: &mut GlobalRuntimeState, caches: &mut Caches, - mut this_ptr: Option<&mut Dynamic>, + this_ptr: Option<&mut Dynamic>, root: &Expr, parent: &Expr, target: &mut Target, @@ -539,6 +535,9 @@ impl Engine { let is_ref_mut = target.is_ref(); let op_pos = parent.position(); + #[cfg(feature = "debugging")] + let mut this_ptr = this_ptr; + #[cfg(feature = "debugging")] let scope = &mut Scope::new(); @@ -837,17 +836,13 @@ impl Engine { // {xxx:map}.sub_lhs[expr] | {xxx:map}.sub_lhs.expr Expr::Index(x, ..) | Expr::Dot(x, ..) if target.is_map() => { let _node = &x.lhs; + let mut _this_ptr = this_ptr; + let _tp = _this_ptr.as_deref_mut(); let val_target = &mut match x.lhs { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger( - global, - caches, - scope, - this_ptr.as_deref_mut(), - _node, - )?; + self.run_debugger(global, caches, scope, _tp, _node)?; let index = &mut p.2.clone().into(); self.get_indexed_mut( @@ -857,13 +852,8 @@ impl Engine { // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr Expr::MethodCall(ref x, pos) if !x.is_qualified() => { #[cfg(feature = "debugging")] - let reset = self.run_debugger_with_reset( - global, - caches, - scope, - this_ptr.as_deref_mut(), - _node, - )?; + let reset = self + .run_debugger_with_reset(global, caches, scope, _tp, _node)?; #[cfg(feature = "debugging")] auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); @@ -892,25 +882,21 @@ impl Engine { }; self.eval_dot_index_chain_raw( - global, caches, this_ptr, root, rhs, val_target, &x.rhs, idx_values, + global, caches, _this_ptr, root, rhs, val_target, &x.rhs, idx_values, new_val, ) } // xxx.sub_lhs[expr] | xxx.sub_lhs.expr Expr::Index(x, ..) | Expr::Dot(x, ..) => { let _node = &x.lhs; + let mut _this_ptr = this_ptr; + let _tp = _this_ptr.as_deref_mut(); match x.lhs { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger( - global, - caches, - scope, - this_ptr.as_deref_mut(), - _node, - )?; + self.run_debugger(global, caches, scope, _tp, _node)?; let ((getter, hash_get), (setter, hash_set), name) = &**p; let args = &mut [target.as_mut()]; @@ -942,7 +928,7 @@ impl Engine { let val = &mut (&mut val).into(); let (result, may_be_changed) = self.eval_dot_index_chain_raw( - global, caches, this_ptr, root, rhs, val, &x.rhs, idx_values, + global, caches, _this_ptr, root, rhs, val, &x.rhs, idx_values, new_val, )?; @@ -986,11 +972,7 @@ impl Engine { let val = { #[cfg(feature = "debugging")] let reset = self.run_debugger_with_reset( - global, - caches, - scope, - this_ptr.as_deref_mut(), - _node, + global, caches, scope, _tp, _node, )?; #[cfg(feature = "debugging")] auto_restore!(global if Some(reset) => move |g| g.debugger_mut().reset_status(reset)); @@ -1014,7 +996,7 @@ impl Engine { let val = &mut val.into(); self.eval_dot_index_chain_raw( - global, caches, this_ptr, root, rhs, val, &x.rhs, idx_values, + global, caches, _this_ptr, root, rhs, val, &x.rhs, idx_values, new_val, ) } diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index e852e46e..1bc1a5a5 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -385,13 +385,11 @@ impl Debugger { } /// Get the custom state. #[inline(always)] - #[must_use] pub const fn state(&self) -> &Dynamic { &self.state } /// Get a mutable reference to the custom state. #[inline(always)] - #[must_use] pub fn state_mut(&mut self) -> &mut Dynamic { &mut self.state } diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 66c4539f..9f4350bc 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -72,13 +72,11 @@ impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { } /// Custom state kept in a [`Dynamic`]. #[inline(always)] - #[must_use] pub const fn tag(&self) -> &Dynamic { &self.global.tag } /// Mutable reference to the custom state kept in a [`Dynamic`]. #[inline(always)] - #[must_use] pub fn tag_mut(&mut self) -> &mut Dynamic { &mut self.global.tag } diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 4cd202f5..69e08c5e 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -317,6 +317,7 @@ impl GlobalRuntimeState { /// /// Panics if the debugging interface is not set. #[cfg(feature = "debugging")] + #[must_use] pub fn debugger(&self) -> &super::Debugger { self.debugger.as_ref().unwrap() } @@ -326,6 +327,7 @@ impl GlobalRuntimeState { /// /// Panics if the debugging interface is not set. #[cfg(feature = "debugging")] + #[must_use] pub fn debugger_mut(&mut self) -> &mut super::Debugger { self.debugger.as_deref_mut().unwrap() } diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 7ef26bf4..21fa73b3 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -3,7 +3,7 @@ use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::api::events::VarDefInfo; use crate::ast::{ - ASTFlags, BinaryExpr, Expr, OpAssignment, Stmt, SwitchCasesCollection, TryCatchBlock, + ASTFlags, BinaryExpr, Expr, FlowControl, OpAssignment, Stmt, SwitchCasesCollection, }; use crate::func::{get_builtin_op_assignment_fn, get_hasher}; use crate::types::dynamic::{AccessMode, Union}; @@ -310,7 +310,11 @@ impl Engine { // If statement Stmt::If(x, ..) => { - let (expr, if_block, else_block) = &**x; + let FlowControl { + expr, + body: if_block, + branch: else_block, + } = &**x; let guard_val = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? @@ -402,8 +406,10 @@ impl Engine { } // Loop - Stmt::While(x, ..) if matches!(x.0, Expr::Unit(..) | Expr::BoolConstant(true, ..)) => { - let (.., body) = &**x; + Stmt::While(x, ..) + if matches!(x.expr, Expr::Unit(..) | Expr::BoolConstant(true, ..)) => + { + let FlowControl { body, .. } = &**x; if body.is_empty() { loop { @@ -427,7 +433,7 @@ impl Engine { // While loop Stmt::While(x, ..) => { - let (expr, body) = &**x; + let FlowControl { expr, body, .. } = &**x; loop { let condition = self @@ -458,7 +464,7 @@ impl Engine { // Do loop Stmt::Do(x, options, ..) => { - let (expr, body) = &**x; + let FlowControl { expr, body, .. } = &**x; let is_while = !options.contains(ASTFlags::NEGATED); loop { @@ -488,7 +494,7 @@ impl Engine { // For loop Stmt::For(x, ..) => { - let (var_name, counter, expr, statements) = &**x; + let (var_name, counter, FlowControl { expr, body, .. }) = &**x; let iter_obj = self .eval_expr(global, caches, scope, this_ptr.as_deref_mut(), expr)? @@ -566,15 +572,15 @@ impl Engine { *scope.get_mut_by_index(index).write_lock().unwrap() = value; // Run block - self.track_operation(global, statements.position())?; + self.track_operation(global, body.position())?; - if statements.is_empty() { + if body.is_empty() { continue; } let this_ptr = this_ptr.as_deref_mut(); - match self.eval_stmt_block(global, caches, scope, this_ptr, statements, true) { + match self.eval_stmt_block(global, caches, scope, this_ptr, body, true) { Ok(_) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => (), @@ -605,10 +611,10 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x, ..) => { - let TryCatchBlock { - try_block, - catch_var, - catch_block, + let FlowControl { + body: try_block, + expr: catch_var, + branch: catch_block, } = &**x; match self.eval_stmt_block( @@ -659,10 +665,10 @@ impl Engine { }; // Restore scope at end of block - auto_restore! { scope if !catch_var.is_empty() => rewind; let orig_scope_len = scope.len(); } + auto_restore! { scope if !catch_var.is_unit() => rewind; let orig_scope_len = scope.len(); } - if !catch_var.is_empty() { - scope.push(catch_var.name.clone(), err_value); + if let Expr::Variable(x, ..) = catch_var { + scope.push(x.3.clone(), err_value); } let this_ptr = this_ptr.as_deref_mut(); diff --git a/src/eval/target.rs b/src/eval/target.rs index e1980d1b..6679d614 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -235,7 +235,6 @@ impl<'a> Target<'a> { /// Get the source [`Dynamic`] of the [`Target`]. #[allow(dead_code)] #[inline] - #[must_use] pub fn source(&self) -> &Dynamic { match self { Self::RefMut(r) => r, @@ -399,7 +398,6 @@ impl Deref for Target<'_> { impl AsRef for Target<'_> { #[inline(always)] - #[must_use] fn as_ref(&self) -> &Dynamic { self } @@ -407,7 +405,6 @@ impl AsRef for Target<'_> { impl Borrow for Target<'_> { #[inline(always)] - #[must_use] fn borrow(&self) -> &Dynamic { self } @@ -432,7 +429,6 @@ impl DerefMut for Target<'_> { impl AsMut for Target<'_> { #[inline(always)] - #[must_use] fn as_mut(&mut self) -> &mut Dynamic { self } diff --git a/src/func/builtin.rs b/src/func/builtin.rs index ad15da11..d27a39e7 100644 --- a/src/func/builtin.rs +++ b/src/func/builtin.rs @@ -71,11 +71,13 @@ fn is_numeric(type_id: TypeId) -> bool { /// A function that returns `true`. #[inline(always)] +#[allow(clippy::unnecessary_wraps)] fn const_true_fn(_: Option, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::TRUE) } /// A function that returns `false`. #[inline(always)] +#[allow(clippy::unnecessary_wraps)] fn const_false_fn(_: Option, _: &mut [&mut Dynamic]) -> RhaiResult { Ok(Dynamic::FALSE) } diff --git a/src/func/call.rs b/src/func/call.rs index 3724e2d6..42452d47 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -182,7 +182,7 @@ impl Engine { match cache.map.entry(hash) { Entry::Occupied(entry) => entry.into_mut().as_ref(), Entry::Vacant(entry) => { - let num_args = args.as_deref().map_or(0, |a| a.len()); + let num_args = args.as_deref().map_or(0, FnCallArgs::len); let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. // Set later when a specific matching function is not found. let mut bitmask = 1usize; // Bitmask of which parameter to replace with `Dynamic` @@ -1167,12 +1167,12 @@ impl Engine { .as_int() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; - return Ok(if !(0..=crate::MAX_USIZE_INT).contains(&num_params) { - false - } else { - #[allow(clippy::cast_sign_loss)] + return Ok(if (0..=crate::MAX_USIZE_INT).contains(&num_params) { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let hash_script = calc_fn_hash(None, &fn_name, num_params as usize); self.has_script_fn(global, caches, hash_script) + } else { + false } .into()); } @@ -1588,7 +1588,7 @@ impl Engine { .0 .flatten(); - return value.as_bool().and_then(|r| Ok((!r).into())).or_else(|_| { + return value.as_bool().map(|r| (!r).into()).or_else(|_| { let operand = &mut [&mut value]; self.exec_fn_call( global, caches, None, name, op_token, *hashes, operand, false, false, pos, diff --git a/src/func/native.rs b/src/func/native.rs index ed4c5117..7f0aba59 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -201,7 +201,7 @@ impl<'a> NativeCallContext<'a> { pub fn store_data(&self) -> NativeCallContextStore { NativeCallContextStore { fn_name: self.fn_name.to_string(), - source: self.source.map(|s| s.to_string()), + source: self.source.map(ToString::to_string), global: self.global.clone(), pos: self.pos, } diff --git a/src/func/register.rs b/src/func/register.rs index ee309f47..de5d835c 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -39,7 +39,6 @@ pub struct Mut(T); /// Dereference into [`DynamicWriteLock`] #[inline(always)] -#[must_use] pub fn by_ref(data: &mut Dynamic) -> DynamicWriteLock { // Directly cast the &mut Dynamic into DynamicWriteLock to access the underlying data. data.write_lock::().expect("checked") diff --git a/src/func/script.rs b/src/func/script.rs index 06be7d54..b864b4e8 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -134,7 +134,6 @@ impl Engine { fn_def.name.to_string(), #[cfg(not(feature = "no_module"))] _environ - .as_deref() .and_then(|environ| environ.lib.id()) .unwrap_or_else(|| global.source().unwrap_or("")) .to_string(), diff --git a/src/lib.rs b/src/lib.rs index 6368156a..b6be66eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -52,7 +52,11 @@ //! } //! ``` //! -//! # Documentation +//! # Features +//! +#![cfg_attr(feature = "document-features", doc = document_features::document_features!(feature_label = "**`{feature}`**"))] +//! +//! # On-Line Documentation //! //! See [The Rhai Book](https://rhai.rs/book) for details on the Rhai scripting engine and language. @@ -61,7 +65,7 @@ // #![warn(clippy::all)] // #![warn(clippy::pedantic)] // #![warn(clippy::nursery)] -// #![warn(clippy::cargo)] +#![warn(clippy::cargo)] // #![warn(clippy::undocumented_unsafe_blocks)] #![allow(clippy::unit_arg)] #![allow(clippy::missing_errors_doc)] @@ -74,6 +78,13 @@ #![allow(clippy::box_collection)] #![allow(clippy::too_many_arguments)] #![allow(clippy::upper_case_acronyms)] +#![allow(clippy::match_same_arms)] +// The lints below can be turned off to reduce signal/noise ratio +// #![allow(clippy::too_many_lines)] +// #![allow(clippy::let_underscore_drop)] +// #![allow(clippy::absurd_extreme_comparisons)] +// #![allow(clippy::unnecessary_cast)] +// #![allow(clippy::wildcard_imports)] #[cfg(feature = "no_std")] extern crate alloc; @@ -325,8 +336,8 @@ pub use parser::ParseState; #[cfg(feature = "internals")] pub use ast::{ - ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident, - OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, TryCatchBlock, + ASTFlags, ASTNode, BinaryExpr, ConditionalExpr, Expr, FlowControl, FnCallExpr, FnCallHashes, + Ident, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, SwitchCasesCollection, }; #[cfg(feature = "internals")] diff --git a/src/module/mod.rs b/src/module/mod.rs index 082c4493..f0f3716a 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1962,7 +1962,7 @@ impl Module { #[inline] #[allow(dead_code)] pub(crate) fn iter_fn(&self) -> impl Iterator { - self.functions.iter().flat_map(|m| m.values()) + self.functions.iter().flat_map(StraightHashMap::values) } /// Get an iterator over all script-defined functions in the [`Module`]. @@ -2187,9 +2187,11 @@ impl Module { let f = module.functions.as_mut().unwrap().get_mut(&hash).unwrap(); // Encapsulate AST environment - match &mut f.func { - CallableFunction::Script { environ: e, .. } => *e = Some(environ.clone()), - _ => (), + if let CallableFunction::Script { + environ: ref mut e, .. + } = f.func + { + *e = Some(environ.clone()) } }); diff --git a/src/optimizer.rs b/src/optimizer.rs index 6bf6f280..89d66e47 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -2,7 +2,8 @@ #![cfg(not(feature = "no_optimize"))] use crate::ast::{ - ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCasesCollection, + ASTFlags, Expr, FlowControl, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, + SwitchCasesCollection, }; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::eval::{Caches, GlobalRuntimeState}; @@ -430,8 +431,8 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // if expr {} - Stmt::If(x, ..) if x.1.is_empty() && x.2.is_empty() => { - let condition = &mut x.0; + Stmt::If(x, ..) if x.body.is_empty() && x.branch.is_empty() => { + let condition = &mut x.expr; state.set_dirty(); let pos = condition.start_position(); @@ -452,8 +453,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b }; } // if false { if_block } -> Noop - Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) && x.2.is_empty() => { - if let Expr::BoolConstant(false, pos) = x.0 { + Stmt::If(x, ..) + if matches!(x.expr, Expr::BoolConstant(false, ..)) && x.branch.is_empty() => + { + if let Expr::BoolConstant(false, pos) = x.expr { state.set_dirty(); *stmt = Stmt::Noop(pos); } else { @@ -461,33 +464,31 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } // if false { if_block } else { else_block } -> else_block - Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => { + Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => { state.set_dirty(); - *stmt = - match optimize_stmt_block(mem::take(&mut *x.2), state, preserve_result, true, false) - { - statements if statements.is_empty() => Stmt::Noop(x.2.position()), - statements => (statements, x.2.span()).into(), - } + let body = mem::take(&mut *x.branch); + *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { + statements if statements.is_empty() => Stmt::Noop(x.branch.position()), + statements => (statements, x.branch.span()).into(), + } } // if true { if_block } else { else_block } -> if_block - Stmt::If(x, ..) if matches!(x.0, Expr::BoolConstant(true, ..)) => { + Stmt::If(x, ..) if matches!(x.expr, Expr::BoolConstant(true, ..)) => { state.set_dirty(); - *stmt = - match optimize_stmt_block(mem::take(&mut *x.1), state, preserve_result, true, false) - { - statements if statements.is_empty() => Stmt::Noop(x.1.position()), - statements => (statements, x.1.span()).into(), - } + let body = mem::take(&mut *x.body); + *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { + statements if statements.is_empty() => Stmt::Noop(x.body.position()), + statements => (statements, x.body.span()).into(), + } } // if expr { if_block } else { else_block } Stmt::If(x, ..) => { - let (condition, body, other) = &mut **x; - optimize_expr(condition, state, false); - **body = - optimize_stmt_block(mem::take(&mut **body), state, preserve_result, true, false); - **other = - optimize_stmt_block(mem::take(&mut **other), state, preserve_result, true, false); + let FlowControl { expr, body, branch } = &mut **x; + optimize_expr(expr, state, false); + let statements = mem::take(&mut **body); + **body = optimize_stmt_block(statements, state, preserve_result, true, false); + let statements = mem::take(&mut **branch); + **branch = optimize_stmt_block(statements, state, preserve_result, true, false); } // switch const { ... } @@ -524,7 +525,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } optimize_expr(&mut b.condition, state, false); - let else_stmt = match def_case { + let branch = match def_case { Some(index) => { let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); @@ -533,14 +534,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } _ => StmtBlock::NONE, }; + let body = Stmt::Expr(mem::take(&mut b.expr).into()).into(); + let expr = mem::take(&mut b.condition); *stmt = Stmt::If( - ( - mem::take(&mut b.condition), - Stmt::Expr(mem::take(&mut b.expr).into()).into(), - else_stmt, - ) - .into(), + FlowControl { expr, body, branch }.into(), match_expr.start_position(), ); } @@ -585,12 +583,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_stmt(&mut statements, state, true); *stmt = statements; } else { - let mut condition = mem::take(&mut range_block.condition); + let mut expr = mem::take(&mut range_block.condition); // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } - optimize_expr(&mut condition, state, false); + optimize_expr(&mut expr, state, false); - let else_stmt = match def_case { + let branch = match def_case { Some(index) => { let mut def_stmt = Stmt::Expr(mem::take(&mut expressions[*index].expr).into()); @@ -600,12 +598,12 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b _ => StmtBlock::NONE, }; - let if_stmt = + let body = Stmt::Expr(mem::take(&mut expressions[r.index()].expr).into()) .into(); *stmt = Stmt::If( - (condition, if_stmt, else_stmt).into(), + FlowControl { expr, body, branch }.into(), match_expr.start_position(), ); } @@ -738,7 +736,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } // while false { block } -> Noop - Stmt::While(x, ..) if matches!(x.0, Expr::BoolConstant(false, ..)) => match x.0 { + Stmt::While(x, ..) if matches!(x.expr, Expr::BoolConstant(false, ..)) => match x.expr { Expr::BoolConstant(false, pos) => { state.set_dirty(); *stmt = Stmt::Noop(pos); @@ -747,22 +745,22 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b }, // while expr { block } Stmt::While(x, ..) => { - let (condition, body) = &mut **x; - optimize_expr(condition, state, false); - if let Expr::BoolConstant(true, pos) = condition { - *condition = Expr::Unit(*pos); + let FlowControl { expr, body, .. } = &mut **x; + optimize_expr(expr, state, false); + if let Expr::BoolConstant(true, pos) = expr { + *expr = Expr::Unit(*pos); } **body = optimize_stmt_block(mem::take(&mut **body), state, false, true, false); } // do { block } while|until expr Stmt::Do(x, ..) => { - optimize_expr(&mut x.0, state, false); - *x.1 = optimize_stmt_block(mem::take(&mut *x.1), state, false, true, false); + optimize_expr(&mut x.expr, state, false); + *x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false); } // for id in expr { block } Stmt::For(x, ..) => { - optimize_expr(&mut x.2, state, false); - *x.3 = optimize_stmt_block(mem::take(&mut *x.3), state, false, true, false); + optimize_expr(&mut x.2.expr, state, false); + *x.2.body = optimize_stmt_block(mem::take(&mut *x.2.body), state, false, true, false); } // let id = expr; Stmt::Var(x, options, ..) if !options.contains(ASTFlags::CONSTANT) => { @@ -791,21 +789,19 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b } } // try { pure try_block } catch ( var ) { catch_block } -> try_block - Stmt::TryCatch(x, ..) if x.try_block.iter().all(Stmt::is_pure) => { + Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); *stmt = ( - optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false), - x.try_block.span(), + optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false), + x.body.span(), ) .into(); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, ..) => { - *x.try_block = - optimize_stmt_block(mem::take(&mut *x.try_block), state, false, true, false); - *x.catch_block = - optimize_stmt_block(mem::take(&mut *x.catch_block), state, false, true, false); + *x.body = optimize_stmt_block(mem::take(&mut *x.body), state, false, true, false); + *x.branch = optimize_stmt_block(mem::take(&mut *x.branch), state, false, true, false); } // expr(stmt) @@ -932,6 +928,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { } // lhs[rhs] #[cfg(not(feature = "no_index"))] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Expr::Index(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // array[int] (Expr::Array(a, pos), Expr::IntegerConstant(i, ..)) if *i >= 0 && *i <= crate::MAX_USIZE_INT && (*i as usize) < a.len() && a.iter().all(Expr::is_pure) => { @@ -1339,7 +1336,11 @@ impl Engine { let lib: crate::Shared<_> = { let mut module = crate::Module::new(); - if optimization_level != OptimizationLevel::None { + if optimization_level == OptimizationLevel::None { + for fn_def in functions { + module.set_script_fn(fn_def); + } + } else { // We only need the script library's signatures for optimization purposes let mut lib2 = crate::Module::new(); @@ -1365,10 +1366,6 @@ impl Engine { *fn_def.body = self.optimize_top_level(body, scope, lib2, optimization_level); - module.set_script_fn(fn_def); - } - } else { - for fn_def in functions { module.set_script_fn(fn_def); } } diff --git a/src/packages/arithmetic.rs b/src/packages/arithmetic.rs index 771982b7..5d8b14e2 100644 --- a/src/packages/arithmetic.rs +++ b/src/packages/arithmetic.rs @@ -276,6 +276,7 @@ gen_signed_functions!(signed_num_128 => i128); #[export_module] mod f32_functions { #[cfg(not(feature = "f32_float"))] + #[allow(clippy::cast_precision_loss)] pub mod basic_arithmetic { #[rhai_fn(name = "+")] pub fn add(x: f32, y: f32) -> f32 { @@ -381,6 +382,7 @@ mod f32_functions { "Number raised to too large an index: {x} ** {y}" ))) } else { + #[allow(clippy::cast_possible_truncation)] Ok(x.powi(y as i32)) } } diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index 501f2306..51af68a0 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -220,14 +220,18 @@ pub mod array_functions { len: INT, item: Dynamic, ) -> RhaiResultOf<()> { - let len = len.min(MAX_USIZE_INT); + if len <= 0 { + return Ok(()); + } - if len <= 0 || (len as usize) <= array.len() { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let len = len.min(MAX_USIZE_INT) as usize; + + if len <= array.len() { return Ok(()); } let _ctx = ctx; - let len = len as usize; // Check if array will be over max size limit #[cfg(not(feature = "unchecked"))] @@ -373,11 +377,16 @@ pub mod array_functions { /// print(x); // prints "[1, 2, 3]" /// ``` pub fn truncate(array: &mut Array, len: INT) { + if len <= 0 { + array.clear(); + return; + } if !array.is_empty() { - let len = len.min(MAX_USIZE_INT); + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let len = len.min(MAX_USIZE_INT) as usize; if len > 0 { - array.truncate(len as usize); + array.truncate(len); } else { array.clear(); } @@ -402,13 +411,18 @@ pub mod array_functions { /// print(x); // prints "[3, 4, 5]" /// ``` pub fn chop(array: &mut Array, len: INT) { + if len <= 0 { + array.clear(); + return; + } if !array.is_empty() { - let len = len.min(MAX_USIZE_INT); + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let len = len.min(MAX_USIZE_INT) as usize; if len <= 0 { array.clear(); - } else if (len as usize) < array.len() { - array.drain(0..array.len() - len as usize); + } else if len < array.len() { + array.drain(0..array.len() - len); } } } @@ -633,6 +647,10 @@ pub mod array_functions { /// Iterate through all the elements in the array, applying a `mapper` function to each element /// in turn, and return the results as a new array. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -652,17 +670,17 @@ pub mod array_functions { /// print(y); // prints "[0, 2, 6, 12, 20]" /// ``` #[rhai_fn(return_raw)] - pub fn map(ctx: NativeCallContext, array: Array, map: FnPtr) -> RhaiResultOf { + pub fn map(ctx: NativeCallContext, array: &mut Array, map: FnPtr) -> RhaiResultOf { if array.is_empty() { - return Ok(array); + return Ok(Array::new()); } let mut ar = Array::with_capacity(array.len()); - for (i, item) in array.into_iter().enumerate() { + for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; - ar.push(map.call_raw_with_extra_args("map", &ctx, None, [item], ex)?); + ar.push(map.call_raw_with_extra_args("map", &ctx, Some(item), [], ex)?); } Ok(ar) @@ -671,6 +689,10 @@ pub mod array_functions { /// Iterate through all the elements in the array, applying a `filter` function to each element /// in turn, and return a copy of all elements (in order) that return `true` as a new array. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -690,22 +712,22 @@ pub mod array_functions { /// print(y); // prints "[12, 20]" /// ``` #[rhai_fn(return_raw)] - pub fn filter(ctx: NativeCallContext, array: Array, filter: FnPtr) -> RhaiResultOf { + pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { - return Ok(array); + return Ok(Array::new()); } let mut ar = Array::new(); - for (i, item) in array.into_iter().enumerate() { + for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if filter - .call_raw_with_extra_args("filter", &ctx, None, [item.clone()], ex)? + .call_raw_with_extra_args("filter", &ctx, Some(item), [], ex)? .as_bool() .unwrap_or(false) { - ar.push(item); + ar.push(item.clone()); } } @@ -857,6 +879,10 @@ pub mod array_functions { /// in turn, and return the index of the first element that returns `true`. /// If no element returns `true`, `-1` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -893,6 +919,10 @@ pub mod array_functions { /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -928,11 +958,11 @@ pub mod array_functions { let (start, ..) = calc_offset_len(array.len(), start, 0); - for (i, item) in array.iter().enumerate().skip(start) { + for (i, item) in array.iter_mut().enumerate().skip(start) { let ex = [(i as INT).into()]; if filter - .call_raw_with_extra_args("index_of", &ctx, None, [item.clone()], ex)? + .call_raw_with_extra_args("index_of", &ctx, Some(item), [], ex)? .as_bool() .unwrap_or(false) { @@ -946,6 +976,10 @@ pub mod array_functions { /// in turn, and return a copy of the first element that returns `true`. If no element returns /// `true`, `()` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -958,7 +992,7 @@ pub mod array_functions { /// /// print(x.find(|v| v > 3)); // prints 5: 5 > 3 /// - /// x.find(|v| v > 13) ?? print("not found"); // prints "not found": nothing is > 13 + /// print(x.find(|v| v > 13) ?? "not found"); // prints "not found": nothing is > 13 /// /// print(x.find(|v, i| v * i > 13)); // prints 5: 3 * 5 > 13 /// ``` @@ -974,6 +1008,10 @@ pub mod array_functions { /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -986,9 +1024,9 @@ pub mod array_functions { /// /// print(x.find(|v| v > 1, 2)); // prints 3: 3 > 1 /// - /// x.find(|v| v < 2, 3) ?? print("not found"); // prints "not found": nothing < 2 past index 3 + /// print(x.find(|v| v < 2, 3) ?? "not found"); // prints "not found": nothing < 2 past index 3 /// - /// x.find(|v| v > 1, 8) ?? print("not found"); // prints "not found": nothing found past end of array + /// print(x.find(|v| v > 1, 8) ?? "not found"); // prints "not found": nothing found past end of array /// /// print(x.find(|v| v > 1, -3)); // prints 5: -3 = start from index 4 /// @@ -1014,6 +1052,10 @@ pub mod array_functions { /// Iterate through all the elements in the array, applying a `mapper` function to each element /// in turn, and return the first result that is not `()`. Otherwise, `()` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1026,7 +1068,9 @@ pub mod array_functions { /// /// print(x.find_map(|v| v.alice)); // prints 1 /// - /// x.find_map(|v| v.dave) ?? print("not found"); // prints "not found" + /// print(x.find_map(|v| v.dave) ?? "not found"); // prints "not found" + /// + /// print(x.find_map(|| this.dave) ?? "not found"); // prints "not found" /// ``` #[rhai_fn(return_raw, pure)] pub fn find_map(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResult { @@ -1040,6 +1084,10 @@ pub mod array_functions { /// * If `start` < -length of array, position counts from the beginning of the array. /// * If `start` ≥ length of array, `-1` is returned. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1052,13 +1100,17 @@ pub mod array_functions { /// /// print(x.find_map(|v| v.alice, 2)); // prints 0 /// - /// x.find_map(|v| v.bob, 4) ?? print("not found"); // prints "not found" + /// print(x.find_map(|v| v.bob, 4) ?? "not found"); // prints "not found" /// - /// x.find_map(|v| v.alice, 8) ?? print("not found"); // prints "not found" + /// print(x.find_map(|v| v.alice, 8) ?? "not found"); // prints "not found" + /// + /// print(x.find_map(|| this.alice, 8) ?? "not found"); // prints "not found" /// /// print(x.find_map(|v| v.bob, -4)); // prints 3: -4 = start from index 2 /// /// print(x.find_map(|v| v.alice, -99)); // prints 1: -99 = start from beginning + /// + /// print(x.find_map(|| this.alice, -99)); // prints 1: -99 = start from beginning /// ``` #[rhai_fn(name = "find_map", return_raw, pure)] pub fn find_map_starting_from( @@ -1073,11 +1125,10 @@ pub mod array_functions { let (start, ..) = calc_offset_len(array.len(), start, 0); - for (i, item) in array.iter().enumerate().skip(start) { + for (i, item) in array.iter_mut().enumerate().skip(start) { let ex = [(i as INT).into()]; - let value = - filter.call_raw_with_extra_args("find_map", &ctx, None, [item.clone()], ex)?; + let value = filter.call_raw_with_extra_args("find_map", &ctx, Some(item), [], ex)?; if !value.is_unit() { return Ok(value); @@ -1088,6 +1139,10 @@ pub mod array_functions { } /// Return `true` if any element in the array that returns `true` when applied the `filter` function. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1110,11 +1165,11 @@ pub mod array_functions { return Ok(false); } - for (i, item) in array.iter().enumerate() { + for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if filter - .call_raw_with_extra_args("some", &ctx, None, [item.clone()], ex)? + .call_raw_with_extra_args("some", &ctx, Some(item), [], ex)? .as_bool() .unwrap_or(false) { @@ -1126,6 +1181,10 @@ pub mod array_functions { } /// Return `true` if all elements in the array return `true` when applied the `filter` function. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1148,11 +1207,11 @@ pub mod array_functions { return Ok(true); } - for (i, item) in array.iter().enumerate() { + for (i, item) in array.iter_mut().enumerate() { let ex = [(i as INT).into()]; if !filter - .call_raw_with_extra_args("all", &ctx, None, [item.clone()], ex)? + .call_raw_with_extra_args("all", &ctx, Some(item), [], ex)? .as_bool() .unwrap_or(false) { @@ -1178,7 +1237,7 @@ pub mod array_functions { /// ``` pub fn dedup(ctx: NativeCallContext, array: &mut Array) { let comparer = FnPtr::new_unchecked(OP_EQUALS, StaticVec::new_const()); - dedup_by_comparer(ctx, array, comparer) + dedup_by_comparer(ctx, array, comparer); } /// Remove duplicated _consecutive_ elements from the array that return `true` when applied the /// `comparer` function. @@ -1502,6 +1561,10 @@ pub mod array_functions { /// Remove all elements in the array that returns `true` when applied the `filter` function and /// return them as a new array. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1539,7 +1602,7 @@ pub mod array_functions { let ex = [(i as INT).into()]; if filter - .call_raw_with_extra_args("drain", &ctx, None, [array[x].clone()], ex)? + .call_raw_with_extra_args("drain", &ctx, Some(&mut array[x]), [], ex)? .as_bool() .unwrap_or(false) { @@ -1645,6 +1708,10 @@ pub mod array_functions { /// Remove all elements in the array that do not return `true` when applied the `filter` /// function and return them as a new array. /// + /// # No Function Parameter + /// + /// Array element (mutable) is bound to `this`. + /// /// # Function Parameters /// /// * `element`: copy of array element @@ -1682,7 +1749,7 @@ pub mod array_functions { let ex = [(i as INT).into()]; if filter - .call_raw_with_extra_args("retain", &ctx, None, [array[x].clone()], ex)? + .call_raw_with_extra_args("retain", &ctx, Some(&mut array[x]), [], ex)? .as_bool() .unwrap_or(false) { diff --git a/src/packages/bit_field.rs b/src/packages/bit_field.rs index 9da781cf..50d705a6 100644 --- a/src/packages/bit_field.rs +++ b/src/packages/bit_field.rs @@ -132,6 +132,7 @@ mod bit_field_functions { ERR::ErrorBitFieldBounds(INT_BITS, start, Position::NONE).into() })?; + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let bits = if bit + bits as usize > INT_BITS { INT_BITS - bit } else { @@ -143,6 +144,7 @@ mod bit_field_functions { } // 2^bits - 1 + #[allow(clippy::cast_possible_truncation)] let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; Ok(((value & (mask << bit)) >> bit) & mask) @@ -218,6 +220,7 @@ mod bit_field_functions { ERR::ErrorBitFieldBounds(INT_BITS, bit, Position::NONE).into() })?; + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let bits = if bit + bits as usize > INT_BITS { INT_BITS - bit } else { @@ -230,6 +233,7 @@ mod bit_field_functions { } // 2^bits - 1 + #[allow(clippy::cast_possible_truncation)] let mask = ((2 as UNSIGNED_INT).pow(bits as u32) - 1) as INT; *value &= !(mask << bit); diff --git a/src/packages/blob_basic.rs b/src/packages/blob_basic.rs index 515cef06..430b89c0 100644 --- a/src/packages/blob_basic.rs +++ b/src/packages/blob_basic.rs @@ -75,7 +75,8 @@ pub mod blob_functions { len: INT, value: INT, ) -> RhaiResultOf { - let len = len.min(MAX_USIZE_INT).max(0) as usize; + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let len = len.clamp(0, MAX_USIZE_INT) as usize; let _ctx = ctx; // Check if blob will be over max size limit @@ -83,6 +84,7 @@ pub mod blob_functions { _ctx.engine().throw_on_size((len, 0, 0))?; let mut blob = Blob::new(); + #[allow(clippy::cast_sign_loss)] blob.resize(len, (value & 0x0000_00ff) as u8); Ok(blob) } @@ -155,6 +157,7 @@ pub mod blob_functions { /// ``` #[rhai_fn(name = "contains")] pub fn contains(blob: &mut Blob, value: INT) -> bool { + #[allow(clippy::cast_sign_loss)] blob.contains(&((value & 0x0000_00ff) as u8)) } /// Get the byte value at the `index` position in the BLOB. @@ -221,6 +224,7 @@ pub mod blob_functions { let (index, ..) = calc_offset_len(blob.len(), index, 0); + #[allow(clippy::cast_sign_loss)] if index < blob.len() { blob[index] = (value & 0x0000_00ff) as u8; } @@ -240,6 +244,7 @@ pub mod blob_functions { /// ``` #[rhai_fn(name = "push", name = "append")] pub fn push(blob: &mut Blob, value: INT) { + #[allow(clippy::cast_sign_loss)] blob.push((value & 0x0000_00ff) as u8); } /// Add another BLOB to the end of the BLOB. @@ -315,6 +320,7 @@ pub mod blob_functions { /// print(b); // prints "[4242184242]" /// ``` pub fn insert(blob: &mut Blob, index: INT, value: INT) { + #[allow(clippy::cast_sign_loss)] let value = (value & 0x0000_00ff) as u8; if blob.is_empty() { @@ -354,8 +360,10 @@ pub mod blob_functions { if len <= 0 { return Ok(()); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; + #[allow(clippy::cast_sign_loss)] let value = (value & 0x0000_00ff) as u8; let _ctx = ctx; @@ -470,11 +478,16 @@ pub mod blob_functions { /// print(b); // prints "[010203]" /// ``` pub fn truncate(blob: &mut Blob, len: INT) { + if len <= 0 { + blob.clear(); + return; + } if !blob.is_empty() { - let len = len.min(MAX_USIZE_INT); + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let len = len.min(MAX_USIZE_INT) as usize; if len > 0 { - blob.truncate(len as usize); + blob.truncate(len); } else { blob.clear(); } @@ -500,6 +513,7 @@ pub mod blob_functions { /// /// print(b); // prints "[030405]" /// ``` + #[allow(clippy::cast_sign_loss, clippy::needless_pass_by_value)] pub fn chop(blob: &mut Blob, len: INT) { if !blob.is_empty() { if len <= 0 { diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 9e4588c6..50dc7b63 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -22,6 +22,7 @@ use rust_decimal::Decimal; #[cfg(not(feature = "unchecked"))] #[inline(always)] +#[allow(clippy::needless_pass_by_value)] fn std_add(x: T, y: T) -> Option where T: num_traits::CheckedAdd, @@ -30,6 +31,7 @@ where } #[inline(always)] #[allow(dead_code)] +#[allow(clippy::unnecessary_wraps, clippy::needless_pass_by_value)] fn regular_add(x: T, y: T) -> Option where T: std::ops::Add, @@ -123,6 +125,7 @@ impl FusedIterator for StepRange {} pub struct BitRange(INT, usize); impl BitRange { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn new(value: INT, from: INT, len: INT) -> RhaiResultOf { let from = calc_index(INT_BITS, from, true, || { ERR::ErrorBitFieldBounds(INT_BITS, from, Position::NONE).into() @@ -174,6 +177,7 @@ impl ExactSizeIterator for BitRange { pub struct CharsStream(Vec, usize); impl CharsStream { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn new(string: &str, from: INT, len: INT) -> Self { if len <= 0 || from > MAX_USIZE_INT { return Self(Vec::new(), 0); diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index a19b0f72..6b55f2e1 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -153,8 +153,9 @@ mod reflection_functions { /// Return an array of object maps containing metadata of all script-defined functions /// matching the specified name and arity (number of parameters). #[rhai_fn(name = "get_fn_metadata_list")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array { - if params < 0 || params > crate::MAX_USIZE_INT { + if !(0..=crate::MAX_USIZE_INT).contains(¶ms) { Array::new() } else { collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name) diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 4c54f570..25294e47 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -477,7 +477,7 @@ mod decimal_functions { } } - #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp(digits as u32)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -495,7 +495,7 @@ mod decimal_functions { } } - #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::AwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -513,7 +513,7 @@ mod decimal_functions { } } - #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::ToZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -531,7 +531,7 @@ mod decimal_functions { } } - #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointAwayFromZero)) } /// Round the decimal number to the specified number of `digits` after the decimal point and return it. @@ -549,33 +549,34 @@ mod decimal_functions { } } - #[allow(clippy::cast_sign_loss)] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } /// Convert the decimal number into an integer. #[rhai_fn(return_raw)] pub fn to_int(x: Decimal) -> RhaiResultOf { - let n = x.to_i64().and_then(|n| { - #[cfg(feature = "only_i32")] - return if n > (INT::MAX as i64) || n < (INT::MIN as i64) { - None - } else { - Some(n as i32) - }; + x.to_i64() + .and_then(|n| { + #[cfg(feature = "only_i32")] + return if n > (INT::MAX as i64) || n < (INT::MIN as i64) { + None + } else { + Some(n as i32) + }; - #[cfg(not(feature = "only_i32"))] - return Some(n); - }); - - n.map_or_else( - || { - Err( - ERR::ErrorArithmetic(format!("Integer overflow: to_int({x})"), Position::NONE) - .into(), - ) - }, - Ok, - ) + #[cfg(not(feature = "only_i32"))] + return Some(n); + }) + .map_or_else( + || { + Err(ERR::ErrorArithmetic( + format!("Integer overflow: to_int({x})"), + Position::NONE, + ) + .into()) + }, + Ok, + ) } /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 70e95363..3819295d 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -95,11 +95,11 @@ mod string_functions { #[rhai_fn(name = "+=")] pub fn add_assign_append_str(string1: &mut ImmutableString, string2: ImmutableString) { - *string1 += string2 + *string1 += string2; } #[rhai_fn(name = "+=", pure)] pub fn add_assign_append_char(string: &mut ImmutableString, character: char) { - *string += character + *string += character; } #[rhai_fn(name = "+=")] pub fn add_assign_append_unit(string: &mut ImmutableString, item: ()) { @@ -246,9 +246,9 @@ mod string_functions { pub fn clear(string: &mut ImmutableString) { if !string.is_empty() { if let Some(s) = string.get_mut() { - s.clear() + s.clear(); } else { - *string = ImmutableString::new() + *string = ImmutableString::new(); } } } @@ -272,7 +272,7 @@ mod string_functions { /// ``` pub fn truncate(string: &mut ImmutableString, len: INT) { if len > 0 { - #[allow(clippy::cast_sign_loss)] + #[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(); @@ -355,6 +355,7 @@ mod string_functions { if string.is_empty() || len <= 0 { return ctx.engine().const_empty_string(); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let mut chars = StaticVec::::with_capacity(len); @@ -598,6 +599,7 @@ mod string_functions { return -1; } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let start = if start < 0 { let abs_start = start.unsigned_abs(); @@ -608,6 +610,7 @@ mod string_functions { let abs_start = abs_start as usize; let chars: Vec<_> = string.chars().collect(); let num_chars = chars.len(); + if abs_start > num_chars { 0 } else { @@ -680,6 +683,7 @@ mod string_functions { return -1; } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let start = if start < 0 { let abs_start = start.unsigned_abs(); @@ -690,6 +694,7 @@ mod string_functions { let abs_start = abs_start as usize; let chars = string.chars().collect::>(); let num_chars = chars.len(); + if abs_start > num_chars { 0 } else { @@ -763,9 +768,12 @@ mod string_functions { return Dynamic::UNIT; } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let index = index as usize; + string .chars() - .nth(index as usize) + .nth(index) .map_or_else(|| Dynamic::UNIT, Into::into) } else { // Count from end if negative @@ -775,10 +783,13 @@ mod string_functions { return Dynamic::UNIT; } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + let abs_index = abs_index as usize; + string .chars() .rev() - .nth((abs_index as usize) - 1) + .nth(abs_index - 1) .map_or_else(|| Dynamic::UNIT, Into::into) } } @@ -811,6 +822,7 @@ mod string_functions { return; } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let index = index as usize; *string = string @@ -825,6 +837,7 @@ mod string_functions { return; } + #[allow(clippy::cast_possible_truncation)] let abs_index = abs_index as usize; let string_len = string.chars().count(); @@ -894,13 +907,14 @@ mod string_functions { /// /// print(text.sub_string(-8, 3)); // prints ", w" /// ``` + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn sub_string( ctx: NativeCallContext, string: &str, start: INT, len: INT, ) -> ImmutableString { - if string.is_empty() { + if string.is_empty() || len <= 0 { return ctx.engine().const_empty_string(); } @@ -915,8 +929,11 @@ mod string_functions { return ctx.engine().const_empty_string(); } + #[allow(clippy::cast_possible_truncation)] let abs_start = abs_start as usize; + chars.extend(string.chars()); + if abs_start > chars.len() { 0 } else { @@ -932,10 +949,12 @@ mod string_functions { chars.extend(string.chars()); } - let len = if offset + len as usize > chars.len() { + let len = len.min(MAX_USIZE_INT) as usize; + + let len = if offset + len > chars.len() { chars.len() - offset } else { - len as usize + len }; chars @@ -988,10 +1007,10 @@ mod string_functions { /// print(text); // prints "llo, w" /// ``` #[rhai_fn(name = "crop")] - pub fn crop_range(string: &mut ImmutableString, range: ExclusiveRange) { + pub fn crop_range(ctx: NativeCallContext, string: &mut ImmutableString, range: ExclusiveRange) { let start = INT::max(range.start, 0); let end = INT::max(range.end, start); - crop(string, start, end - start); + crop(ctx, string, start, end - start); } /// Remove all characters from the string except those within an inclusive `range`. /// @@ -1005,10 +1024,14 @@ mod string_functions { /// print(text); // prints "llo, wo" /// ``` #[rhai_fn(name = "crop")] - pub fn crop_inclusive_range(string: &mut ImmutableString, range: InclusiveRange) { + pub fn crop_inclusive_range( + ctx: NativeCallContext, + string: &mut ImmutableString, + range: InclusiveRange, + ) { let start = INT::max(*range.start(), 0); let end = INT::max(*range.end(), start); - crop(string, start, end - start + 1); + crop(ctx, string, start, end - start + 1); } /// Remove all characters from the string except those within a range. @@ -1033,10 +1056,15 @@ mod string_functions { /// print(text); // prints ", w" /// ``` #[rhai_fn(name = "crop")] - pub fn crop(string: &mut ImmutableString, start: INT, len: INT) { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] + pub fn crop(ctx: NativeCallContext, string: &mut ImmutableString, start: INT, len: INT) { if string.is_empty() { return; } + if len <= 0 { + *string = ctx.engine().const_empty_string(); + return; + } let mut chars = StaticVec::with_capacity(string.len()); @@ -1051,7 +1079,9 @@ mod string_functions { } let abs_start = abs_start as usize; + chars.extend(string.chars()); + if abs_start > chars.len() { 0 } else { @@ -1068,10 +1098,12 @@ mod string_functions { chars.extend(string.chars()); } - let len = if offset + len as usize > chars.len() { + let len = len.min(MAX_USIZE_INT) as usize; + + let len = if offset + len > chars.len() { chars.len() - offset } else { - len as usize + len }; let copy = string.make_mut(); @@ -1098,8 +1130,12 @@ mod string_functions { /// print(text); // prints "ld!" /// ``` #[rhai_fn(name = "crop")] - pub fn crop_string_starting_from(string: &mut ImmutableString, start: INT) { - crop(string, start, string.len() as INT); + pub fn crop_string_starting_from( + ctx: NativeCallContext, + string: &mut ImmutableString, + start: INT, + ) { + crop(ctx, string, start, string.len() as INT); } /// Replace all occurrences of the specified sub-string in the string with another string. @@ -1219,6 +1255,7 @@ mod string_functions { if len <= 0 { return Ok(()); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let _ctx = ctx; @@ -1275,6 +1312,7 @@ mod string_functions { if len <= 0 { return Ok(()); } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] let len = len.min(MAX_USIZE_INT) as usize; let _ctx = ctx; @@ -1339,6 +1377,7 @@ mod string_functions { /// print(text.split(-99)); // prints ["", "hello, world!"] /// ``` #[rhai_fn(name = "split")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn split_at(ctx: NativeCallContext, string: &mut ImmutableString, index: INT) -> Array { if index <= 0 { let abs_index = index.unsigned_abs(); @@ -1349,9 +1388,9 @@ mod string_functions { string.as_str().into(), ]; } - let abs_index = abs_index as usize; let num_chars = string.chars().count(); + if abs_index > num_chars { vec![ ctx.engine().const_empty_string().into(), @@ -1432,7 +1471,11 @@ mod string_functions { /// print(text.split("ll", 2)); // prints ["he", "o, world! hello, foo!"] /// ``` #[rhai_fn(name = "split")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn splitn(string: &str, delimiter: &str, segments: INT) -> Array { + if segments < 1 { + return [string.into()].into(); + } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.splitn(pieces, delimiter).map(Into::into).collect() @@ -1463,7 +1506,11 @@ mod string_functions { /// print(text.split('l', 3)); // prints ["he", "", "o, world! hello, foo!"] /// ``` #[rhai_fn(name = "split")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn splitn_char(string: &str, delimiter: char, segments: INT) -> Array { + if segments < 1 { + return [string.into()].into(); + } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.splitn(pieces, delimiter).map(Into::into).collect() @@ -1495,7 +1542,11 @@ mod string_functions { /// print(text.split_rev("ll", 2)); // prints ["o, foo!", "hello, world! he"] /// ``` #[rhai_fn(name = "split_rev")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn rsplitn(string: &str, delimiter: &str, segments: INT) -> Array { + if segments < 1 { + return [string.into()].into(); + } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.rsplitn(pieces, delimiter).map(Into::into).collect() @@ -1527,7 +1578,11 @@ mod string_functions { /// print(text.split('l', 3)); // prints ["o, foo!", "", "hello, world! he" /// ``` #[rhai_fn(name = "split_rev")] + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn rsplitn_char(string: &str, delimiter: char, segments: INT) -> Array { + if segments < 1 { + return [string.into()].into(); + } let segments = segments.min(MAX_USIZE_INT) as usize; let pieces: usize = if segments < 1 { 1 } else { segments }; string.rsplitn(pieces, delimiter).map(Into::into).collect() diff --git a/src/packages/time_basic.rs b/src/packages/time_basic.rs index 2ea495c2..828c8d8a 100644 --- a/src/packages/time_basic.rs +++ b/src/packages/time_basic.rs @@ -114,11 +114,12 @@ mod time_functions { #[cfg(not(feature = "no_float"))] pub mod float_functions { + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn add_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { subtract_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { - if seconds > (INT::MAX as FLOAT) { + if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) { Err(make_arithmetic_err(format!( "Integer overflow for timestamp add: {seconds}" ))) @@ -135,20 +136,21 @@ mod time_functions { Ok(timestamp + Duration::from_millis((seconds * 1000.0) as u64)) } } + #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] fn subtract_impl(timestamp: Instant, seconds: FLOAT) -> RhaiResultOf { if seconds < 0.0 { add_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { - if seconds > (INT::MAX as FLOAT) { + if seconds > (INT::MAX as FLOAT).min(u64::MAX as FLOAT) { Err(make_arithmetic_err(format!( - "Integer overflow for timestamp add: {seconds}" + "Integer overflow for timestamp subtract: {seconds}" ))) } else { timestamp .checked_sub(Duration::from_millis((seconds * 1000.0) as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {seconds} second(s)" + "Timestamp overflow when subtracting {seconds} second(s)" )) }) } @@ -182,6 +184,7 @@ mod time_functions { } fn add_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { + #[allow(clippy::cast_sign_loss)] if seconds < 0 { subtract_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { @@ -197,6 +200,7 @@ mod time_functions { } } fn subtract_impl(timestamp: Instant, seconds: INT) -> RhaiResultOf { + #[allow(clippy::cast_sign_loss)] if seconds < 0 { add_impl(timestamp, -seconds) } else if cfg!(not(feature = "unchecked")) { @@ -204,7 +208,7 @@ mod time_functions { .checked_sub(Duration::from_secs(seconds as u64)) .ok_or_else(|| { make_arithmetic_err(format!( - "Timestamp overflow when adding {seconds} second(s)" + "Timestamp overflow when subtracting {seconds} second(s)" )) }) } else { diff --git a/src/parser.rs b/src/parser.rs index 469c1ff5..8089c78d 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3,9 +3,9 @@ use crate::api::events::VarDefInfo; use crate::api::options::LangOptions; use crate::ast::{ - ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FnCallExpr, FnCallHashes, Ident, - Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, - SwitchCasesCollection, TryCatchBlock, + ASTFlags, BinaryExpr, CaseBlocksList, ConditionalExpr, Expr, FlowControl, FnCallExpr, + FnCallHashes, Ident, Namespace, OpAssignment, RangeCase, ScriptFnDef, Stmt, StmtBlock, + StmtBlockContainer, SwitchCasesCollection, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; use crate::eval::{Caches, GlobalRuntimeState}; @@ -26,6 +26,7 @@ use bitflags::bitflags; use std::prelude::v1::*; use std::{ collections::BTreeMap, + convert::TryFrom, fmt, hash::{Hash, Hasher}, num::{NonZeroU8, NonZeroUsize}, @@ -147,7 +148,7 @@ impl<'e, 's> ParseState<'e, 's> { .stack .as_deref() .into_iter() - .flat_map(|s| s.iter_rev_raw()) + .flat_map(Scope::iter_rev_raw) .enumerate() .find(|&(.., (n, ..))| { if n == SCOPE_SEARCH_BARRIER_MARKER { @@ -363,7 +364,7 @@ pub fn make_anonymous_fn(hash: u64) -> Identifier { use std::fmt::Write; let mut buf = Identifier::new_const(); - write!(&mut buf, "{}{:016x}", crate::engine::FN_ANONYMOUS, hash).unwrap(); + write!(&mut buf, "{}{hash:016x}", crate::engine::FN_ANONYMOUS).unwrap(); buf } @@ -1198,14 +1199,14 @@ impl Engine { }; let (action_expr, need_comma) = - if !settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { + if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { + (self.parse_expr(input, state, lib, settings)?, true) + } else { let stmt = self.parse_stmt(input, state, lib, settings)?; let need_comma = !stmt.is_self_terminated(); let stmt_block: StmtBlock = stmt.into(); (Expr::Stmt(stmt_block.into()), need_comma) - } else { - (self.parse_expr(input, state, lib, settings)?, true) }; let has_condition = !matches!(condition, Expr::BoolConstant(true, ..)); @@ -1293,8 +1294,8 @@ impl Engine { let cases = SwitchCasesCollection { expressions, cases, - def_case, ranges, + def_case, }; Ok(Stmt::Switch((item, cases).into(), settings.pos)) @@ -1636,13 +1637,9 @@ impl Engine { ); } - let short_index = index.and_then(|x| { - if x.get() <= u8::MAX as usize { - NonZeroU8::new(x.get() as u8) - } else { - None - } - }); + let short_index = index + .and_then(|x| u8::try_from(x.get()).ok()) + .and_then(NonZeroU8::new); let name = state.get_interned_string(*s); Expr::Variable((index, ns, 0, name).into(), short_index, settings.pos) } @@ -2023,16 +2020,16 @@ impl Engine { } } - let op_info = if op != NO_TOKEN { - OpAssignment::new_op_assignment_from_token(op, op_pos) - } else { + let op_info = if op == NO_TOKEN { OpAssignment::new_assignment(op_pos) + } else { + OpAssignment::new_op_assignment_from_token(op, op_pos) }; match lhs { // const_expr = rhs ref expr if expr.is_constant() => { - Err(PERR::AssignmentToConstant("".into()).into_err(lhs.start_position())) + Err(PERR::AssignmentToConstant(String::new()).into_err(lhs.start_position())) } // var (non-indexed) = rhs Expr::Variable(ref x, None, _) if x.0.is_none() => { @@ -2384,7 +2381,7 @@ impl Engine { let not_base = FnCallExpr { namespace: Namespace::NONE, name: state.get_interned_string(op), - hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1).into()), + hashes: FnCallHashes::from_native(calc_fn_hash(None, op, 1)), args, op_token: Token::Bang, capture_parent_scope: false, @@ -2652,14 +2649,14 @@ impl Engine { // if guard { if_body } ensure_not_statement_expr(input, "a boolean")?; - let guard = self + let expr = self .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; - let if_body = self.parse_block(input, state, lib, settings)?; + let body = self.parse_block(input, state, lib, settings)?.into(); // if guard { if_body } else ... - let else_body = if match_token(input, Token::Else).0 { + let branch = if match_token(input, Token::Else).0 { if let (Token::If, ..) = input.peek().expect(NEVER_ENDS) { // if guard { if_body } else if ... self.parse_if(input, state, lib, settings)? @@ -2669,10 +2666,11 @@ impl Engine { } } else { Stmt::Noop(Position::NONE) - }; + } + .into(); Ok(Stmt::If( - (guard, if_body.into(), else_body.into()).into(), + FlowControl { expr, body, branch }.into(), settings.pos, )) } @@ -2688,7 +2686,7 @@ impl Engine { let mut settings = settings.level_up()?; // while|loops ... - let (guard, token_pos) = match input.next().expect(NEVER_ENDS) { + let (expr, token_pos) = match input.next().expect(NEVER_ENDS) { (Token::While, pos) => { ensure_not_statement_expr(input, "a boolean")?; let expr = self @@ -2703,9 +2701,13 @@ impl Engine { settings.pos = token_pos; settings.flags |= ParseSettingFlags::BREAKABLE; - let body = self.parse_block(input, state, lib, settings)?; + let body = self.parse_block(input, state, lib, settings)?.into(); + let branch = StmtBlock::NONE; - Ok(Stmt::While((guard, body.into()).into(), settings.pos)) + Ok(Stmt::While( + FlowControl { expr, body, branch }.into(), + settings.pos, + )) } /// Parse a do loop. @@ -2725,7 +2727,7 @@ impl Engine { // do { body } [while|until] guard - let body = self.parse_block(input, state, lib, settings)?; + let body = self.parse_block(input, state, lib, settings)?.into(); let negated = match input.next().expect(NEVER_ENDS) { (Token::While, ..) => ASTFlags::NONE, @@ -2743,12 +2745,18 @@ impl Engine { } ensure_not_statement_expr(input, "a boolean")?; - let guard = self + let expr = self .parse_expr(input, state, lib, settings)? .ensure_bool_expr()?; ensure_not_assignment(input)?; - Ok(Stmt::Do((guard, body.into()).into(), negated, settings.pos)) + let branch = StmtBlock::NONE; + + Ok(Stmt::Do( + FlowControl { expr, body, branch }.into(), + negated, + settings.pos, + )) } /// Parse a for loop. @@ -2840,12 +2848,14 @@ impl Engine { }; settings.flags |= ParseSettingFlags::BREAKABLE; - let body = self.parse_block(input, state, lib, settings)?; + let body = self.parse_block(input, state, lib, settings)?.into(); state.stack.as_deref_mut().unwrap().rewind(prev_stack_len); + let branch = StmtBlock::NONE; + Ok(Stmt::For( - Box::new((loop_var, counter_var, expr, body.into())), + Box::new((loop_var, counter_var, FlowControl { expr, body, branch })), settings.pos, )) } @@ -3480,7 +3490,7 @@ impl Engine { settings.pos = eat_token(input, Token::Try); // try { try_block } - let try_block = self.parse_block(input, state, lib, settings)?; + let body = self.parse_block(input, state, lib, settings)?.into(); // try { try_block } catch let (matched, catch_pos) = match_token(input, Token::Catch); @@ -3519,20 +3529,23 @@ impl Engine { }; // try { try_block } catch ( var ) { catch_block } - let catch_block = self.parse_block(input, state, lib, settings)?; + let branch = self.parse_block(input, state, lib, settings)?.into(); - if !catch_var.is_empty() { + let expr = if !catch_var.is_empty() { // Remove the error variable from the stack state.stack.as_deref_mut().unwrap().pop(); - } + + Expr::Variable( + (None, Namespace::default(), 0, catch_var.name).into(), + None, + catch_var.pos, + ) + } else { + Expr::Unit(catch_var.pos) + }; Ok(Stmt::TryCatch( - TryCatchBlock { - try_block: try_block.into(), - catch_var, - catch_block: catch_block.into(), - } - .into(), + FlowControl { body, expr, branch }.into(), settings.pos, )) } @@ -3661,7 +3674,7 @@ impl Engine { Some(n) if !is_func && n.get() <= u8::MAX as usize => NonZeroU8::new(n.get() as u8), _ => None, }; - Expr::Variable((index, Default::default(), 0, name).into(), idx, pos) + Expr::Variable((index, Namespace::default(), 0, name).into(), idx, pos) })); let expr = FnCallExpr { diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 34595826..580bb344 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -98,6 +98,7 @@ impl Serialize for Scope<'_> { pub is_constant: bool, } + #[allow(clippy::trivially_copy_pass_by_ref)] fn is_false(value: &bool) -> bool { !value } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index dd9a5b6f..e180aedf 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -21,12 +21,15 @@ use std::{ #[derive(Debug, Clone, Eq, PartialEq, Default, Hash)] pub struct TokenizerControlBlock { /// Is the current tokenizer position within an interpolated text string? + /// /// This flag allows switching the tokenizer back to _text_ parsing after an interpolation stream. pub is_within_text: bool, /// Global comments. #[cfg(feature = "metadata")] pub global_comments: String, /// Whitespace-compressed version of the script (if any). + /// + /// Set to `Some` in order to collect a compressed script. pub compressed: Option, } @@ -882,7 +885,9 @@ pub struct TokenizeState { pub include_comments: bool, /// Is the current tokenizer position within the text stream of an interpolated string? pub is_within_text_terminated_by: Option, - /// Last token + /// Textual syntax of the current token, if any. + /// + /// Set to `Some` to begin tracking this information. pub last_token: Option, } @@ -961,10 +966,10 @@ pub fn parse_string_literal( let mut skip_whitespace_until = 0; state.is_within_text_terminated_by = Some(termination_char); - state.last_token.as_mut().map(|last| { + if let Some(ref mut last) = state.last_token { last.clear(); last.push(termination_char); - }); + } loop { assert!( @@ -983,7 +988,7 @@ pub fn parse_string_literal( break; } None if allow_line_continuation && !escape.is_empty() => { - assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); + assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); pos.advance(); break; } @@ -994,7 +999,9 @@ pub fn parse_string_literal( } }; - state.last_token.as_mut().map(|last| last.push(next_char)); + if let Some(ref mut last) = state.last_token { + last.push(next_char); + } // String interpolation? if allow_interpolation @@ -1015,10 +1022,9 @@ pub fn parse_string_literal( // Double wrapper if stream.peek_next().map_or(false, |c| c == termination_char) { eat_next(stream, pos); - state - .last_token - .as_mut() - .map(|last| last.push(termination_char)); + if let Some(ref mut last) = state.last_token { + last.push(termination_char); + } } else { state.is_within_text_terminated_by = None; break; @@ -1075,9 +1081,11 @@ pub fn parse_string_literal( .get_next() .ok_or_else(|| (LERR::MalformedEscapeSequence(seq.to_string()), *pos))?; - state.last_token.as_mut().map(|last| last.push(c)); - seq.push(c); pos.advance(); + seq.push(c); + if let Some(ref mut last) = state.last_token { + last.push(c); + } out_val *= 16; out_val += c @@ -1106,7 +1114,7 @@ pub fn parse_string_literal( // Line continuation '\n' if allow_line_continuation && !escape.is_empty() => { - assert_eq!(escape, "\\", "unexpected escape {} at end of line", escape); + assert_eq!(escape, "\\", "unexpected escape {escape} at end of line"); escape.clear(); pos.new_line(); @@ -1256,7 +1264,7 @@ fn get_next_token_inner( state: &mut TokenizeState, pos: &mut Position, ) -> Option<(Token, Position)> { - state.last_token.as_mut().map(|last| last.clear()); + state.last_token.as_mut().map(SmartString::clear); // Still inside a comment? if state.comment_level > 0 { @@ -1338,7 +1346,7 @@ fn get_next_token_inner( pos.advance(); } // _ - cannot follow a decimal point - '_' => { + NUMBER_SEPARATOR => { stream.unget(next_char); break; } @@ -1416,7 +1424,9 @@ fn get_next_token_inner( negated_pos }); - state.last_token.as_mut().map(|last| *last = result.clone()); + if let Some(ref mut last) = state.last_token { + *last = result.clone(); + } // Parse number let token = radix_base.map_or_else( @@ -1471,17 +1481,11 @@ fn get_next_token_inner( // letter or underscore ... #[cfg(not(feature = "unicode-xid-ident"))] ('a'..='z' | '_' | 'A'..='Z', ..) => { - return Some( - parse_identifier_token(stream, state, pos, start_pos, c) - .unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)), - ); + return Some(parse_identifier_token(stream, state, pos, start_pos, c)); } #[cfg(feature = "unicode-xid-ident")] (ch, ..) if unicode_xid::UnicodeXID::is_xid_start(ch) || ch == '_' => { - return Some( - parse_identifier_token(stream, state, pos, start_pos, c) - .unwrap_or_else(|err| (Token::LexError(err.into()), start_pos)), - ); + return Some(parse_identifier_token(stream, state, pos, start_pos, c)); } // " - string literal @@ -1966,40 +1970,42 @@ fn parse_identifier_token( pos: &mut Position, start_pos: Position, first_char: char, -) -> Result<(Token, Position), LexError> { +) -> (Token, Position) { let mut identifier = SmartString::new_const(); identifier.push(first_char); - state.last_token.as_mut().map(|last| { + if let Some(ref mut last) = state.last_token { last.clear(); last.push(first_char); - }); + } while let Some(next_char) = stream.peek_next() { match next_char { x if is_id_continue(x) => { eat_next(stream, pos); identifier.push(x); - state.last_token.as_mut().map(|last| last.push(x)); + if let Some(ref mut last) = state.last_token { + last.push(x); + } } _ => break, } } if let Some(token) = Token::lookup_symbol_from_syntax(&identifier) { - return Ok((token, start_pos)); + return (token, start_pos); } if Token::is_reserved_keyword(&identifier) { - return Ok((Token::Reserved(Box::new(identifier)), start_pos)); + return (Token::Reserved(Box::new(identifier)), start_pos); } if !is_valid_identifier(&identifier) { - return Ok(( + return ( Token::LexError(LERR::MalformedIdentifier(identifier.to_string()).into()), start_pos, - )); + ); } - Ok((Token::Identifier(identifier.into()), start_pos)) + (Token::Identifier(identifier.into()), start_pos) } /// Is a keyword allowed as a function? @@ -2256,9 +2262,10 @@ impl<'a> Iterator for TokenIterator<'a> { }; // Run the mapper, if any - let token = match self.token_mapper { - Some(map_func) => map_func(token, pos, &self.state), - None => token, + let token = if let Some(func) = self.token_mapper { + func(token, pos, &self.state) + } else { + token }; // Collect the compressed script, if needed @@ -2285,21 +2292,19 @@ impl<'a> Iterator for TokenIterator<'a> { buf = last_token.clone(); } - if !buf.is_empty() { - if !compressed.is_empty() { + if !buf.is_empty() && !compressed.is_empty() { + let cur = buf.chars().next().unwrap(); + + if cur == '_' || is_id_first_alphabetic(cur) || is_id_continue(cur) { let prev = compressed.chars().last().unwrap(); - let cur = buf.chars().next().unwrap(); - if (prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev)) - && (cur == '_' - || is_id_first_alphabetic(cur) - || is_id_continue(cur)) - { + + if prev == '_' || is_id_first_alphabetic(prev) || is_id_continue(prev) { compressed.push(' '); } } - - compressed.push_str(&buf); } + + compressed.push_str(&buf); } } } diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 57cd0440..49cafcf5 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -2055,6 +2055,7 @@ impl Dynamic { /// /// Shared values are _NOT_ scanned. #[inline] + #[allow(clippy::only_used_in_recursion)] pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) { fn scan_inner(value: &mut Dynamic, filter: &mut impl FnMut(&mut Dynamic)) { match &mut value.0 { diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index c307c912..952a402c 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -37,11 +37,11 @@ impl Hash for FnPtr { self.curry.hash(state); // Hash the shared [`EncapsulatedEnviron`] by hashing its shared pointer. - self.environ.as_ref().map(|e| Shared::as_ptr(e)).hash(state); + self.environ.as_ref().map(Shared::as_ptr).hash(state); // Hash the linked [`ScriptFnDef`][crate::ast::ScriptFnDef] by hashing its shared pointer. #[cfg(not(feature = "no_function"))] - self.fn_def.as_ref().map(|f| Shared::as_ptr(f)).hash(state); + self.fn_def.as_ref().map(Shared::as_ptr).hash(state); } } @@ -124,7 +124,6 @@ impl FnPtr { } /// Get the curried arguments. #[inline(always)] - #[must_use] pub fn curry(&self) -> &[Dynamic] { self.curry.as_ref() } @@ -304,15 +303,14 @@ impl FnPtr { global.level += 1; let caches = &mut crate::eval::Caches::new(); - let mut this_ptr = this_ptr; return context.engine().call_script_fn( global, caches, &mut crate::Scope::new(), - this_ptr.as_deref_mut(), + this_ptr, self.encapsulated_environ(), - &fn_def, + fn_def, args, true, context.position(), @@ -361,6 +359,9 @@ impl FnPtr { /// Make a call to a function pointer with either a specified number of arguments, or with extra /// arguments attached. /// + /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`. + /// When an appropriate function is not found, it is then removed and mapped to the first parameter. + /// /// This is useful for calling predicate closures within an iteration loop where the extra argument /// is the current element's index. /// @@ -382,6 +383,9 @@ impl FnPtr { /// or with extra arguments attached. /// Exported under the `internals` feature only. /// + /// If `this_ptr` is provided, it is first provided to script-defined functions bound to `this`. + /// When an appropriate function is not found, it is then removed and mapped to the first parameter. + /// /// This is useful for calling predicate closures within an iteration loop where the extra /// argument is the current element's index. /// @@ -411,24 +415,46 @@ impl FnPtr { ) -> RhaiResult { #[cfg(not(feature = "no_function"))] if let Some(arity) = self.fn_def().map(|f| f.params.len()) { + if arity == N + 1 && this_ptr.is_some() { + let mut args = FnArgsVec::with_capacity(items.len() + 1); + args.push(this_ptr.as_mut().unwrap().clone()); + args.extend(items); + return self.call_raw(ctx, None, args); + } if arity == N { - return self.call_raw(&ctx, None, items); + return self.call_raw(ctx, this_ptr, items); } if arity == N + E { let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len()); items2.extend(IntoIterator::into_iter(items)); items2.extend(IntoIterator::into_iter(extras)); - return self.call_raw(&ctx, this_ptr, items2); + return self.call_raw(ctx, this_ptr, items2); } } - self.call_raw(&ctx, this_ptr.as_deref_mut(), items.clone()) + self.call_raw(ctx, this_ptr.as_deref_mut(), items.clone()) + .or_else(|err| match *err { + ERR::ErrorFunctionNotFound(sig, ..) + if this_ptr.is_some() && sig.starts_with(self.fn_name()) => + { + let mut args = FnArgsVec::with_capacity(items.len() + 1); + args.push(this_ptr.as_mut().unwrap().clone()); + args.extend(IntoIterator::into_iter(items.clone())); + self.call_raw(ctx, this_ptr.as_deref_mut(), args) + } + _ => Err(err), + }) .or_else(|err| match *err { ERR::ErrorFunctionNotFound(sig, ..) if sig.starts_with(self.fn_name()) => { - let mut items2 = FnArgsVec::with_capacity(items.len() + extras.len()); - items2.extend(IntoIterator::into_iter(items)); - items2.extend(IntoIterator::into_iter(extras)); - self.call_raw(&ctx, this_ptr, items2) + let mut args = FnArgsVec::with_capacity( + items.len() + extras.len() + if this_ptr.is_some() { 1 } else { 0 }, + ); + if let Some(ref mut this_ptr) = this_ptr { + args.push(this_ptr.clone()); + } + args.extend(IntoIterator::into_iter(items)); + args.extend(IntoIterator::into_iter(extras)); + self.call_raw(ctx, this_ptr, args) } _ => Err(err), }) diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 6e29e96f..19156ef8 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -220,7 +220,7 @@ impl fmt::Display for ParseErrorType { Self::ModuleUndefined(s) => write!(f, "Undefined module: {s}"), Self::MismatchedType(r, a) => write!(f, "Expecting {r}, not {a}"), - Self::ExprExpected(s) => write!(f, "Expecting {} expression", s), + Self::ExprExpected(s) => write!(f, "Expecting {s} expression"), Self::MissingToken(token, s) => write!(f, "Expecting '{token}' {s}"), Self::MissingSymbol(s) if s.is_empty() => f.write_str("Expecting a symbol"), diff --git a/src/types/position.rs b/src/types/position.rs index 2deee226..8bdaaab4 100644 --- a/src/types/position.rs +++ b/src/types/position.rs @@ -78,9 +78,7 @@ impl Position { assert!(!self.is_none(), "cannot advance Position::none"); // Advance up to maximum position - if self.pos < u16::MAX { - self.pos += 1; - } + self.pos = self.pos.saturating_add(1); } /// Go backwards by one character position. /// @@ -136,7 +134,7 @@ impl Position { #[inline] pub(crate) fn debug_print(self, _f: &mut fmt::Formatter<'_>) -> fmt::Result { if !self.is_none() { - write!(_f, " @ {:?}", self)?; + write!(_f, " @ {self:?}")?; } Ok(()) } @@ -166,12 +164,10 @@ impl fmt::Debug for Position { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_none() { f.write_str("none") + } else if self.is_beginning_of_line() { + write!(f, "{}", self.line) } else { - if self.is_beginning_of_line() { - write!(f, "{}", self.line) - } else { - write!(f, "{}:{}", self.line, self.pos) - } + write!(f, "{}:{}", self.line, self.pos) } } } @@ -261,10 +257,10 @@ impl fmt::Display for Span { match (self.start(), self.end()) { (Position::NONE, Position::NONE) => write!(_f, "{:?}", Position::NONE), - (Position::NONE, end) => write!(_f, "..{:?}", end), - (start, Position::NONE) => write!(_f, "{:?}", start), + (Position::NONE, end) => write!(_f, "..{end:?}"), + (start, Position::NONE) => write!(_f, "{start:?}"), (start, end) if start.line() != end.line() => { - write!(_f, "{:?}-{:?}", start, end) + write!(_f, "{start:?}-{end:?}") } (start, end) => write!( _f, diff --git a/src/types/scope.rs b/src/types/scope.rs index c6320861..712298fe 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -696,7 +696,6 @@ impl Scope<'_> { /// /// Panics if the index is out of bounds. #[inline] - #[must_use] pub(crate) fn get_mut_by_index(&mut self, index: usize) -> &mut Dynamic { self.values.get_mut(index).unwrap() } diff --git a/tests/arrays.rs b/tests/arrays.rs index cb27331c..f2343301 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -298,6 +298,7 @@ fn test_arrays_map_reduce() -> Result<(), Box> { let engine = Engine::new(); assert_eq!(engine.eval::("[1].map(|x| x + 41)[0]")?, 42); + assert_eq!(engine.eval::("[1].map(|| this + 41)[0]")?, 42); assert_eq!(engine.eval::("([1].map(|x| x + 41))[0]")?, 42); assert_eq!( engine.eval::("let c = 40; let y = 1; [1].map(|x, i| c + x + y + i)[0]")?, @@ -316,6 +317,18 @@ fn test_arrays_map_reduce() -> Result<(), Box> { [3] ); + assert_eq!( + engine + .eval::( + " + let x = [1, 2, 3]; + x.filter(|| this > 2) + " + )? + .into_typed_array::()?, + [3] + ); + assert_eq!( engine .eval::( @@ -340,6 +353,18 @@ fn test_arrays_map_reduce() -> Result<(), Box> { [2, 4, 6] ); + assert_eq!( + engine + .eval::( + " + let x = [1, 2, 3]; + x.map(|| this * 2) + " + )? + .into_typed_array::()?, + [2, 4, 6] + ); + assert_eq!( engine .eval::(