diff --git a/crates/oxc_ecmascript/src/side_effects/context.rs b/crates/oxc_ecmascript/src/side_effects/context.rs index a4a27fbd76b9c..8910e5d436087 100644 --- a/crates/oxc_ecmascript/src/side_effects/context.rs +++ b/crates/oxc_ecmascript/src/side_effects/context.rs @@ -2,6 +2,18 @@ use oxc_ast::ast::Expression; use crate::is_global_reference::IsGlobalReference; +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)] +pub enum PropertyReadSideEffects { + /// Treat all property read accesses as side effect free. + None, + /// Treat non-member property accesses as side effect free. + /// Member property accesses are still considered to have side effects. + OnlyMemberPropertyAccess, + /// Treat all property read accesses as possible side effects. + #[default] + All, +} + pub trait MayHaveSideEffectsContext: IsGlobalReference { /// Whether to respect the pure annotations. /// @@ -14,4 +26,7 @@ pub trait MayHaveSideEffectsContext: IsGlobalReference { /// This function is called for normal function calls, new calls, and /// tagged template calls (`foo()`, `new Foo()`, ``foo`b` ``). fn is_pure_call(&self, callee: &Expression) -> bool; + + /// Whether property read accesses have side effects. + fn property_read_side_effects(&self) -> PropertyReadSideEffects; } diff --git a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs index d8b691cd24ba3..2bc08672491b6 100644 --- a/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs +++ b/crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs @@ -5,7 +5,7 @@ use crate::{ to_primitive::ToPrimitive, }; -use super::context::MayHaveSideEffectsContext; +use super::{PropertyReadSideEffects, context::MayHaveSideEffectsContext}; /// Returns true if subtree changes application state. /// @@ -398,7 +398,9 @@ impl MayHaveSideEffects for MemberExpression<'_> { match self { MemberExpression::ComputedMemberExpression(e) => e.may_have_side_effects(ctx), MemberExpression::StaticMemberExpression(e) => e.may_have_side_effects(ctx), - MemberExpression::PrivateFieldExpression(_) => true, + MemberExpression::PrivateFieldExpression(_) => { + ctx.property_read_side_effects() != PropertyReadSideEffects::None + } } } } @@ -446,6 +448,9 @@ fn property_access_may_have_side_effects( if object.may_have_side_effects(ctx) { return true; } + if ctx.property_read_side_effects() == PropertyReadSideEffects::None { + return false; + } match property { "length" => { @@ -464,6 +469,9 @@ fn integer_index_property_access_may_have_side_effects( if object.may_have_side_effects(ctx) { return true; } + if ctx.property_read_side_effects() == PropertyReadSideEffects::None { + return false; + } match object { Expression::StringLiteral(s) => property as usize >= s.value.encode_utf16().count(), Expression::ArrayExpression(arr) => property as usize >= get_array_minimum_length(arr), diff --git a/crates/oxc_ecmascript/src/side_effects/mod.rs b/crates/oxc_ecmascript/src/side_effects/mod.rs index 9c356cf5f289e..5c54fd8895a89 100644 --- a/crates/oxc_ecmascript/src/side_effects/mod.rs +++ b/crates/oxc_ecmascript/src/side_effects/mod.rs @@ -1,5 +1,5 @@ mod context; mod may_have_side_effects; -pub use context::MayHaveSideEffectsContext; +pub use context::{MayHaveSideEffectsContext, PropertyReadSideEffects}; pub use may_have_side_effects::MayHaveSideEffects; diff --git a/crates/oxc_minifier/src/ctx.rs b/crates/oxc_minifier/src/ctx.rs index b0e9f48e09328..0c38b906c6496 100644 --- a/crates/oxc_minifier/src/ctx.rs +++ b/crates/oxc_minifier/src/ctx.rs @@ -4,7 +4,7 @@ use oxc_ast::{AstBuilder, ast::*}; use oxc_ecmascript::constant_evaluation::{ ConstantEvaluation, ConstantEvaluationCtx, ConstantValue, binary_operation_evaluate_value, }; -use oxc_ecmascript::side_effects::MayHaveSideEffects; +use oxc_ecmascript::side_effects::{MayHaveSideEffects, PropertyReadSideEffects}; use oxc_semantic::{IsGlobalReference, Scoping}; use oxc_traverse::TraverseCtx; @@ -33,6 +33,10 @@ impl oxc_ecmascript::side_effects::MayHaveSideEffectsContext for Ctx<'_, '_> { fn is_pure_call(&self, _callee: &Expression) -> bool { false } + + fn property_read_side_effects(&self) -> PropertyReadSideEffects { + PropertyReadSideEffects::All + } } impl<'a> ConstantEvaluationCtx<'a> for Ctx<'a, '_> { diff --git a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs index 5e6fecec16eb8..c4b1287a4a71f 100644 --- a/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs +++ b/crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs @@ -2,7 +2,7 @@ use oxc_allocator::Allocator; use oxc_ast::ast::{Expression, IdentifierReference, Statement}; use oxc_ecmascript::{ is_global_reference::IsGlobalReference, - side_effects::{MayHaveSideEffects, MayHaveSideEffectsContext}, + side_effects::{MayHaveSideEffects, MayHaveSideEffectsContext, PropertyReadSideEffects}, }; use oxc_parser::Parser; use oxc_span::SourceType; @@ -11,10 +11,16 @@ struct Ctx { global_variable_names: Vec, annotation: bool, pure_function_names: Vec, + property_read_side_effects: PropertyReadSideEffects, } impl Default for Ctx { fn default() -> Self { - Self { global_variable_names: vec![], annotation: true, pure_function_names: vec![] } + Self { + global_variable_names: vec![], + annotation: true, + pure_function_names: vec![], + property_read_side_effects: PropertyReadSideEffects::All, + } } } impl IsGlobalReference for Ctx { @@ -34,6 +40,10 @@ impl MayHaveSideEffectsContext for Ctx { false } } + + fn property_read_side_effects(&self) -> PropertyReadSideEffects { + self.property_read_side_effects + } } fn test(source_text: &str, expected: bool) { @@ -750,6 +760,31 @@ fn test_is_pure_call_support() { test_with_ctx("bar``", &ctx, true); } +#[test] +fn test_property_read_side_effects_support() { + let all_ctx = + Ctx { property_read_side_effects: PropertyReadSideEffects::All, ..Default::default() }; + let only_member_ctx = Ctx { + property_read_side_effects: PropertyReadSideEffects::OnlyMemberPropertyAccess, + ..Default::default() + }; + let none_ctx = + Ctx { property_read_side_effects: PropertyReadSideEffects::None, ..Default::default() }; + + test_with_ctx("foo.bar", &all_ctx, true); + test_with_ctx("foo.bar", &only_member_ctx, true); + test_with_ctx("foo.bar", &none_ctx, false); + test_with_ctx("foo[0]", &none_ctx, false); + test_with_ctx("foo[0n]", &none_ctx, false); + test_with_ctx("foo[bar()]", &none_ctx, true); + test_with_ctx("foo.#bar", &all_ctx, true); + test_with_ctx("foo.#bar", &only_member_ctx, true); + test_with_ctx("foo.#bar", &none_ctx, false); + test_with_ctx("({ bar } = foo)", &all_ctx, true); + // test_with_ctx("({ bar } = foo)", &only_member_ctx, false); + // test_with_ctx("({ bar } = foo)", &none_ctx, false); +} + #[test] fn test_object_with_to_primitive_related_properties_overridden() { test("+{}", false);