diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index 478741be2e1b7..db8e33834c3b9 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -25,7 +25,7 @@ oxc-browserslist = { workspace = true } oxc_allocator = { workspace = true } oxc_ast = { workspace = true } oxc_ast_visit = { workspace = true } -oxc_data_structures = { workspace = true, features = ["inline_string", "rope", "stack"] } +oxc_data_structures = { workspace = true, features = ["assert_unchecked", "inline_string", "rope", "stack"] } oxc_diagnostics = { workspace = true } oxc_ecmascript = { workspace = true } oxc_parser = { workspace = true } diff --git a/crates/oxc_transformer/src/plugins/styled_components.rs b/crates/oxc_transformer/src/plugins/styled_components.rs index 845df41cb4a9b..07fd71ef26a8b 100644 --- a/crates/oxc_transformer/src/plugins/styled_components.rs +++ b/crates/oxc_transformer/src/plugins/styled_components.rs @@ -65,7 +65,7 @@ use serde::Deserialize; use oxc_allocator::{TakeIn, Vec as ArenaVec}; use oxc_ast::{AstBuilder, NONE, ast::*}; -use oxc_data_structures::inline_string::InlineString; +use oxc_data_structures::{assert_unchecked, inline_string::InlineString}; use oxc_semantic::SymbolId; use oxc_span::SPAN; use oxc_traverse::{Ancestor, Traverse}; @@ -1093,11 +1093,26 @@ fn minify_template_literal<'a>(lit: &mut TemplateLiteral<'a>, ast: AstBuilder<'a let output_str = unsafe { std::str::from_utf8_unchecked(&output) }; quasis.last_mut().unwrap().value.raw = ast.atom(output_str); - // Remove quasis that are marked for removal, and the expressions following them + // Remove quasis that are marked for removal, and the expressions following them. + // TODO: Remove scopes, symbols, and references for removed `Expression`s. if delete_some { + // We assert the lengths of `quasis` and `expressions` here so that we can safely remove + // the bounds check in the `retain` loop. + // It should always be true that `quasis.len() == expressions.len() + 1` + // but `quasis.len() >= expressions.len()` is all we need to ensure safety of `assert_unchecked!` + // below, and it's a cheaper check. + assert!(quasis.len() >= expressions.len()); + let mut quasis_iter = quasis.iter(); - // TODO: Remove scopes, symbols, and references for removed `Expression`. - expressions.retain(|_| quasis_iter.next().unwrap().span != REMOVE_SENTINEL); + expressions.retain(|_| { + // This unchecked assertion removes bounds check in `unwrap`. + // SAFETY: We asserted above that there are at least as many quasis as expressions, + // so `quasis_iter` cannot be exhausted in this loop. + unsafe { assert_unchecked!(!quasis_iter.as_slice().is_empty()) }; + let quasi = quasis_iter.next().unwrap(); + quasi.span != REMOVE_SENTINEL + }); + quasis.retain(|quasi| quasi.span != REMOVE_SENTINEL); } }