From 2af3d1b86658d9ccb91acd9a706dc7df9877e60c Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 30 Oct 2024 06:13:24 +0100 Subject: [PATCH 1/6] Better logic to simplify and/or. Current logic for code generated by untagged unions pattern matching only focuses on certain cases of x == b where b is a boolean. There are more cases, and there are other constants than booleans: nil, undefined. --- compiler/core/js_exp_make.ml | 78 ++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 5997dafcce..673d70eb0e 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -662,31 +662,43 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t = be careful for side effect *) -let rec filter_bool (e : t) ~j ~b = +type filter_const = Ctrue | Cnull + +let rec filter_const (e : t) ~j ~b:eq ~const = match e.expression_desc with | Bin (And, e1, e2) -> ( - match (filter_bool e1 ~j ~b, filter_bool e2 ~j ~b) with + match + (filter_const e1 ~j ~b:eq ~const, filter_const e2 ~j ~b:eq ~const) + with | None, None -> None | Some e, None | None, Some e -> Some e | Some e1, Some e2 -> Some {e with expression_desc = Bin (And, e1, e2)}) | Bin (Or, e1, e2) -> ( - match (filter_bool e1 ~j ~b, filter_bool e2 ~j ~b) with + match + (filter_const e1 ~j ~b:eq ~const, filter_const e2 ~j ~b:eq ~const) + with | None, _ | _, None -> None | Some e1, Some e2 -> Some {e with expression_desc = Bin (Or, e1, e2)}) | Bin (EqEqEq, {expression_desc = Var i}, {expression_desc = Bool b1}) | Bin (EqEqEq, {expression_desc = Bool b1}, {expression_desc = Var i}) - when Js_op_util.same_vident i j -> - if b1 = b then None else Some e + when Js_op_util.same_vident i j && const = Ctrue -> + if b1 = eq then None else Some e | Bin (NotEqEq, {expression_desc = Var i}, {expression_desc = Bool b1}) | Bin (NotEqEq, {expression_desc = Bool b1}, {expression_desc = Var i}) - when Js_op_util.same_vident i j -> - if b1 <> b then None else Some e + when Js_op_util.same_vident i j && const = Ctrue -> + if b1 <> eq then None else Some e | Bin ( NotEqEq, {expression_desc = Typeof {expression_desc = Var i}}, - {expression_desc = Str {txt}} ) - when Js_op_util.same_vident i j -> - if txt <> "bool" then None else assert false + {expression_desc = Str {txt = "bool"}} ) + when Js_op_util.same_vident i j && const = Ctrue -> + None + | Bin + ( NotEqEq, + {expression_desc = Typeof {expression_desc = Var i}}, + {expression_desc = Str {txt = "string"}} ) + when Js_op_util.same_vident i j && (const = Cnull || const = Ctrue) -> + None | Js_not { expression_desc = @@ -695,7 +707,7 @@ let rec filter_bool (e : t) ~j ~b = [{expression_desc = Var i}], _ ); } - when Js_op_util.same_vident i j -> + when Js_op_util.same_vident i j && (const = Ctrue || const = Cnull) -> None | _ -> Some e @@ -714,8 +726,32 @@ let and_ ?comment (e1 : t) (e2 : t) : t = ) when Js_op_util.same_vident i j -> e2 - | _, Bin (EqEqEq, {expression_desc = Var j}, {expression_desc = Bool b}) -> ( - match filter_bool e1 ~j ~b with + | ( _, + Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Bool b} ) ) + | ( Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Bool b} ), + _ ) -> ( + match + filter_const e1 ~j ~b:(if op = EqEqEq then b else not b) ~const:Ctrue + with + | None -> e2 + | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) + | ( _, + Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Null} ) ) + | ( Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Null} ), + _ ) -> ( + match filter_const e1 ~j ~b:(op = EqEqEq) ~const:Cnull with | None -> e2 | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) | _, _ -> {expression_desc = Bin (And, e1, e2); comment} @@ -729,6 +765,22 @@ let or_ ?comment (e1 : t) (e2 : t) = | Var i, Bin (Or, l, ({expression_desc = Var j; _} as r)) when Js_op_util.same_vident i j -> {e2 with expression_desc = Bin (Or, r, l)} + | ( _, + Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Null} ) ) -> ( + match filter_const e1 ~j ~b:(op <> EqEqEq) ~const:Cnull with + | None -> e2 + | Some e1 -> {expression_desc = Bin (Or, e1, e2); comment}) + | ( Bin + ( ((EqEqEq | NotEqEq) as op), + {expression_desc = Var j}, + {expression_desc = Null} ), + _ ) -> ( + match filter_const e2 ~j ~b:(op <> EqEqEq) ~const:Cnull with + | None -> e1 + | Some e2 -> {expression_desc = Bin (Or, e1, e2); comment}) | _, _ -> {expression_desc = Bin (Or, e1, e2); comment} (* return a value of type boolean *) From dfc207beccec45fbab2e57124ab1c422cb53aab3 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 30 Oct 2024 11:13:11 +0100 Subject: [PATCH 2/6] Begin adding tests. --- compiler/core/js_dump.ml | 2 ++ compiler/core/js_exp_make.ml | 40 ++++++++++++++--------------- compiler/core/js_exp_make.mli | 2 ++ tests/tests/src/and_or_simplify.mjs | 24 +++++++++++++++++ tests/tests/src/and_or_simplify.res | 14 ++++++++++ 5 files changed, 62 insertions(+), 20 deletions(-) create mode 100644 tests/tests/src/and_or_simplify.mjs create mode 100644 tests/tests/src/and_or_simplify.res diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 5bad4c9602..4807fd0484 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -1348,3 +1348,5 @@ let string_of_expression (e : J.expression) = let (_ : cxt) = expression ~level:0 Ext_pp_scope.empty f e in P.flush f (); Buffer.contents buffer + +let () = E.string_of_expression := string_of_expression diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 673d70eb0e..bd3c5cc162 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -663,20 +663,26 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t = *) type filter_const = Ctrue | Cnull - -let rec filter_const (e : t) ~j ~b:eq ~const = +let string_of_filter_const = function + | Ctrue -> "Ctrue" + | Cnull -> "Cnull" + +let string_of_expression = ref (fun _ -> "") +let debug = false + +let rec filter_const (e : t) ~j ~eq ~const = + if debug then + Printf.eprintf "filter_const e:%s eq:%b const:%s\n" + (!string_of_expression e) eq + (string_of_filter_const const); match e.expression_desc with | Bin (And, e1, e2) -> ( - match - (filter_const e1 ~j ~b:eq ~const, filter_const e2 ~j ~b:eq ~const) - with + match (filter_const e1 ~j ~eq ~const, filter_const e2 ~j ~eq ~const) with | None, None -> None | Some e, None | None, Some e -> Some e | Some e1, Some e2 -> Some {e with expression_desc = Bin (And, e1, e2)}) | Bin (Or, e1, e2) -> ( - match - (filter_const e1 ~j ~b:eq ~const, filter_const e2 ~j ~b:eq ~const) - with + match (filter_const e1 ~j ~eq ~const, filter_const e2 ~j ~eq ~const) with | None, _ | _, None -> None | Some e1, Some e2 -> Some {e with expression_desc = Bin (Or, e1, e2)}) | Bin (EqEqEq, {expression_desc = Var i}, {expression_desc = Bool b1}) @@ -690,14 +696,8 @@ let rec filter_const (e : t) ~j ~b:eq ~const = | Bin ( NotEqEq, {expression_desc = Typeof {expression_desc = Var i}}, - {expression_desc = Str {txt = "bool"}} ) - when Js_op_util.same_vident i j && const = Ctrue -> - None - | Bin - ( NotEqEq, - {expression_desc = Typeof {expression_desc = Var i}}, - {expression_desc = Str {txt = "string"}} ) - when Js_op_util.same_vident i j && (const = Cnull || const = Ctrue) -> + {expression_desc = Str {txt = "boolean" | "string"}} ) + when Js_op_util.same_vident i j && (const = Ctrue || const = Cnull) -> None | Js_not { @@ -737,7 +737,7 @@ let and_ ?comment (e1 : t) (e2 : t) : t = {expression_desc = Bool b} ), _ ) -> ( match - filter_const e1 ~j ~b:(if op = EqEqEq then b else not b) ~const:Ctrue + filter_const e1 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue with | None -> e2 | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) @@ -751,7 +751,7 @@ let and_ ?comment (e1 : t) (e2 : t) : t = {expression_desc = Var j}, {expression_desc = Null} ), _ ) -> ( - match filter_const e1 ~j ~b:(op = EqEqEq) ~const:Cnull with + match filter_const e1 ~j ~eq:(op = EqEqEq) ~const:Cnull with | None -> e2 | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) | _, _ -> {expression_desc = Bin (And, e1, e2); comment} @@ -770,7 +770,7 @@ let or_ ?comment (e1 : t) (e2 : t) = ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, {expression_desc = Null} ) ) -> ( - match filter_const e1 ~j ~b:(op <> EqEqEq) ~const:Cnull with + match filter_const e1 ~j ~eq:(op <> EqEqEq) ~const:Cnull with | None -> e2 | Some e1 -> {expression_desc = Bin (Or, e1, e2); comment}) | ( Bin @@ -778,7 +778,7 @@ let or_ ?comment (e1 : t) (e2 : t) = {expression_desc = Var j}, {expression_desc = Null} ), _ ) -> ( - match filter_const e2 ~j ~b:(op <> EqEqEq) ~const:Cnull with + match filter_const e2 ~j ~eq:(op <> EqEqEq) ~const:Cnull with | None -> e1 | Some e2 -> {expression_desc = Bin (Or, e1, e2); comment}) | _, _ -> {expression_desc = Bin (Or, e1, e2); comment} diff --git a/compiler/core/js_exp_make.mli b/compiler/core/js_exp_make.mli index 0c6d9fe5f3..e890e4c6aa 100644 --- a/compiler/core/js_exp_make.mli +++ b/compiler/core/js_exp_make.mli @@ -372,3 +372,5 @@ val is_null_undefined : ?comment:string -> t -> t val make_exception : string -> t val variadic_args : t list -> t list + +val string_of_expression : (t -> string) ref diff --git a/tests/tests/src/and_or_simplify.mjs b/tests/tests/src/and_or_simplify.mjs new file mode 100644 index 0000000000..3d7fe142bd --- /dev/null +++ b/tests/tests/src/and_or_simplify.mjs @@ -0,0 +1,24 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + + +function check_null_typeof(x) { + if (x !== null) { + return 4; + } else { + return 3; + } +} + +function check_undefined_typeof(x) { + if (typeof x !== "boolean" || x !== undefined) { + return 4; + } else { + return 3; + } +} + +export { + check_null_typeof, + check_undefined_typeof, +} +/* No side effect */ diff --git a/tests/tests/src/and_or_simplify.res b/tests/tests/src/and_or_simplify.res new file mode 100644 index 0000000000..8d86471a3d --- /dev/null +++ b/tests/tests/src/and_or_simplify.res @@ -0,0 +1,14 @@ +@unboxed +type t = | @as(null) Null | @as(undefined) Undefined | B(bool) + +let check_null_typeof = x => + switch x { + | B(_) if x == Null => 3 + | _ => 4 + } + +let check_undefined_typeof = x => + switch x { + | B(_) if x == Undefined => 3 + | _ => 4 + } From c622282911af1b1188226df87106099bdffde885 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 30 Oct 2024 11:22:19 +0100 Subject: [PATCH 3/6] Handle undefined too. --- compiler/core/js_exp_make.ml | 47 +++++++++++++++++++---------- tests/tests/src/and_or_simplify.mjs | 2 +- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index bd3c5cc162..09c6e559f4 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -662,10 +662,11 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t = be careful for side effect *) -type filter_const = Ctrue | Cnull +type filter_const = Ctrue | Cnull | Cundefined let string_of_filter_const = function | Ctrue -> "Ctrue" | Cnull -> "Cnull" + | Cundefined -> "Cundefined" let string_of_expression = ref (fun _ -> "") let debug = false @@ -697,7 +698,8 @@ let rec filter_const (e : t) ~j ~eq ~const = ( NotEqEq, {expression_desc = Typeof {expression_desc = Var i}}, {expression_desc = Str {txt = "boolean" | "string"}} ) - when Js_op_util.same_vident i j && (const = Ctrue || const = Cnull) -> + when Js_op_util.same_vident i j + && (const = Ctrue || const = Cnull || const = Cundefined) -> None | Js_not { @@ -707,7 +709,8 @@ let rec filter_const (e : t) ~j ~eq ~const = [{expression_desc = Var i}], _ ); } - when Js_op_util.same_vident i j && (const = Ctrue || const = Cnull) -> + when Js_op_util.same_vident i j + && (const = Ctrue || const = Cnull || const = Cundefined) -> None | _ -> Some e @@ -730,30 +733,40 @@ let and_ ?comment (e1 : t) (e2 : t) : t = Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, - {expression_desc = Bool b} ) ) + {expression_desc = Bool b} ) ) -> ( + match + filter_const e1 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue + with + | None -> e2 + | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) | ( Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, {expression_desc = Bool b} ), _ ) -> ( match - filter_const e1 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue + filter_const e2 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue with - | None -> e2 - | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) + | None -> e1 + | Some e2 -> {expression_desc = Bin (And, e1, e2); comment}) | ( _, Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, - {expression_desc = Null} ) ) + {expression_desc = (Null | Undefined _) as c} ) ) -> ( + let const = if c == Null then Cnull else Cundefined in + match filter_const e1 ~j ~eq:(op = EqEqEq) ~const with + | None -> e2 + | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) | ( Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, - {expression_desc = Null} ), + {expression_desc = (Null | Undefined _) as c} ), _ ) -> ( - match filter_const e1 ~j ~eq:(op = EqEqEq) ~const:Cnull with - | None -> e2 - | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) + let const = if c == Null then Cnull else Cundefined in + match filter_const e2 ~j ~eq:(op = EqEqEq) ~const with + | None -> e1 + | Some e2 -> {expression_desc = Bin (And, e1, e2); comment}) | _, _ -> {expression_desc = Bin (And, e1, e2); comment} let or_ ?comment (e1 : t) (e2 : t) = @@ -769,16 +782,18 @@ let or_ ?comment (e1 : t) (e2 : t) = Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, - {expression_desc = Null} ) ) -> ( - match filter_const e1 ~j ~eq:(op <> EqEqEq) ~const:Cnull with + {expression_desc = (Null | Undefined _) as c} ) ) -> ( + let const = if c == Null then Cnull else Cundefined in + match filter_const e1 ~j ~eq:(op <> EqEqEq) ~const with | None -> e2 | Some e1 -> {expression_desc = Bin (Or, e1, e2); comment}) | ( Bin ( ((EqEqEq | NotEqEq) as op), {expression_desc = Var j}, - {expression_desc = Null} ), + {expression_desc = (Null | Undefined _) as c} ), _ ) -> ( - match filter_const e2 ~j ~eq:(op <> EqEqEq) ~const:Cnull with + let const = if c == Null then Cnull else Cundefined in + match filter_const e2 ~j ~eq:(op <> EqEqEq) ~const with | None -> e1 | Some e2 -> {expression_desc = Bin (Or, e1, e2); comment}) | _, _ -> {expression_desc = Bin (Or, e1, e2); comment} diff --git a/tests/tests/src/and_or_simplify.mjs b/tests/tests/src/and_or_simplify.mjs index 3d7fe142bd..233b9ca9ca 100644 --- a/tests/tests/src/and_or_simplify.mjs +++ b/tests/tests/src/and_or_simplify.mjs @@ -10,7 +10,7 @@ function check_null_typeof(x) { } function check_undefined_typeof(x) { - if (typeof x !== "boolean" || x !== undefined) { + if (x !== undefined) { return 4; } else { return 3; From a3f43efe4374fad4dd32ed64296fe8484511dd0d Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 30 Oct 2024 18:17:24 +0100 Subject: [PATCH 4/6] rewrite more directly --- compiler/core/js_exp_make.ml | 229 +++++++++++++++------------ tests/tests/src/UntaggedVariants.mjs | 4 +- tests/tests/src/and_or_simplify.mjs | 4 +- tests/tests/src/and_or_simplify.res | 2 +- tests/tests/src/option_repr_test.mjs | 4 +- 5 files changed, 130 insertions(+), 113 deletions(-) diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index 09c6e559f4..b2ca9e53c0 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -662,57 +662,126 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t = be careful for side effect *) -type filter_const = Ctrue | Cnull | Cundefined -let string_of_filter_const = function - | Ctrue -> "Ctrue" - | Cnull -> "Cnull" - | Cundefined -> "Cundefined" - let string_of_expression = ref (fun _ -> "") let debug = false -let rec filter_const (e : t) ~j ~eq ~const = +let rec simplify_and (e1 : t) (e2 : t) : t option = if debug then - Printf.eprintf "filter_const e:%s eq:%b const:%s\n" - (!string_of_expression e) eq - (string_of_filter_const const); - match e.expression_desc with - | Bin (And, e1, e2) -> ( - match (filter_const e1 ~j ~eq ~const, filter_const e2 ~j ~eq ~const) with - | None, None -> None - | Some e, None | None, Some e -> Some e - | Some e1, Some e2 -> Some {e with expression_desc = Bin (And, e1, e2)}) - | Bin (Or, e1, e2) -> ( - match (filter_const e1 ~j ~eq ~const, filter_const e2 ~j ~eq ~const) with - | None, _ | _, None -> None - | Some e1, Some e2 -> Some {e with expression_desc = Bin (Or, e1, e2)}) - | Bin (EqEqEq, {expression_desc = Var i}, {expression_desc = Bool b1}) - | Bin (EqEqEq, {expression_desc = Bool b1}, {expression_desc = Var i}) - when Js_op_util.same_vident i j && const = Ctrue -> - if b1 = eq then None else Some e - | Bin (NotEqEq, {expression_desc = Var i}, {expression_desc = Bool b1}) - | Bin (NotEqEq, {expression_desc = Bool b1}, {expression_desc = Var i}) - when Js_op_util.same_vident i j && const = Ctrue -> - if b1 <> eq then None else Some e - | Bin - ( NotEqEq, - {expression_desc = Typeof {expression_desc = Var i}}, - {expression_desc = Str {txt = "boolean" | "string"}} ) - when Js_op_util.same_vident i j - && (const = Ctrue || const = Cnull || const = Cundefined) -> - None - | Js_not - { - expression_desc = - Call - ( {expression_desc = Str {txt = "Array.isArray"}}, - [{expression_desc = Var i}], - _ ); - } - when Js_op_util.same_vident i j - && (const = Ctrue || const = Cnull || const = Cundefined) -> - None - | _ -> Some e + Printf.eprintf "simplify_and %s %s\n" (!string_of_expression e1) + (!string_of_expression e2); + + match (e1.expression_desc, e2.expression_desc) with + | Bool false, _ -> Some false_ + | _, Bool false -> Some false_ + | Bool true, _ -> Some e2 + | _, Bool true -> Some e1 + | Bin (And, a, b), _ -> ( + let ao = simplify_and a e2 in + let bo = simplify_and b e2 in + if ao = None && bo = None then None + else + let a_ = + match ao with + | None -> a + | Some a_ -> a_ + in + let b_ = + match bo with + | None -> b + | Some b_ -> b_ + in + match simplify_and a_ b_ with + | None -> Some {expression_desc = Bin (And, a_, b_); comment = None} + | Some e -> Some e) + | Bin (Or, a, b), _ -> ( + let ao = simplify_and a e2 in + let bo = simplify_and b e2 in + if ao = None && bo = None then None + else + let a_ = + match ao with + | None -> a + | Some a_ -> a_ + in + let b_ = + match bo with + | None -> b + | Some b_ -> b_ + in + match simplify_or a_ b_ with + | None -> Some {expression_desc = Bin (Or, a_, b_); comment = None} + | Some e -> Some e) + | ( Bin + ( ((EqEqEq | NotEqEq) as op1), + {expression_desc = Var i1}, + {expression_desc = Bool b1} ), + Bin + ( ((EqEqEq | NotEqEq) as op2), + {expression_desc = Var i2}, + {expression_desc = Bool b2} ) ) + when Js_op_util.same_vident i1 i2 -> + let op_eq = op1 = op2 in + let consistent = if op_eq then b1 = b2 else b1 <> b2 in + if consistent then Some e1 else Some false_ + | ( Bin + ( EqEqEq, + {expression_desc = Typeof {expression_desc = Var ia}}, + {expression_desc = Str {txt = "boolean"}} ), + (Bin (EqEqEq, {expression_desc = Var ib}, {expression_desc = Bool _}) as b) + ) + | ( (Bin (EqEqEq, {expression_desc = Var ib}, {expression_desc = Bool _}) as b), + Bin + ( EqEqEq, + {expression_desc = Typeof {expression_desc = Var ia}}, + {expression_desc = Str {txt = "boolean"}} ) ) + when Js_op_util.same_vident ia ib -> + 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"}} ), + Bin + ( EqEqEq, + {expression_desc = Var ib}, + {expression_desc = Bool _ | Null | Undefined _} ) ) + | ( Bin + ( EqEqEq, + {expression_desc = Var ib}, + {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"}} ) ) + when Js_op_util.same_vident ia ib -> + (* Note: case boolean / Bool _ is handled above *) + Some false_ + | _ -> None + +and simplify_or (e1 : t) (e2 : t) : t option = + if debug then + Printf.eprintf "simplify_or %s %s\n" (!string_of_expression e1) + (!string_of_expression e2); + + match (e1.expression_desc, e2.expression_desc) with + | Bool true, _ -> Some true_ + | _, Bool true -> Some true_ + | Bool false, _ -> Some e2 + | _, Bool false -> Some e1 + | _ -> None let and_ ?comment (e1 : t) (e2 : t) : t = match (e1.expression_desc, e2.expression_desc) with @@ -729,45 +798,10 @@ let and_ ?comment (e1 : t) (e2 : t) : t = ) when Js_op_util.same_vident i j -> e2 - | ( _, - Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = Bool b} ) ) -> ( - match - filter_const e1 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue - with - | None -> e2 - | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) - | ( Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = Bool b} ), - _ ) -> ( - match - filter_const e2 ~j ~eq:(if op = EqEqEq then b else not b) ~const:Ctrue - with - | None -> e1 - | Some e2 -> {expression_desc = Bin (And, e1, e2); comment}) - | ( _, - Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = (Null | Undefined _) as c} ) ) -> ( - let const = if c == Null then Cnull else Cundefined in - match filter_const e1 ~j ~eq:(op = EqEqEq) ~const with - | None -> e2 - | Some e1 -> {expression_desc = Bin (And, e1, e2); comment}) - | ( Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = (Null | Undefined _) as c} ), - _ ) -> ( - let const = if c == Null then Cnull else Cundefined in - match filter_const e2 ~j ~eq:(op = EqEqEq) ~const with - | None -> e1 - | Some e2 -> {expression_desc = Bin (And, e1, e2); comment}) - | _, _ -> {expression_desc = Bin (And, e1, e2); comment} + | _, _ -> ( + match simplify_and e1 e2 with + | Some e -> e + | None -> {expression_desc = Bin (And, e1, e2); comment}) let or_ ?comment (e1 : t) (e2 : t) = match (e1.expression_desc, e2.expression_desc) with @@ -778,25 +812,10 @@ let or_ ?comment (e1 : t) (e2 : t) = | Var i, Bin (Or, l, ({expression_desc = Var j; _} as r)) when Js_op_util.same_vident i j -> {e2 with expression_desc = Bin (Or, r, l)} - | ( _, - Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = (Null | Undefined _) as c} ) ) -> ( - let const = if c == Null then Cnull else Cundefined in - match filter_const e1 ~j ~eq:(op <> EqEqEq) ~const with - | None -> e2 - | Some e1 -> {expression_desc = Bin (Or, e1, e2); comment}) - | ( Bin - ( ((EqEqEq | NotEqEq) as op), - {expression_desc = Var j}, - {expression_desc = (Null | Undefined _) as c} ), - _ ) -> ( - let const = if c == Null then Cnull else Cundefined in - match filter_const e2 ~j ~eq:(op <> EqEqEq) ~const with - | None -> e1 - | Some e2 -> {expression_desc = Bin (Or, e1, e2); comment}) - | _, _ -> {expression_desc = Bin (Or, e1, e2); comment} + | _, _ -> ( + match simplify_or e1 e2 with + | Some e -> e + | None -> {expression_desc = Bin (Or, e1, e2); comment}) (* return a value of type boolean *) (* TODO: diff --git a/tests/tests/src/UntaggedVariants.mjs b/tests/tests/src/UntaggedVariants.mjs index 6043df57ac..f1755ab616 100644 --- a/tests/tests/src/UntaggedVariants.mjs +++ b/tests/tests/src/UntaggedVariants.mjs @@ -386,9 +386,9 @@ function check$1(s) { return; } let match = s[0]; - if (match === true) { + if (match === undefined || match === null || match === true) { let match$1 = s[1]; - if (match$1 === false) { + if (match$1 === undefined || match$1 === null || match$1 === false) { let match$2 = s[2]; if (match$2 === undefined || match$2 === null || match$2 === false || match$2 === true) { console.log("Nope..."); diff --git a/tests/tests/src/and_or_simplify.mjs b/tests/tests/src/and_or_simplify.mjs index 233b9ca9ca..bad1f1d18e 100644 --- a/tests/tests/src/and_or_simplify.mjs +++ b/tests/tests/src/and_or_simplify.mjs @@ -2,7 +2,7 @@ function check_null_typeof(x) { - if (x !== null) { + if (typeof x !== "boolean" || x !== null) { return 4; } else { return 3; @@ -10,7 +10,7 @@ function check_null_typeof(x) { } function check_undefined_typeof(x) { - if (x !== undefined) { + if (typeof x !== "boolean" || x !== undefined) { return 4; } else { return 3; diff --git a/tests/tests/src/and_or_simplify.res b/tests/tests/src/and_or_simplify.res index 8d86471a3d..9a6779bb85 100644 --- a/tests/tests/src/and_or_simplify.res +++ b/tests/tests/src/and_or_simplify.res @@ -1,5 +1,5 @@ @unboxed -type t = | @as(null) Null | @as(undefined) Undefined | B(bool) +type t = | @as(null) Null | @as(undefined) Undefined | B(bool) //| S(string) let check_null_typeof = x => switch x { diff --git a/tests/tests/src/option_repr_test.mjs b/tests/tests/src/option_repr_test.mjs index 4022fd2cbe..d67be245ef 100644 --- a/tests/tests/src/option_repr_test.mjs +++ b/tests/tests/src/option_repr_test.mjs @@ -218,8 +218,6 @@ let xs$1 = { b("File \"option_repr_test.res\", line 127, characters 3-10", Belt_List.every(xs$1, x => x)); -let xs_0$2 = true && true; - let xs_1$1 = { hd: neqx(undefined, null), tl: { @@ -235,7 +233,7 @@ let xs_1$1 = { }; let xs$2 = { - hd: xs_0$2, + hd: true, tl: xs_1$1 }; From 155c4356c9d5d6b845ac7dc38139eab8424afed4 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Wed, 30 Oct 2024 18:37:06 +0100 Subject: [PATCH 5/6] extend example --- tests/tests/src/and_or_simplify.mjs | 26 ++++++++++++++++++++++---- tests/tests/src/and_or_simplify.res | 16 ++++++++++++++-- 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/tests/tests/src/and_or_simplify.mjs b/tests/tests/src/and_or_simplify.mjs index bad1f1d18e..9f12c3b8b3 100644 --- a/tests/tests/src/and_or_simplify.mjs +++ b/tests/tests/src/and_or_simplify.mjs @@ -1,7 +1,7 @@ // Generated by ReScript, PLEASE EDIT WITH CARE -function check_null_typeof(x) { +function check_null_eq_typeof(x) { if (typeof x !== "boolean" || x !== null) { return 4; } else { @@ -9,7 +9,15 @@ function check_null_typeof(x) { } } -function check_undefined_typeof(x) { +function check_null_neq_typeof(x) { + if (typeof x !== "boolean" || x === null) { + return 4; + } else { + return 3; + } +} + +function check_undefined_eq_typeof(x) { if (typeof x !== "boolean" || x !== undefined) { return 4; } else { @@ -17,8 +25,18 @@ function check_undefined_typeof(x) { } } +function check_undefined_neq_typeof(x) { + if (typeof x !== "boolean" || x === undefined) { + return 4; + } else { + return 3; + } +} + export { - check_null_typeof, - check_undefined_typeof, + check_null_eq_typeof, + check_null_neq_typeof, + check_undefined_eq_typeof, + check_undefined_neq_typeof, } /* No side effect */ diff --git a/tests/tests/src/and_or_simplify.res b/tests/tests/src/and_or_simplify.res index 9a6779bb85..2b4520cb4f 100644 --- a/tests/tests/src/and_or_simplify.res +++ b/tests/tests/src/and_or_simplify.res @@ -1,14 +1,26 @@ @unboxed type t = | @as(null) Null | @as(undefined) Undefined | B(bool) //| S(string) -let check_null_typeof = x => +let check_null_eq_typeof = x => switch x { | B(_) if x == Null => 3 | _ => 4 } -let check_undefined_typeof = x => +let check_null_neq_typeof = x => + switch x { + | B(_) if x != Null => 3 + | _ => 4 + } + +let check_undefined_eq_typeof = x => switch x { | B(_) if x == Undefined => 3 | _ => 4 } + +let check_undefined_neq_typeof = x => + switch x { + | B(_) if x != Undefined => 3 + | _ => 4 + } From 38c9527d659239a0f03662bb8b89dbe0f5f18cbf Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 31 Oct 2024 08:44:34 +0100 Subject: [PATCH 6/6] Doc comments and changelog. --- CHANGELOG.md | 1 + compiler/core/js_exp_make.ml | 36 ++++++++++++++++++++++++++++++++---- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ef3f326b63..200f135ac4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,6 +40,7 @@ - Fix variant cast to int. https://github.com/rescript-lang/rescript-compiler/pull/7058 - Fix comments formatted away in function without arguments. https://github.com/rescript-lang/rescript-compiler/pull/7095 - Fix genType JSX component compilation. https://github.com/rescript-lang/rescript-compiler/pull/7107 +- Fix and clean up boolean and/or optimizations. https://github.com/rescript-lang/rescript-compiler/pull/7134 #### :nail_care: Polish diff --git a/compiler/core/js_exp_make.ml b/compiler/core/js_exp_make.ml index b2ca9e53c0..884bdfed7d 100644 --- a/compiler/core/js_exp_make.ml +++ b/compiler/core/js_exp_make.ml @@ -665,6 +665,28 @@ let bin ?comment (op : J.binop) (e0 : t) (e1 : t) : t = let string_of_expression = ref (fun _ -> "") let debug = false +(** + [simplify_and e1 e2] attempts to simplify the boolean AND expression [e1 && e2]. + Returns [Some simplified] if simplification is possible, [None] otherwise. + + Basic simplification rules: + - [false && e] -> [false] + - [true && e] -> [e] + - [e && false] -> [false] + - [e && true] -> [e] + - [(a && b) && e] -> If either [a && e] or [b && e] can be simplified, + creates new AND expression with simplified parts: [(a' && b')] + - [(a || b) && e] -> If either [a && e] or [b && e] can be simplified, + creates new OR expression with simplified parts: [(a' || b')] + + Type check optimizations: + - [(typeof x === "boolean") && (x === true/false)] -> [x === true/false] + - [(typeof x === "string" || Array.isArray(x)) && (x === boolean/null/undefined)] -> [false] + + Note: The function preserves the semantics of the original expression while + attempting to reduce it to a simpler form. If no simplification is possible, + returns [None]. +*) let rec simplify_and (e1 : t) (e2 : t) : t option = if debug then Printf.eprintf "simplify_and %s %s\n" (!string_of_expression e1) @@ -771,6 +793,16 @@ let rec simplify_and (e1 : t) (e2 : t) : t option = Some false_ | _ -> None +(** + [simplify_or e1 e2] attempts to simplify the boolean OR expression [e1 || e2]. + Returns [Some simplified] if simplification is possible, [None] otherwise. + + Basic simplification rules: + - [true || e] -> [true] + - [e || true] -> [true] + - [false || e] -> [e] + - [e || false] -> [e] +*) and simplify_or (e1 : t) (e2 : t) : t option = if debug then Printf.eprintf "simplify_or %s %s\n" (!string_of_expression e1) @@ -817,10 +849,6 @@ let or_ ?comment (e1 : t) (e2 : t) = | Some e -> e | None -> {expression_desc = Bin (Or, e1, e2); comment}) -(* return a value of type boolean *) -(* TODO: - when comparison with Int - it is right that !(x > 3 ) -> x <= 3 *) let not (e : t) : t = match e.expression_desc with | Number (Int {i; _}) -> bool (i = 0l)