diff --git a/CHANGELOG.md b/CHANGELOG.md index d07ebaba51c..926d2c908a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ - Rewatch: add `--prod` flag to `build`, `watch`, and `clean` to skip dev-dependencies and dev sources (`"type": "dev"`), enabling builds in environments where dev packages aren't installed (e.g. after `pnpm install --prod`). https://github.com/rescript-lang/rescript/pull/8347 - Add `Dict.assignMany`, `Dict.concat`, `Dict.concatMany`, `Dict.concatAll`, `Array.concatAll` to the stdlib. https://github.com/rescript-lang/rescript/pull/8364 +- Implement `for...of` and `for await...of` loops. https://github.com/rescript-lang/rescript/pull/7887 #### :bug: Bug fix diff --git a/analysis/reanalyze/src/Arnold.ml b/analysis/reanalyze/src/Arnold.ml index cc917725a92..f6484080223 100644 --- a/analysis/reanalyze/src/Arnold.ml +++ b/analysis/reanalyze/src/Arnold.ml @@ -982,9 +982,15 @@ module Compile = struct | Texp_while _ -> notImplemented "Texp_while"; assert false - | Texp_for _ -> - notImplemented "Texp_for"; - assert false + | Texp_for (_id, _pat, e1, e2, _dir, e3) -> + let open Command in + expression ~ctx e1 +++ expression ~ctx e2 +++ expression ~ctx e3 + | Texp_for_of (_id, _pat, e1, e2) -> + let open Command in + expression ~ctx e1 +++ expression ~ctx e2 + | Texp_for_await_of (_id, _pat, e1, e2) -> + let open Command in + expression ~ctx e1 +++ expression ~ctx e2 | Texp_send _ -> notImplemented "Texp_send"; assert false diff --git a/analysis/reanalyze/src/SideEffects.ml b/analysis/reanalyze/src/SideEffects.ml index 44442bcc968..ab49b36ea9a 100644 --- a/analysis/reanalyze/src/SideEffects.ml +++ b/analysis/reanalyze/src/SideEffects.ml @@ -62,6 +62,7 @@ let rec exprNoSideEffects (expr : Typedtree.expression) = | Texp_for (_id, _pat, e1, e2, _dir, e3) -> e1 |> exprNoSideEffects && e2 |> exprNoSideEffects && e3 |> exprNoSideEffects + | Texp_for_of _ | Texp_for_await_of _ -> false | 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 fced702ba39..abc286af599 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -102,6 +102,8 @@ let identifyPexp pexp = | Pexp_continue -> "Pexp_continue" | Pexp_while _ -> "Pexp_while" | Pexp_for _ -> "Pexp_for" + | Pexp_for_of _ -> "Pexp_for_of" + | Pexp_for_await_of _ -> "Pexp_for_await_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 218456fbd8d..f20b22ec727 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -266,6 +266,8 @@ and statement_desc = * for_ident * for_direction * block + | ForOf of label option * for_ident * expression * block + | ForAwaitOf of label option * for_ident * expression * block | Continue of label option | Break of label option (* only used when inline a fucntion *) | Return of expression diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index 84af6bf4b41..25852412667 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -135,6 +135,7 @@ let no_side_effect_obj = | Throw _ | Debugger | Break _ | Variable _ | Continue _ -> raise_notrace Not_found | Exp e -> self.expression self e + | ForOf _ | ForAwaitOf _ -> raise_notrace Not_found | Int_switch _ | String_switch _ | ForRange _ | If _ | While _ | Block _ | Return _ | Try _ -> super.statement self s); @@ -255,8 +256,8 @@ 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 _ - | String_switch _ | Throw _ | Try _ -> + | Variable _ | If _ | While _ | ForRange _ | ForOf _ | ForAwaitOf _ + | Continue _ | Int_switch _ | String_switch _ | Throw _ | Try _ -> false let rev_flatten_seq (x : J.expression) = diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index c447c9dba59..4310b1f80d1 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -1498,6 +1498,60 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = brace_block cxt f s) in action cxt + | ForOf (label, id, iterable, s) -> + P.vgroup f 0 (fun _ -> + let cxt = + P.group f 0 (fun _ -> + let cxt = + match label with + | None -> cxt + | Some label -> + P.string f label; + P.string f L.colon; + P.space f; + cxt + in + P.string f L.for_; + P.space f; + P.paren_group f 1 (fun _ -> + P.string f L.let_; + P.space f; + let cxt = Ext_pp_scope.ident cxt f id in + P.space f; + P.string f L.of_; + P.space f; + expression ~level:0 cxt f iterable)) + in + P.space f; + brace_block cxt f s) + | ForAwaitOf (label, id, iterable, s) -> + P.vgroup f 0 (fun _ -> + let cxt = + P.group f 0 (fun _ -> + let cxt = + match label with + | None -> cxt + | Some label -> + P.string f label; + P.string f L.colon; + P.space f; + cxt + in + P.string f L.for_; + P.space f; + P.string f L.await; + P.space f; + P.paren_group f 1 (fun _ -> + P.string f L.let_; + P.space f; + let cxt = Ext_pp_scope.ident cxt f id in + P.space f; + P.string f L.of_; + P.space f; + expression ~level:0 cxt f iterable)) + in + P.space f; + brace_block cxt f s) | Continue label -> P.string f L.continue; (match label with diff --git a/compiler/core/js_dump_lit.ml b/compiler/core/js_dump_lit.ml index fc982710ae9..a3ac8452465 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 b0733a5d279..e080f501196 100644 --- a/compiler/core/js_fold.ml +++ b/compiler/core/js_fold.ml @@ -241,6 +241,16 @@ class fold = let _self = _self#for_direction _x3 in let _self = _self#block _x4 in _self + | ForOf (_label, _x0, _x1, _x2) -> + let _self = _self#for_ident _x0 in + let _self = _self#expression _x1 in + let _self = _self#block _x2 in + _self + | ForAwaitOf (_label, _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 d5ed5ae6ffb..004f3e5b040 100644 --- a/compiler/core/js_pass_scope.ml +++ b/compiler/core/js_pass_scope.ml @@ -238,7 +238,9 @@ let record_scope_pass = statement = (fun self state x -> match x.statement_desc with - | ForRange (_, _, _, loop_id, _, _) -> + | ForRange (_, _, _, loop_id, _, _) + | ForOf (_, loop_id, _, _) + | ForAwaitOf (_, 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 fcc08093ed4..d3e0de74358 100644 --- a/compiler/core/js_record_fold.ml +++ b/compiler/core/js_record_fold.ml @@ -245,6 +245,16 @@ 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 (_label, _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 + | ForAwaitOf (_label, _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 ea9d9f58e98..da86618ae3c 100644 --- a/compiler/core/js_record_iter.ml +++ b/compiler/core/js_record_iter.ml @@ -179,6 +179,14 @@ let statement_desc : statement_desc fn = _self.for_ident _self _x2; for_direction _self _x3; _self.block _self _x4 + | ForOf (_label, _x0, _x1, _x2) -> + _self.for_ident _self _x0; + _self.expression _self _x1; + _self.block _self _x2 + | ForAwaitOf (_label, _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 628f104d56f..26551861718 100644 --- a/compiler/core/js_record_map.ml +++ b/compiler/core/js_record_map.ml @@ -243,6 +243,16 @@ let statement_desc : statement_desc fn = let _x3 = for_direction _self _x3 in let _x4 = _self.block _self _x4 in ForRange (_label, _x0, _x1, _x2, _x3, _x4) + | ForOf (_label, _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 (_label, _x0, _x1, _x2) + | ForAwaitOf (_label, _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 + ForAwaitOf (_label, _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 b0a0ab87c59..c0edf8bc321 100644 --- a/compiler/core/js_stmt_make.ml +++ b/compiler/core/js_stmt_make.ml @@ -326,6 +326,12 @@ let for_ ?comment ?label for_ident_expression finish_ident_expression id comment; } +let for_of ?comment ?label iterable_expression id (b : J.block) : t = + {statement_desc = ForOf (label, id, iterable_expression, b); comment} + +let for_await_of ?comment ?label iterable_expression id (b : J.block) : t = + {statement_desc = ForAwaitOf (label, 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 d4c11561518..d58ced95248 100644 --- a/compiler/core/js_stmt_make.mli +++ b/compiler/core/js_stmt_make.mli @@ -142,6 +142,12 @@ val for_ : J.block -> t +val for_of : + ?comment:string -> ?label:J.label -> J.expression -> J.ident -> J.block -> t + +val for_await_of : + ?comment:string -> ?label:J.label -> 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 f184820711d..15875608b97 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -108,6 +108,8 @@ module Types = struct | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t + | Lfor_await_of of ident * t * t | Lassign of ident * t (* | Lsend of Lam_compat.meth_kind * t * t * t list * Location.t *) end @@ -162,6 +164,8 @@ module X = struct | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t + | Lfor_await_of of ident * t * t | Lassign of ident * t (* | Lsend of Lam_compat.meth_kind * t * t * t list * Location.t *) end @@ -252,6 +256,14 @@ 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) + | Lfor_await_of (v, e1, e2) -> + let e1 = f e1 in + let e2 = f e2 in + Lfor_await_of (v, e1, e2) | Lassign (id, e) -> let e = f e in Lassign (id, e) @@ -409,7 +421,9 @@ let rec eq_approx (l1 : t) (l2 : t) = | Lfunction _ | Llet (_, _, _, _) | Lletrec _ | Lswitch _ | Lstaticcatch _ | Ltrywith _ - | Lfor (_, _, _, _, _) -> + | Lfor (_, _, _, _, _) + | Lfor_of (_, _, _) + | Lfor_await_of (_, _, _) -> false and eq_option l1 l2 = @@ -469,6 +483,8 @@ 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 for_await_of v e1 e2 : t = Lfor_await_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 46a76585b97..f6a398d677b 100644 --- a/compiler/core/lam.mli +++ b/compiler/core/lam.mli @@ -81,6 +81,8 @@ and t = private | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t + | Lfor_of of ident * t * t + | Lfor_await_of of ident * t * t | Lassign of ident * t (* | Lsend of Lambda.meth_kind * t * t * t list * Location.t *) @@ -170,6 +172,10 @@ val staticraise : int -> t list -> t val for_ : ident -> t -> t -> Asttypes.direction_flag -> t -> t +val for_of : ident -> t -> t -> t + +val for_await_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 ca3ff554cab..a6b815fdad2 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -120,6 +120,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 _ | Lfor_await_of _ -> false | Lassign _ -> false (* actually it depends ... *) (* | Lsend _ -> false *) | Lapply @@ -181,6 +182,7 @@ let rec size (lam : Lam.t) = | Lbreak | Lcontinue -> 1 | Lwhile _ -> really_big () | Lfor _ -> really_big () + | Lfor_of _ | Lfor_await_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 daa964b942a..5a5d4bbccda 100644 --- a/compiler/core/lam_arity_analysis.ml +++ b/compiler/core/lam_arity_analysis.ml @@ -130,7 +130,8 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = | Lsequence (_, l2) -> get_arity meta l2 | Lstaticraise _ (* since it will not be in tail position *) -> Lam_arity.na | Lbreak | Lcontinue -> Lam_arity.non_function_arity_info - | Lwhile _ | Lfor _ | Lassign _ -> Lam_arity.non_function_arity_info + | Lwhile _ | Lfor _ | Lfor_of _ | Lfor_await_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 8705a4e52b3..5499bb77ab4 100644 --- a/compiler/core/lam_bounded_vars.ml +++ b/compiler/core/lam_bounded_vars.ml @@ -103,6 +103,16 @@ 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 + | Lfor_await_of (ident, l1, l2) -> + let ident = rebind ident in + let l1 = aux l1 in + let l2 = aux l2 in + Lam.for_await_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 4a40080bcde..f5e63d45afe 100644 --- a/compiler/core/lam_check.ml +++ b/compiler/core/lam_check.ml @@ -62,6 +62,12 @@ 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 + | Lfor_await_of (_v, e1, e2) -> + check_staticfails e1 cxt; + check_staticfails e2 Set_int.empty | Lbreak | Lcontinue -> () | Llet (_str, _id, arg, body) -> check_list [arg; body] cxt | Lletrec (decl, body) -> @@ -148,6 +154,14 @@ let check file lam = iter e2; def v; iter e3 + | Lfor_of (v, e1, e2) -> + iter e1; + def v; + iter e2 + | Lfor_await_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 5865a150af0..2092c92b9ff 100644 --- a/compiler/core/lam_closure.ml +++ b/compiler/core/lam_closure.ml @@ -135,6 +135,14 @@ 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 + | Lfor_await_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 69197cb13c3..9897f0c01e4 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -1232,6 +1232,52 @@ 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 emitted_id = + if Set_ident.mem (Lam_free_variables.pass_free_variables body) id then id + else Ext_ident.create_tmp ~name:"_for_of" () + in + let block = + match compile_lambda new_cxt iterable with + | {value = None} -> assert false + | {block = b1; value = Some e1} -> + let loop_cxt, loop_frame = Lam_compile_context.push_loop lambda_cxt in + let block_body = + Js_output.output_as_block + (compile_lambda + {loop_cxt with continuation = EffectCall Not_tail} + body) + in + Ext_list.append b1 + [S.for_of ?label:loop_frame.label e1 emitted_id block_body] + in + Js_output.output_of_block_and_expression lambda_cxt.continuation block + E.unit + and compile_for_await_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 emitted_id = + if Set_ident.mem (Lam_free_variables.pass_free_variables body) id then id + else Ext_ident.create_tmp ~name:"_for_await_of" () + in + let block = + match compile_lambda new_cxt iterable with + | {value = None} -> assert false + | {block = b1; value = Some e1} -> + let loop_cxt, loop_frame = Lam_compile_context.push_loop lambda_cxt in + let block_body = + Js_output.output_as_block + (compile_lambda + {loop_cxt with continuation = EffectCall Not_tail} + body) + in + Ext_list.append b1 + [S.for_await_of ?label:loop_frame.label e1 emitted_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 @@ -1910,6 +1956,9 @@ 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 + | Lfor_await_of (id, iterable, body) -> + compile_for_await_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 bb61fd48c89..3c0c7c058d0 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) | Lfor_await_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 @@ -510,6 +511,10 @@ 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) + | Lfor_await_of (id, iterable, body) -> + Lam.for_await_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 97e5b2e5c20..045435a44e8 100644 --- a/compiler/core/lam_exit_count.ml +++ b/compiler/core/lam_exit_count.ml @@ -97,6 +97,12 @@ let count_helper (lam : Lam.t) : collection = count l1; count l2; count l3 + | Lfor_of (_, l1, l2) -> + count l1; + count l2 + | Lfor_await_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 ec17527043a..d269c78c7cb 100644 --- a/compiler/core/lam_free_variables.ml +++ b/compiler/core/lam_free_variables.ml @@ -57,6 +57,14 @@ 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 + | Lfor_await_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 405c7d0c8f8..fba5f7bdf74 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) | Lfor_await_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 @@ -79,6 +80,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) | Lfor_await_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 b7e6ef9100e..eae83894b77 100644 --- a/compiler/core/lam_iter.ml +++ b/compiler/core/lam_iter.ml @@ -80,6 +80,12 @@ let inner_iter (l : t) (f : t -> unit) : unit = f e1; f e2; f e3 + | Lfor_of (_v, e1, e2) -> + f e1; + f e2 + | Lfor_await_of (_v, e1, e2) -> + f e1; + f e2 | Lassign (_id, e) -> f e let inner_exists (l : t) (f : t -> bool) : bool = @@ -114,4 +120,5 @@ let inner_exists (l : t) (f : t -> bool) : bool = | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> f e1 || f e2 | Lfor (_v, e1, e2, _dir, e3) -> f e1 || f e2 || f e3 + | Lfor_of (_v, e1, e2) | Lfor_await_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 25ea6619051..7965cfc6011 100644 --- a/compiler/core/lam_pass_alpha_conversion.ml +++ b/compiler/core/lam_pass_alpha_conversion.ml @@ -121,6 +121,9 @@ 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) + | Lfor_await_of (flag, l1, l2) -> + Lam.for_await_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 7df5a4452a8..807e9323f3b 100644 --- a/compiler/core/lam_pass_collect.ml +++ b/compiler/core/lam_pass_collect.ml @@ -145,6 +145,12 @@ let collect_info (meta : Lam_stats.t) (lam : Lam.t) = collect l1; collect l2; collect l3 + | Lfor_of (_, l1, l2) -> + collect l1; + collect l2 + | Lfor_await_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 4683c2de724..53bdf406ddf 100644 --- a/compiler/core/lam_pass_count.ml +++ b/compiler/core/lam_pass_count.ml @@ -119,6 +119,12 @@ 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 + | Lfor_await_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 1e6637db16c..6e94a78a587 100644 --- a/compiler/core/lam_pass_deep_flatten.ml +++ b/compiler/core/lam_pass_deep_flatten.ml @@ -261,6 +261,8 @@ 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) + | Lfor_await_of (flag, l1, l2) -> Lam.for_await_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 a37eaaebce9..030f0c54f10 100644 --- a/compiler/core/lam_pass_eliminate_ref.ml +++ b/compiler/core/lam_pass_eliminate_ref.ml @@ -101,4 +101,8 @@ 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) + | Lfor_await_of (v, e1, e2) -> + Lam.for_await_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 e4f07d7a6f5..e47be329551 100644 --- a/compiler/core/lam_pass_exits.ml +++ b/compiler/core/lam_pass_exits.ml @@ -54,6 +54,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 _ | Lfor_await_of _ -> false | Ltrywith _ -> false | Llet _ -> false | Lletrec (decl, body) -> decl = [] && no_bounded_variables body @@ -237,6 +238,8 @@ 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) + | Lfor_await_of (v, l1, l2) -> Lam.for_await_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 697a7a6b2d9..503e90c1f81 100644 --- a/compiler/core/lam_pass_lets_dce.ml +++ b/compiler/core/lam_pass_lets_dce.ml @@ -202,6 +202,8 @@ 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) + | Lfor_await_of (v, l1, l2) -> Lam.for_await_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 dc18a68bd48..52a88ad02eb 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -284,6 +284,9 @@ 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) + | Lfor_await_of (flag, l1, l2) -> + Lam.for_await_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 4db2fa5e09b..42b9a294b30 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -411,6 +411,12 @@ 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 + | Lfor_await_of (param, iterable, body) -> + fprintf ppf "@[<2>(for await %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 370a168f120..6f1e1b7583c 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) | Lfor_await_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 b028a607395..e449102dc5e 100644 --- a/compiler/core/lam_subst.ml +++ b/compiler/core/lam_subst.ml @@ -68,6 +68,9 @@ 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) + | Lfor_await_of (v, e1, e2) -> + Lam.for_await_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 9e9b9d641d4..7144cc776a5 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -396,6 +396,12 @@ 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_for_await_of (pat, e1, e2) -> + Exp.mk ~loc ~attrs + (Pexp_for_await_of (sub.pat sub pat, sub.expr sub e1, sub.expr sub e2)) | Pexp_jsx_element (Jsx_container_element { diff --git a/compiler/frontend/bs_builtin_ppx.ml b/compiler/frontend/bs_builtin_ppx.ml index 772cb1cff6a..40e9ff79bfb 100644 --- a/compiler/frontend/bs_builtin_ppx.ml +++ b/compiler/frontend/bs_builtin_ppx.ml @@ -396,6 +396,9 @@ let expr_mapper ~async_context ~in_function_def (self : mapper) | Pexp_await e -> check_await (); Ast_await.create_await_expression e + | Pexp_for_await_of _ -> + check_await (); + result | _ -> result) let typ_mapper (self : mapper) (typ : Parsetree.core_type) = diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index ee6c658a9c6..d8d3b350cb4 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -181,6 +181,9 @@ module Exp = struct let continue ?loc ?attrs () = mk ?loc ?attrs Pexp_continue 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 for_await_of ?loc ?attrs a b c = + mk ?loc ?attrs (Pexp_for_await_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 b80a356702b..6538c50419f 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -186,6 +186,20 @@ module Exp : sig direction_flag -> expression -> expression + val for_of : + ?loc:loc -> + ?attrs:attrs -> + pattern -> + expression -> + expression -> + expression + val for_await_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 e2562392600..474fec12d68 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -338,6 +338,14 @@ 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_for_await_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 e09cfc80fd3..0970b4b3ee6 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -326,6 +326,12 @@ 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_for_await_of (p, e1, e2) -> + Exp.mk ~loc ~attrs + (Pexp_for_await_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_from0.ml b/compiler/ml/ast_mapper_from0.ml index d3a34b2de24..c4e8f80bb35 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -88,6 +88,9 @@ let map_constant = function | Pconst_string (s, q) -> Pconst_string (s, q) | Pconst_float (s, suffix) -> Pconst_float (s, suffix) +let for_of_attr_name = "_res.for_of" +let for_await_of_attr_name = "_res.for_await_of" + let map_loc sub {loc; txt} = {loc = sub.location sub loc; txt} module T = struct @@ -323,6 +326,37 @@ module E = struct | _ -> true) attrs + let extract_for_of_attribute attrs = + List.find_map + (function + | {Location.txt}, Pt.PPat (_, Some expr) when txt = for_of_attr_name -> + Some expr + | _ -> None) + attrs + + let extract_for_await_of_attribute attrs = + List.find_map + (function + | {Location.txt}, Pt.PPat (_, Some expr) + when txt = for_await_of_attr_name -> + Some expr + | _ -> None) + attrs + + let remove_for_of_attribute attrs = + List.filter + (function + | {Location.txt}, _ when txt = for_of_attr_name -> false + | _ -> true) + attrs + + let remove_for_await_of_attribute attrs = + List.filter + (function + | {Location.txt}, _ when txt = for_await_of_attr_name -> false + | _ -> true) + attrs + let map_jsx_children sub (e : expression) : Pt.jsx_children = let rec visit (e : expression) : Pt.expression list = match e.pexp_desc with @@ -557,9 +591,22 @@ module E = struct continue ~loc ~attrs () | Pexp_while (e1, e2) -> while_ ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) - | 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 (p, e1, e2, d, e3) -> ( + let async_iterable_expr = extract_for_await_of_attribute attrs in + let array_expr = extract_for_of_attribute attrs in + let attrs = + remove_for_await_of_attribute (remove_for_of_attribute attrs) + in + match (async_iterable_expr, array_expr) with + | Some iterable, _ -> + for_await_of ~loc ~attrs (sub.pat sub p) iterable (sub.expr sub e3) + | None, Some array -> + (* This is actually a for...of loop, decode it *) + for_of ~loc ~attrs (sub.pat sub p) array (sub.expr sub e3) + | None, None -> + (* Regular for loop *) + for_ ~loc ~attrs (sub.pat sub p) (sub.expr sub e1) (sub.expr sub e2) d + (sub.expr sub e3)) | 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 a200acc8b1a..c204651070e 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -82,6 +82,9 @@ let map_constant = function | Pconst_string (s, q) -> Pconst_string (s, q) | Pconst_float (s, suffix) -> Pconst_float (s, suffix) +let for_of_attr_name = "_res.for_of" +let for_await_of_attr_name = "_res.for_await_of" + let map_loc sub {loc; txt} = {loc = sub.location sub loc; txt} module T = struct @@ -464,6 +467,31 @@ 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) -> + (* Encode for...of as a for loop with attributes *) + let for_of_attr = + sub.attribute sub + ( Location.mkloc for_of_attr_name loc, + PPat (Ast_helper.Pat.any (), Some array_expr) ) + in + (* Use dummy bounds since the iterable is carried by the internal attribute. *) + let start_expr = Exp.constant ~loc (Pconst_integer ("0", None)) in + let end_expr = Exp.constant ~loc (Pconst_integer ("0", None)) in + (* Use Upto direction flag (arbitrary choice) *) + for_ ~loc ~attrs:(for_of_attr :: attrs) (sub.pat sub pat) start_expr + end_expr Asttypes.Upto (sub.expr sub body_expr) + | Pexp_for_await_of (pat, iterable_expr, body_expr) -> + let for_await_of_attr = + sub.attribute sub + ( Location.mkloc for_await_of_attr_name loc, + PPat (Ast_helper.Pat.any (), Some iterable_expr) ) + in + let start_expr = Exp.constant ~loc (Pconst_integer ("0", None)) in + let end_expr = Exp.constant ~loc (Pconst_integer ("0", None)) in + for_ ~loc + ~attrs:(for_await_of_attr :: attrs) + (sub.pat sub pat) start_expr end_expr Asttypes.Upto + (sub.expr sub body_expr) | 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 58739b7639b..43a625160be 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -264,6 +264,14 @@ 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_for_await_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 4b010e1470a..a111bc388f8 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -368,6 +368,8 @@ type lambda = | Lcontinue | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * Asttypes.direction_flag * lambda + | Lfor_of of Ident.t * lambda * lambda + | Lfor_await_of of Ident.t * lambda * lambda | Lassign of Ident.t * lambda | Lsend of string * lambda * Location.t @@ -481,7 +483,9 @@ let make_key e = | Lcontinue -> Lcontinue | 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 _ | Lfor_await_of _ | Lwhile _ + -> + raise_notrace Not_simple and tr_recs env es = List.map (tr_rec env) es and tr_sw env sw = { @@ -554,6 +558,12 @@ let iter f = function f e1; f e2; f e3 + | Lfor_of (_v, e1, e2) -> + f e1; + f e2 + | Lfor_await_of (_v, e1, e2) -> + f e1; + f e2 | Lassign (_, e) -> f e | Lsend (_k, obj, _) -> f obj @@ -574,6 +584,8 @@ 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) | Lfor_await_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 _ | Lbreak | Lcontinue @@ -685,6 +697,8 @@ let subst_lambda s lam = | Lcontinue -> Lcontinue | 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) + | Lfor_await_of (v, e1, e2) -> Lfor_await_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 8a2b5bea2cf..b55caf8c364 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -339,6 +339,8 @@ type lambda = | Lcontinue | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * direction_flag * lambda + | Lfor_of of Ident.t * lambda * lambda + | Lfor_await_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 1a4027c80fc..29207d0150b 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -290,6 +290,10 @@ 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_for_await_of of pattern * expression * expression + (* for await pattern of iterable_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 d9d3faf0236..a2f0fd45305 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -799,6 +799,18 @@ 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_for_await_of (s, e1, e2) -> + let fmt : (_, _, _) format = + "@[@[@[<2>for await %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 0c9fc076cb4..f583acef641 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -318,6 +318,16 @@ 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_for_await_of (p, e1, e2) -> + line i ppf "Pexp_for_await_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 47764084004..27fa0452461 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -387,6 +387,12 @@ 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 + | Lfor_await_of (param, iterable, body) -> + fprintf ppf "@[<2>(for_await_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 d461abe171f..0c30f5eb1fe 100644 --- a/compiler/ml/printtyped.ml +++ b/compiler/ml/printtyped.ml @@ -352,6 +352,14 @@ 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_for_await_of (s, _, e1, e2) -> + line i ppf "Texp_for_await_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 2463a447c18..709b358086d 100644 --- a/compiler/ml/rec_check.ml +++ b/compiler/ml/rec_check.ml @@ -194,10 +194,11 @@ 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_break | Texp_continue -> + | Texp_ident _ | Texp_for _ | Texp_for_of _ | Texp_for_await_of _ + | Texp_constant _ | Texp_tuple _ | Texp_array _ | Texp_construct _ + | Texp_variant _ | Texp_record _ | Texp_setfield _ | Texp_while _ + | Texp_pack _ | Texp_function _ | Texp_extension_constructor _ | Texp_break + | Texp_continue -> Static | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}} when is_ref vd -> Static @@ -224,13 +225,27 @@ 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_for_await_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_break | Texp_continue -> Use.empty | Texp_apply diff --git a/compiler/ml/tast_iterator.ml b/compiler/ml/tast_iterator.ml index 26c343eb250..86f77420bd2 100644 --- a/compiler/ml/tast_iterator.ml +++ b/compiler/ml/tast_iterator.ml @@ -192,6 +192,12 @@ 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_for_await_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 63015636c7e..1d0e49efd35 100644 --- a/compiler/ml/tast_mapper.ml +++ b/compiler/ml/tast_mapper.ml @@ -245,6 +245,10 @@ 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_for_await_of (id, p, exp1, exp2) -> + Texp_for_await_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 d07bcbac7c5..dc7f8aa1a91 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -905,6 +905,10 @@ 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_for_await_of (param, _, iterable, body) -> + Lfor_await_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 f069a9412e0..9f06915d2ef 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -74,6 +74,7 @@ type error = | Unqualified_gadt_pattern of Path.t * string | Invalid_interval | Invalid_for_loop_index + | Invalid_for_of_pattern | No_value_clauses | Exception_pattern_below_toplevel | Inlined_record_escape @@ -204,6 +205,9 @@ let iter_expression f e = expr e1; expr e2; expr e3 + | Pexp_for_of (_, e1, e2) | Pexp_for_await_of (_, e1, e2) -> + expr e1; + expr e2 | Pexp_break | Pexp_continue -> () | Pexp_letmodule (_, me, e) -> expr e; @@ -3034,6 +3038,94 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) exp_attributes = sexp.pexp_attributes; exp_env = env; } + | Pexp_for_of (param, scollection, sbody) -> + let ty_elem = newvar () in + let collection = + let collection = type_exp ~context:None env scollection in + let iterable_state = (Btype.snapshot (), Ctype.save_levels ()) in + try + unify_exp_types ~context:None scollection.pexp_loc env + collection.exp_type + (Predef.type_iterable ty_elem); + collection + with Error (_, _, Expr_type_clash _) as iterable_error -> ( + let snapshot, levels = iterable_state in + Btype.backtrack snapshot; + Ctype.set_levels levels; + (* ReScript accepts raw arrays in `for...of` as a convenience, but + keeps `iterable<'a>` as the primary typing rule. If the array + fallback also fails, re-raise the original iterable error so other + non-iterable inputs still report the usual expectation. *) + let array_state = (Btype.snapshot (), Ctype.save_levels ()) in + try + unify_exp_types ~context:None scollection.pexp_loc env + collection.exp_type + (Predef.type_array ty_elem); + collection + with Error (_, _, Expr_type_clash _) -> + let snapshot, levels = array_state in + Btype.backtrack snapshot; + Ctype.set_levels levels; + raise iterable_error) + 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_of_pattern)) + in + let body = + with_depth loop_depth (fun () -> + 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_for_await_of (param, scollection, sbody) -> + let ty_elem = newvar () in + let collection = + type_expect ~context:None env scollection + (Predef.type_async_iterable ty_elem) + in + let id, new_env = + match param.ppat_desc with + | Ppat_any -> (Ident.create "_for_await_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_of_pattern)) + in + let body = + with_depth loop_depth (fun () -> + type_statement ~context:None new_env sbody) + in + rue + { + exp_desc = Texp_for_await_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 *) @@ -4679,6 +4771,9 @@ let report_error env loc ppf error = fprintf ppf "@[Only character intervals are supported in patterns.@]" | Invalid_for_loop_index -> fprintf ppf "@[Invalid for-loop index: only variables and _ are allowed.@]" + | Invalid_for_of_pattern -> + fprintf ppf + "@[Invalid for...of binding: only variables and _ are allowed.@]" | No_value_clauses -> fprintf ppf "None of the patterns in this 'match' expression match values." | Exception_pattern_below_toplevel -> diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index 63028f96602..2a57356e73b 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -107,6 +107,7 @@ type error = | Unqualified_gadt_pattern of Path.t * string | Invalid_interval | Invalid_for_loop_index + | Invalid_for_of_pattern | No_value_clauses | Exception_pattern_below_toplevel | Inlined_record_escape diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index e73331598d9..f772a0eb64b 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -119,6 +119,8 @@ and expression_desc = * expression * direction_flag * expression + | Texp_for_of of Ident.t * Parsetree.pattern * expression * expression + | Texp_for_await_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 56214067f67..538405a7691 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -220,6 +220,8 @@ and expression_desc = * expression * direction_flag * expression + | Texp_for_of of Ident.t * Parsetree.pattern * expression * expression + | Texp_for_await_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 0cff0688b91..72c24352b98 100644 --- a/compiler/ml/typedtreeIter.ml +++ b/compiler/ml/typedtreeIter.ml @@ -279,6 +279,12 @@ end = struct iter_expression exp1; iter_expression exp2; iter_expression exp3 + | Texp_for_of (_id, _, exp1, exp2) -> + iter_expression exp1; + iter_expression exp2 + | Texp_for_await_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 056b27912c8..5b3e5ecf01e 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -671,6 +671,17 @@ 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_for_await_of (pat, e1, e2) -> + Sexp.list + [ + Sexp.atom "Pexp_for_await_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 58d0bf91bea..2130cb19fae 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1807,6 +1807,11 @@ 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) + | Pexp_for_await_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 8583dab8fb7..2b6d7fe25ae 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -3816,28 +3816,65 @@ and parse_if_or_if_let_expression p = Parser.eat_breadcrumb 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 +and normalize_for_of_pattern p pattern = + match pattern.Parsetree.ppat_desc with + | Ppat_any | Ppat_var _ -> pattern + | _ -> + Parser.err ~start_pos:pattern.ppat_loc.loc_start + ~end_pos:pattern.ppat_loc.loc_end p + (Diagnostics.message + "A `for...of` or `for await...of` loop only supports a variable or \ + `_` on the left side. Destructuring patterns are not supported here."); + Ast_helper.Pat.any ~loc:pattern.ppat_loc () + +and parse_for_rest has_opening_paren ~await pattern start_pos p = + 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 pattern = normalize_for_of_pattern p pattern in + let array_expr = parse_expr ~context:WhenExpr p in + let body_expr = parse_loop_body () in + let loc = mk_loc start_pos p.prev_end_pos in + if await then Ast_helper.Exp.for_await_of ~loc pattern array_expr body_expr + else Ast_helper.Exp.for_of ~loc pattern array_expr body_expr + | In -> + if await then + Parser.err ~start_pos p + (Diagnostics.message + "A `for await` loop must use `of`, like `for await item of items`."); + (* 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.message + "A for-loop has the following form: `for i in 0 to 10` or `for item \ + of items`. Did you forget an `in` or `of` here?"); + Recover.default_expr () and parse_for_expression p = let start_pos = p.Parser.start_pos in @@ -3857,7 +3894,7 @@ and parse_for_expression p = let lid = Location.mkloc (Longident.Lident "()") loc in Ast_helper.Pat.construct lid None in - parse_for_rest false + parse_for_rest false ~await:false (parse_alias_pattern ~attrs:[] unit_pattern p) start_pos p | _ -> ( @@ -3871,13 +3908,48 @@ and parse_for_expression p = parse_tuple_pattern ~attrs:[] ~start_pos:lparen ~first:pat p in let pattern = parse_alias_pattern ~attrs:[] tuple_pattern p in - parse_for_rest false pattern start_pos p - | _ -> parse_for_rest true pat start_pos p)) + parse_for_rest false ~await:false pattern start_pos p + | _ -> parse_for_rest true ~await:false pat start_pos p)) + | Await -> ( + Parser.next p; + match p.token with + | Lparen -> ( + let lparen = p.start_pos in + Parser.next p; + match p.token with + | Rparen -> + Parser.next p; + let unit_pattern = + let loc = mk_loc lparen p.prev_end_pos in + let lid = Location.mkloc (Longident.Lident "()") loc in + Ast_helper.Pat.construct lid None + in + parse_for_rest false ~await:true + (parse_alias_pattern ~attrs:[] unit_pattern p) + start_pos p + | _ -> ( + Parser.leave_breadcrumb p Grammar.Pattern; + let pat = parse_pattern p in + Parser.eat_breadcrumb p; + match p.token with + | Comma -> + Parser.next p; + let tuple_pattern = + parse_tuple_pattern ~attrs:[] ~start_pos:lparen ~first:pat p + in + let pattern = parse_alias_pattern ~attrs:[] tuple_pattern p in + parse_for_rest false ~await:true pattern start_pos p + | _ -> parse_for_rest true ~await:true pat start_pos p)) + | _ -> + Parser.leave_breadcrumb p Grammar.Pattern; + let pat = parse_pattern p in + Parser.eat_breadcrumb p; + parse_for_rest false ~await:true pat start_pos p) | _ -> Parser.leave_breadcrumb p Grammar.Pattern; let pat = parse_pattern p in Parser.eat_breadcrumb p; - parse_for_rest false pat start_pos p + parse_for_rest false ~await:false pat start_pos p in Parser.eat_breadcrumb p; Parser.end_region p; diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml index 8d8aeef1406..09bfa4196c5 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_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when Ast_uncurried.expr_is_uncurried_fun expr -> Parenthesized @@ -103,8 +103,8 @@ let unary_expr_operand expr = pexp_desc = ( Pexp_assert _ | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ | Pexp_extension _ (* readability? maybe remove *) - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_ifthenelse _ ); + | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when ParsetreeViewer.expr_is_await expr -> Parenthesized @@ -222,7 +222,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_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when (not in_await) && ParsetreeViewer.expr_is_await expr -> @@ -267,8 +267,8 @@ let field_expr expr = pexp_desc = ( Pexp_assert _ | Pexp_extension _ (* %extension.x vs (%extension).x *) | Pexp_fun _ | Pexp_newtype _ | Pexp_constraint _ | Pexp_setfield _ - | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ - | Pexp_ifthenelse _ ); + | Pexp_match _ | Pexp_try _ | Pexp_while _ | Pexp_for _ | Pexp_for_of _ + | Pexp_for_await_of _ | Pexp_ifthenelse _ ); } -> Parenthesized | _ when ParsetreeViewer.expr_is_await expr -> Parenthesized diff --git a/compiler/syntax/src/res_parsetree_viewer.ml b/compiler/syntax/src/res_parsetree_viewer.ml index 391e51bede5..7e8652b1cfa 100644 --- a/compiler/syntax/src/res_parsetree_viewer.ml +++ b/compiler/syntax/src/res_parsetree_viewer.ml @@ -575,7 +575,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_for_await_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 682116611a8..3ec990fcfde 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3624,6 +3624,40 @@ 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_for_await_of (pattern, iterable_expr, body) -> + Doc.breakable_group ~force_break:true + (Doc.concat + [ + Doc.text "for await "; + print_pattern ~state pattern cmt_tbl; + Doc.text " of "; + (let doc = + print_expression_with_comments ~state iterable_expr cmt_tbl + in + match Parens.expr iterable_expr with + | Parens.Parenthesized -> add_parens doc + | Braced braces -> print_braces doc iterable_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/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index a413c9c60ea..7d00a6a60fc 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -350,6 +350,18 @@ addRecordLabelDeclaration x FirstClassModulesInterface.resi:3:2 path:FirstClassModulesInterface.record addRecordLabelDeclaration y FirstClassModulesInterface.resi:4:2 path:FirstClassModulesInterface.record addValueDeclaration +r FirstClassModulesInterface.resi:7:0 path:FirstClassModulesInterface + Scanning ForAwaitOf.cmt Source:ForAwaitOf.res + addValueDeclaration +keep ForAwaitOf.res:2:4 path:+ForAwaitOf + addValueDeclaration +sideEffects ForAwaitOf.res:10:4 path:+ForAwaitOf + addValueReference ForAwaitOf.res:2:4 --> ForAwaitOf.res:3:2 + addValueReference ForAwaitOf.res:2:4 --> ForAwaitOf.res:2:18 + addValueReference ForAwaitOf.res:10:4 --> ForAwaitOf.res:8:0 + Scanning ForOf.cmt Source:ForOf.res + addValueDeclaration +keep ForOf.res:2:4 path:+ForOf + addValueDeclaration +sideEffects ForOf.res:10:4 path:+ForOf + addValueReference ForOf.res:2:4 --> ForOf.res:3:2 + addValueReference ForOf.res:2:4 --> ForOf.res:2:11 + addValueReference ForOf.res:10:4 --> ForOf.res:8:0 Scanning Hooks.cmt Source:Hooks.res addValueDeclaration +make Hooks.res:4:4 path:+Hooks addValueDeclaration +default Hooks.res:25:4 path:+Hooks @@ -1932,9 +1944,9 @@ Forward Liveness Analysis - decls: 696 + decls: 700 roots(external targets): 134 - decl-deps: decls_with_out=407 edges_to_decls=288 + decl-deps: decls_with_out=411 edges_to_decls=288 Root (annotated): Value +Hooks.+default Root (external ref): Value +FirstClassModules.M.InnerModule2.+k @@ -2203,6 +2215,7 @@ Forward Liveness Analysis Root (annotated): Value +VariantsWithPayload.+printManyPayloads Root (annotated): Value +TestFirstClassModules.+convertFirstClassModuleWithTypeEquations Root (annotated): Value +TransitiveType1.+convertAlias + Root (annotated): Value +ForOf.+keep Root (external ref): VariantCase +Unison.stack.Cons Root (external ref): Exception +DeadExn.Inside.Einside Root (annotated): Value +TestImport.+defaultValue2 @@ -2248,6 +2261,7 @@ Forward Liveness Analysis Root (external ref): RecordLabel +VariantsWithPayload.payload.y Root (annotated): RecordLabel +ImportHookDefault.props.children Root (annotated): Value +TestModuleAliases.+testInner1 + Root (annotated): Value +ForAwaitOf.+keep Root (annotated): Value +OcamlWarningSuppressToplevel.+suppressed2 Root (annotated): Value +VariantsWithPayload.+testWithPayload Root (annotated): Value +Types.+testConvertNull @@ -2257,7 +2271,7 @@ Forward Liveness Analysis Root (annotated): Value +UseImportJsValue.+useGetProp Root (external ref): RecordLabel +Hooks.RenderPropRequiresConversion.props.renderVehicle - 320 roots found + 322 roots found Propagate: +Hooks.+default -> +Hooks.+make Propagate: DeadRT.moduleAccessPath.Root -> +DeadRT.moduleAccessPath.Root @@ -2736,6 +2750,10 @@ Forward Liveness Analysis Dead Value FirstClassModulesInterface.+r deps: in=0 (live=0 dead=0) out=1 -> +FirstClassModulesInterface.+r + Live (annotated) Value +ForAwaitOf.+keep + Dead Value +ForAwaitOf.+sideEffects + Live (annotated) Value +ForOf.+keep + Dead Value +ForOf.+sideEffects Live (external ref) RecordLabel +Hooks.vehicle.name deps: in=4 (live=4 dead=0) out=0 <- +Hooks.+make (live) @@ -4225,6 +4243,14 @@ Forward Liveness Analysis FirstClassModulesInterface.resi:7:1-13 r is never used + Warning Dead Value With Side Effects + ForAwaitOf.res:10:1-148 + sideEffects is never used and could have side effects + + Warning Dead Value With Side Effects + ForOf.res:10:1-133 + sideEffects is never used and could have side effects + Warning Dead Type Hooks.res:50:11-19 r.x is a record label never used to read a value @@ -5229,4 +5255,4 @@ Forward Liveness Analysis OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 318 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:179, Warning Dead Value With Side Effects:3, Warning Redundant Optional Argument:6, Warning Unused Argument:12) + Analysis reported 320 issues (Incorrect Dead Annotation:1, Warning Dead Exception:2, Warning Dead Module:22, Warning Dead Type:93, Warning Dead Value:179, Warning Dead Value With Side Effects:5, Warning Redundant Optional Argument:6, Warning Unused Argument:12) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ForAwaitOf.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ForAwaitOf.res new file mode 100644 index 00000000000..2b55fa18844 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ForAwaitOf.res @@ -0,0 +1,15 @@ +@live +let keep = async (xs: AsyncIterable.t) => { + for await x of xs { + ignore(x) + } +} + +@val external asyncIterable: AsyncIterable.t = "asyncIterable" + +let sideEffects = { + // Keep the body pure so the loop's effect comes only from iteration itself. + for await value of asyncIterable { + () + } +} diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/ForOf.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/ForOf.res new file mode 100644 index 00000000000..af663c50b31 --- /dev/null +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/ForOf.res @@ -0,0 +1,15 @@ +@live +let keep = xs => { + for x of (xs->Array.asIterable) { + ignore(x) + } +} + +@val external iterable: Iterable.t = "iterable" + +let sideEffects = { + // Keep the body pure so the loop's effect comes only from iteration itself. + for _ of iterable { + () + } +} diff --git a/tests/build_tests/super_errors/expected/for_await_requires_async_iterable.res.expected b/tests/build_tests/super_errors/expected/for_await_requires_async_iterable.res.expected new file mode 100644 index 00000000000..d189904b045 --- /dev/null +++ b/tests/build_tests/super_errors/expected/for_await_requires_async_iterable.res.expected @@ -0,0 +1,12 @@ + + We've found a bug for you! + /.../fixtures/for_await_requires_async_iterable.res:5:18-42 + + 3 │ + 4 │ let consume = async () => { + 5 │ for await x of numbers->Array.asIterable { + 6 │ Console.log(x) + 7 │ } + + This has type: Iterable.t (defined as iterable) + But it's expected to have type: asyncIterable<'a> 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 00000000000..3eb3c8871ea --- /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 │ Console.log(x) + 4 │ } + + This has type: int + But it's expected to have type: iterable<'a> diff --git a/tests/build_tests/super_errors/expected/for_of_string_requires_iterable.res.expected b/tests/build_tests/super_errors/expected/for_of_string_requires_iterable.res.expected new file mode 100644 index 00000000000..13b459549a4 --- /dev/null +++ b/tests/build_tests/super_errors/expected/for_of_string_requires_iterable.res.expected @@ -0,0 +1,12 @@ + + We've found a bug for you! + /.../fixtures/for_of_string_requires_iterable.res:4:10-14 + + 2 │ let chars = "abc" + 3 │ + 4 │ for x of chars { + 5 │ Console.log(x) + 6 │ } + + This has type: string + But it's expected to have type: iterable<'a> 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 00000000000..3f298541572 --- /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->Array.asIterable { + 4 │ let y: string = x + 5 │ Console.log(y) + 6 │ } + + This has type: int + But it's expected to have type: string + + You can convert int to string with Int.toString. diff --git a/tests/build_tests/super_errors/fixtures/for_await_requires_async_iterable.res b/tests/build_tests/super_errors/fixtures/for_await_requires_async_iterable.res new file mode 100644 index 00000000000..90d985c30ed --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/for_await_requires_async_iterable.res @@ -0,0 +1,8 @@ +// Test for await...of with a sync iterable (should fail type checking) +let numbers = [1, 2, 3] + +let consume = async () => { + for await x of numbers->Array.asIterable { + Console.log(x) + } +} 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 00000000000..9f2403bd8a0 --- /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 { + Console.log(x) +} diff --git a/tests/build_tests/super_errors/fixtures/for_of_string_requires_iterable.res b/tests/build_tests/super_errors/fixtures/for_of_string_requires_iterable.res new file mode 100644 index 00000000000..1594e69907b --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/for_of_string_requires_iterable.res @@ -0,0 +1,6 @@ +// Test for...of with a string (should fail type checking) +let chars = "abc" + +for x of chars { + Console.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 00000000000..2b6833d8402 --- /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->Array.asIterable { + let y: string = x + Console.log(y) +} diff --git a/tests/ounit_tests/ounit_js_analyzer_tests.ml b/tests/ounit_tests/ounit_js_analyzer_tests.ml new file mode 100644 index 00000000000..6a595cfd1a0 --- /dev/null +++ b/tests/ounit_tests/ounit_js_analyzer_tests.ml @@ -0,0 +1,30 @@ +let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) + +let pure_iterable = Js_exp_make.var (Ident.create "iterable") +let empty_body = [] + +let for_of_statement = + { + J.statement_desc = + ForOf (None, Ident.create "_for_of", pure_iterable, empty_body); + comment = None; + } + +let for_await_of_statement = + { + J.statement_desc = + ForAwaitOf (None, Ident.create "_for_await_of", pure_iterable, empty_body); + comment = None; + } + +let suites = + __FILE__ + >::: [ + ( __LOC__ >:: fun _ -> + OUnit.assert_bool __LOC__ + (not (Js_analyzer.no_side_effect_statement for_of_statement)) ); + ( __LOC__ >:: fun _ -> + OUnit.assert_bool __LOC__ + (not (Js_analyzer.no_side_effect_statement for_await_of_statement)) + ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index b463e105027..e05401e34c5 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -18,6 +18,7 @@ let suites = Ounit_utf8_test.suites; Ounit_unicode_tests.suites; Ounit_util_tests.suites; + Ounit_js_analyzer_tests.suites; Ounit_jsx_loc_tests.suites; ] diff --git a/tests/syntax_tests/data/ast-mapping/ForAwaitOfExpressions.res b/tests/syntax_tests/data/ast-mapping/ForAwaitOfExpressions.res new file mode 100644 index 00000000000..de4ed562f6e --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/ForAwaitOfExpressions.res @@ -0,0 +1,16 @@ +// Test for await..of AST mapping + +let testForAwaitOf = async () => { + let iterable = asyncIterable + + // Basic for await..of + for await x of iterable { + Console.log(x) + } + + // Nested async loop body + for await item of iterable { + let result = await Promise.resolve(item + 1) + Console.log(result) + } +} diff --git a/tests/syntax_tests/data/ast-mapping/ForOfExpressions.res b/tests/syntax_tests/data/ast-mapping/ForOfExpressions.res new file mode 100644 index 00000000000..fac764ed9a6 --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/ForOfExpressions.res @@ -0,0 +1,34 @@ +// Test for..of AST mapping + +let testForOf = () => { + let arr = [1, 2, 3] + + // Basic for..of + for x of arr { + Console.log(x) + } + + // For..of with complex expression + for item of Array.map(arr, x => x + 1) { + Console.log(item) + } + + // For..of with piped iterable expression + for item of arr->Array.map(x => x + 1) { + Console.log(item) + } + + // Nested for..of loops + for x of arr { + for y of arr { + Console.log((x, y)) + } + } + + // Mixed for..of and regular for loops + for x of arr { + for i in 1 to 3 { + Console.log((x, i)) + } + } +} diff --git a/tests/syntax_tests/data/ast-mapping/expected/ForAwaitOfExpressions.res.txt b/tests/syntax_tests/data/ast-mapping/expected/ForAwaitOfExpressions.res.txt new file mode 100644 index 00000000000..113504d8853 --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/expected/ForAwaitOfExpressions.res.txt @@ -0,0 +1,18 @@ +// Test for await..of AST mapping + +let testForAwaitOf = + @res.async + async () => { + let iterable = asyncIterable + + // Basic for await..of + for await x of iterable { + Console.log(x) + } + + // Nested async loop body + for await item of iterable { + let result = await Promise.resolve(item + 1) + Console.log(result) + } + } diff --git a/tests/syntax_tests/data/ast-mapping/expected/ForOfExpressions.res.txt b/tests/syntax_tests/data/ast-mapping/expected/ForOfExpressions.res.txt new file mode 100644 index 00000000000..fac764ed9a6 --- /dev/null +++ b/tests/syntax_tests/data/ast-mapping/expected/ForOfExpressions.res.txt @@ -0,0 +1,34 @@ +// Test for..of AST mapping + +let testForOf = () => { + let arr = [1, 2, 3] + + // Basic for..of + for x of arr { + Console.log(x) + } + + // For..of with complex expression + for item of Array.map(arr, x => x + 1) { + Console.log(item) + } + + // For..of with piped iterable expression + for item of arr->Array.map(x => x + 1) { + Console.log(item) + } + + // Nested for..of loops + for x of arr { + for y of arr { + Console.log((x, y)) + } + } + + // Mixed for..of and regular for loops + for x of arr { + for i in 1 to 3 { + Console.log((x, i)) + } + } +} diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/for_of_pattern.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/for_of_pattern.res.txt new file mode 100644 index 00000000000..2a7d98037b0 --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/for_of_pattern.res.txt @@ -0,0 +1,14 @@ + + Syntax error! + syntax_tests/data/parsing/errors/expressions/for_of_pattern.res:3:5-17 + + 1 │ let arr = [1, 2, 3] + 2 │ + 3 │ for (item, index) of arr { + 4 │ ignore((item, index)) + 5 │ } + + A `for...of` or `for await...of` loop only supports a variable or `_` on the left side. Destructuring patterns are not supported here. + +let arr = [|1;2;3|] +;;for _ of arr do ignore (item, index) done \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/errors/expressions/for_of_pattern.res b/tests/syntax_tests/data/parsing/errors/expressions/for_of_pattern.res new file mode 100644 index 00000000000..161e206909a --- /dev/null +++ b/tests/syntax_tests/data/parsing/errors/expressions/for_of_pattern.res @@ -0,0 +1,5 @@ +let arr = [1, 2, 3] + +for (item, index) of arr { + ignore((item, index)) +} 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 ab4cdf2cb44..930f4691746 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? + A for-loop has the following form: `for i in 0 to 10` or `for item of items`. Did you forget an `in` or `of` here? -;;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 7f85176c8d9..b6381351e61 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_await_of.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_await_of.res.txt new file mode 100644 index 00000000000..a1d425f3b54 --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_await_of.res.txt @@ -0,0 +1,2 @@ +let iterable = asyncIterable +;;for await x of iterable do x + 1 done \ 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 00000000000..19a5419d61c --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/for_of.res.txt @@ -0,0 +1,4 @@ +let arr = [|1;2;3|] +;;for x of arr do ignore (x + 1) done +;;for item of arr -> (Array.map (fun [arity:1]x -> x + 1)) do ignore item + done \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/loopControl.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/loopControl.res.txt index f8a0f4036ce..bf2ecfe9b90 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/loopControl.res.txt +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/loopControl.res.txt @@ -7,4 +7,7 @@ | KeepGoing -> work ()) done ;;for i = 0 to 10 do match i with | 3 -> continue | 8 -> break | _ -> work () + done +;;for i of [|0;1;2;3;4;5|] do + match i with | 1 -> continue | 4 -> break | _ -> work () done \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/for_await_of.res b/tests/syntax_tests/data/parsing/grammar/expressions/for_await_of.res new file mode 100644 index 00000000000..c0ec988dc78 --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/for_await_of.res @@ -0,0 +1,5 @@ +let iterable = asyncIterable + +for await x of iterable { + x + 1 +} 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 00000000000..e664faa60b2 --- /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 piped iterable expression +for item of arr->Array.map(x => x + 1) { + ignore(item) +} diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res b/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res index aa6899bc523..8c625004438 100644 --- a/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res +++ b/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res @@ -21,3 +21,11 @@ for i in 0 to 10 { | _ => work() } } + +for i of [0, 1, 2, 3, 4, 5] { + switch i { + | 1 => continue + | 4 => break + | _ => work() + } +} diff --git a/tests/syntax_tests/data/printer/expr/expected/for_await_of.res.txt b/tests/syntax_tests/data/printer/expr/expected/for_await_of.res.txt new file mode 100644 index 00000000000..d65b212d285 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/expected/for_await_of.res.txt @@ -0,0 +1,13 @@ +// Test basic for await...of syntax +let iterable = asyncIterable + +for await x of iterable { + Console.log(x) +} + +let consume = async () => { + for await item of iterable { + let result = await Promise.resolve(item + 1) + Console.log(result) + } +} 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 00000000000..71b95cf9ba7 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/expected/for_of.res.txt @@ -0,0 +1,25 @@ +// Test basic for...of syntax +let arr = [1, 2, 3] + +for x of arr { + Console.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 +} + +// Test for...of with piped iterable expression +for item of arr->Array.map(x => x + 1) { + Console.log(item) +} diff --git a/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt b/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt index aa6899bc523..8c625004438 100644 --- a/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt @@ -21,3 +21,11 @@ for i in 0 to 10 { | _ => work() } } + +for i of [0, 1, 2, 3, 4, 5] { + switch i { + | 1 => continue + | 4 => break + | _ => work() + } +} diff --git a/tests/syntax_tests/data/printer/expr/for_await_of.res b/tests/syntax_tests/data/printer/expr/for_await_of.res new file mode 100644 index 00000000000..d65b212d285 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/for_await_of.res @@ -0,0 +1,13 @@ +// Test basic for await...of syntax +let iterable = asyncIterable + +for await x of iterable { + Console.log(x) +} + +let consume = async () => { + for await item of iterable { + let result = await Promise.resolve(item + 1) + Console.log(result) + } +} 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 00000000000..71b95cf9ba7 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/for_of.res @@ -0,0 +1,25 @@ +// Test basic for...of syntax +let arr = [1, 2, 3] + +for x of arr { + Console.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 +} + +// Test for...of with piped iterable expression +for item of arr->Array.map(x => x + 1) { + Console.log(item) +} diff --git a/tests/syntax_tests/data/printer/expr/loopControl.res b/tests/syntax_tests/data/printer/expr/loopControl.res index aa6899bc523..8c625004438 100644 --- a/tests/syntax_tests/data/printer/expr/loopControl.res +++ b/tests/syntax_tests/data/printer/expr/loopControl.res @@ -21,3 +21,11 @@ for i in 0 to 10 { | _ => work() } } + +for i of [0, 1, 2, 3, 4, 5] { + switch i { + | 1 => continue + | 4 => break + | _ => work() + } +} diff --git a/tests/tests/src/loop_control_test.mjs b/tests/tests/src/loop_control_test.mjs index 9b679a630db..71ca7f6a085 100644 --- a/tests/tests/src/loop_control_test.mjs +++ b/tests/tests/src/loop_control_test.mjs @@ -169,6 +169,93 @@ Mocha.describe("Loop_control_test", () => { 3 ], Belt_List.toArray(Belt_List.reverse(values))); }); + Mocha.test("for..of loop break and continue", () => { + let values = /* [] */0; + for (let i of [ + 0, + 1, + 2, + 3, + 4, + 5 + ]) { + if (i === 1) { + continue; + } + if (i === 4) { + break; + } + values = { + hd: i, + tl: values + }; + } + Test_utils.eq("File \"loop_control_test.res\", line 164, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("switch inside for..of targets the loop", () => { + let values = /* [] */0; + for (let i of [ + 0, + 1, + 2, + 3, + 4, + 5 + ]) { + if (i !== 1) { + if (i !== 4) { + values = { + hd: i, + tl: values + }; + } else { + break; + } + } else { + continue; + } + } + Test_utils.eq("File \"loop_control_test.res\", line 178, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("string switch inside for..of targets the loop via JS switch", () => { + let values = /* [] */0; + loop_2: for (let i of [ + 0, + 1, + 2, + 3, + 4, + 5 + ]) { + let state = i !== 1 ? ( + i !== 4 ? "keep" : "stop" + ) : "skip"; + switch (state) { + case "skip" : + continue loop_2; + case "stop" : + break loop_2; + default: + values = { + hd: i, + tl: values + }; + } + } + Test_utils.eq("File \"loop_control_test.res\", line 199, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); }); /* Not a pure module */ diff --git a/tests/tests/src/loop_control_test.res b/tests/tests/src/loop_control_test.res index f85b78591b5..715b32f446b 100644 --- a/tests/tests/src/loop_control_test.res +++ b/tests/tests/src/loop_control_test.res @@ -145,4 +145,57 @@ describe(__MODULE__, () => { eq(__LOC__, [0, 2, 3], values.contents->Belt.List.reverse->Belt.List.toArray) }) + + test("for..of loop break and continue", () => { + let values = ref(list{}) + + for i of [0, 1, 2, 3, 4, 5]->Array.asIterable { + if i == 1 { + continue + } + + if i == 4 { + break + } + + values := list{i, ...values.contents} + } + + eq(__LOC__, [0, 2, 3], values.contents->Belt.List.reverse->Belt.List.toArray) + }) + + test("switch inside for..of targets the loop", () => { + let values = ref(list{}) + + for i of [0, 1, 2, 3, 4, 5]->Array.asIterable { + switch i { + | 1 => continue + | 4 => break + | _ => values := list{i, ...values.contents} + } + } + + eq(__LOC__, [0, 2, 3], values.contents->Belt.List.reverse->Belt.List.toArray) + }) + + // Keep a JS `switch` in the generated output so this exercises labeled loop control. + test("string switch inside for..of targets the loop via JS switch", () => { + let values = ref(list{}) + + for i of [0, 1, 2, 3, 4, 5]->Array.asIterable { + let state = switch i { + | 1 => "skip" + | 4 => "stop" + | _ => "keep" + } + + switch state { + | "skip" => continue + | "stop" => break + | _ => values := list{i, ...values.contents} + } + } + + eq(__LOC__, [0, 2, 3], values.contents->Belt.List.reverse->Belt.List.toArray) + }) }) diff --git a/tests/tests/src/stdlib/Stdlib_IteratorTests.mjs b/tests/tests/src/stdlib/Stdlib_IteratorTests.mjs index 31082f5d2f3..8b26a7cb3e1 100644 --- a/tests/tests/src/stdlib/Stdlib_IteratorTests.mjs +++ b/tests/tests/src/stdlib/Stdlib_IteratorTests.mjs @@ -643,6 +643,112 @@ Test.run([ 3 ]); +let asyncIterableForAwaitValues = { + contents: [] +}; + +let asyncIterableForAwait = ((async function* () { + yield 1 + yield 2 + yield 3 +})()); + +for await (let value of asyncIterableForAwait) { + asyncIterableForAwaitValues.contents = Belt_Array.concatMany([ + asyncIterableForAwaitValues.contents, + [value] + ]); +} + +Test.run([ + [ + "Stdlib_IteratorTests.res", + 485, + 20, + 36 + ], + "for await...of" +], asyncIterableForAwaitValues.contents, eq, [ + 1, + 2, + 3 +]); + +let asyncIterableForAwaitLoopControlValues = { + contents: [] +}; + +let asyncIterableForAwaitLoopControl = ((async function* () { + yield 1 + yield 2 + yield 3 + yield 4 +})()); + +loop_0: for await (let value$1 of asyncIterableForAwaitLoopControl) { + let exit = 0; + switch (value$1) { + case 1 : + continue loop_0; + case 3 : + break loop_0; + default: + exit = 1; + } + if (exit === 1) { + asyncIterableForAwaitLoopControlValues.contents = Belt_Array.concatMany([ + asyncIterableForAwaitLoopControlValues.contents, + [value$1] + ]); + } +} + +Test.run([ + [ + "Stdlib_IteratorTests.res", + 506, + 13, + 44 + ], + "for await...of break/continue" +], asyncIterableForAwaitLoopControlValues.contents, eq, [2]); + +async function collectStartupMessages(logs) { + let messages = []; + for await (let line of logs) { + if (line === ":heartbeat") { + continue; + } + if (line === "server ready") { + break; + } + messages.push(line); + } + return messages; +} + +let startupLogs = ((async function* () { + yield ":heartbeat" + yield "Booting app" + yield ":heartbeat" + yield "Connecting to database" + yield "server ready" + yield "This line should not be collected" +})()); + +Test.run([ + [ + "Stdlib_IteratorTests.res", + 540, + 13, + 54 + ], + "for await...of realistic break/continue" +], await collectStartupMessages(startupLogs), eq, [ + "Booting app", + "Connecting to database" +]); + let _asyncIterableView = ((async function* () { yield 1 })()); @@ -667,7 +773,7 @@ if (match$19.done !== false && match$19.value === "stopped") { Test.run([ [ "Stdlib_IteratorTests.res", - 497, + 569, 13, 41 ], @@ -698,7 +804,7 @@ if (match$21.done !== false) { Test.run([ [ "Stdlib_IteratorTests.res", - 526, + 598, 13, 40 ], @@ -725,7 +831,7 @@ await Stdlib_AsyncIterableIterator.forEach(createdAsyncIterableIterator, value = Test.run([ [ "Stdlib_IteratorTests.res", - 550, + 622, 13, 56 ], @@ -761,6 +867,12 @@ export { asyncGeneratorNextValueResult, asyncGeneratorIterableValues, asyncGeneratorIterable, + asyncIterableForAwaitValues, + asyncIterableForAwait, + asyncIterableForAwaitLoopControlValues, + asyncIterableForAwaitLoopControl, + collectStartupMessages, + startupLogs, _asyncIterableView, asyncGeneratorReturnValueResult, asyncGeneratorReturnValue, diff --git a/tests/tests/src/stdlib/Stdlib_IteratorTests.res b/tests/tests/src/stdlib/Stdlib_IteratorTests.res index 96e7ae08f25..4a7aff085d1 100644 --- a/tests/tests/src/stdlib/Stdlib_IteratorTests.res +++ b/tests/tests/src/stdlib/Stdlib_IteratorTests.res @@ -471,6 +471,78 @@ Test.run( [2, 3], ) +let asyncIterableForAwaitValues = ref([]) +let asyncIterableForAwait: AsyncIterable.t = %raw(`(async function* () { + yield 1 + yield 2 + yield 3 +})()`) + +for await value of asyncIterableForAwait { + asyncIterableForAwaitValues := [...asyncIterableForAwaitValues.contents, value] +} + +Test.run(__POS_OF__("for await...of"), asyncIterableForAwaitValues.contents, eq, [1, 2, 3]) + +let asyncIterableForAwaitLoopControlValues = ref([]) +let asyncIterableForAwaitLoopControl: AsyncIterable.t = %raw(`(async function* () { + yield 1 + yield 2 + yield 3 + yield 4 +})()`) + +for await value of asyncIterableForAwaitLoopControl { + switch value { + | 1 => continue + | 3 => break + | _ => + asyncIterableForAwaitLoopControlValues := + [...asyncIterableForAwaitLoopControlValues.contents, value] + } +} + +Test.run( + __POS_OF__("for await...of break/continue"), + asyncIterableForAwaitLoopControlValues.contents, + eq, + [2], +) + +let collectStartupMessages = async (logs: AsyncIterable.t) => { + let messages = [] + + for await line of logs { + if line == ":heartbeat" { + continue + } + + if line == "server ready" { + break + } + + messages->Array.push(line) + } + + messages +} + +let startupLogs: AsyncIterable.t = %raw(`(async function* () { + yield ":heartbeat" + yield "Booting app" + yield ":heartbeat" + yield "Connecting to database" + yield "server ready" + yield "This line should not be collected" +})()`) + +Test.run( + __POS_OF__("for await...of realistic break/continue"), + await collectStartupMessages(startupLogs), + eq, + ["Booting app", "Connecting to database"], +) + let _asyncIterableView: AsyncIterable.t = ( %raw(`(async function* () { yield 1 diff --git a/tests/tests/src/stdlib/Stdlib_TestSuite.mjs b/tests/tests/src/stdlib/Stdlib_TestSuite.mjs index 8176f2fe80f..77fd06e5a0d 100644 --- a/tests/tests/src/stdlib/Stdlib_TestSuite.mjs +++ b/tests/tests/src/stdlib/Stdlib_TestSuite.mjs @@ -139,6 +139,18 @@ let asyncGeneratorIterableValues = Stdlib_IteratorTests.asyncGeneratorIterableVa let asyncGeneratorIterable = Stdlib_IteratorTests.asyncGeneratorIterable; +let asyncIterableForAwaitValues = Stdlib_IteratorTests.asyncIterableForAwaitValues; + +let asyncIterableForAwait = Stdlib_IteratorTests.asyncIterableForAwait; + +let asyncIterableForAwaitLoopControlValues = Stdlib_IteratorTests.asyncIterableForAwaitLoopControlValues; + +let asyncIterableForAwaitLoopControl = Stdlib_IteratorTests.asyncIterableForAwaitLoopControl; + +let collectStartupMessages = Stdlib_IteratorTests.collectStartupMessages; + +let startupLogs = Stdlib_IteratorTests.startupLogs; + let _asyncIterableView = Stdlib_IteratorTests._asyncIterableView; let asyncGeneratorReturnValueResult = Stdlib_IteratorTests.asyncGeneratorReturnValueResult; @@ -216,6 +228,12 @@ export { asyncGeneratorNextValueResult, asyncGeneratorIterableValues, asyncGeneratorIterable, + asyncIterableForAwaitValues, + asyncIterableForAwait, + asyncIterableForAwaitLoopControlValues, + asyncIterableForAwaitLoopControl, + collectStartupMessages, + startupLogs, _asyncIterableView, asyncGeneratorReturnValueResult, asyncGeneratorReturnValue, diff --git a/tests/tests/src/test_for_of.mjs b/tests/tests/src/test_for_of.mjs new file mode 100644 index 00000000000..6a112759f42 --- /dev/null +++ b/tests/tests/src/test_for_of.mjs @@ -0,0 +1,169 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Belt_Array from "@rescript/runtime/lib/es6/Belt_Array.mjs"; +import * as Test_utils from "./test_utils.mjs"; + +function firstLargeOpenOrder(orders) { + let matchId; + loop_0: for (let order of orders) { + let match = order.status; + switch (match) { + case "Open" : + if (order.total >= 500) { + matchId = order.id; + break loop_0; + } + break; + case "Paid" : + case "Cancelled" : + continue loop_0; + } + } + return matchId; +} + +function countItems(arr) { + let count = 0; + for (let _for_of of arr) { + count = count + 1 | 0; + } + return count; +} + +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 44, 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 55, characters 7-14", result, 42); + }); + Mocha.test("empty array", () => { + let arr = []; + for (let _for_of of arr) { + Test_utils.ok("File \"test_for_of.res\", line 63, characters 9-16", false); + } + }); + Mocha.test("unused named loop variable", () => Test_utils.eq("File \"test_for_of.res\", line 68, characters 7-14", countItems([ + 1, + 2, + 3 + ]), 3)); + Mocha.test("body bindings can shadow the loop variable", () => { + let arr = [ + 1, + 2, + 3 + ]; + let sum = 0; + for (let x of arr) { + let x$1 = x + 10 | 0; + sum = sum + x$1 | 0; + } + Test_utils.eq("File \"test_for_of.res\", line 80, characters 7-14", sum, 36); + }); + Mocha.test("break and continue target the for..of loop from nested switches", () => { + let arr = [ + 1, + 2, + 3, + 4 + ]; + let seen = []; + loop_0: for (let x of arr) { + switch (x) { + case 1 : + continue loop_0; + case 3 : + break loop_0; + default: + seen = Belt_Array.concatMany([ + seen, + [x] + ]); + } + } + Test_utils.eq("File \"test_for_of.res\", line 95, characters 7-14", seen, [2]); + }); + Mocha.test("blog-style order scan uses continue and break", () => { + let orders = [ + { + id: 101, + status: "Cancelled", + total: 900 + }, + { + id: 102, + status: "Paid", + total: 700 + }, + { + id: 103, + status: "Open", + total: 120 + }, + { + id: 104, + status: "Open", + total: 650 + }, + { + id: 105, + status: "Open", + total: 900 + } + ]; + Test_utils.eq("File \"test_for_of.res\", line 107, characters 7-14", firstLargeOpenOrder(orders), 104); + }); +}); + +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 { + firstLargeOpenOrder, + countItems, + 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 00000000000..e02fc3b2a80 --- /dev/null +++ b/tests/tests/src/test_for_of.res @@ -0,0 +1,137 @@ +open Mocha +open Test_utils + +type orderStatus = Open | Paid | Cancelled +type order = {id: int, status: orderStatus, total: int} + +let firstLargeOpenOrder = (orders: array) => { + let matchId = ref(None) + + for order of orders { + switch order.status { + | Cancelled | Paid => continue + | Open => + if order.total >= 500 { + matchId := Some(order.id) + break + } + } + } + + matchId.contents +} + +// Test for...of with an unused named loop variable +let countItems = (arr: array) => { + let count = ref(0) + + for item of arr { + count := count.contents + 1 + } + + count.contents +} + +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) + } + }) + + test("unused named loop variable", () => { + eq(__LOC__, countItems([1, 2, 3]), 3) + }) + + test("body bindings can shadow the loop variable", () => { + let arr = [1, 2, 3] + let sum = ref(0) + + for x of arr { + let x = x + 10 + sum := sum.contents + x + } + + eq(__LOC__, sum.contents, 36) + }) + + test("break and continue target the for..of loop from nested switches", () => { + let arr = [1, 2, 3, 4] + let seen = ref([]) + + for x of arr { + switch x { + | 1 => continue + | 3 => break + | _ => seen := [...seen.contents, x] + } + } + + eq(__LOC__, seen.contents, [2]) + }) + + test("blog-style order scan uses continue and break", () => { + let orders = [ + {id: 101, status: Cancelled, total: 900}, + {id: 102, status: Paid, total: 700}, + {id: 103, status: Open, total: 120}, + {id: 104, status: Open, total: 650}, + {id: 105, status: Open, total: 900}, + ] + + eq(__LOC__, firstLargeOpenOrder(orders), Some(104)) + }) +}) + +// Compilation tests + +// Test for...of with async function +let processData = async (data: int) => { + data + 10 +} + +let asyncProcess = async (arr: array) => { + 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 +}