Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
- Provide additional context in error message when `unit` is expected. https://github.com/rescript-lang/rescript-compiler/pull/7045
- Improve error message when passing an object where a record is expected. https://github.com/rescript-lang/rescript-compiler/pull/7101
- Improve code generation or pattern matching of untagged variants. https://github.com/rescript-lang/rescript-compiler/pull/7128
- Improve negation handling in combination with and/or to simplify generated code (especially coming out of pattern matching). https://github.com/rescript-lang/rescript-compiler/pull/7138


#### :house: Internal

Expand Down
147 changes: 127 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,81 @@ 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}
| x, y when x = y -> Some e1
| _ -> None

(**
Expand All @@ -802,6 +893,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 +914,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
2 changes: 1 addition & 1 deletion tests/tests/src/and_or_simplify.res
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
@unboxed
type t = | @as(null) Null | @as(undefined) Undefined | B(bool) //| S(string)
type t = | @as(null) Null | @as(undefined) Undefined | B(bool) | S(string) | I(int)

let check_null_eq_typeof = x =>
switch x {
Expand Down
4 changes: 2 additions & 2 deletions tests/tests/src/option_repr_test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ b("File \"option_repr_test.res\", line 127, characters 3-10", Belt_List.every(xs
let xs_1$1 = {
hd: neqx(undefined, null),
tl: {
hd: Primitive_object.equal(Primitive_option.some(undefined), Primitive_option.some(undefined)) && Primitive_object.equal(Primitive_option.some(undefined), Primitive_option.some(undefined)),
hd: Primitive_object.equal(Primitive_option.some(undefined), Primitive_option.some(undefined)),
tl: {
hd: Primitive_object.equal(Primitive_option.some(Primitive_option.some(undefined)), Primitive_option.some(Primitive_option.some(undefined))) && Primitive_object.equal(Primitive_option.some(Primitive_option.some(undefined)), Primitive_option.some(Primitive_option.some(undefined))),
hd: Primitive_object.equal(Primitive_option.some(Primitive_option.some(undefined)), Primitive_option.some(Primitive_option.some(undefined))),
tl: {
hd: Primitive_object.notequal(Primitive_option.some(Primitive_option.some(Primitive_option.some(undefined))), Primitive_option.some(Primitive_option.some(undefined))) && Primitive_object.notequal(Primitive_option.some(Primitive_option.some(undefined)), Primitive_option.some(Primitive_option.some(Primitive_option.some(undefined)))),
tl: /* [] */0
Expand Down
Loading