From 1f552e242f834bbfe43e40bcdab07adac16735d6 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Fri, 12 Sep 2025 22:48:10 +0200 Subject: [PATCH 1/2] Very basic implementation of for..of loops --- analysis/reanalyze/src/Arnold.ml | 3 + analysis/reanalyze/src/SideEffects.ml | 2 + analysis/src/Utils.ml | 1 + compiler/core/j.ml | 1 + compiler/core/js_analyzer.ml | 6 +- compiler/core/js_dump.ml | 15 ++++ compiler/core/js_dump_lit.ml | 2 + compiler/core/js_fold.ml | 5 ++ compiler/core/js_pass_scope.ml | 2 +- compiler/core/js_record_fold.ml | 5 ++ compiler/core/js_record_iter.ml | 4 ++ compiler/core/js_record_map.ml | 5 ++ compiler/core/js_stmt_make.ml | 3 + compiler/core/js_stmt_make.mli | 2 + compiler/core/lam.ml | 10 ++- compiler/core/lam.mli | 3 + compiler/core/lam_analysis.ml | 2 + compiler/core/lam_arity_analysis.ml | 3 +- compiler/core/lam_bounded_vars.ml | 5 ++ compiler/core/lam_check.ml | 7 ++ compiler/core/lam_closure.ml | 4 ++ compiler/core/lam_compile.ml | 18 +++++ compiler/core/lam_convert.ml | 3 + compiler/core/lam_exit_count.ml | 3 + compiler/core/lam_free_variables.ml | 4 ++ compiler/core/lam_hit.ml | 2 + compiler/core/lam_iter.ml | 4 ++ compiler/core/lam_pass_alpha_conversion.ml | 1 + compiler/core/lam_pass_collect.ml | 3 + compiler/core/lam_pass_count.ml | 3 + compiler/core/lam_pass_deep_flatten.ml | 1 + compiler/core/lam_pass_eliminate_ref.ml | 2 + compiler/core/lam_pass_exits.ml | 2 + compiler/core/lam_pass_lets_dce.ml | 1 + compiler/core/lam_pass_remove_alias.ml | 1 + compiler/core/lam_print.ml | 3 + compiler/core/lam_scc.ml | 1 + compiler/core/lam_subst.ml | 1 + compiler/frontend/bs_ast_mapper.ml | 3 + compiler/ml/ast_helper.ml | 1 + compiler/ml/ast_helper.mli | 7 ++ compiler/ml/ast_iterator.ml | 4 ++ compiler/ml/ast_mapper.ml | 3 + compiler/ml/ast_mapper_to0.ml | 3 + compiler/ml/depend.ml | 4 ++ compiler/ml/lambda.ml | 9 ++- compiler/ml/lambda.mli | 1 + compiler/ml/parsetree.ml | 2 + compiler/ml/pprintast.ml | 6 ++ compiler/ml/printast.ml | 5 ++ compiler/ml/printlambda.ml | 3 + compiler/ml/printtyped.ml | 4 ++ compiler/ml/rec_check.ml | 19 ++++-- compiler/ml/tast_iterator.ml | 3 + compiler/ml/tast_mapper.ml | 2 + compiler/ml/translcore.ml | 2 + compiler/ml/typecore.ml | 31 +++++++++ compiler/ml/typedtree.ml | 1 + compiler/ml/typedtree.mli | 1 + compiler/ml/typedtreeIter.ml | 3 + compiler/syntax/src/res_ast_debugger.ml | 3 + compiler/syntax/src/res_comments_table.ml | 4 ++ compiler/syntax/src/res_core.ml | 60 ++++++++++------ compiler/syntax/src/res_parens.ml | 4 +- compiler/syntax/src/res_parsetree_viewer.ml | 3 +- compiler/syntax/src/res_printer.ml | 17 +++++ .../expected/for_of_non_array.res.expected | 11 +++ .../for_of_type_mismatch.res.expected | 14 ++++ .../fixtures/for_of_non_array.res | 4 ++ .../fixtures/for_of_type_mismatch.res | 6 ++ .../parsing/errors/other/expected/for.res.txt | 8 +-- .../errors/pattern/expected/missing.res.txt | 18 ++++- .../expressions/expected/for_of.res.txt | 3 + .../parsing/grammar/expressions/for_of.res | 11 +++ .../data/printer/expr/expected/for_of.res.txt | 20 ++++++ .../syntax_tests/data/printer/expr/for_of.res | 20 ++++++ tests/tests/src/test_for_of.mjs | 68 +++++++++++++++++++ tests/tests/src/test_for_of.res | 66 ++++++++++++++++++ 78 files changed, 557 insertions(+), 43 deletions(-) create mode 100644 tests/build_tests/super_errors/expected/for_of_non_array.res.expected create mode 100644 tests/build_tests/super_errors/expected/for_of_type_mismatch.res.expected create mode 100644 tests/build_tests/super_errors/fixtures/for_of_non_array.res create mode 100644 tests/build_tests/super_errors/fixtures/for_of_type_mismatch.res create mode 100644 tests/syntax_tests/data/parsing/grammar/expressions/expected/for_of.res.txt create mode 100644 tests/syntax_tests/data/parsing/grammar/expressions/for_of.res create mode 100644 tests/syntax_tests/data/printer/expr/expected/for_of.res.txt create mode 100644 tests/syntax_tests/data/printer/expr/for_of.res create mode 100644 tests/tests/src/test_for_of.mjs create mode 100644 tests/tests/src/test_for_of.res diff --git a/analysis/reanalyze/src/Arnold.ml b/analysis/reanalyze/src/Arnold.ml index 9eec8d729e..8d3faba06c 100644 --- a/analysis/reanalyze/src/Arnold.ml +++ b/analysis/reanalyze/src/Arnold.ml @@ -979,6 +979,9 @@ module Compile = struct | Texp_for _ -> notImplemented "Texp_for"; assert false + | Texp_for_of _ -> + notImplemented "Texp_for_of"; + assert false | Texp_send _ -> notImplemented "Texp_send"; assert false diff --git a/analysis/reanalyze/src/SideEffects.ml b/analysis/reanalyze/src/SideEffects.ml index 5aceaf124d..172cd3cdb1 100644 --- a/analysis/reanalyze/src/SideEffects.ml +++ b/analysis/reanalyze/src/SideEffects.ml @@ -59,6 +59,8 @@ let rec exprNoSideEffects (expr : Typedtree.expression) = | Texp_for (_id, _pat, e1, e2, _dir, e3) -> e1 |> exprNoSideEffects && e2 |> exprNoSideEffects && e3 |> exprNoSideEffects + | Texp_for_of (_id, _pat, e1, e2) -> + e1 |> exprNoSideEffects && e2 |> exprNoSideEffects | Texp_send _ -> false | Texp_letexception (_ec, e) -> e |> exprNoSideEffects | Texp_pack _ -> false diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 863598dc56..bd767db24c 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -100,6 +100,7 @@ let identifyPexp pexp = | Pexp_sequence _ -> "Pexp_sequence" | Pexp_while _ -> "Pexp_while" | Pexp_for _ -> "Pexp_for" + | Pexp_for_of _ -> "Pexp_for_of" | Pexp_constraint _ -> "Pexp_constraint" | Pexp_coerce _ -> "Pexp_coerce" | Pexp_send _ -> "Pexp_send" diff --git a/compiler/core/j.ml b/compiler/core/j.ml index dc5aa2514d..99376a2a00 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -264,6 +264,7 @@ and statement_desc = * for_ident * for_direction * block + | ForOf of for_ident * expression * block | Continue | Break (* only used when inline a fucntion *) | Return of expression diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index d728ae4f9c..9a71b7940d 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -135,8 +135,8 @@ let no_side_effect_obj = | Throw _ | Debugger | Break | Variable _ | Continue -> raise_notrace Not_found | Exp e -> self.expression self e - | Int_switch _ | String_switch _ | ForRange _ | If _ | While _ | Block _ - | Return _ | Try _ -> + | Int_switch _ | String_switch _ | ForRange _ | ForOf _ | If _ | While _ + | Block _ | Return _ | Try _ -> super.statement self s); expression = (fun _ s -> @@ -255,7 +255,7 @@ and eq_statement ({statement_desc = x0} : J.statement) match y0 with | Block ys0 -> eq_block xs0 ys0 | _ -> false) - | Variable _ | If _ | While _ | ForRange _ | Continue | Int_switch _ + | Variable _ | If _ | While _ | ForRange _ | ForOf _ | Continue | Int_switch _ | String_switch _ | Throw _ | Try _ -> false diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 49ace5fa6c..ac747c8f75 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -1478,6 +1478,21 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = brace_block cxt f s) in action cxt + | ForOf (id, iterable, s) -> + P.vgroup f 0 (fun _ -> + P.group f 0 (fun _ -> + P.string f L.for_; + P.space f; + P.paren_group f 1 (fun _ -> + P.string f L.let_; + P.space f; + ignore (Ext_pp_scope.ident cxt f id); + P.space f; + P.string f L.of_; + P.space f; + ignore (expression ~level:0 cxt f iterable))); + P.space f; + brace_block cxt f s) | Continue -> continue f; cxt diff --git a/compiler/core/js_dump_lit.ml b/compiler/core/js_dump_lit.ml index fc982710ae..a3ac845246 100644 --- a/compiler/core/js_dump_lit.ml +++ b/compiler/core/js_dump_lit.ml @@ -90,6 +90,8 @@ let if_ = "if" let for_ = "for" +let of_ = "of" + let try_ = "try" let finally = "finally" diff --git a/compiler/core/js_fold.ml b/compiler/core/js_fold.ml index 1ffa5e0270..32f8083780 100644 --- a/compiler/core/js_fold.ml +++ b/compiler/core/js_fold.ml @@ -241,6 +241,11 @@ class fold = let _self = _self#for_direction _x3 in let _self = _self#block _x4 in _self + | ForOf (_x0, _x1, _x2) -> + let _self = _self#for_ident _x0 in + let _self = _self#expression _x1 in + let _self = _self#block _x2 in + _self | Continue -> _self | Break -> _self | Return _x0 -> diff --git a/compiler/core/js_pass_scope.ml b/compiler/core/js_pass_scope.ml index 646357b5c3..ba699e8796 100644 --- a/compiler/core/js_pass_scope.ml +++ b/compiler/core/js_pass_scope.ml @@ -238,7 +238,7 @@ let record_scope_pass = statement = (fun self state x -> match x.statement_desc with - | ForRange (_, _, loop_id, _, _) -> + | ForRange (_, _, loop_id, _, _) | ForOf (loop_id, _, _) -> (* TODO: simplify definition of For *) let { defined_idents = defined_idents'; diff --git a/compiler/core/js_record_fold.ml b/compiler/core/js_record_fold.ml index fe71e6f5f1..f5bb4f29ad 100644 --- a/compiler/core/js_record_fold.ml +++ b/compiler/core/js_record_fold.ml @@ -245,6 +245,11 @@ let statement_desc : 'a. ('a, statement_desc) fn = let st = for_direction _self st _x3 in let st = _self.block _self st _x4 in st + | ForOf (_x0, _x1, _x2) -> + let st = _self.for_ident _self st _x0 in + let st = _self.expression _self st _x1 in + let st = _self.block _self st _x2 in + st | Continue -> st | Break -> st | Return _x0 -> diff --git a/compiler/core/js_record_iter.ml b/compiler/core/js_record_iter.ml index e6c9ab9646..c96928656b 100644 --- a/compiler/core/js_record_iter.ml +++ b/compiler/core/js_record_iter.ml @@ -179,6 +179,10 @@ let statement_desc : statement_desc fn = _self.for_ident _self _x2; for_direction _self _x3; _self.block _self _x4 + | ForOf (_x0, _x1, _x2) -> + _self.for_ident _self _x0; + _self.expression _self _x1; + _self.block _self _x2 | Continue -> () | Break -> () | Return _x0 -> _self.expression _self _x0 diff --git a/compiler/core/js_record_map.ml b/compiler/core/js_record_map.ml index b13fdb2a55..9b999eccd1 100644 --- a/compiler/core/js_record_map.ml +++ b/compiler/core/js_record_map.ml @@ -243,6 +243,11 @@ let statement_desc : statement_desc fn = let _x3 = for_direction _self _x3 in let _x4 = _self.block _self _x4 in ForRange (_x0, _x1, _x2, _x3, _x4) + | ForOf (_x0, _x1, _x2) -> + let _x0 = _self.for_ident _self _x0 in + let _x1 = _self.expression _self _x1 in + let _x2 = _self.block _self _x2 in + ForOf (_x0, _x1, _x2) | Continue as v -> v | Break as v -> v | Return _x0 -> diff --git a/compiler/core/js_stmt_make.ml b/compiler/core/js_stmt_make.ml index e4d87302e9..647a530e62 100644 --- a/compiler/core/js_stmt_make.ml +++ b/compiler/core/js_stmt_make.ml @@ -325,6 +325,9 @@ let for_ ?comment for_ident_expression finish_ident_expression id direction comment; } +let for_of ?comment iterable_expression id (b : J.block) : t = + {statement_desc = ForOf (id, iterable_expression, b); comment} + let try_ ?comment ?with_ ?finally body : t = {statement_desc = Try (body, with_, finally); comment} diff --git a/compiler/core/js_stmt_make.mli b/compiler/core/js_stmt_make.mli index 00a5daae83..057a0d6e1a 100644 --- a/compiler/core/js_stmt_make.mli +++ b/compiler/core/js_stmt_make.mli @@ -141,6 +141,8 @@ val for_ : J.block -> t +val for_of : ?comment:string -> J.expression -> J.ident -> J.block -> t + val try_ : ?comment:string -> ?with_:J.ident * J.block -> diff --git a/compiler/core/lam.ml b/compiler/core/lam.ml index 51b8bb3e38..f085ab81c7 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -106,6 +106,7 @@ module Types = struct | Lsequence of t * t | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t | Lassign of ident * t (* | Lsend of Lam_compat.meth_kind * t * t * t list * Location.t *) end @@ -158,6 +159,7 @@ module X = struct | Lsequence of t * t | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t | Lassign of ident * t (* | Lsend of Lam_compat.meth_kind * t * t * t list * Location.t *) end @@ -246,6 +248,10 @@ let inner_map (l : t) (f : t -> X.t) : X.t = let e2 = f e2 in let e3 = f e3 in Lfor (v, e1, e2, dir, e3) + | Lfor_of (v, e1, e2) -> + let e1 = f e1 in + let e2 = f e2 in + Lfor_of (v, e1, e2) | Lassign (id, e) -> let e = f e in Lassign (id, e) @@ -401,7 +407,8 @@ let rec eq_approx (l1 : t) (l2 : t) = | Lfunction _ | Llet (_, _, _, _) | Lletrec _ | Lswitch _ | Lstaticcatch _ | Ltrywith _ - | Lfor (_, _, _, _, _) -> + | Lfor (_, _, _, _, _) + | Lfor_of (_, _, _) -> false and eq_option l1 l2 = @@ -459,6 +466,7 @@ let letrec bindings body : t = Lletrec (bindings, body) let while_ a b : t = Lwhile (a, b) let try_ body id handler : t = Ltrywith (body, id, handler) let for_ v e1 e2 dir e3 : t = Lfor (v, e1, e2, dir, e3) +let for_of v e1 e2 : t = Lfor_of (v, e1, e2) let assign v l : t = Lassign (v, l) let staticcatch a b c : t = Lstaticcatch (a, b, c) let staticraise a b : t = Lstaticraise (a, b) diff --git a/compiler/core/lam.mli b/compiler/core/lam.mli index 560d247669..f64366fe0d 100644 --- a/compiler/core/lam.mli +++ b/compiler/core/lam.mli @@ -79,6 +79,7 @@ and t = private | Lsequence of t * t | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t | Lassign of ident * t (* | Lsend of Lambda.meth_kind * t * t * t list * Location.t *) @@ -164,6 +165,8 @@ val staticraise : int -> t list -> t val for_ : ident -> t -> t -> Asttypes.direction_flag -> t -> t +val for_of : ident -> t -> t -> t + (**************************************************************) val eq_approx : t -> t -> bool diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index a4b78bea0e..3bd56ea603 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -119,6 +119,7 @@ let rec no_side_effects (lam : Lam.t) : bool = | Lwhile _ -> false (* conservative here, non-terminating loop does have side effect *) | Lfor _ -> false + | Lfor_of _ -> false | Lassign _ -> false (* actually it depends ... *) (* | Lsend _ -> false *) | Lapply @@ -179,6 +180,7 @@ let rec size (lam : Lam.t) = | Lsequence (l1, l2) -> size l1 + size l2 | Lwhile _ -> really_big () | Lfor _ -> really_big () + | Lfor_of _ -> really_big () | Lassign (_, v) -> 1 + size v (* This is side effectful, be careful *) (* | Lsend _ -> really_big () *) diff --git a/compiler/core/lam_arity_analysis.ml b/compiler/core/lam_arity_analysis.ml index ee3d91f154..10ed80371c 100644 --- a/compiler/core/lam_arity_analysis.ml +++ b/compiler/core/lam_arity_analysis.ml @@ -129,7 +129,8 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = | Lifthenelse (_, l2, l3) -> all_lambdas meta [l2; l3] | Lsequence (_, l2) -> get_arity meta l2 | Lstaticraise _ (* since it will not be in tail position *) -> Lam_arity.na - | Lwhile _ | Lfor _ | Lassign _ -> Lam_arity.non_function_arity_info + | Lwhile _ | Lfor _ | Lfor_of _ | Lassign _ -> + Lam_arity.non_function_arity_info and all_lambdas meta (xs : Lam.t list) = match xs with diff --git a/compiler/core/lam_bounded_vars.ml b/compiler/core/lam_bounded_vars.ml index e038e56798..38d547522c 100644 --- a/compiler/core/lam_bounded_vars.ml +++ b/compiler/core/lam_bounded_vars.ml @@ -103,6 +103,11 @@ let rewrite (map : _ Hash_ident.t) (lam : Lam.t) : Lam.t = let l2 = aux l2 in let l3 = aux l3 in Lam.for_ ident (aux l1) l2 dir l3 + | Lfor_of (ident, l1, l2) -> + let ident = rebind ident in + let l1 = aux l1 in + let l2 = aux l2 in + Lam.for_of ident l1 l2 | Lconst _ -> lam | Lprim {primitive; args; loc} -> (* here it makes sure that global vars are not rebound *) diff --git a/compiler/core/lam_check.ml b/compiler/core/lam_check.ml index 0ef71f2cee..ca7df6bca0 100644 --- a/compiler/core/lam_check.ml +++ b/compiler/core/lam_check.ml @@ -62,6 +62,9 @@ let check file lam = check_staticfails e1 cxt; check_staticfails e2 cxt; check_staticfails e3 Set_int.empty + | Lfor_of (_v, e1, e2) -> + check_staticfails e1 cxt; + check_staticfails e2 Set_int.empty | Llet (_str, _id, arg, body) -> check_list [arg; body] cxt | Lletrec (decl, body) -> check_list_snd decl cxt; @@ -146,6 +149,10 @@ let check file lam = iter e2; def v; iter e3 + | Lfor_of (v, e1, e2) -> + iter e1; + def v; + iter e2 | Lassign (id, e) -> use id; iter e diff --git a/compiler/core/lam_closure.ml b/compiler/core/lam_closure.ml index c02f05b705..32cfb3c08c 100644 --- a/compiler/core/lam_closure.ml +++ b/compiler/core/lam_closure.ml @@ -134,6 +134,10 @@ let free_variables (export_idents : Set_ident.t) (params : stats Map_ident.t) iter sink_pos e1; iter sink_pos e2; iter sink_pos e3 + | Lfor_of (v, e1, e2) -> + local_add v; + iter sink_pos e1; + iter sink_pos e2 | Lassign (id, e) -> used top id; iter top e diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 739056132b..3b6ba1c6a1 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1181,6 +1181,23 @@ let compile output_prefix = in Js_output.output_of_block_and_expression lambda_cxt.continuation block E.unit + and compile_for_of (id : J.for_ident) (iterable : Lam.t) (body : Lam.t) + (lambda_cxt : Lam_compile_context.t) = + let new_cxt = {lambda_cxt with continuation = NeedValue Not_tail} in + let block = + match compile_lambda new_cxt iterable with + | {value = None} -> assert false + | {block = b1; value = Some e1} -> + let block_body = + Js_output.output_as_block + (compile_lambda + {lambda_cxt with continuation = EffectCall Not_tail} + body) + in + Ext_list.append b1 [S.for_of e1 id block_body] + in + Js_output.output_of_block_and_expression lambda_cxt.continuation block + E.unit and compile_assign id (lambda : Lam.t) (lambda_cxt : Lam_compile_context.t) = let block = match lambda with @@ -1824,6 +1841,7 @@ let compile output_prefix = compile_for id start finish (if direction = Upto then Upto else Downto) body lambda_cxt) + | Lfor_of (id, iterable, body) -> compile_for_of id iterable body lambda_cxt | Lassign (id, lambda) -> compile_assign id lambda lambda_cxt | Ltrywith (lam, id, catch) -> (* generate documentation *) diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 2e88a3b703..00c02c4683 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -89,6 +89,7 @@ let exception_id_destructed (l : Lam.t) (fv : Ident.t) : bool = | Llet (_str, _id, arg, body) -> hit arg || hit body | Lletrec (decl, body) -> hit body || hit_list_snd decl | Lfor (_v, e1, e2, _dir, e3) -> hit e1 || hit e2 || hit e3 + | Lfor_of (_v, e1, e2) -> hit e1 || hit e2 | Lconst _ -> false | Lapply {ap_func; ap_args; _} -> hit ap_func || hit_list ap_args | Lglobal_module _ (* global persistent module, play safe *) -> false @@ -507,6 +508,8 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : | Lwhile (b, body) -> Lam.while_ (convert_aux b) (convert_aux body) | Lfor (id, from_, to_, dir, loop) -> Lam.for_ id (convert_aux from_) (convert_aux to_) dir (convert_aux loop) + | Lfor_of (id, iterable, body) -> + Lam.for_of id (convert_aux iterable) (convert_aux body) | Lassign (id, body) -> Lam.assign id (convert_aux body) and convert_let (kind : Lam_compat.let_kind) id (e : Lambda.lambda) body : Lam.t = diff --git a/compiler/core/lam_exit_count.ml b/compiler/core/lam_exit_count.ml index d9535ac2ee..8c6acb0c9f 100644 --- a/compiler/core/lam_exit_count.ml +++ b/compiler/core/lam_exit_count.ml @@ -96,6 +96,9 @@ let count_helper (lam : Lam.t) : collection = count l1; count l2; count l3 + | Lfor_of (_, l1, l2) -> + count l1; + count l2 | Lassign (_, l) -> count l and count_default sw = match sw.sw_failaction with diff --git a/compiler/core/lam_free_variables.ml b/compiler/core/lam_free_variables.ml index 1fdd31f1c6..5c173b268f 100644 --- a/compiler/core/lam_free_variables.ml +++ b/compiler/core/lam_free_variables.ml @@ -57,6 +57,10 @@ let pass_free_variables (l : Lam.t) : Set_ident.t = free e2; free e3; fv := Set_ident.remove !fv v + | Lfor_of (v, e1, e2) -> + free e1; + free e2; + fv := Set_ident.remove !fv v | Lconst _ -> () | Lapply {ap_func; ap_args; _} -> free ap_func; diff --git a/compiler/core/lam_hit.ml b/compiler/core/lam_hit.ml index dd1c2c9270..bfc025d1b3 100644 --- a/compiler/core/lam_hit.ml +++ b/compiler/core/lam_hit.ml @@ -43,6 +43,7 @@ let hit_variables (fv : Set_ident.t) (l : t) : bool = | Llet (_str, _id, arg, body) -> hit arg || hit body | Lletrec (decl, body) -> hit body || hit_list_snd decl | Lfor (_v, e1, e2, _dir, e3) -> hit e1 || hit e2 || hit e3 + | Lfor_of (_v, e1, e2) -> hit e1 || hit e2 | Lconst _ -> false | Lapply {ap_func; ap_args; _} -> hit ap_func || hit_list ap_args | Lglobal_module _ (* global persistent module, play safe *) -> false @@ -78,6 +79,7 @@ let hit_variable (fv : Ident.t) (l : t) : bool = | Llet (_str, _id, arg, body) -> hit arg || hit body | Lletrec (decl, body) -> hit body || hit_list_snd decl | Lfor (_v, e1, e2, _dir, e3) -> hit e1 || hit e2 || hit e3 + | Lfor_of (_v, e1, e2) -> hit e1 || hit e2 | Lconst _ -> false | Lapply {ap_func; ap_args; _} -> hit ap_func || hit_list ap_args | Lglobal_module _ (* global persistent module, play safe *) -> false diff --git a/compiler/core/lam_iter.ml b/compiler/core/lam_iter.ml index 94b8729eca..b0771e7747 100644 --- a/compiler/core/lam_iter.ml +++ b/compiler/core/lam_iter.ml @@ -79,6 +79,9 @@ let inner_iter (l : t) (f : t -> unit) : unit = f e1; f e2; f e3 + | Lfor_of (_v, e1, e2) -> + f e1; + f e2 | Lassign (_id, e) -> f e let inner_exists (l : t) (f : t -> bool) : bool = @@ -112,4 +115,5 @@ let inner_exists (l : t) (f : t -> bool) : bool = | Lsequence (e1, e2) -> f e1 || f e2 | Lwhile (e1, e2) -> f e1 || f e2 | Lfor (_v, e1, e2, _dir, e3) -> f e1 || f e2 || f e3 + | Lfor_of (_v, e1, e2) -> f e1 || f e2 | Lassign (_id, e) -> f e diff --git a/compiler/core/lam_pass_alpha_conversion.ml b/compiler/core/lam_pass_alpha_conversion.ml index 1d80ae16ed..db900cdee0 100644 --- a/compiler/core/lam_pass_alpha_conversion.ml +++ b/compiler/core/lam_pass_alpha_conversion.ml @@ -119,6 +119,7 @@ let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = | Lwhile (l1, l2) -> Lam.while_ (simpl l1) (simpl l2) | Lfor (flag, l1, l2, dir, l3) -> Lam.for_ flag (simpl l1) (simpl l2) dir (simpl l3) + | Lfor_of (flag, l1, l2) -> Lam.for_of flag (simpl l1) (simpl l2) | Lassign (v, l) -> (* Lalias-bound variables are never assigned, so don't increase v's refsimpl *) diff --git a/compiler/core/lam_pass_collect.ml b/compiler/core/lam_pass_collect.ml index 8c4cde883b..6f1eb682a2 100644 --- a/compiler/core/lam_pass_collect.ml +++ b/compiler/core/lam_pass_collect.ml @@ -144,6 +144,9 @@ let collect_info (meta : Lam_stats.t) (lam : Lam.t) = collect l1; collect l2; collect l3 + | Lfor_of (_, l1, l2) -> + collect l1; + collect l2 | Lassign (_v, l) -> (* Lalias-bound variables are never assigned, so don't increase v's refcollect *) diff --git a/compiler/core/lam_pass_count.ml b/compiler/core/lam_pass_count.ml index 3fc3a89090..55156b9d2c 100644 --- a/compiler/core/lam_pass_count.ml +++ b/compiler/core/lam_pass_count.ml @@ -119,6 +119,9 @@ let collect_occurs lam : occ_tbl = count bv l1; count bv l2; count Map_ident.empty l3 + | Lfor_of (_, l1, l2) -> + count bv l1; + count Map_ident.empty l2 | Lwhile (l1, l2) -> count Map_ident.empty l1; count Map_ident.empty l2 diff --git a/compiler/core/lam_pass_deep_flatten.ml b/compiler/core/lam_pass_deep_flatten.ml index 11258e6a12..7ad4a9d5b9 100644 --- a/compiler/core/lam_pass_deep_flatten.ml +++ b/compiler/core/lam_pass_deep_flatten.ml @@ -259,6 +259,7 @@ let deep_flatten (lam : Lam.t) : Lam.t = | Lwhile (l1, l2) -> Lam.while_ (aux l1) (aux l2) | Lfor (flag, l1, l2, dir, l3) -> Lam.for_ flag (aux l1) (aux l2) dir (aux l3) + | Lfor_of (flag, l1, l2) -> Lam.for_of flag (aux l1) (aux l2) | Lassign (v, l) -> (* Lalias-bound variables are never assigned, so don't increase v's refaux *) diff --git a/compiler/core/lam_pass_eliminate_ref.ml b/compiler/core/lam_pass_eliminate_ref.ml index eb54fb2067..63c059eaaa 100644 --- a/compiler/core/lam_pass_eliminate_ref.ml +++ b/compiler/core/lam_pass_eliminate_ref.ml @@ -99,4 +99,6 @@ let rec eliminate_ref id (lam : Lam.t) = | Lfor (v, e1, e2, dir, e3) -> Lam.for_ v (eliminate_ref id e1) (eliminate_ref id e2) dir (eliminate_ref id e3) + | Lfor_of (v, e1, e2) -> + Lam.for_of v (eliminate_ref id e1) (eliminate_ref id e2) | Lassign (v, e) -> Lam.assign v (eliminate_ref id e) diff --git a/compiler/core/lam_pass_exits.ml b/compiler/core/lam_pass_exits.ml index 2eb6295699..f01ff80c2d 100644 --- a/compiler/core/lam_pass_exits.ml +++ b/compiler/core/lam_pass_exits.ml @@ -53,6 +53,7 @@ and no_bounded_variables (l : Lam.t) = vars = [] && no_bounded_variables e1 && no_bounded_variables e2 | Lfunction {body; params} -> params = [] && no_bounded_variables body | Lfor _ -> false + | Lfor_of _ -> false | Ltrywith _ -> false | Llet _ -> false | Lletrec (decl, body) -> decl = [] && no_bounded_variables body @@ -234,6 +235,7 @@ let subst_helper (subst : subst_tbl) (query : int -> int) (lam : Lam.t) : Lam.t | Lwhile (l1, l2) -> Lam.while_ (simplif l1) (simplif l2) | Lfor (v, l1, l2, dir, l3) -> Lam.for_ v (simplif l1) (simplif l2) dir (simplif l3) + | Lfor_of (v, l1, l2) -> Lam.for_of v (simplif l1) (simplif l2) | Lassign (v, l) -> Lam.assign v (simplif l) in simplif lam diff --git a/compiler/core/lam_pass_lets_dce.ml b/compiler/core/lam_pass_lets_dce.ml index bf32bbc56b..26430beedd 100644 --- a/compiler/core/lam_pass_lets_dce.ml +++ b/compiler/core/lam_pass_lets_dce.ml @@ -200,6 +200,7 @@ let lets_helper (count_var : Ident.t -> Lam_pass_count.used_info) lam : Lam.t = | Lwhile (l1, l2) -> Lam.while_ (simplif l1) (simplif l2) | Lfor (v, l1, l2, dir, l3) -> Lam.for_ v (simplif l1) (simplif l2) dir (simplif l3) + | Lfor_of (v, l1, l2) -> Lam.for_of v (simplif l1) (simplif l2) | Lassign (v, l) -> Lam.assign v (simplif l) in simplif lam diff --git a/compiler/core/lam_pass_remove_alias.ml b/compiler/core/lam_pass_remove_alias.ml index fe19cf1961..0df1c45ae5 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -275,6 +275,7 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = | Lwhile (l1, l2) -> Lam.while_ (simpl l1) (simpl l2) | Lfor (flag, l1, l2, dir, l3) -> Lam.for_ flag (simpl l1) (simpl l2) dir (simpl l3) + | Lfor_of (flag, l1, l2) -> Lam.for_of flag (simpl l1) (simpl l2) | Lassign (v, l) -> (* Lalias-bound variables are never assigned, so don't increase v's refsimpl *) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 172e219abb..793193e2f7 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -409,6 +409,9 @@ let lambda ppf v = | Upto -> "to" | Downto -> "downto") lam hi lam body + | Lfor_of (param, iterable, body) -> + fprintf ppf "@[<2>(for %a@ of@ %a@ %a)@]" Ident.print param lam iterable + lam body | Lassign (id, expr) -> fprintf ppf "@[<2>(assign@ %a@ %a)@]" Ident.print id lam expr and sequence ppf = function diff --git a/compiler/core/lam_scc.ml b/compiler/core/lam_scc.ml index 556feed559..835fea5194 100644 --- a/compiler/core/lam_scc.ml +++ b/compiler/core/lam_scc.ml @@ -48,6 +48,7 @@ let hit_mask (mask : Hash_set_ident_mask.t) (l : Lam.t) : bool = | Llet (_str, _id, arg, body) -> hit arg || hit body | Lletrec (decl, body) -> hit body || hit_list_snd decl | Lfor (_v, e1, e2, _dir, e3) -> hit e1 || hit e2 || hit e3 + | Lfor_of (_v, e1, e2) -> hit e1 || hit e2 | Lconst _ -> false | Lapply {ap_func; ap_args; _} -> hit ap_func || hit_list ap_args | Lglobal_module _ (* playsafe *) -> false diff --git a/compiler/core/lam_subst.ml b/compiler/core/lam_subst.ml index d5469619a3..fc53d45f6c 100644 --- a/compiler/core/lam_subst.ml +++ b/compiler/core/lam_subst.ml @@ -66,6 +66,7 @@ let subst (s : Lam.t Map_ident.t) lam = | Lwhile (e1, e2) -> Lam.while_ (subst_aux e1) (subst_aux e2) | Lfor (v, e1, e2, dir, e3) -> Lam.for_ v (subst_aux e1) (subst_aux e2) dir (subst_aux e3) + | Lfor_of (v, e1, e2) -> Lam.for_of v (subst_aux e1) (subst_aux e2) | Lassign (id, e) -> Lam.assign id (subst_aux e) and subst_decl (id, exp) = (id, subst_aux exp) and subst_case (key, case) = (key, subst_aux case) diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index 292e199b5a..379ffa33ec 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -394,6 +394,9 @@ module E = struct {jsx_unary_element_tag_name = name; jsx_unary_element_props = props}) -> jsx_unary_element ~loc ~attrs name (map_jsx_props sub props) + | Pexp_for_of (pat, e1, e2) -> + Exp.mk ~loc ~attrs + (Pexp_for_of (sub.pat sub pat, sub.expr sub e1, sub.expr sub e2)) | Pexp_jsx_element (Jsx_container_element { diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 2fae640eb0..8bc3fc49d2 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -179,6 +179,7 @@ module Exp = struct let sequence ?loc ?attrs a b = mk ?loc ?attrs (Pexp_sequence (a, b)) let while_ ?loc ?attrs a b = mk ?loc ?attrs (Pexp_while (a, b)) let for_ ?loc ?attrs a b c d e = mk ?loc ?attrs (Pexp_for (a, b, c, d, e)) + let for_of ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_for_of (a, b, c)) let constraint_ ?loc ?attrs a b = mk ?loc ?attrs (Pexp_constraint (a, b)) let coerce ?loc ?attrs a c = mk ?loc ?attrs (Pexp_coerce (a, (), c)) let send ?loc ?attrs a b = mk ?loc ?attrs (Pexp_send (a, b)) diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 11227b903a..686d6b86fc 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -184,6 +184,13 @@ module Exp : sig direction_flag -> expression -> expression + val for_of : + ?loc:loc -> + ?attrs:attrs -> + pattern -> + expression -> + expression -> + expression val coerce : ?loc:loc -> ?attrs:attrs -> expression -> core_type -> expression val constraint_ : ?loc:loc -> ?attrs:attrs -> expression -> core_type -> expression diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index a430bb0b7b..7b11a7d2a7 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -337,6 +337,10 @@ module E = struct sub.expr sub e1; sub.expr sub e2; sub.expr sub e3 + | Pexp_for_of (p, e1, e2) -> + sub.pat sub p; + sub.expr sub e1; + sub.expr sub e2 | Pexp_coerce (e, (), t2) -> sub.expr sub e; sub.typ sub t2 diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 673465477b..e345f777e4 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -324,6 +324,9 @@ module E = struct | Pexp_for (p, e1, e2, d, e3) -> for_ ~loc ~attrs (sub.pat sub p) (sub.expr sub e1) (sub.expr sub e2) d (sub.expr sub e3) + | Pexp_for_of (p, e1, e2) -> + Exp.mk ~loc ~attrs + (Pexp_for_of (sub.pat sub p, sub.expr sub e1, sub.expr sub e2)) | Pexp_coerce (e, (), t2) -> coerce ~loc ~attrs (sub.expr sub e) (sub.typ sub t2) | Pexp_constraint (e, t) -> diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index d0ac43d737..69da19a056 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -450,6 +450,9 @@ module E = struct | Pexp_for (p, e1, e2, d, e3) -> for_ ~loc ~attrs (sub.pat sub p) (sub.expr sub e1) (sub.expr sub e2) d (sub.expr sub e3) + | Pexp_for_of (_pat, _array_expr, _body_expr) -> + (* Convert for...of to an extension since it doesn't exist in old AST *) + extension ~loc ~attrs (Location.mkloc "rescript.for_of" loc, PStr []) | Pexp_coerce (e, (), t2) -> coerce ~loc ~attrs (sub.expr sub e) (sub.typ sub t2) | Pexp_constraint (e, t) -> diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index e5e39eb4b5..7b260bf464 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -263,6 +263,10 @@ let rec add_expr bv exp = add_expr bv e1; add_expr bv e2; add_expr bv e3 + | Pexp_for_of (pat, e1, e2) -> + add_pattern bv pat |> ignore; + add_expr bv e1; + add_expr bv e2 | Pexp_coerce (e1, (), ty3) -> add_expr bv e1; add_type bv ty3 diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index db810d4f91..f10f5cf62c 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -366,6 +366,7 @@ type lambda = | Lsequence of lambda * lambda | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * Asttypes.direction_flag * lambda + | Lfor_of of Ident.t * lambda * lambda | Lassign of Ident.t * lambda | Lsend of string * lambda * Location.t @@ -477,7 +478,8 @@ let make_key e = | Lsequence (e1, e2) -> Lsequence (tr_rec env e1, tr_rec env e2) | Lassign (x, e) -> Lassign (x, tr_rec env e) | Lsend (m, e1, _loc) -> Lsend (m, tr_rec env e1, Location.none) - | Lletrec _ | Lfunction _ | Lfor _ | Lwhile _ -> raise_notrace Not_simple + | Lletrec _ | Lfunction _ | Lfor _ | Lfor_of _ | Lwhile _ -> + raise_notrace Not_simple and tr_recs env es = List.map (tr_rec env) es and tr_sw env sw = { @@ -549,6 +551,9 @@ let iter f = function f e1; f e2; f e3 + | Lfor_of (_v, e1, e2) -> + f e1; + f e2 | Lassign (_, e) -> f e | Lsend (_k, obj, _) -> f obj @@ -569,6 +574,7 @@ let free_ids get l = List.iter (fun id -> fv := IdentSet.remove id !fv) vars | Ltrywith (_e1, exn, _e2) -> fv := IdentSet.remove exn !fv | Lfor (v, _e1, _e2, _dir, _e3) -> fv := IdentSet.remove v !fv + | Lfor_of (v, _e1, _e2) -> fv := IdentSet.remove v !fv | Lassign (id, _e) -> fv := IdentSet.add id !fv | Lvar _ | Lconst _ | Lapply _ | Lprim _ | Lswitch _ | Lstringswitch _ | Lstaticraise _ | Lifthenelse _ | Lsequence _ | Lwhile _ | Lsend _ -> @@ -677,6 +683,7 @@ let subst_lambda s lam = | Lsequence (e1, e2) -> Lsequence (subst e1, subst e2) | Lwhile (e1, e2) -> Lwhile (subst e1, subst e2) | Lfor (v, e1, e2, dir, e3) -> Lfor (v, subst e1, subst e2, dir, subst e3) + | Lfor_of (v, e1, e2) -> Lfor_of (v, subst e1, subst e2) | Lassign (id, e) -> Lassign (id, subst e) | Lsend (k, obj, loc) -> Lsend (k, subst obj, loc) and subst_decl (id, exp) = (id, subst exp) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index d8eaf57be6..89570c7bda 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -337,6 +337,7 @@ type lambda = | Lsequence of lambda * lambda | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * direction_flag * lambda + | Lfor_of of Ident.t * lambda * lambda | Lassign of Ident.t * lambda | Lsend of string * lambda * Location.t diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 78c9899f74..f4ebc09f9d 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -288,6 +288,8 @@ and expression_desc = (* for i = E1 to E2 do E3 done (flag = Upto) for i = E1 downto E2 do E3 done (flag = Downto) *) + | Pexp_for_of of pattern * expression * expression + (* for pattern of array_expr do body_expr *) | Pexp_constraint of expression * core_type (* (E : T) *) | Pexp_coerce of expression * unit * core_type (* (E :> T) (None, T) diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index 585ac64b81..faef862168 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -797,6 +797,12 @@ and simple_expr ctxt f x = let expression = expression ctxt in pp f fmt (pattern ctxt) s expression e1 direction_flag df expression e2 expression e3 + | Pexp_for_of (s, e1, e2) -> + let fmt : (_, _, _) format = + "@[@[@[<2>for %a of@;%a@;do@]@;%a@]@;done@]" + in + let expression = expression ctxt in + pp f fmt (pattern ctxt) s expression e1 expression e2 | Pexp_jsx_element (Jsx_fragment {jsx_fragment_children = children}) -> pp f "<>%a" (list (simple_expr ctxt)) children | Pexp_jsx_element diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 44d699eb38..6603494ed1 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -316,6 +316,11 @@ and expression i ppf x = expression i ppf e1; expression i ppf e2; expression i ppf e3 + | Pexp_for_of (p, e1, e2) -> + line i ppf "Pexp_for_of\n"; + pattern i ppf p; + expression i ppf e1; + expression i ppf e2 | Pexp_constraint (e, ct) -> line i ppf "Pexp_constraint\n"; expression i ppf e; diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 0282f6e113..aaf7726da7 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -385,6 +385,9 @@ let rec lam ppf = function | Upto -> "to" | Downto -> "downto") lam hi lam body + | Lfor_of (param, iterable, body) -> + fprintf ppf "@[<2>(for_of %a@ %a@ %a)@]" Ident.print param lam iterable lam + body | Lassign (id, expr) -> fprintf ppf "@[<2>(assign@ %a@ %a)@]" Ident.print id lam expr | Lsend (name, obj, _) -> fprintf ppf "@[<2>(send%s@ %a@ )@]" name lam obj diff --git a/compiler/ml/printtyped.ml b/compiler/ml/printtyped.ml index 064f0ab55c..d169742b6a 100644 --- a/compiler/ml/printtyped.ml +++ b/compiler/ml/printtyped.ml @@ -350,6 +350,10 @@ and expression i ppf x = expression i ppf e1; expression i ppf e2; expression i ppf e3 + | Texp_for_of (s, _, e1, e2) -> + line i ppf "Texp_for_of \"%a\"\n" fmt_ident s; + expression i ppf e1; + expression i ppf e2 | Texp_send (e, Tmeth_name s, eo) -> line i ppf "Texp_send \"%s\"\n" s; expression i ppf e; diff --git a/compiler/ml/rec_check.ml b/compiler/ml/rec_check.ml index 3d016a7438..9de7edd738 100644 --- a/compiler/ml/rec_check.ml +++ b/compiler/ml/rec_check.ml @@ -194,10 +194,10 @@ let rec classify_expression : Typedtree.expression -> sd = | Texp_sequence (_, e) | Texp_letexception (_, e) -> classify_expression e - | Texp_ident _ | Texp_for _ | Texp_constant _ | Texp_tuple _ | Texp_array _ - | Texp_construct _ | Texp_variant _ | Texp_record _ | Texp_setfield _ - | Texp_while _ | Texp_pack _ | Texp_function _ | Texp_extension_constructor _ - -> + | Texp_ident _ | Texp_for _ | Texp_for_of _ | Texp_constant _ | Texp_tuple _ + | Texp_array _ | Texp_construct _ | Texp_variant _ | Texp_record _ + | Texp_setfield _ | Texp_while _ | Texp_pack _ | Texp_function _ + | Texp_extension_constructor _ -> Static | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}} when is_ref vd -> Static @@ -224,13 +224,20 @@ let rec expression : Env.env -> Typedtree.expression -> Use.t = let cs = list (case ~scrutinee:t) env val_cases and es = list exn_case env exn_cases in Use.(join cs es) - | Texp_for (_, _, e1, e2, _, e3) -> + | Texp_for (_, _, e1, e2, _, body) -> Use.( join (join (inspect (expression env e1)) (inspect (expression env e2))) (* The body is evaluated, but not used, and not available for inclusion in another value *) - (discard (expression env e3))) + (discard (expression env body))) + | Texp_for_of (_, _, e1, body) -> + Use.( + join + (inspect (expression env e1)) + (* The body is evaluated, but not used, and not available + for inclusion in another value *) + (discard (expression env body))) | Texp_constant _ -> Use.empty | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}; args = [(_, Some arg)]} diff --git a/compiler/ml/tast_iterator.ml b/compiler/ml/tast_iterator.ml index 5c12d3da4d..653279f033 100644 --- a/compiler/ml/tast_iterator.ml +++ b/compiler/ml/tast_iterator.ml @@ -191,6 +191,9 @@ let expr sub {exp_extra; exp_desc; exp_env; _} = sub.expr sub exp1; sub.expr sub exp2; sub.expr sub exp3 + | Texp_for_of (_, _, exp1, exp2) -> + sub.expr sub exp1; + sub.expr sub exp2 | Texp_send (exp, _, expo) -> sub.expr sub exp; Option.iter (sub.expr sub) expo diff --git a/compiler/ml/tast_mapper.ml b/compiler/ml/tast_mapper.ml index 54eba02869..ccfe82017e 100644 --- a/compiler/ml/tast_mapper.ml +++ b/compiler/ml/tast_mapper.ml @@ -243,6 +243,8 @@ let expr sub x = | Texp_for (id, p, exp1, exp2, dir, exp3) -> Texp_for (id, p, sub.expr sub exp1, sub.expr sub exp2, dir, sub.expr sub exp3) + | Texp_for_of (id, p, exp1, exp2) -> + Texp_for_of (id, p, sub.expr sub exp1, sub.expr sub exp2) | Texp_send (exp, meth, expo) -> Texp_send (sub.expr sub exp, meth, opt (sub.expr sub) expo) | Texp_letmodule (id, s, mexpr, exp) -> diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 078cbf133a..a521f28d2c 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -903,6 +903,8 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = | Texp_while (cond, body) -> Lwhile (transl_exp cond, transl_exp body) | Texp_for (param, _, low, high, dir, body) -> Lfor (param, transl_exp low, transl_exp high, dir, transl_exp body) + | Texp_for_of (param, _, iterable, body) -> + Lfor_of (param, transl_exp iterable, transl_exp body) | Texp_send (expr, Tmeth_name nm, _) -> let obj = transl_exp expr in Lsend (nm, obj, e.exp_loc) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 90522f13d2..ad547d4a9f 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -190,6 +190,9 @@ let iter_expression f e = expr e1; expr e2; expr e3 + | Pexp_for_of (_, e1, e2) -> + expr e1; + expr e2 | Pexp_letmodule (_, me, e) -> expr e; module_expr me @@ -2948,6 +2951,34 @@ and type_expect_ ~context ?in_function ?(recarg = Rejected) env sexp ty_expected exp_attributes = sexp.pexp_attributes; exp_env = env; } + | Pexp_for_of (param, scollection, sbody) -> + let ty_elem = newvar () in + let collection = + type_expect ~context:None env scollection (Predef.type_array ty_elem) + in + let id, new_env = + match param.ppat_desc with + | Ppat_any -> (Ident.create "_for_of", env) + | Ppat_var {txt} -> + Env.enter_value txt + { + val_type = ty_elem; + val_attributes = []; + val_kind = Val_reg; + Types.val_loc = loc; + } env ~check:(fun s -> Warnings.Unused_for_index s) + | _ -> raise (Error (param.ppat_loc, env, Invalid_for_loop_index)) + in + let body = type_statement ~context:None new_env sbody in + rue + { + exp_desc = Texp_for_of (id, param, collection, body); + exp_loc = loc; + exp_extra = []; + exp_type = instance_def Predef.type_unit; + exp_attributes = sexp.pexp_attributes; + exp_env = env; + } | Pexp_constraint (sarg, sty) -> let separate = true in (* always separate, 1% slowdown for lablgtk *) diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index 9ef328be4a..439843d8eb 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -117,6 +117,7 @@ and expression_desc = * expression * direction_flag * expression + | Texp_for_of of Ident.t * Parsetree.pattern * expression * expression | Texp_send of expression * meth * expression option | Texp_letmodule of Ident.t * string loc * module_expr * expression | Texp_letexception of extension_constructor * expression diff --git a/compiler/ml/typedtree.mli b/compiler/ml/typedtree.mli index 6e6b1c5159..4def86f9de 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -218,6 +218,7 @@ and expression_desc = * expression * direction_flag * expression + | Texp_for_of of Ident.t * Parsetree.pattern * expression * expression | Texp_send of expression * meth * expression option | Texp_letmodule of Ident.t * string loc * module_expr * expression | Texp_letexception of extension_constructor * expression diff --git a/compiler/ml/typedtreeIter.ml b/compiler/ml/typedtreeIter.ml index 9a31b9b5b9..5e82899c2a 100644 --- a/compiler/ml/typedtreeIter.ml +++ b/compiler/ml/typedtreeIter.ml @@ -278,6 +278,9 @@ end = struct iter_expression exp1; iter_expression exp2; iter_expression exp3 + | Texp_for_of (_id, _, exp1, exp2) -> + iter_expression exp1; + iter_expression exp2 | Texp_send (exp, _meth, expo) -> ( iter_expression exp; match expo with diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index 52a57b89c5..6ad011e3ce 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -669,6 +669,9 @@ module SexpAst = struct direction_flag flag; expression e3; ] + | Pexp_for_of (pat, e1, e2) -> + Sexp.list + [Sexp.atom "Pexp_for_of"; pattern pat; expression e1; expression e2] | Pexp_constraint (expr, typexpr) -> Sexp.list [Sexp.atom "Pexp_constraint"; expression expr; core_type typexpr] diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index b9a81d53a9..8f7ac3c84f 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1805,6 +1805,10 @@ and walk_expression expr t comments = Comments after the closing tag will already be taking into account by the parent node. *) ) | Pexp_await expr -> walk_expression expr t comments + | Pexp_for_of (pattern, expr1, expr2) -> + walk_pattern pattern t comments; + walk_expression expr1 t comments; + walk_expression expr2 t comments | Pexp_send _ -> () and walk_expr_parameter (_attrs, _argLbl, expr_opt, pattern) t comments = diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 380fe21c2e..75676298cd 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -3658,27 +3658,45 @@ and parse_if_or_if_let_expression p = expr and parse_for_rest has_opening_paren pattern start_pos p = - Parser.expect In p; - let e1 = parse_expr p in - let direction = - match p.Parser.token with - | Lident "to" -> Asttypes.Upto - | Lident "downto" -> Asttypes.Downto - | token -> - Parser.err p (Diagnostics.unexpected token p.breadcrumbs); - Asttypes.Upto - in - if p.Parser.token = Eof then - Parser.err ~start_pos:p.start_pos p - (Diagnostics.unexpected p.Parser.token p.breadcrumbs) - else Parser.next p; - let e2 = parse_expr ~context:WhenExpr p in - if has_opening_paren then Parser.expect Rparen p; - Parser.expect Lbrace p; - let body_expr = parse_expr_block p in - Parser.expect Rbrace p; - let loc = mk_loc start_pos p.prev_end_pos in - Ast_helper.Exp.for_ ~loc pattern e1 e2 direction body_expr + let parse_loop_body () = + if has_opening_paren then Parser.expect Rparen p; + Parser.expect Lbrace p; + let body_expr = parse_expr_block p in + Parser.expect Rbrace p; + body_expr + in + match p.Parser.token with + | Of -> + (* for...of loop *) + Parser.next p; + let array_expr = parse_operand_expr ~context:WhenExpr p in + let body_expr = parse_loop_body () in + let loc = mk_loc start_pos p.prev_end_pos in + Ast_helper.Exp.mk ~loc + (Parsetree.Pexp_for_of (pattern, array_expr, body_expr)) + | In -> + (* regular for loop *) + Parser.next p; + let e1 = parse_expr p in + let direction = + match p.Parser.token with + | Lident "to" -> Asttypes.Upto + | Lident "downto" -> Asttypes.Downto + | token -> + Parser.err p (Diagnostics.unexpected token p.breadcrumbs); + Asttypes.Upto + in + if p.Parser.token = Eof then + Parser.err ~start_pos:p.start_pos p + (Diagnostics.unexpected p.Parser.token p.breadcrumbs) + else Parser.next p; + let e2 = parse_expr ~context:WhenExpr p in + let body_expr = parse_loop_body () in + let loc = mk_loc start_pos p.prev_end_pos in + Ast_helper.Exp.for_ ~loc pattern e1 e2 direction body_expr + | _ -> + Parser.err p (Diagnostics.unexpected p.token p.breadcrumbs); + Recover.default_expr () and parse_for_expression p = let start_pos = p.Parser.start_pos in diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index 8c08829461..8388ee6b42 100644 --- a/compiler/syntax/src/res_parens.ml +++ b/compiler/syntax/src/res_parens.ml @@ -52,7 +52,7 @@ let call_expr expr = pexp_desc = ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_ifthenelse _ ); + | Pexp_for_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when Ast_uncurried.expr_is_uncurried_fun expr -> Parenthesized @@ -223,7 +223,7 @@ let assert_or_await_expr_rhs ?(in_await = false) expr = pexp_desc = ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_ifthenelse _ ); + | Pexp_for_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when (not in_await) && ParsetreeViewer.expr_is_await expr -> diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index fe5430ebe5..c5fbcdca0b 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -534,7 +534,8 @@ let should_inline_rhs_binary_expr rhs = match rhs.pexp_desc with | Parsetree.Pexp_constant _ | Pexp_let _ | Pexp_letmodule _ | Pexp_letexception _ | Pexp_sequence _ | Pexp_open _ | Pexp_ifthenelse _ - | Pexp_for _ | Pexp_while _ | Pexp_try _ | Pexp_array _ | Pexp_record _ -> + | Pexp_for _ | Pexp_for_of _ | Pexp_while _ | Pexp_try _ | Pexp_array _ + | Pexp_record _ -> true | _ -> false diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index 2f73bd2684..c445d5117d 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3424,6 +3424,23 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = Doc.space; print_expression_block ~state ~braces:true body cmt_tbl; ]) + | Pexp_for_of (pattern, array_expr, body) -> + Doc.breakable_group ~force_break:true + (Doc.concat + [ + Doc.text "for "; + print_pattern ~state pattern cmt_tbl; + Doc.text " of "; + (let doc = + print_expression_with_comments ~state array_expr cmt_tbl + in + match Parens.expr array_expr with + | Parens.Parenthesized -> add_parens doc + | Braced braces -> print_braces doc array_expr braces + | Nothing -> doc); + Doc.space; + print_expression_block ~state ~braces:true body cmt_tbl; + ]) | Pexp_constraint ( {pexp_desc = Pexp_pack mod_expr}, {ptyp_desc = Ptyp_package package_type; ptyp_loc} ) -> diff --git a/tests/build_tests/super_errors/expected/for_of_non_array.res.expected b/tests/build_tests/super_errors/expected/for_of_non_array.res.expected new file mode 100644 index 0000000000..bffe3d0de2 --- /dev/null +++ b/tests/build_tests/super_errors/expected/for_of_non_array.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/for_of_non_array.res:2:10-11 + + 1 │ // Test for...of with non-array type (should fail type checking) + 2 │ for x of 42 { + 3 │ Js.log(x) + 4 │ } + + This has type: int + But it's expected to have type: array<'a> \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/for_of_type_mismatch.res.expected b/tests/build_tests/super_errors/expected/for_of_type_mismatch.res.expected new file mode 100644 index 0000000000..865768ba32 --- /dev/null +++ b/tests/build_tests/super_errors/expected/for_of_type_mismatch.res.expected @@ -0,0 +1,14 @@ + + We've found a bug for you! + /.../fixtures/for_of_type_mismatch.res:4:19 + + 2 │ let numbers: array = [1, 2, 3] + 3 │ for x of numbers { + 4 │ let y: string = x + 5 │ Js.log(y) + 6 │ } + + This has type: int + But it's expected to have type: string + + You can convert int to string with Int.toString. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/for_of_non_array.res b/tests/build_tests/super_errors/fixtures/for_of_non_array.res new file mode 100644 index 0000000000..17e1bc1003 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/for_of_non_array.res @@ -0,0 +1,4 @@ +// Test for...of with non-array type (should fail type checking) +for x of 42 { + Js.log(x) +} diff --git a/tests/build_tests/super_errors/fixtures/for_of_type_mismatch.res b/tests/build_tests/super_errors/fixtures/for_of_type_mismatch.res new file mode 100644 index 0000000000..2bf5fdeee0 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/for_of_type_mismatch.res @@ -0,0 +1,6 @@ +// Test for...of with incompatible array element type +let numbers: array = [1, 2, 3] +for x of numbers { + let y: string = x + Js.log(y) +} diff --git a/tests/syntax_tests/data/parsing/errors/other/expected/for.res.txt b/tests/syntax_tests/data/parsing/errors/other/expected/for.res.txt index ab4cdf2cb4..e6af2066c4 100644 --- a/tests/syntax_tests/data/parsing/errors/other/expected/for.res.txt +++ b/tests/syntax_tests/data/parsing/errors/other/expected/for.res.txt @@ -1,12 +1,10 @@ Syntax error! - syntax_tests/data/parsing/errors/other/for.res:1:6-2:0 + syntax_tests/data/parsing/errors/other/for.res:2:1 1 │ for x 2 │ - Did you forget a `in` here? + I'm not sure what to parse here when looking at "eof". -;;for x = [%rescript.exprhole ] to [%rescript.exprhole ] do - [%rescript.exprhole ] - done \ No newline at end of file +;;[%rescript.exprhole ] \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/errors/pattern/expected/missing.res.txt b/tests/syntax_tests/data/parsing/errors/pattern/expected/missing.res.txt index 7f85176c8d..b6381351e6 100644 --- a/tests/syntax_tests/data/parsing/errors/pattern/expected/missing.res.txt +++ b/tests/syntax_tests/data/parsing/errors/pattern/expected/missing.res.txt @@ -32,6 +32,18 @@ A for-loop has the following form: `for i in 0 to 10`. Did you forget to supply a name before `in`? + Syntax error! + syntax_tests/data/parsing/errors/pattern/missing.res:4:7-8 + + 2 │ let = 4 + 3 │ + 4 │ for in 0 to 10 { + 5 │ Js.log("for") + 6 │ } + + consecutive statements on a line must be separated by ';' or a newline + + Syntax error! syntax_tests/data/parsing/errors/pattern/missing.res:9:3-4 @@ -44,5 +56,9 @@ I was expecting a pattern to match on before the `=>` let 2 = [%rescript.exprhole ] -let 4 = for [%rescript.patternhole ] = 0 to 10 do Js.log {js|for|js} done +let 4 = [%rescript.exprhole ] +;;0 +;;to +;;10 +;;((Js.log {js|for|js})[@res.braces ]) ;;match x with | () -> [%rescript.exprhole ] \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_of.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_of.res.txt new file mode 100644 index 0000000000..1e4232faf7 --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_of.res.txt @@ -0,0 +1,3 @@ +let arr = [|1;2;3|] +;;for x of arr do ignore (x + 1) done +;;for (item, index) of arr do ignore (item, index) done \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/for_of.res b/tests/syntax_tests/data/parsing/grammar/expressions/for_of.res new file mode 100644 index 0000000000..30db11418b --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/for_of.res @@ -0,0 +1,11 @@ +// Test basic for...of syntax +let arr = [1, 2, 3] + +for x of arr { + ignore(x + 1) +} + +// Test for...of with different pattern +for (item, index) of arr { + ignore((item, index)) +} \ No newline at end of file diff --git a/tests/syntax_tests/data/printer/expr/expected/for_of.res.txt b/tests/syntax_tests/data/printer/expr/expected/for_of.res.txt new file mode 100644 index 0000000000..b50e4caa92 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/expected/for_of.res.txt @@ -0,0 +1,20 @@ +// Test basic for...of syntax +let arr = [1, 2, 3] + +for x of arr { + Js.log(x) +} + +// Test for...of with async function +let processData = (data: int): promise => { + Promise.resolve(data + 10) +} + +let asyncProcess = async () => { + let results = [] + for item of arr { + let result = await processData(item) + results->Array.push(result) + } + results +} diff --git a/tests/syntax_tests/data/printer/expr/for_of.res b/tests/syntax_tests/data/printer/expr/for_of.res new file mode 100644 index 0000000000..cfcc851303 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/for_of.res @@ -0,0 +1,20 @@ +// Test basic for...of syntax +let arr = [1, 2, 3] + +for x of arr { + Js.log(x) +} + +// Test for...of with async function +let processData = (data: int): promise => { + Promise.resolve(data + 10) +} + +let asyncProcess = async () => { + let results = [] + for item of arr { + let result = await processData(item) + results->Array.push(result) + } + results +} \ No newline at end of file diff --git a/tests/tests/src/test_for_of.mjs b/tests/tests/src/test_for_of.mjs new file mode 100644 index 0000000000..f94062fdf6 --- /dev/null +++ b/tests/tests/src/test_for_of.mjs @@ -0,0 +1,68 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Test_utils from "./test_utils.mjs"; + +Mocha.describe("Test_for_of", () => { + Mocha.test("basic iteration", () => { + let arr = [ + 1, + 2, + 3, + 4, + 5 + ]; + let sum = 0; + for (let x of arr) { + sum = sum + x | 0; + } + Test_utils.eq("File \"test_for_of.res\", line 16, characters 7-14", sum, 15); + }); + Mocha.test("single element", () => { + let arr = [42]; + let result = 0; + for (let x of arr) { + result = x; + } + Test_utils.eq("File \"test_for_of.res\", line 27, characters 7-14", result, 42); + }); + Mocha.test("empty array", () => { + let arr = []; + for (let x of arr) { + Test_utils.ok("File \"test_for_of.res\", line 35, characters 9-16", false); + } + }); +}); + +async function processData(data) { + return data + 10 | 0; +} + +async function asyncProcess(arr) { + let results = []; + for (let item of arr) { + let result = await processData(item); + results.push(result); + } + return results; +} + +function testWildcardPattern() { + let arr = [ + 1, + 2, + 3 + ]; + let count = 0; + for (let _for_of of arr) { + count = count + 1 | 0; + } + return count === 3; +} + +export { + processData, + asyncProcess, + testWildcardPattern, +} +/* Not a pure module */ diff --git a/tests/tests/src/test_for_of.res b/tests/tests/src/test_for_of.res new file mode 100644 index 0000000000..39d72479b4 --- /dev/null +++ b/tests/tests/src/test_for_of.res @@ -0,0 +1,66 @@ +open Mocha +open Test_utils + +// Test for...of loop basic functionality +type item = {value: int, name: string} + +describe(__MODULE__, () => { + test("basic iteration", () => { + let arr = [1, 2, 3, 4, 5] + let sum = ref(0) + + for x of arr { + sum := sum.contents + x + } + + eq(__LOC__, sum.contents, 15) + }) + + test("single element", () => { + let arr = [42] + let result = ref(0) + + for x of arr { + result := x + } + + eq(__LOC__, result.contents, 42) + }) + + test("empty array", () => { + let arr = [] + let sum = ref(0) + + for x of arr { + ok(__LOC__, false) + } + }) +}) + +// Compilation tests + +// Test for...of with async function +let processData = async (data: int) => { + data + 10 +} + +let asyncProcess = async arr => { + let results = [] + for item of arr { + let result = await processData(item) + results->Array.push(result) + } + results +} + +// Test for...of with wildcard pattern +let testWildcardPattern = () => { + let arr = [1, 2, 3] + let count = ref(0) + + for _ of arr { + count := count.contents + 1 + } + + count.contents === 3 +} From 7866081a2805372cc9edb66150b8b1d821f1cae8 Mon Sep 17 00:00:00 2001 From: Christoph Knittel Date: Sat, 13 Sep 2025 09:30:33 +0200 Subject: [PATCH 2/2] Remove leftover typedef from test sources --- tests/tests/src/test_for_of.mjs | 6 +++--- tests/tests/src/test_for_of.res | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/tests/tests/src/test_for_of.mjs b/tests/tests/src/test_for_of.mjs index f94062fdf6..0308a12212 100644 --- a/tests/tests/src/test_for_of.mjs +++ b/tests/tests/src/test_for_of.mjs @@ -16,7 +16,7 @@ Mocha.describe("Test_for_of", () => { for (let x of arr) { sum = sum + x | 0; } - Test_utils.eq("File \"test_for_of.res\", line 16, characters 7-14", sum, 15); + Test_utils.eq("File \"test_for_of.res\", line 13, characters 7-14", sum, 15); }); Mocha.test("single element", () => { let arr = [42]; @@ -24,12 +24,12 @@ Mocha.describe("Test_for_of", () => { for (let x of arr) { result = x; } - Test_utils.eq("File \"test_for_of.res\", line 27, characters 7-14", result, 42); + Test_utils.eq("File \"test_for_of.res\", line 24, characters 7-14", result, 42); }); Mocha.test("empty array", () => { let arr = []; for (let x of arr) { - Test_utils.ok("File \"test_for_of.res\", line 35, characters 9-16", false); + Test_utils.ok("File \"test_for_of.res\", line 32, characters 9-16", false); } }); }); diff --git a/tests/tests/src/test_for_of.res b/tests/tests/src/test_for_of.res index 39d72479b4..2956d156cf 100644 --- a/tests/tests/src/test_for_of.res +++ b/tests/tests/src/test_for_of.res @@ -1,9 +1,6 @@ open Mocha open Test_utils -// Test for...of loop basic functionality -type item = {value: int, name: string} - describe(__MODULE__, () => { test("basic iteration", () => { let arr = [1, 2, 3, 4, 5]