Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
146 changes: 126 additions & 20 deletions compiler/core/js_exp_make.ml
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,36 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t =
let string_of_expression = ref (fun _ -> "")
let debug = false

(**
[push_negation e] attempts to simplify a negated expression by pushing the negation
deeper into the expression tree. Returns [Some simplified] if simplification is possible,
[None] otherwise.

Simplification rules:
- [!(not e)] -> [e]
- [!true] -> [false]
- [!false] -> [true]
- [!(a === b)] -> [a !== b]
- [!(a !== b)] -> [a === b]
- [!(a && b)] -> [!a || !b] (De Morgan's law)
- [!(a || b)] -> [!a && !b] (De Morgan's law)
*)
let rec push_negation (e : t) : t option =
match e.expression_desc with
| Js_not e -> Some e
| Bool b -> Some (bool (not b))
| Bin (EqEqEq, a, b) -> Some {e with expression_desc = Bin (NotEqEq, a, b)}
| Bin (NotEqEq, a, b) -> Some {e with expression_desc = Bin (EqEqEq, a, b)}
| Bin (And, a, b) -> (
match (push_negation a, push_negation b) with
| Some a_, Some b_ -> Some {e with expression_desc = Bin (Or, a_, b_)}
| _ -> None)
| Bin (Or, a, b) -> (
match (push_negation a, push_negation b) with
| Some a_, Some b_ -> Some {e with expression_desc = Bin (And, a_, b_)}
| _ -> None)
| _ -> None

(**
[simplify_and e1 e2] attempts to simplify the boolean AND expression [e1 && e2].
Returns [Some simplified] if simplification is possible, [None] otherwise.
Expand All @@ -681,7 +711,12 @@ let debug = false

Type check optimizations:
- [(typeof x === "boolean") && (x === true/false)] -> [x === true/false]
- [(typeof x === "string" || Array.isArray(x)) && (x === boolean/null/undefined)] -> [false]
- [(typeof x ==="boolean" | "string" | "number") && (x === boolean/null/undefined)] -> [false]
- [(Array.isArray(x)) && (x === boolean/null/undefined)] -> [false]

- [(typeof x === "boolean") && (x !== true/false)] -> unchanged
- [(typeof x === "boolean" | "string" | "number") && (x !== boolean/null/undefined)] -> [typeof x === ...]
- [(Array.isArray(x)) && (x !== boolean/null/undefined)] -> [Array.isArray(x)]

Note: The function preserves the semantics of the original expression while
attempting to reduce it to a simpler form. If no simplification is possible,
Expand Down Expand Up @@ -760,15 +795,8 @@ let rec simplify_and (e1 : t) (e2 : t) : t option =
Some {expression_desc = b; comment = None}
| ( Bin
( EqEqEq,
{
expression_desc =
( Typeof {expression_desc = Var ia}
| Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ) );
},
{expression_desc = Str {txt = "boolean" | "string"}} ),
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean" | "string" | "number"}} ),
Bin
( EqEqEq,
{expression_desc = Var ib},
Expand All @@ -779,18 +807,80 @@ let rec simplify_and (e1 : t) (e2 : t) : t option =
{expression_desc = Bool _ | Null | Undefined _} ),
Bin
( EqEqEq,
{
expression_desc =
( Typeof {expression_desc = Var ia}
| Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ) );
},
{expression_desc = Str {txt = "boolean" | "string"}} ) )
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean" | "string" | "number"}} ) )
when Js_op_util.same_vident ia ib ->
(* Note: case boolean / Bool _ is handled above *)
Some false_
| ( Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ),
Bin
( EqEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ) )
| ( Bin
( EqEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ),
Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ) )
when Js_op_util.same_vident ia ib ->
Some false_
| ( Bin
( EqEqEq,
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean"}} ),
Bin (NotEqEq, {expression_desc = Var ib}, {expression_desc = Bool _}) )
| ( Bin (NotEqEq, {expression_desc = Var ib}, {expression_desc = Bool _}),
Bin
( EqEqEq,
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean"}} ) )
when Js_op_util.same_vident ia ib ->
None
| ( (Bin
( EqEqEq,
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean" | "string" | "number"}} ) as
typeof),
Bin
( NotEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ) )
| ( Bin
( NotEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ),
(Bin
( EqEqEq,
{expression_desc = Typeof {expression_desc = Var ia}},
{expression_desc = Str {txt = "boolean" | "string" | "number"}} ) as
typeof) )
when Js_op_util.same_vident ia ib ->
(* Note: case boolean / Bool _ is handled above *)
Some {expression_desc = typeof; comment = None}
| ( (Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ) as is_array),
Bin
( NotEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ) )
| ( Bin
( NotEqEq,
{expression_desc = Var ib},
{expression_desc = Bool _ | Null | Undefined _} ),
(Call
( {expression_desc = Str {txt = "Array.isArray"}},
[{expression_desc = Var ia}],
_ ) as is_array) )
when Js_op_util.same_vident ia ib ->
Some {expression_desc = is_array; comment = None}
| _ -> None

(**
Expand All @@ -802,6 +892,16 @@ let rec simplify_and (e1 : t) (e2 : t) : t option =
- [e || true] -> [true]
- [false || e] -> [e]
- [e || false] -> [e]

Algebraic transformation strategy:
When basic rules don't apply, attempts to leverage [simplify_and] by:
1. Using the equivalence: [e1 || e2 ≡ !(!(e1) && !(e2))]
2. Applying [push_negation] to get [!(e1)] and [!(e2)]
3. Using [simplify_and] on these negated terms
4. If successful, applying [push_negation] again to get the final result

This transformation allows reuse of [simplify_and]'s more extensive optimizations
in the context of OR expressions.
*)
and simplify_or (e1 : t) (e2 : t) : t option =
if debug then
Expand All @@ -813,7 +913,13 @@ and simplify_or (e1 : t) (e2 : t) : t option =
| _, Bool true -> Some true_
| Bool false, _ -> Some e2
| _, Bool false -> Some e1
| _ -> None
| _ -> (
match (push_negation e1, push_negation e2) with
| Some e1_, Some e2_ -> (
match simplify_and e1_ e2_ with
| Some e -> push_negation e
| None -> None)
| _ -> None)

let and_ ?comment (e1 : t) (e2 : t) : t =
match (e1.expression_desc, e2.expression_desc) with
Expand Down
16 changes: 4 additions & 12 deletions tests/tests/src/and_or_simplify.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,23 @@


function check_null_eq_typeof(x) {
if (typeof x !== "boolean" || x !== null) {
return 4;
} else {
return 3;
}
return 4;
}

function check_null_neq_typeof(x) {
if (typeof x !== "boolean" || x === null) {
if (typeof x !== "boolean") {
return 4;
} else {
return 3;
}
}

function check_undefined_eq_typeof(x) {
if (typeof x !== "boolean" || x !== undefined) {
return 4;
} else {
return 3;
}
return 4;
}

function check_undefined_neq_typeof(x) {
if (typeof x !== "boolean" || x === undefined) {
if (typeof x !== "boolean") {
return 4;
} else {
return 3;
Expand Down
Loading