Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions crates/oxc_ecmascript/src/side_effects/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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;
}
12 changes: 10 additions & 2 deletions crates/oxc_ecmascript/src/side_effects/may_have_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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
}
}
}
}
Expand Down Expand Up @@ -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" => {
Expand All @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion crates/oxc_ecmascript/src/side_effects/mod.rs
Original file line number Diff line number Diff line change
@@ -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;
6 changes: 5 additions & 1 deletion crates/oxc_minifier/src/ctx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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, '_> {
Expand Down
39 changes: 37 additions & 2 deletions crates/oxc_minifier/tests/ecmascript/may_have_side_effects.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -11,10 +11,16 @@ struct Ctx {
global_variable_names: Vec<String>,
annotation: bool,
pure_function_names: Vec<String>,
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 {
Expand All @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down
Loading