diff --git a/Cargo.lock b/Cargo.lock index e9b84e2a181..5a803c54543 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,6 +347,7 @@ dependencies = [ "clap", "color-eyre", "colored", + "cow-utils", "dhat", "jemallocator", "phf", @@ -508,6 +509,7 @@ dependencies = [ name = "boa_macros" version = "0.20.0" dependencies = [ + "cow-utils", "proc-macro2", "quote", "syn", @@ -587,6 +589,7 @@ dependencies = [ "color-eyre", "colored", "comfy-table", + "cow-utils", "phf", "rayon", "rustc-hash 2.1.0", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index d4d5bf6c0be..ed4987adddb 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -25,6 +25,7 @@ phf = { workspace = true, features = ["macros"] } pollster.workspace = true dhat = { workspace = true, optional = true } color-eyre.workspace = true +cow-utils.workspace = true [features] default = ["boa_engine/annex-b", "boa_engine/experimental", "boa_engine/intl_bundled"] diff --git a/cli/src/debug/function.rs b/cli/src/debug/function.rs index 8337ba708b7..8edc167d16b 100644 --- a/cli/src/debug/function.rs +++ b/cli/src/debug/function.rs @@ -5,6 +5,7 @@ use boa_engine::{ vm::flowgraph::{Direction, Graph}, Context, JsArgs, JsNativeError, JsObject, JsResult, JsValue, NativeFunction, }; +use cow_utils::CowUtils; use crate::FlowgraphFormat; @@ -14,7 +15,7 @@ fn flowgraph_parse_format_option(value: &JsValue) -> JsResult { } if let Some(string) = value.as_string() { - return match string.to_std_string_escaped().to_lowercase().as_str() { + return match string.to_std_string_escaped().cow_to_lowercase().as_ref() { "mermaid" => Ok(FlowgraphFormat::Mermaid), "graphviz" => Ok(FlowgraphFormat::Graphviz), format => Err(JsNativeError::typ() @@ -34,7 +35,7 @@ fn flowgraph_parse_direction_option(value: &JsValue) -> JsResult { } if let Some(string) = value.as_string() { - return match string.to_std_string_escaped().to_lowercase().as_str() { + return match string.to_std_string_escaped().cow_to_lowercase().as_ref() { "leftright" | "lr" => Ok(Direction::LeftToRight), "rightleft" | "rl" => Ok(Direction::RightToLeft), "topbottom" | "tb" => Ok(Direction::TopToBottom), diff --git a/clippy.toml b/clippy.toml index 59ef99e1fe1..5b248b4ae4b 100644 --- a/clippy.toml +++ b/clippy.toml @@ -1,5 +1,10 @@ doc-valid-idents = ['ECMAScript', 'JavaScript', 'SpiderMonkey', 'GitHub'] allow-print-in-tests = true disallowed-methods = [ + { path = "str::to_ascii_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_lowercase` instead." }, { path = "str::to_ascii_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_ascii_uppercase` instead." }, + { path = "str::to_lowercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_lowercase` instead." }, + { path = "str::to_uppercase", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_to_uppercase` instead." }, + { path = "str::replace", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replace` instead." }, + { path = "str::replacen", reason = "To avoid memory allocation, use `cow_utils::CowUtils::cow_replacen` instead." }, ] diff --git a/core/engine/src/builtins/number/globals.rs b/core/engine/src/builtins/number/globals.rs index c9ef2a4b2b8..1418e32837f 100644 --- a/core/engine/src/builtins/number/globals.rs +++ b/core/engine/src/builtins/number/globals.rs @@ -8,6 +8,7 @@ use crate::{ }; use boa_macros::js_str; +use cow_utils::CowUtils; /// Builtin javascript 'isFinite(number)' function. /// @@ -304,8 +305,8 @@ pub(crate) fn parse_float( // TODO: parse float with optimal utf16 algorithm let input_string = val.to_string(context)?.to_std_string_escaped(); let s = input_string.trim_start_matches(is_trimmable_whitespace); - let s_prefix_lower = s.chars().take(4).collect::().to_ascii_lowercase(); - + let s_prefix = s.chars().take(4).collect::(); + let s_prefix_lower = s_prefix.cow_to_ascii_lowercase(); // TODO: write our own lexer to match syntax StrDecimalLiteral if s.starts_with("Infinity") || s.starts_with("+Infinity") { Ok(JsValue::new(f64::INFINITY)) diff --git a/core/engine/src/builtins/number/mod.rs b/core/engine/src/builtins/number/mod.rs index 2611800feba..b9b0d86e019 100644 --- a/core/engine/src/builtins/number/mod.rs +++ b/core/engine/src/builtins/number/mod.rs @@ -26,6 +26,7 @@ use crate::{ Context, JsArgs, JsResult, JsString, }; use boa_profiler::Profiler; +use cow_utils::CowUtils; use num_traits::float::FloatCore; mod globals; @@ -916,7 +917,7 @@ impl Number { /// Helper function that formats a float as a ES6-style exponential number string. fn f64_to_exponential(n: f64) -> JsString { js_string!(match n.abs() { - x if x >= 1.0 || x == 0.0 => format!("{n:e}").replace('e', "e+"), + x if x >= 1.0 || x == 0.0 => format!("{n:e}").cow_replace('e', "e+").to_string(), _ => format!("{n:e}"), }) } diff --git a/core/engine/src/builtins/string/mod.rs b/core/engine/src/builtins/string/mod.rs index 77d75771af5..96113d4580b 100644 --- a/core/engine/src/builtins/string/mod.rs +++ b/core/engine/src/builtins/string/mod.rs @@ -25,6 +25,7 @@ use crate::{ use boa_macros::utf16; use boa_profiler::Profiler; +use cow_utils::CowUtils; use icu_normalizer::{ComposingNormalizer, DecomposingNormalizer}; use std::cmp::{max, min}; @@ -1727,9 +1728,9 @@ impl String { // the Unicode Default Case Conversion algorithm. let text = string.map_valid_segments(|s| { if UPPER { - s.to_uppercase() + s.cow_to_uppercase().to_string() } else { - s.to_lowercase() + s.cow_to_lowercase().to_string() } }); diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 5d989d086f6..e9979fc2d0e 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -17,6 +17,7 @@ use crate::{ }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; +use cow_utils::CowUtils; use num_traits::ToPrimitive; use temporal_rs::{ options::{ArithmeticOverflow, Disambiguation, OffsetDisambiguation}, @@ -453,7 +454,7 @@ impl ZonedDateTime { let era = zdt.inner.era_with_provider(context.tz_provider())?; Ok(era - .map(|tinystr| JsString::from(tinystr.to_lowercase())) + .map(|tinystr| JsString::from(tinystr.cow_to_lowercase().to_string())) .into_or_undefined()) } diff --git a/core/macros/Cargo.toml b/core/macros/Cargo.toml index 6f43d7dd8cb..81fc6c29872 100644 --- a/core/macros/Cargo.toml +++ b/core/macros/Cargo.toml @@ -12,6 +12,7 @@ rust-version.workspace = true proc-macro = true [dependencies] +cow-utils.workspace = true quote.workspace = true syn = { workspace = true, features = ["full"] } proc-macro2.workspace = true diff --git a/core/macros/src/lib.rs b/core/macros/src/lib.rs index b5247c4b59b..9f22e884a4f 100644 --- a/core/macros/src/lib.rs +++ b/core/macros/src/lib.rs @@ -6,6 +6,7 @@ )] #![cfg_attr(not(test), forbid(clippy::unwrap_used))] +use cow_utils::CowUtils; use proc_macro::TokenStream; use proc_macro2::Literal; use quote::{quote, ToTokens}; @@ -65,14 +66,14 @@ impl Parse for Static { let ident = if let Some(ident) = ident { syn::parse2::(ident.into_token_stream())? } else { - Ident::new(&literal.value().to_uppercase(), literal.span()) + Ident::new(&literal.value().cow_to_uppercase(), literal.span()) }; Ok(Self { literal, ident }) } Expr::Lit(expr) => match expr.lit { Lit::Str(str) => Ok(Self { - ident: Ident::new(&str.value().to_uppercase(), str.span()), + ident: Ident::new(&str.value().cow_to_uppercase(), str.span()), literal: str, }), _ => Err(syn::Error::new_spanned( @@ -108,9 +109,9 @@ pub fn static_syms(input: TokenStream) -> TokenStream { "Symbol for the \"{}\" string.", lit.literal .value() - .replace('<', r"\<") - .replace('>', r"\>") - .replace('*', r"\*") + .cow_replace('<', r"\<") + .cow_replace('>', r"\>") + .cow_replace('*', r"\*") ); let ident = &lit.ident; idx += 1; diff --git a/tests/tester/Cargo.toml b/tests/tester/Cargo.toml index f2e5f165df3..ce6f3b9023b 100644 --- a/tests/tester/Cargo.toml +++ b/tests/tester/Cargo.toml @@ -29,6 +29,7 @@ phf = { workspace = true, features = ["macros"] } comfy-table.workspace = true serde_repr.workspace = true bus.workspace = true +cow-utils.workspace = true [features] default = ["boa_engine/intl_bundled", "boa_engine/experimental", "boa_engine/annex-b"] diff --git a/tests/tester/src/read.rs b/tests/tester/src/read.rs index 1cc85a3a925..f9af9bd4ce9 100644 --- a/tests/tester/src/read.rs +++ b/tests/tester/src/read.rs @@ -11,6 +11,7 @@ use color_eyre::{ eyre::{OptionExt, WrapErr}, Result, }; +use cow_utils::CowUtils; use rustc_hash::{FxBuildHasher, FxHashMap}; use serde::Deserialize; @@ -247,7 +248,7 @@ fn read_metadata(test: &Path) -> Result { let (metadata, _) = metadata .split_once("---*/") .ok_or_eyre("invalid test metadata")?; - let metadata = metadata.replace('\r', "\n"); + let metadata = metadata.cow_replace('\r', "\n"); serde_yaml::from_str(&metadata).map_err(Into::into) }