diff --git a/analysis/examples/larger-project/src/Unison.res b/analysis/examples/larger-project/src/Unison.res index cfef1fa1d34..e33b8ee6f2b 100644 --- a/analysis/examples/larger-project/src/Unison.res +++ b/analysis/examples/larger-project/src/Unison.res @@ -1,12 +1,12 @@ // Exmple of several DCE checks operating in unison -type break = +type break_ = | IfNeed | Never | Always type t = { - break: break, + break_: break_, doc: string, } @@ -14,7 +14,7 @@ type rec stack = | Empty | Cons(t, stack) -let group = (~break=IfNeed, doc) => {break: break, doc: doc} +let group = (~break_=IfNeed, doc) => {break_, doc: doc} let rec fits = (w, stack) => switch stack { @@ -25,8 +25,8 @@ let rec fits = (w, stack) => let rec toString = (~width, stack) => switch stack { - | Cons({break, doc}, stack) => - switch break { + | Cons({break_, doc}, stack) => + switch break_ { | IfNeed => (fits(width, stack) ? "fits " : "no ") ++ (stack |> toString(~width=width - 1)) | Never => "never " ++ (doc ++ (stack |> toString(~width=width - 1))) | Always => "always " ++ (doc ++ (stack |> toString(~width=width - 1))) @@ -35,5 +35,5 @@ let rec toString = (~width, stack) => } toString(~width=80, Empty) -toString(~width=80, Cons(group(~break=Never, "abc"), Empty)) -toString(~width=80, Cons(group(~break=Always, "d"), Empty)) +toString(~width=80, Cons(group(~break_=Never, "abc"), Empty)) +toString(~width=80, Cons(group(~break_=Always, "d"), Empty)) diff --git a/analysis/examples/larger-project/src/res_doc.res b/analysis/examples/larger-project/src/res_doc.res index efa0f9ef541..477c86335ad 100644 --- a/analysis/examples/larger-project/src/res_doc.res +++ b/analysis/examples/larger-project/src/res_doc.res @@ -330,8 +330,8 @@ let debug = t => { text(")"), }), ) - | LineBreak(break) => - let breakTxt = switch break { + | LineBreak(break_) => + let breakTxt = switch break_ { | Classic => "Classic" | Soft => "Soft" | Hard => "Hard" diff --git a/analysis/reanalyze/src/SideEffects.ml b/analysis/reanalyze/src/SideEffects.ml index 89d5756bf13..44442bcc968 100644 --- a/analysis/reanalyze/src/SideEffects.ml +++ b/analysis/reanalyze/src/SideEffects.ml @@ -26,6 +26,9 @@ let rec exprNoSideEffects (expr : Typedtree.expression) = | Texp_ident _ | Texp_constant _ -> true | Texp_construct (_, _, el) -> el |> List.for_all exprNoSideEffects | Texp_function _ -> true + (* Loop control changes whether subsequent code in the enclosing loop runs, + so it should not be treated as a removable pure expression. *) + | Texp_break | Texp_continue -> false | Texp_apply {funct = {exp_desc = Texp_ident (path, _, _)}; args} when path |> pathIsWhitelistedForSideEffects -> args |> List.for_all (fun (_, eo) -> eo |> exprOptNoSideEffects) diff --git a/analysis/src/DumpAst.ml b/analysis/src/DumpAst.ml index 39b05b3e6f0..c21cff6b66d 100644 --- a/analysis/src/DumpAst.ml +++ b/analysis/src/DumpAst.ml @@ -170,6 +170,8 @@ and printExprItem expr ~pos ~indentation = |> String.concat "\n") | Pexp_ident {txt} -> "Pexp_ident:" ^ (Utils.flattenLongIdent txt |> SharedTypes.ident) + | Pexp_break -> "Pexp_break" + | Pexp_continue -> "Pexp_continue" | Pexp_apply {funct = expr; args} -> let printLabel labelled ~pos = match labelled with diff --git a/analysis/src/Utils.ml b/analysis/src/Utils.ml index 863598dc568..fced702ba39 100644 --- a/analysis/src/Utils.ml +++ b/analysis/src/Utils.ml @@ -98,6 +98,8 @@ let identifyPexp pexp = | Pexp_array _ -> "Pexp_array" | Pexp_ifthenelse _ -> "Pexp_ifthenelse" | Pexp_sequence _ -> "Pexp_sequence" + | Pexp_break -> "Pexp_break" + | Pexp_continue -> "Pexp_continue" | Pexp_while _ -> "Pexp_while" | Pexp_for _ -> "Pexp_for" | Pexp_constraint _ -> "Pexp_constraint" diff --git a/compiler/core/j.ml b/compiler/core/j.ml index dc5aa2514d0..218456fbd8d 100644 --- a/compiler/core/j.ml +++ b/compiler/core/j.ml @@ -249,6 +249,7 @@ and case_clause = { and string_clause = Ast_untagged_variants.tag_type * case_clause and int_clause = int * case_clause +and label = string and statement_desc = | Block of block @@ -256,16 +257,17 @@ and statement_desc = (* Function declaration and Variable declaration *) | Exp of expression | If of expression * block * block - | While of expression * block + | While of label option * expression * block (* check if it contains loop mutable values, happens in nested loop *) | ForRange of - for_ident_expression option + label option + * for_ident_expression option * finish_ident_expression * for_ident * for_direction * block - | Continue - | Break (* only used when inline a fucntion *) + | Continue of label option + | Break of label option (* only used when inline a fucntion *) | Return of expression (* Here we need track back a bit ?, move Return to Function ... Then we can only have one Return, which is not good *) diff --git a/compiler/core/js_analyzer.ml b/compiler/core/js_analyzer.ml index d728ae4f9ce..84af6bf4b41 100644 --- a/compiler/core/js_analyzer.ml +++ b/compiler/core/js_analyzer.ml @@ -132,7 +132,7 @@ let no_side_effect_obj = statement = (fun self s -> match s.statement_desc with - | Throw _ | Debugger | Break | Variable _ | Continue -> + | Throw _ | Debugger | Break _ | Variable _ | Continue _ -> raise_notrace Not_found | Exp e -> self.expression self e | Int_switch _ | String_switch _ | ForRange _ | If _ | While _ | Block _ @@ -250,12 +250,12 @@ and eq_statement ({statement_desc = x0} : J.statement) | Return b -> eq_expression a b | _ -> false) | Debugger -> y0 = Debugger - | Break -> y0 = Break + | Break label -> y0 = Break label | Block xs0 -> ( match y0 with | Block ys0 -> eq_block xs0 ys0 | _ -> false) - | Variable _ | If _ | While _ | ForRange _ | Continue | Int_switch _ + | Variable _ | If _ | While _ | ForRange _ | Continue _ | Int_switch _ | String_switch _ | Throw _ | Try _ -> false diff --git a/compiler/core/js_dump.ml b/compiler/core/js_dump.ml index 43967a3f1ce..b8fd283bf97 100644 --- a/compiler/core/js_dump.ml +++ b/compiler/core/js_dump.ml @@ -237,15 +237,6 @@ let debugger_nl f = semi f; P.newline f -let break_nl f = - P.string f L.break; - semi f; - P.newline f - -let continue f = - P.string f L.continue; - semi f - let formal_parameter_list cxt f l = iter_lst cxt f l Ext_pp_scope.ident comma_sp (* IdentMap *) @@ -1390,9 +1381,18 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = P.string f L.else_; P.space f; brace_block cxt f s2) - | While (e, s) -> + | While (label, e, s) -> (* FIXME: print scope as well *) let cxt = + let cxt = + match label with + | None -> cxt + | Some label -> + P.string f label; + P.string f L.colon; + P.space f; + cxt + in match e.expression_desc with | Number (Int {i = 1l}) -> P.string f L.while_; @@ -1412,9 +1412,18 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = let cxt = brace_block cxt f s in semi f; cxt - | ForRange (for_ident_expression, finish, id, direction, s) -> + | ForRange (label, for_ident_expression, finish, id, direction, s) -> let action cxt = P.vgroup 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 let cxt = P.group f 0 (fun _ -> (* The only place that [semi] may have semantics here *) @@ -1489,15 +1498,28 @@ and statement_desc top cxt f (s : J.statement_desc) : cxt = brace_block cxt f s) in action cxt - | Continue -> - continue f; + | Continue label -> + P.string f L.continue; + (match label with + | None -> () + | Some label -> + P.space f; + P.string f label); + semi f; cxt (* P.newline f; #2642 *) | Debugger -> debugger_nl f; cxt - | Break -> - break_nl f; + | Break label -> + P.string f L.break; + (match label with + | None -> () + | Some label -> + P.space f; + P.string f label); + semi f; + P.newline f; cxt | Return e -> ( match e.expression_desc with diff --git a/compiler/core/js_fold.ml b/compiler/core/js_fold.ml index 1ffa5e02701..b0733a5d279 100644 --- a/compiler/core/js_fold.ml +++ b/compiler/core/js_fold.ml @@ -228,11 +228,11 @@ class fold = let _self = _self#block _x1 in let _self = _self#block _x2 in _self - | While (_x0, _x1) -> + | While (_label, _x0, _x1) -> let _self = _self#expression _x0 in let _self = _self#block _x1 in _self - | ForRange (_x0, _x1, _x2, _x3, _x4) -> + | ForRange (_label, _x0, _x1, _x2, _x3, _x4) -> let _self = option (fun _self -> _self#for_ident_expression) _self _x0 in @@ -241,8 +241,8 @@ class fold = let _self = _self#for_direction _x3 in let _self = _self#block _x4 in _self - | Continue -> _self - | Break -> _self + | Continue _ -> _self + | Break _ -> _self | Return _x0 -> let _self = _self#expression _x0 in _self diff --git a/compiler/core/js_pass_scope.ml b/compiler/core/js_pass_scope.ml index 646357b5c38..d5ed5ae6ffb 100644 --- a/compiler/core/js_pass_scope.ml +++ b/compiler/core/js_pass_scope.ml @@ -238,7 +238,7 @@ let record_scope_pass = statement = (fun self state x -> match x.statement_desc with - | ForRange (_, _, loop_id, _, _) -> + | ForRange (_, _, _, loop_id, _, _) -> (* TODO: simplify definition of For *) let { defined_idents = defined_idents'; @@ -287,7 +287,7 @@ let record_scope_pass = closured_idents = Set_ident.union state.closured_idents lexical_scope; } - | While (pred, body) -> + | While (_, pred, body) -> with_in_loop (self.block self (with_in_loop (self.expression self state pred) true) diff --git a/compiler/core/js_record_fold.ml b/compiler/core/js_record_fold.ml index fe71e6f5f14..fcc08093ed4 100644 --- a/compiler/core/js_record_fold.ml +++ b/compiler/core/js_record_fold.ml @@ -234,19 +234,19 @@ let statement_desc : 'a. ('a, statement_desc) fn = let st = _self.block _self st _x1 in let st = _self.block _self st _x2 in st - | While (_x0, _x1) -> + | While (_label, _x0, _x1) -> let st = _self.expression _self st _x0 in let st = _self.block _self st _x1 in st - | ForRange (_x0, _x1, _x2, _x3, _x4) -> + | ForRange (_label, _x0, _x1, _x2, _x3, _x4) -> let st = option for_ident_expression _self st _x0 in let st = finish_ident_expression _self st _x1 in let st = _self.for_ident _self st _x2 in let st = for_direction _self st _x3 in let st = _self.block _self st _x4 in st - | Continue -> st - | Break -> st + | Continue _ -> st + | Break _ -> st | Return _x0 -> let st = _self.expression _self st _x0 in st diff --git a/compiler/core/js_record_iter.ml b/compiler/core/js_record_iter.ml index e6c9ab9646b..ea9d9f58e98 100644 --- a/compiler/core/js_record_iter.ml +++ b/compiler/core/js_record_iter.ml @@ -170,17 +170,17 @@ let statement_desc : statement_desc fn = _self.expression _self _x0; _self.block _self _x1; _self.block _self _x2 - | While (_x0, _x1) -> + | While (_label, _x0, _x1) -> _self.expression _self _x0; _self.block _self _x1 - | ForRange (_x0, _x1, _x2, _x3, _x4) -> + | ForRange (_label, _x0, _x1, _x2, _x3, _x4) -> option for_ident_expression _self _x0; finish_ident_expression _self _x1; _self.for_ident _self _x2; for_direction _self _x3; _self.block _self _x4 - | Continue -> () - | Break -> () + | Continue _ -> () + | Break _ -> () | Return _x0 -> _self.expression _self _x0 | Int_switch (_x0, _x1, _x2) -> _self.expression _self _x0; diff --git a/compiler/core/js_record_map.ml b/compiler/core/js_record_map.ml index b13fdb2a557..628f104d56f 100644 --- a/compiler/core/js_record_map.ml +++ b/compiler/core/js_record_map.ml @@ -232,19 +232,19 @@ let statement_desc : statement_desc fn = let _x1 = _self.block _self _x1 in let _x2 = _self.block _self _x2 in If (_x0, _x1, _x2) - | While (_x0, _x1) -> + | While (_label, _x0, _x1) -> let _x0 = _self.expression _self _x0 in let _x1 = _self.block _self _x1 in - While (_x0, _x1) - | ForRange (_x0, _x1, _x2, _x3, _x4) -> + While (_label, _x0, _x1) + | ForRange (_label, _x0, _x1, _x2, _x3, _x4) -> let _x0 = option for_ident_expression _self _x0 in let _x1 = finish_ident_expression _self _x1 in let _x2 = _self.for_ident _self _x2 in let _x3 = for_direction _self _x3 in let _x4 = _self.block _self _x4 in - ForRange (_x0, _x1, _x2, _x3, _x4) - | Continue as v -> v - | Break as v -> v + ForRange (_label, _x0, _x1, _x2, _x3, _x4) + | Continue _ as v -> v + | Break _ as v -> v | Return _x0 -> let _x0 = _self.expression _self _x0 in Return _x0 diff --git a/compiler/core/js_stmt_make.ml b/compiler/core/js_stmt_make.ml index e4d87302e93..b0a0ab87c59 100644 --- a/compiler/core/js_stmt_make.ml +++ b/compiler/core/js_stmt_make.ml @@ -188,7 +188,7 @@ let rec block_last_is_return_throw_or_continue (x : J.block) = | [] -> false | [x] -> ( match x.statement_desc with - | Return _ | Throw _ | Continue -> true + | Return _ | Throw _ | Continue _ | Break _ -> true | _ -> false) | _ :: rest -> block_last_is_return_throw_or_continue rest @@ -314,20 +314,23 @@ let if_ ?comment ?declaration ?else_ (e : J.expression) (then_ : J.block) : t = let assign ?comment id e : t = {statement_desc = J.Exp (E.assign (E.var id) e); comment} -let while_ ?comment (e : E.t) (st : J.block) : t = - {statement_desc = While (e, st); comment} +let while_ ?comment ?label (e : E.t) (st : J.block) : t = + {statement_desc = While (label, e, st); comment} -let for_ ?comment for_ident_expression finish_ident_expression id direction - (b : J.block) : t = +let for_ ?comment ?label for_ident_expression finish_ident_expression id + direction (b : J.block) : t = { statement_desc = - ForRange (for_ident_expression, finish_ident_expression, id, direction, b); + ForRange + (label, for_ident_expression, finish_ident_expression, id, direction, b); comment; } let try_ ?comment ?with_ ?finally body : t = {statement_desc = Try (body, with_, finally); comment} -let continue_ : t = {statement_desc = Continue; comment = None} +let break_ ?label () : t = {statement_desc = Break label; comment = None} + +let continue_ ?label () : t = {statement_desc = Continue label; comment = None} let debugger_block : t list = [{statement_desc = Debugger; comment = None}] diff --git a/compiler/core/js_stmt_make.mli b/compiler/core/js_stmt_make.mli index 00a5daae838..d4c11561518 100644 --- a/compiler/core/js_stmt_make.mli +++ b/compiler/core/js_stmt_make.mli @@ -130,10 +130,11 @@ val assign : ?comment:string -> J.ident -> J.expression -> t J.ident -> t *) -val while_ : ?comment:string -> J.expression -> J.block -> t +val while_ : ?comment:string -> ?label:J.label -> J.expression -> J.block -> t val for_ : ?comment:string -> + ?label:J.label -> J.for_ident_expression option -> J.finish_ident_expression -> J.for_ident -> @@ -163,6 +164,8 @@ val return_stmt : ?comment:string -> J.expression -> t unit -> t *) -val continue_ : t +val break_ : ?label:J.label -> unit -> t + +val continue_ : ?label:J.label -> unit -> t val debugger_block : t list diff --git a/compiler/core/lam.ml b/compiler/core/lam.ml index 51b8bb3e382..f184820711d 100644 --- a/compiler/core/lam.ml +++ b/compiler/core/lam.ml @@ -104,6 +104,8 @@ module Types = struct | Ltrywith of t * ident * t | Lifthenelse of t * t * t | Lsequence of t * t + | Lbreak + | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t | Lassign of ident * t @@ -156,6 +158,8 @@ module X = struct | Ltrywith of t * ident * t | Lifthenelse of t * t * t | Lsequence of t * t + | Lbreak + | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t | Lassign of ident * t @@ -237,6 +241,8 @@ let inner_map (l : t) (f : t -> X.t) : X.t = let e1 = f e1 in let e2 = f e2 in Lsequence (e1, e2) + | Lbreak -> Lbreak + | Lcontinue -> Lcontinue | Lwhile (e1, e2) -> let e1 = f e1 in let e2 = f e2 in @@ -373,6 +379,8 @@ let rec eq_approx (l1 : t) (l2 : t) = match l2 with | Lsequence (a0, b0) -> eq_approx a a0 && eq_approx b b0 | _ -> false) + | Lbreak -> l2 = Lbreak + | Lcontinue -> l2 = Lcontinue | Lwhile (p, b) -> ( match l2 with | Lwhile (p0, b0) -> eq_approx p p0 && eq_approx b b0 @@ -437,6 +445,8 @@ let stringswitch (lam : t) cases default : t = let true_ : t = Lconst Const_js_true let false_ : t = Lconst Const_js_false let unit : t = Lconst (Const_js_undefined {is_unit = true}) +let break : t = Lbreak +let continue : t = Lcontinue let rec seq (a : t) b : t = match a with diff --git a/compiler/core/lam.mli b/compiler/core/lam.mli index 560d247669d..46a76585b97 100644 --- a/compiler/core/lam.mli +++ b/compiler/core/lam.mli @@ -77,6 +77,8 @@ and t = private | Ltrywith of t * ident * t | Lifthenelse of t * t * t | Lsequence of t * t + | Lbreak + | Lcontinue | Lwhile of t * t | Lfor of ident * t * t * Asttypes.direction_flag * t | Lassign of ident * t @@ -148,6 +150,10 @@ val not_ : Location.t -> t -> t val seq : t -> t -> t (** drop unused block *) +val break : t + +val continue : t + val while_ : t -> t -> t (* val event : t -> Lambda.lambda_event -> t *) diff --git a/compiler/core/lam_analysis.ml b/compiler/core/lam_analysis.ml index a4b78bea0eb..ca3ff554cab 100644 --- a/compiler/core/lam_analysis.ml +++ b/compiler/core/lam_analysis.ml @@ -114,6 +114,7 @@ let rec no_side_effects (lam : Lam.t) : bool = | Lifthenelse (a, b, c) -> no_side_effects a && no_side_effects b && no_side_effects c | Lsequence (a, b) -> no_side_effects a && no_side_effects b + | Lbreak | Lcontinue -> false | Lletrec (bindings, body) -> Ext_list.for_all_snd bindings no_side_effects && no_side_effects body | Lwhile _ -> @@ -177,6 +178,7 @@ let rec size (lam : Lam.t) = | Ltrywith _ -> really_big () | Lifthenelse (l1, l2, l3) -> 1 + size l1 + size l2 + size l3 | Lsequence (l1, l2) -> size l1 + size l2 + | Lbreak | Lcontinue -> 1 | Lwhile _ -> really_big () | Lfor _ -> really_big () | Lassign (_, v) -> 1 + size v diff --git a/compiler/core/lam_arity_analysis.ml b/compiler/core/lam_arity_analysis.ml index ee3d91f1541..daa964b942a 100644 --- a/compiler/core/lam_arity_analysis.ml +++ b/compiler/core/lam_arity_analysis.ml @@ -129,6 +129,7 @@ let rec get_arity (meta : Lam_stats.t) (lam : Lam.t) : Lam_arity.t = | Lifthenelse (_, l2, l3) -> all_lambdas meta [l2; l3] | Lsequence (_, l2) -> get_arity meta l2 | Lstaticraise _ (* since it will not be in tail position *) -> Lam_arity.na + | Lbreak | Lcontinue -> Lam_arity.non_function_arity_info | Lwhile _ | Lfor _ | Lassign _ -> Lam_arity.non_function_arity_info and all_lambdas meta (xs : Lam.t list) = diff --git a/compiler/core/lam_bounded_vars.ml b/compiler/core/lam_bounded_vars.ml index e038e56798e..8705a4e52b3 100644 --- a/compiler/core/lam_bounded_vars.ml +++ b/compiler/core/lam_bounded_vars.ml @@ -150,6 +150,8 @@ let rewrite (map : _ Hash_ident.t) (lam : Lam.t) : Lam.t = let l1 = aux l1 in let l2 = aux l2 in Lam.seq l1 l2 + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | Lwhile (l1, l2) -> let l1 = aux l1 in let l2 = aux l2 in diff --git a/compiler/core/lam_check.ml b/compiler/core/lam_check.ml index 0ef71f2ceed..4a40080bcde 100644 --- a/compiler/core/lam_check.ml +++ b/compiler/core/lam_check.ml @@ -62,6 +62,7 @@ let check file lam = check_staticfails e1 cxt; check_staticfails e2 cxt; check_staticfails e3 Set_int.empty + | Lbreak | Lcontinue -> () | Llet (_str, _id, arg, body) -> check_list [arg; body] cxt | Lletrec (decl, body) -> check_list_snd decl cxt; @@ -138,6 +139,7 @@ let check file lam = | Lsequence (e1, e2) -> iter e1; iter e2 + | Lbreak | Lcontinue -> () | Lwhile (e1, e2) -> iter e1; iter e2 diff --git a/compiler/core/lam_closure.ml b/compiler/core/lam_closure.ml index c02f05b7057..5865a150af0 100644 --- a/compiler/core/lam_closure.ml +++ b/compiler/core/lam_closure.ml @@ -126,6 +126,7 @@ let free_variables (export_idents : Set_ident.t) (params : stats Map_ident.t) | Lsequence (e1, e2) -> iter top e1; iter sink_pos e2 + | Lbreak | Lcontinue -> () | Lwhile (e1, e2) -> iter sink_pos e1; iter sink_pos e2 (* in the loop, no substitution any way *) diff --git a/compiler/core/lam_compile.ml b/compiler/core/lam_compile.ml index 42844bc99c0..69197cb13c3 100644 --- a/compiler/core/lam_compile.ml +++ b/compiler/core/lam_compile.ml @@ -356,6 +356,8 @@ let compile output_prefix = (Maybe_tail_is_return (Tail_with_name {label = Some ret; in_staticcatch = false})); jmp_table = Lam_compile_context.empty_handler_map; + switch_depth = 0; + loop_stack = []; } body in @@ -591,13 +593,14 @@ let compile output_prefix = List.filter (fun (_, lam1) -> not (Lam.eq_approx lam lam1)) cases | _ -> cases in + let switch_cxt = Lam_compile_context.enter_switch cxt in let default = match default with | Complete -> None | NonComplete -> None | Default lam -> ( let statements = - Js_output.output_as_block (compile_lambda cxt lam) + Js_output.output_as_block (compile_lambda switch_cxt lam) in match statements with | [] -> None @@ -608,7 +611,7 @@ let compile output_prefix = if last then (* merge and shared *) let switch_body, should_break = - Js_output.to_break_block (compile_lambda cxt lam) + Js_output.to_break_block (compile_lambda switch_cxt lam) in let should_break = if @@ -1159,15 +1162,16 @@ let compile output_prefix = | [] -> e | _ -> E.of_block block ~e in - let block = - [ - S.while_ e - (Js_output.output_as_block - @@ compile_lambda - {lambda_cxt with continuation = EffectCall Not_tail} - body); - ] + let loop_cxt, loop_frame = Lam_compile_context.push_loop lambda_cxt in + let body_block = + Js_output.output_as_block + @@ compile_lambda + {loop_cxt with continuation = EffectCall Not_tail} + body in + (* The label stays absent for ordinary loops and is filled in lazily if a + nested switch emits break/continue for this loop. *) + let block = [S.while_ ?label:loop_frame.label e body_block] in Js_output.output_of_block_and_expression lambda_cxt.continuation block E.unit (* all non-tail @@ -1197,15 +1201,21 @@ let compile output_prefix = we can guarantee e1 is pure, if it literally contains a side effect call, put it in the beginning *) + let loop_cxt, loop_frame = Lam_compile_context.push_loop lambda_cxt in let block_body = Js_output.output_as_block (compile_lambda - {lambda_cxt with continuation = EffectCall Not_tail} + {loop_cxt with continuation = EffectCall Not_tail} body) in + let make_for for_ident_expression = + (* See compile_while above: only loops that need labeled control flow + end up with a concrete JS label. *) + S.for_ ?label:loop_frame.label for_ident_expression e2 id direction + block_body + in match (b1, b2) with - | _, [] -> - Ext_list.append_one b1 (S.for_ (Some e1) e2 id direction block_body) + | _, [] -> Ext_list.append_one b1 (make_for (Some e1)) | _, _ when Js_analyzer.no_side_effect_expression e1 (* @@ -1214,13 +1224,11 @@ let compile output_prefix = b2 > e1 > e2 *) -> - Ext_list.append b1 - (Ext_list.append_one b2 - (S.for_ (Some e1) e2 id direction block_body)) + Ext_list.append b1 (Ext_list.append_one b2 (make_for (Some e1))) | _, _ -> Ext_list.append b1 (S.define_variable ~kind:Variable id e1 - :: Ext_list.append_one b2 (S.for_ None e2 id direction block_body))) + :: Ext_list.append_one b2 (make_for None))) in Js_output.output_of_block_and_expression lambda_cxt.continuation block E.unit @@ -1567,8 +1575,9 @@ let compile output_prefix = Map_ident.disjoint_merge_exn new_params ret.new_params (fun _ _ _ -> assert false); let block = - Ext_list.map_append assigned_params [S.continue_] (fun (param, arg) -> - S.assign param arg) + Ext_list.map_append assigned_params + [S.continue_ ()] + (fun (param, arg) -> S.assign param arg) in (* Note true and continue needed to be handled together*) Js_output.make ~output_finished:True (Ext_list.append args_code block) @@ -1799,6 +1808,8 @@ let compile output_prefix = (Maybe_tail_is_return (Tail_with_name {label = None; in_staticcatch = false})); jmp_table = Lam_compile_context.empty_handler_map; + switch_depth = 0; + loop_stack = []; } body))) | Lapply appinfo -> compile_apply appinfo lambda_cxt @@ -1852,6 +1863,38 @@ let compile output_prefix = | Lswitch (switch_arg, sw) -> compile_switch switch_arg sw lambda_cxt | Lstaticraise (i, largs) -> compile_staticraise i largs lambda_cxt | Lstaticcatch _ -> compile_staticcatch cur_lam lambda_cxt + | Lbreak -> ( + match lambda_cxt.loop_stack with + | [] -> assert false + | frame :: _ -> + let stmt = + if lambda_cxt.switch_depth > 0 then + (* In JS, break inside a switch breaks the switch unless we target + the enclosing loop explicitly. *) + let label = + Lam_compile_context.ensure_loop_label lambda_cxt frame + in + S.break_ ~label () + else S.break_ () + in + (* [break] is accepted inside braced expressions like [{break}], so keep + the usual NeedValue invariant even though JS only has a statement form. *) + Js_output.make [stmt] ~value:E.undefined ~output_finished:True) + | Lcontinue -> ( + match lambda_cxt.loop_stack with + | [] -> assert false + | frame :: _ -> + let stmt = + if lambda_cxt.switch_depth > 0 then + (* Keep continue consistent with break by routing nested-switch loop + control through the same labeled path. *) + let label = + Lam_compile_context.ensure_loop_label lambda_cxt frame + in + S.continue_ ~label () + else S.continue_ () + in + Js_output.make [stmt] ~value:E.undefined ~output_finished:True) | Lwhile (p, body) -> compile_while p body lambda_cxt | Lfor (id, start, finish, direction, body) -> ( match (direction, finish) with diff --git a/compiler/core/lam_compile_context.ml b/compiler/core/lam_compile_context.ml index 606bae315c4..c6341e743db 100644 --- a/compiler/core/lam_compile_context.ml +++ b/compiler/core/lam_compile_context.ml @@ -55,6 +55,7 @@ type tail_type = Not_tail | Maybe_tail_is_return of maybe_tail (* anonoymous function does not have identifier *) type let_kind = Lam_compat.let_kind +type loop_frame = {mutable label: J.label option} type continuation = | EffectCall of tail_type @@ -71,10 +72,34 @@ let continuation_is_return (x : continuation) = true | EffectCall Not_tail | NeedValue Not_tail | Declare _ | Assign _ -> false -type t = {continuation: continuation; jmp_table: jmp_table; meta: Lam_stats.t} +type t = { + continuation: continuation; + jmp_table: jmp_table; + meta: Lam_stats.t; + switch_depth: int; + loop_stack: loop_frame list; + loop_label_counter: int ref; +} let empty_handler_map = HandlerMap.empty +let enter_switch cxt = {cxt with switch_depth = cxt.switch_depth + 1} + +let push_loop cxt = + let frame = {label = None} in + ({cxt with loop_stack = frame :: cxt.loop_stack}, frame) + +let ensure_loop_label cxt frame = + match frame.label with + | Some label -> label + | None -> + (* Only allocate a JS loop label when a nested switch actually needs one. *) + let next_id = !(cxt.loop_label_counter) in + cxt.loop_label_counter := next_id + 1; + let label = Printf.sprintf "loop_%d" next_id in + frame.label <- Some label; + label + type handler = {label: jbl_label; handler: Lam.t; bindings: Ident.t list} let no_static_raise_in_handler (x : handler) : bool = diff --git a/compiler/core/lam_compile_context.mli b/compiler/core/lam_compile_context.mli index d16905f8006..6f4b4010cab 100644 --- a/compiler/core/lam_compile_context.mli +++ b/compiler/core/lam_compile_context.mli @@ -42,6 +42,7 @@ type return_label = { type value = {exit_id: Ident.t; bindings: Ident.t list; order_id: int} type let_kind = Lam_compat.let_kind +type loop_frame = {mutable label: J.label option} type tail = {label: return_label option; in_staticcatch: bool} @@ -67,9 +68,19 @@ type jmp_table = value Map_int.t val continuation_is_return : continuation -> bool -type t = {continuation: continuation; jmp_table: jmp_table; meta: Lam_stats.t} +type t = { + continuation: continuation; + jmp_table: jmp_table; + meta: Lam_stats.t; + switch_depth: int; + loop_stack: loop_frame list; + loop_label_counter: int ref; +} val empty_handler_map : jmp_table +val enter_switch : t -> t +val push_loop : t -> t * loop_frame +val ensure_loop_label : t -> loop_frame -> J.label type handler = {label: jbl_label; handler: Lam.t; bindings: Ident.t list} diff --git a/compiler/core/lam_compile_main.ml b/compiler/core/lam_compile_main.ml index 5871d643ebd..cc33a2bcfb5 100644 --- a/compiler/core/lam_compile_main.ml +++ b/compiler/core/lam_compile_main.ml @@ -62,6 +62,9 @@ let compile_group output_prefix (meta : Lam_stats.t) (* ([Js_stmt_make.comment (Gen_of_env.query_type id env )], None) ++ *) Lam_compile.compile_lambda ~output_prefix { continuation = Declare (kind, id); jmp_table = Lam_compile_context.empty_handler_map; + switch_depth = 0; + loop_stack = []; + loop_label_counter = ref 0; meta } lam @@ -69,12 +72,18 @@ let compile_group output_prefix (meta : Lam_stats.t) Lam_compile.compile_recursive_lets ~output_prefix { continuation = EffectCall Not_tail; jmp_table = Lam_compile_context.empty_handler_map; + switch_depth = 0; + loop_stack = []; + loop_label_counter = ref 0; meta } id_lams | Nop lam -> (* TODO: Side effect callls, log and see statistics *) Lam_compile.compile_lambda ~output_prefix {continuation = EffectCall Not_tail; jmp_table = Lam_compile_context.empty_handler_map; + switch_depth = 0; + loop_stack = []; + loop_label_counter = ref 0; meta } lam diff --git a/compiler/core/lam_convert.ml b/compiler/core/lam_convert.ml index 2e88a3b7036..bb61fd48c89 100644 --- a/compiler/core/lam_convert.ml +++ b/compiler/core/lam_convert.ml @@ -100,6 +100,7 @@ let exception_id_destructed (l : Lam.t) (fv : Ident.t) : bool = | Lstaticraise (_, args) -> hit_list args | Lifthenelse (e1, e2, e3) -> hit e1 || hit e2 || hit e3 | Lsequence (e1, e2) -> hit e1 || hit e2 + | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> hit e1 || hit e2 in hit l @@ -504,6 +505,8 @@ let convert (exports : Set_ident.t) (lam : Lambda.lambda) : | Lifthenelse (b, then_, else_) -> Lam.if_ (convert_aux b) (convert_aux then_) (convert_aux else_) | Lsequence (a, b) -> Lam.seq (convert_aux a) (convert_aux b) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_exit_count.ml b/compiler/core/lam_exit_count.ml index d9535ac2eea..97e5b2e5c20 100644 --- a/compiler/core/lam_exit_count.ml +++ b/compiler/core/lam_exit_count.ml @@ -89,6 +89,7 @@ let count_helper (lam : Lam.t) : collection = | Lsequence (l1, l2) -> count l1; count l2 + | Lbreak | Lcontinue -> () | Lwhile (l1, l2) -> count l1; count l2 diff --git a/compiler/core/lam_free_variables.ml b/compiler/core/lam_free_variables.ml index 1fdd31f1c69..ec17527043a 100644 --- a/compiler/core/lam_free_variables.ml +++ b/compiler/core/lam_free_variables.ml @@ -83,6 +83,7 @@ let pass_free_variables (l : Lam.t) : Set_ident.t = | Lsequence (e1, e2) -> free e1; free e2 + | Lbreak | Lcontinue -> () | Lwhile (e1, e2) -> free e1; free e2 diff --git a/compiler/core/lam_hit.ml b/compiler/core/lam_hit.ml index dd1c2c9270a..405c7d0c8f8 100644 --- a/compiler/core/lam_hit.ml +++ b/compiler/core/lam_hit.ml @@ -55,6 +55,7 @@ let hit_variables (fv : Set_ident.t) (l : t) : bool = | Lstaticraise (_, args) -> hit_list args | Lifthenelse (e1, e2, e3) -> hit e1 || hit e2 || hit e3 | Lsequence (e1, e2) -> hit e1 || hit e2 + | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> hit e1 || hit e2 in hit l @@ -90,6 +91,7 @@ let hit_variable (fv : Ident.t) (l : t) : bool = | Lstaticraise (_, args) -> hit_list args | Lifthenelse (e1, e2, e3) -> hit e1 || hit e2 || hit e3 | Lsequence (e1, e2) -> hit e1 || hit e2 + | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> hit e1 || hit e2 in hit l diff --git a/compiler/core/lam_iter.ml b/compiler/core/lam_iter.ml index 94b8729eca2..b7e6ef9100e 100644 --- a/compiler/core/lam_iter.ml +++ b/compiler/core/lam_iter.ml @@ -72,6 +72,7 @@ let inner_iter (l : t) (f : t -> unit) : unit = | Lsequence (e1, e2) -> f e1; f e2 + | Lbreak | Lcontinue -> () | Lwhile (e1, e2) -> f e1; f e2 @@ -110,6 +111,7 @@ let inner_exists (l : t) (f : t -> bool) : bool = | Ltrywith (e1, _exn, e2) -> f e1 || f e2 | Lifthenelse (e1, e2, e3) -> f e1 || f e2 || f e3 | Lsequence (e1, e2) -> f e1 || f e2 + | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> f e1 || f e2 | Lfor (_v, e1, e2, _dir, e3) -> f e1 || f e2 || f e3 | Lassign (_id, e) -> f e diff --git a/compiler/core/lam_pass_alpha_conversion.ml b/compiler/core/lam_pass_alpha_conversion.ml index 1d80ae16ed4..25ea6619051 100644 --- a/compiler/core/lam_pass_alpha_conversion.ml +++ b/compiler/core/lam_pass_alpha_conversion.ml @@ -116,6 +116,8 @@ let alpha_conversion (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = | Ltrywith (l1, v, l2) -> Lam.try_ (simpl l1) v (simpl l2) | Lifthenelse (l1, l2, l3) -> Lam.if_ (simpl l1) (simpl l2) (simpl l3) | Lsequence (l1, l2) -> Lam.seq (simpl l1) (simpl l2) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_pass_collect.ml b/compiler/core/lam_pass_collect.ml index 8c4cde883b3..7df5a4452a8 100644 --- a/compiler/core/lam_pass_collect.ml +++ b/compiler/core/lam_pass_collect.ml @@ -137,6 +137,7 @@ let collect_info (meta : Lam_stats.t) (lam : Lam.t) = | Lsequence (l1, l2) -> collect l1; collect l2 + | Lbreak | Lcontinue -> () | Lwhile (l1, l2) -> collect l1; collect l2 diff --git a/compiler/core/lam_pass_count.ml b/compiler/core/lam_pass_count.ml index 3fc3a89090f..4683c2de724 100644 --- a/compiler/core/lam_pass_count.ml +++ b/compiler/core/lam_pass_count.ml @@ -186,6 +186,7 @@ let collect_occurs lam : occ_tbl = | Lsequence (l1, l2) -> count bv l1; count bv l2 + | Lbreak | Lcontinue -> () and count_default bv sw = match sw.sw_failaction with | None -> () diff --git a/compiler/core/lam_pass_deep_flatten.ml b/compiler/core/lam_pass_deep_flatten.ml index 11258e6a129..1e6637db16c 100644 --- a/compiler/core/lam_pass_deep_flatten.ml +++ b/compiler/core/lam_pass_deep_flatten.ml @@ -256,6 +256,8 @@ let deep_flatten (lam : Lam.t) : Lam.t = | Lstaticcatch (l1, ids, l2) -> Lam.staticcatch (aux l1) ids (aux l2) | Ltrywith (l1, v, l2) -> Lam.try_ (aux l1) v (aux l2) | Lifthenelse (l1, l2, l3) -> Lam.if_ (aux l1) (aux l2) (aux l3) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_pass_eliminate_ref.ml b/compiler/core/lam_pass_eliminate_ref.ml index eb54fb20677..a37eaaebce9 100644 --- a/compiler/core/lam_pass_eliminate_ref.ml +++ b/compiler/core/lam_pass_eliminate_ref.ml @@ -95,6 +95,8 @@ let rec eliminate_ref id (lam : Lam.t) = | Lifthenelse (e1, e2, e3) -> Lam.if_ (eliminate_ref id e1) (eliminate_ref id e2) (eliminate_ref id e3) | Lsequence (e1, e2) -> Lam.seq (eliminate_ref id e1) (eliminate_ref id e2) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | Lwhile (e1, e2) -> Lam.while_ (eliminate_ref id e1) (eliminate_ref id e2) | Lfor (v, e1, e2, dir, e3) -> Lam.for_ v (eliminate_ref id e1) (eliminate_ref id e2) dir diff --git a/compiler/core/lam_pass_exits.ml b/compiler/core/lam_pass_exits.ml index 2eb6295699d..e4f07d7a6f5 100644 --- a/compiler/core/lam_pass_exits.ml +++ b/compiler/core/lam_pass_exits.ml @@ -48,6 +48,7 @@ and no_bounded_variables (l : Lam.t) = no_bounded_variables e1 && no_bounded_variables e2 && no_bounded_variables e3 | Lsequence (e1, e2) -> no_bounded_variables e1 && no_bounded_variables e2 + | Lbreak | Lcontinue -> true | Lwhile (e1, e2) -> no_bounded_variables e1 && no_bounded_variables e2 | Lstaticcatch (e1, (_, vars), e2) -> vars = [] && no_bounded_variables e1 && no_bounded_variables e2 @@ -231,6 +232,8 @@ let subst_helper (subst : subst_tbl) (query : int -> int) (lam : Lam.t) : Lam.t | Ltrywith (l1, v, l2) -> Lam.try_ (simplif l1) v (simplif l2) | Lifthenelse (l1, l2, l3) -> Lam.if_ (simplif l1) (simplif l2) (simplif l3) | Lsequence (l1, l2) -> Lam.seq (simplif l1) (simplif l2) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_pass_lets_dce.ml b/compiler/core/lam_pass_lets_dce.ml index bf32bbc56be..697a7a6b2d9 100644 --- a/compiler/core/lam_pass_lets_dce.ml +++ b/compiler/core/lam_pass_lets_dce.ml @@ -197,6 +197,8 @@ let lets_helper (count_var : Ident.t -> Lam_pass_count.used_info) lam : Lam.t = Lam.staticcatch (simplif l1) (i, args) (simplif l2) | Ltrywith (l1, v, l2) -> Lam.try_ (simplif l1) v (simplif l2) | Lifthenelse (l1, l2, l3) -> Lam.if_ (simplif l1) (simplif l2) (simplif l3) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_pass_remove_alias.ml b/compiler/core/lam_pass_remove_alias.ml index 1dad7d3865c..dc18a68bd48 100644 --- a/compiler/core/lam_pass_remove_alias.ml +++ b/compiler/core/lam_pass_remove_alias.ml @@ -279,6 +279,8 @@ let simplify_alias (meta : Lam_stats.t) (lam : Lam.t) : Lam.t = | Lstaticcatch (l1, ids, l2) -> Lam.staticcatch (simpl l1) ids (simpl l2) | Ltrywith (l1, v, l2) -> Lam.try_ (simpl l1) v (simpl l2) | Lsequence (l1, l2) -> Lam.seq (simpl l1) (simpl l2) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/core/lam_print.ml b/compiler/core/lam_print.ml index 172e219abb7..4db2fa5e09b 100644 --- a/compiler/core/lam_print.ml +++ b/compiler/core/lam_print.ml @@ -401,6 +401,8 @@ let lambda ppf v = fprintf ppf "@[<2>(if@ %a@ %a@ %a)@]" lam lcond lam lif lam lelse | Lsequence (l1, l2) -> fprintf ppf "@[<2>(seq@ %a@ %a)@]" lam l1 sequence l2 + | Lbreak -> fprintf ppf "break" + | Lcontinue -> fprintf ppf "continue" | Lwhile (lcond, lbody) -> fprintf ppf "@[<2>(while@ %a@ %a)@]" lam lcond lam lbody | Lfor (param, lo, hi, dir, body) -> diff --git a/compiler/core/lam_scc.ml b/compiler/core/lam_scc.ml index 556feed5593..370a168f120 100644 --- a/compiler/core/lam_scc.ml +++ b/compiler/core/lam_scc.ml @@ -60,6 +60,7 @@ let hit_mask (mask : Hash_set_ident_mask.t) (l : Lam.t) : bool = | Lstaticraise (_, args) -> hit_list args | Lifthenelse (e1, e2, e3) -> hit e1 || hit e2 || hit e3 | Lsequence (e1, e2) -> hit e1 || hit e2 + | Lbreak | Lcontinue -> false | Lwhile (e1, e2) -> hit e1 || hit e2 in hit l diff --git a/compiler/core/lam_subst.ml b/compiler/core/lam_subst.ml index d5469619a3e..b028a607395 100644 --- a/compiler/core/lam_subst.ml +++ b/compiler/core/lam_subst.ml @@ -63,6 +63,8 @@ let subst (s : Lam.t Map_ident.t) lam = | Lifthenelse (e1, e2, e3) -> Lam.if_ (subst_aux e1) (subst_aux e2) (subst_aux e3) | Lsequence (e1, e2) -> Lam.seq (subst_aux e1) (subst_aux e2) + | Lbreak -> Lam.break + | Lcontinue -> Lam.continue | 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) diff --git a/compiler/ext/warnings.ml b/compiler/ext/warnings.ml index f25b91b4f89..936f34c4e02 100644 --- a/compiler/ext/warnings.ml +++ b/compiler/ext/warnings.ml @@ -373,7 +373,7 @@ let message = function ] | Unused_argument -> "this argument will not be used by the function." | Nonreturning_statement -> - "this statement never returns (or has an unsound type.)" + "This statement does not continue execution; following code is unreachable." | Preprocessor s -> s | Useless_record_with -> "All the fields are already explicitly listed in this record. You can \ diff --git a/compiler/frontend/bs_ast_mapper.ml b/compiler/frontend/bs_ast_mapper.ml index 292e199b5ae..9e9b9d641d4 100644 --- a/compiler/frontend/bs_ast_mapper.ml +++ b/compiler/frontend/bs_ast_mapper.ml @@ -356,6 +356,8 @@ module E = struct (map_opt (sub.expr sub) e3) | Pexp_sequence (e1, e2) -> sequence ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) + | Pexp_break -> break ~loc ~attrs () + | Pexp_continue -> 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) -> diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml index 2fae640eb07..ee6c658a9c6 100644 --- a/compiler/ml/ast_helper.ml +++ b/compiler/ml/ast_helper.ml @@ -177,6 +177,8 @@ module Exp = struct let array ?loc ?attrs a = mk ?loc ?attrs (Pexp_array a) let ifthenelse ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_ifthenelse (a, b, c)) let sequence ?loc ?attrs a b = mk ?loc ?attrs (Pexp_sequence (a, b)) + let break ?loc ?attrs () = mk ?loc ?attrs Pexp_break + 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 constraint_ ?loc ?attrs a b = mk ?loc ?attrs (Pexp_constraint (a, b)) diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli index 11227b903ad..b80a356702b 100644 --- a/compiler/ml/ast_helper.mli +++ b/compiler/ml/ast_helper.mli @@ -173,6 +173,8 @@ module Exp : sig expression val sequence : ?loc:loc -> ?attrs:attrs -> expression -> expression -> expression + val break : ?loc:loc -> ?attrs:attrs -> unit -> expression + val continue : ?loc:loc -> ?attrs:attrs -> unit -> expression val while_ : ?loc:loc -> ?attrs:attrs -> expression -> expression -> expression val for_ : diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml index a430bb0b7bd..e2562392600 100644 --- a/compiler/ml/ast_iterator.ml +++ b/compiler/ml/ast_iterator.ml @@ -329,6 +329,7 @@ module E = struct | Pexp_sequence (e1, e2) -> sub.expr sub e1; sub.expr sub e2 + | Pexp_break | Pexp_continue -> () | Pexp_while (e1, e2) -> sub.expr sub e1; sub.expr sub e2 diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml index 673465477bd..e09cfc80fd3 100644 --- a/compiler/ml/ast_mapper.ml +++ b/compiler/ml/ast_mapper.ml @@ -319,6 +319,8 @@ module E = struct (map_opt (sub.expr sub) e3) | Pexp_sequence (e1, e2) -> sequence ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) + | Pexp_break -> break ~loc ~attrs () + | Pexp_continue -> 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) -> diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml index 3f91d6ac1ee..1ed68d96eb8 100644 --- a/compiler/ml/ast_mapper_from0.ml +++ b/compiler/ml/ast_mapper_from0.ml @@ -522,6 +522,9 @@ module E = struct (map_opt (sub.expr sub) e3) | Pexp_sequence (e1, e2) -> sequence ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) + | Pexp_extension ({txt = "res.break"; _}, PStr []) -> break ~loc ~attrs () + | Pexp_extension ({txt = "res.continue"; _}, PStr []) -> + 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) -> diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml index d0ac43d737a..e12245abcb3 100644 --- a/compiler/ml/ast_mapper_to0.ml +++ b/compiler/ml/ast_mapper_to0.ml @@ -445,6 +445,10 @@ module E = struct (map_opt (sub.expr sub) e3) | Pexp_sequence (e1, e2) -> sequence ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) + | Pexp_break -> + extension ~loc ~attrs (Location.mkloc "res.break" loc, PStr []) + | Pexp_continue -> + extension ~loc ~attrs (Location.mkloc "res.continue" loc, PStr []) | Pexp_while (e1, e2) -> while_ ~loc ~attrs (sub.expr sub e1) (sub.expr sub e2) | Pexp_for (p, e1, e2, d, e3) -> diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml index e5e39eb4b55..58739b7639b 100644 --- a/compiler/ml/depend.ml +++ b/compiler/ml/depend.ml @@ -256,6 +256,7 @@ let rec add_expr bv exp = | Pexp_sequence (e1, e2) -> add_expr bv e1; add_expr bv e2 + | Pexp_break | Pexp_continue -> () | Pexp_while (e1, e2) -> add_expr bv e1; add_expr bv e2 diff --git a/compiler/ml/lambda.ml b/compiler/ml/lambda.ml index db810d4f912..4b010e1470a 100644 --- a/compiler/ml/lambda.ml +++ b/compiler/ml/lambda.ml @@ -364,6 +364,8 @@ type lambda = | Ltrywith of lambda * Ident.t * lambda | Lifthenelse of lambda * lambda * lambda | Lsequence of lambda * lambda + | Lbreak + | Lcontinue | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * Asttypes.direction_flag * lambda | Lassign of Ident.t * lambda @@ -475,6 +477,8 @@ let make_key e = | Lifthenelse (cond, ifso, ifnot) -> Lifthenelse (tr_rec env cond, tr_rec env ifso, tr_rec env ifnot) | Lsequence (e1, e2) -> Lsequence (tr_rec env e1, tr_rec env e2) + | Lbreak -> Lbreak + | 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 @@ -542,6 +546,7 @@ let iter f = function | Lsequence (e1, e2) -> f e1; f e2 + | Lbreak | Lcontinue -> () | Lwhile (e1, e2) -> f e1; f e2 @@ -571,7 +576,8 @@ let free_ids get l = | Lfor (v, _e1, _e2, _dir, _e3) -> fv := IdentSet.remove v !fv | Lassign (id, _e) -> fv := IdentSet.add id !fv | Lvar _ | Lconst _ | Lapply _ | Lprim _ | Lswitch _ | Lstringswitch _ - | Lstaticraise _ | Lifthenelse _ | Lsequence _ | Lwhile _ | Lsend _ -> + | Lstaticraise _ | Lifthenelse _ | Lsequence _ | Lbreak | Lcontinue + | Lwhile _ | Lsend _ -> () in free l; @@ -675,6 +681,8 @@ let subst_lambda s lam = | Ltrywith (e1, exn, e2) -> Ltrywith (subst e1, exn, subst e2) | Lifthenelse (e1, e2, e3) -> Lifthenelse (subst e1, subst e2, subst e3) | Lsequence (e1, e2) -> Lsequence (subst e1, subst e2) + | Lbreak -> Lbreak + | 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) | Lassign (id, e) -> Lassign (id, subst e) diff --git a/compiler/ml/lambda.mli b/compiler/ml/lambda.mli index d8eaf57be6f..8a2b5bea2cf 100644 --- a/compiler/ml/lambda.mli +++ b/compiler/ml/lambda.mli @@ -335,6 +335,8 @@ type lambda = | Ltrywith of lambda * Ident.t * lambda | Lifthenelse of lambda * lambda * lambda | Lsequence of lambda * lambda + | Lbreak + | Lcontinue | Lwhile of lambda * lambda | Lfor of Ident.t * lambda * lambda * direction_flag * lambda | Lassign of Ident.t * lambda diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml index 78c9899f744..1a4027c80fc 100644 --- a/compiler/ml/parsetree.ml +++ b/compiler/ml/parsetree.ml @@ -283,6 +283,8 @@ and expression_desc = | Pexp_ifthenelse of expression * expression * expression option (* if E1 then E2 else E3 *) | Pexp_sequence of expression * expression (* E1; E2 *) + | Pexp_break (* break *) + | Pexp_continue (* continue *) | Pexp_while of expression * expression (* while E1 do E2 done *) | Pexp_for of pattern * expression * expression * direction_flag * expression (* for i = E1 to E2 do E3 done (flag = Upto) diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml index 585ac64b81b..d9d3faf0236 100644 --- a/compiler/ml/pprintast.ml +++ b/compiler/ml/pprintast.ml @@ -787,6 +787,8 @@ and simple_expr ctxt f x = pp f "@[<0>@[<2>[|%a|]@]@]" (list (simple_expr (under_semi ctxt)) ~sep:";") l + | Pexp_break -> pp f "break" + | Pexp_continue -> pp f "continue" | Pexp_while (e1, e2) -> let fmt : (_, _, _) format = "@[<2>while@;%a@;do@;%a@;done@]" in pp f fmt (expression ctxt) e1 (expression ctxt) e2 diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml index 44d699eb388..0c9fc076cb4 100644 --- a/compiler/ml/printast.ml +++ b/compiler/ml/printast.ml @@ -306,6 +306,8 @@ and expression i ppf x = line i ppf "Pexp_sequence\n"; expression i ppf e1; expression i ppf e2 + | Pexp_break -> line i ppf "Pexp_break\n" + | Pexp_continue -> line i ppf "Pexp_continue\n" | Pexp_while (e1, e2) -> line i ppf "Pexp_while\n"; expression i ppf e1; diff --git a/compiler/ml/printlambda.ml b/compiler/ml/printlambda.ml index 0282f6e113c..47764084004 100644 --- a/compiler/ml/printlambda.ml +++ b/compiler/ml/printlambda.ml @@ -377,6 +377,8 @@ let rec lam ppf = function | Lifthenelse (lcond, lif, lelse) -> fprintf ppf "@[<2>(if@ %a@ %a@ %a)@]" lam lcond lam lif lam lelse | Lsequence (l1, l2) -> fprintf ppf "@[<2>(seq@ %a@ %a)@]" lam l1 sequence l2 + | Lbreak -> fprintf ppf "break" + | Lcontinue -> fprintf ppf "continue" | Lwhile (lcond, lbody) -> fprintf ppf "@[<2>(while@ %a@ %a)@]" lam lcond lam lbody | Lfor (param, lo, hi, dir, body) -> diff --git a/compiler/ml/printtyped.ml b/compiler/ml/printtyped.ml index 6e36b4276c0..d461abe171f 100644 --- a/compiler/ml/printtyped.ml +++ b/compiler/ml/printtyped.ml @@ -341,6 +341,8 @@ and expression i ppf x = line i ppf "Texp_sequence\n"; expression i ppf e1; expression i ppf e2 + | Texp_break -> line i ppf "Texp_break\n" + | Texp_continue -> line i ppf "Texp_continue\n" | Texp_while (e1, e2) -> line i ppf "Texp_while\n"; expression i ppf e1; diff --git a/compiler/ml/rec_check.ml b/compiler/ml/rec_check.ml index 3d016a74383..2463a447c18 100644 --- a/compiler/ml/rec_check.ml +++ b/compiler/ml/rec_check.ml @@ -197,7 +197,7 @@ let rec classify_expression : Typedtree.expression -> sd = | 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 -> Static | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}} when is_ref vd -> Static @@ -232,6 +232,7 @@ let rec expression : Env.env -> Typedtree.expression -> Use.t = for inclusion in another value *) (discard (expression env e3))) | Texp_constant _ -> Use.empty + | Texp_break | Texp_continue -> Use.empty | Texp_apply {funct = {exp_desc = Texp_ident (_, _, vd)}; args = [(_, Some arg)]} when is_ref vd -> diff --git a/compiler/ml/tast_iterator.ml b/compiler/ml/tast_iterator.ml index 5c12d3da4d8..26c343eb250 100644 --- a/compiler/ml/tast_iterator.ml +++ b/compiler/ml/tast_iterator.ml @@ -184,6 +184,7 @@ let expr sub {exp_extra; exp_desc; exp_env; _} = | Texp_sequence (exp1, exp2) -> sub.expr sub exp1; sub.expr sub exp2 + | Texp_break | Texp_continue -> () | Texp_while (exp1, exp2) -> sub.expr sub exp1; sub.expr sub exp2 diff --git a/compiler/ml/tast_mapper.ml b/compiler/ml/tast_mapper.ml index 54eba028699..63015636c7e 100644 --- a/compiler/ml/tast_mapper.ml +++ b/compiler/ml/tast_mapper.ml @@ -238,6 +238,8 @@ let expr sub x = (sub.expr sub exp1, sub.expr sub exp2, opt (sub.expr sub) expo) | Texp_sequence (exp1, exp2) -> Texp_sequence (sub.expr sub exp1, sub.expr sub exp2) + | Texp_break -> Texp_break + | Texp_continue -> Texp_continue | Texp_while (exp1, exp2) -> Texp_while (sub.expr sub exp1, sub.expr sub exp2) | Texp_for (id, p, exp1, exp2, dir, exp3) -> diff --git a/compiler/ml/translcore.ml b/compiler/ml/translcore.ml index 078cbf133a0..d07bcbac7c5 100644 --- a/compiler/ml/translcore.ml +++ b/compiler/ml/translcore.ml @@ -900,6 +900,8 @@ and transl_exp0 (e : Typedtree.expression) : Lambda.lambda = Lifthenelse (transl_exp cond, transl_exp ifso, lambda_unit) | Texp_sequence (expr1, expr2) -> Lsequence (transl_exp expr1, transl_exp expr2) + | Texp_break -> Lbreak + | Texp_continue -> Lcontinue | 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) diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml index 029f04b3a06..d7bc0aadaa2 100644 --- a/compiler/ml/typecore.ml +++ b/compiler/ml/typecore.ml @@ -80,6 +80,8 @@ type error = | Inlined_record_expected | Invalid_extension_constructor_payload | Not_an_extension_constructor + | Break_outside_loop + | Continue_outside_loop | Literal_overflow of string | Unknown_literal of string * char | Illegal_letrec_pat @@ -141,6 +143,18 @@ let rp node = type recarg = Allowed | Required | Rejected +let loop_depth = ref 0 + +let with_depth depth_ref f = + let saved = !depth_ref in + depth_ref := saved + 1; + Misc.try_finally f (fun () -> depth_ref := saved) + +let with_reset_control_flow f = + let saved_loop_depth = !loop_depth in + loop_depth := 0; + Misc.try_finally f (fun () -> loop_depth := saved_loop_depth) + let case lhs rhs = {c_lhs = lhs; c_guard = None; c_rhs = rhs} (* Upper approximation of free identifiers on the parse tree *) @@ -190,6 +204,7 @@ let iter_expression f e = expr e1; expr e2; expr e3 + | Pexp_break | Pexp_continue -> () | Pexp_letmodule (_, me, e) -> expr e; module_expr me @@ -2903,6 +2918,13 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) }) | Pexp_sequence (sexp1, sexp2) -> let exp1 = type_statement ~context:None env sexp1 in + (match exp1.exp_desc with + | Texp_break | Texp_continue -> + (* Loop control should only reuse the nonreturning-statement warning when + there is a following statement in the same block/sequence. *) + Location.prerr_warning (final_subexpression sexp1).pexp_loc + Warnings.Nonreturning_statement + | _ -> ()); let exp2 = type_expect ~context:None env sexp2 ty_expected in re { @@ -2913,11 +2935,37 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) exp_attributes = sexp.pexp_attributes; exp_env = env; } + | Pexp_break -> + if !loop_depth = 0 then raise (Error (loc, env, Break_outside_loop)) + else + rue + { + exp_desc = Texp_break; + exp_loc = loc; + exp_extra = []; + exp_type = instance_def Predef.type_unit; + exp_attributes = sexp.pexp_attributes; + exp_env = env; + } + | Pexp_continue -> + if !loop_depth = 0 then raise (Error (loc, env, Continue_outside_loop)) + else + rue + { + exp_desc = Texp_continue; + exp_loc = loc; + exp_extra = []; + exp_type = instance_def Predef.type_unit; + exp_attributes = sexp.pexp_attributes; + exp_env = env; + } | Pexp_while (scond, sbody) -> let cond = type_expect ~context:(Some WhileCondition) env scond Predef.type_bool in - let body = type_statement ~context:None env sbody in + let body = + with_depth loop_depth (fun () -> type_statement ~context:None env sbody) + in rue { exp_desc = Texp_while (cond, body); @@ -2949,7 +2997,10 @@ and type_expect_ ?deprecated_context ~context ?in_function ?(recarg = Rejected) ~check:(fun s -> Warnings.Unused_for_index s) | _ -> raise (Error (param.ppat_loc, env, Invalid_for_loop_index)) in - let body = type_statement ~context:None new_env sbody in + let body = + with_depth loop_depth (fun () -> + type_statement ~context:None new_env sbody) + in rue { exp_desc = Texp_for (id, param, low, high, dir, body); @@ -3311,8 +3362,9 @@ and type_function ?in_function ~arity ~async loc attrs env ty_expected_ l generalize_structure ty_arg; generalize_structure ty_res); let cases, partial = - type_cases ~call_context:`Function ~in_function:(loc_fun, ty_fun) env ty_arg - ty_res true loc caselist + with_reset_control_flow (fun () -> + type_cases ~call_context:`Function ~in_function:(loc_fun, ty_fun) env + ty_arg ty_res true loc caselist) in let case = List.hd cases in if is_optional l && not_function env ty_res then @@ -4625,6 +4677,10 @@ let report_error env loc ppf error = "Invalid [%%extension_constructor] payload, a constructor is expected." | Not_an_extension_constructor -> fprintf ppf "This constructor is not an extension constructor." + | Break_outside_loop -> + fprintf ppf "`break` can only be used directly inside a loop body." + | Continue_outside_loop -> + fprintf ppf "`continue` can only be used inside a loop body." | Literal_overflow ty -> fprintf ppf "Integer literal exceeds the range of representable integers of type %s" diff --git a/compiler/ml/typecore.mli b/compiler/ml/typecore.mli index eef17d05a84..63028f96602 100644 --- a/compiler/ml/typecore.mli +++ b/compiler/ml/typecore.mli @@ -113,6 +113,8 @@ type error = | Inlined_record_expected | Invalid_extension_constructor_payload | Not_an_extension_constructor + | Break_outside_loop + | Continue_outside_loop | Literal_overflow of string | Unknown_literal of string * char | Illegal_letrec_pat diff --git a/compiler/ml/typedtree.ml b/compiler/ml/typedtree.ml index e7274f12456..e73331598d9 100644 --- a/compiler/ml/typedtree.ml +++ b/compiler/ml/typedtree.ml @@ -109,6 +109,8 @@ and expression_desc = | Texp_array of expression list | Texp_ifthenelse of expression * expression * expression option | Texp_sequence of expression * expression + | Texp_break + | Texp_continue | Texp_while of expression * expression | Texp_for of Ident.t diff --git a/compiler/ml/typedtree.mli b/compiler/ml/typedtree.mli index b1e7083fc75..56214067f67 100644 --- a/compiler/ml/typedtree.mli +++ b/compiler/ml/typedtree.mli @@ -210,6 +210,8 @@ and expression_desc = | Texp_array of expression list | Texp_ifthenelse of expression * expression * expression option | Texp_sequence of expression * expression + | Texp_break + | Texp_continue | Texp_while of expression * expression | Texp_for of Ident.t diff --git a/compiler/ml/typedtreeIter.ml b/compiler/ml/typedtreeIter.ml index 9a31b9b5b96..0cff0688b91 100644 --- a/compiler/ml/typedtreeIter.ml +++ b/compiler/ml/typedtreeIter.ml @@ -271,6 +271,7 @@ end = struct | Texp_sequence (exp1, exp2) -> iter_expression exp1; iter_expression exp2 + | Texp_break | Texp_continue -> () | Texp_while (exp1, exp2) -> iter_expression exp1; iter_expression exp2 diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml index 52a57b89c55..056b27912c8 100644 --- a/compiler/syntax/src/res_ast_debugger.ml +++ b/compiler/syntax/src/res_ast_debugger.ml @@ -657,6 +657,8 @@ module SexpAst = struct | Pexp_sequence (expr1, expr2) -> Sexp.list [Sexp.atom "Pexp_sequence"; expression expr1; expression expr2] + | Pexp_break -> Sexp.atom "Pexp_break" + | Pexp_continue -> Sexp.atom "Pexp_continue" | Pexp_while (expr1, expr2) -> Sexp.list [Sexp.atom "Pexp_while"; expression expr1; expression expr2] | Pexp_for (pat, e1, e2, flag, e3) -> diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml index 6774b2bc2b1..58d0bf91bea 100644 --- a/compiler/syntax/src/res_comments_table.ml +++ b/compiler/syntax/src/res_comments_table.ml @@ -1220,6 +1220,7 @@ and walk_expression expr t comments = attach t.trailing typexpr.ptyp_loc trailing | Pexp_tuple [] | Pexp_array [] + | Pexp_break | Pexp_continue | Pexp_construct ({txt = Longident.Lident "[]"}, _) -> attach t.inside expr.pexp_loc comments | Pexp_construct ({txt = Longident.Lident "::"}, _) -> diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml index 366c444effc..8583dab8fb7 100644 --- a/compiler/syntax/src/res_core.ml +++ b/compiler/syntax/src/res_core.ml @@ -3089,6 +3089,18 @@ and parse_braced_or_record_expr p = let start_pos = p.Parser.start_pos in Parser.expect Lbrace p; match p.Parser.token with + | Break -> + let expr = parse_expr_block p in + Parser.expect Rbrace p; + let loc = mk_loc start_pos p.prev_end_pos in + let braces = make_braces_attr loc in + {expr with pexp_attributes = braces :: expr.pexp_attributes} + | Continue -> + let expr = parse_expr_block p in + Parser.expect Rbrace p; + let loc = mk_loc start_pos p.prev_end_pos in + let braces = make_braces_attr loc in + {expr with pexp_attributes = braces :: expr.pexp_attributes} | token when Token.is_keyword token -> ( match recover_keyword_field_name_if_probably_field p @@ -3564,6 +3576,14 @@ and parse_expr_block_item p = let start_pos = p.Parser.start_pos in let attrs = parse_attributes p in match p.Parser.token with + | Break -> + Parser.next p; + let loc = mk_loc start_pos p.prev_end_pos in + Ast_helper.Exp.break ~loc ~attrs () + | Continue -> + Parser.next p; + let loc = mk_loc start_pos p.prev_end_pos in + Ast_helper.Exp.continue ~loc ~attrs () | Module -> ( Parser.next p; match p.token with diff --git a/compiler/syntax/src/res_grammar.ml b/compiler/syntax/src/res_grammar.ml index 9c06bb84f49..c33e6dcebdb 100644 --- a/compiler/syntax/src/res_grammar.ml +++ b/compiler/syntax/src/res_grammar.ml @@ -264,11 +264,11 @@ let is_attribute_start = function let is_jsx_child_start = is_atomic_expr_start let is_block_expr_start = function - | Token.Assert | At | Await | Backtick | Bang | Codepoint _ | Exception - | False | Float _ | For | Forwardslash | ForwardslashDot | Hash | If | Int _ - | Lbrace | Lbracket | LessThan | Let _ | Lident _ | List | Lparen | Minus - | MinusDot | Module | Open | Percent | Plus | PlusDot | String _ | Switch - | True | Try | Uident _ | Underscore | While | Dict -> + | Token.Assert | At | Await | Backtick | Bang | Break | Codepoint _ | Continue + | Exception | False | Float _ | For | Forwardslash | ForwardslashDot | Hash + | If | Int _ | Lbrace | Lbracket | LessThan | Let _ | Lident _ | List | Lparen + | Minus | MinusDot | Module | Open | Percent | Plus | PlusDot | String _ + | Switch | True | Try | Uident _ | Underscore | While | Dict -> true | _ -> false diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml index a8351bfe5fb..682116611a8 100644 --- a/compiler/syntax/src/res_printer.ml +++ b/compiler/syntax/src/res_printer.ml @@ -3582,6 +3582,8 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl = | Pexp_ifthenelse (_ifExpr, _thenExpr, _elseExpr) -> let ifs, else_expr = ParsetreeViewer.collect_if_expressions e in print_if_chain ~state e.pexp_attributes ifs else_expr cmt_tbl + | Pexp_break -> Doc.text "break" + | Pexp_continue -> Doc.text "continue" | Pexp_while (expr1, expr2) -> let condition = let doc = print_expression_with_comments ~state expr1 cmt_tbl in diff --git a/compiler/syntax/src/res_token.ml b/compiler/syntax/src/res_token.ml index 192c976ab3e..92e02afcbf3 100644 --- a/compiler/syntax/src/res_token.ml +++ b/compiler/syntax/src/res_token.ml @@ -3,6 +3,8 @@ module Comment = Res_comment type t = | Await | Open + | Break + | Continue | True | False | Codepoint of {c: int; original: string} @@ -125,6 +127,8 @@ let precedence = function let to_string = function | Await -> "await" | Open -> "open" + | Break -> "break" + | Continue -> "continue" | True -> "true" | False -> "false" | Codepoint {original} -> "codepoint '" ^ original ^ "'" @@ -232,7 +236,9 @@ let keyword_table = function | "as" -> As | "assert" -> Assert | "await" -> Await + | "break" -> Break | "constraint" -> Constraint + | "continue" -> Continue | "else" -> Else | "exception" -> Exception | "external" -> External @@ -261,9 +267,10 @@ let keyword_table = function [@@raises Not_found] let is_keyword = function - | Await | And | As | Assert | Constraint | Else | Exception | External | False - | For | If | In | Include | Land | Let _ | List | Lor | Module | Mutable | Of - | Open | Private | Rec | Switch | True | Try | Typ | When | While | Dict -> + | Await | Break | Continue | And | As | Assert | Constraint | Else | Exception + | External | False | For | If | In | Include | Land | Let _ | List | Lor + | Module | Mutable | Of | Open | Private | Rec | Switch | True | Try | Typ + | When | While | Dict -> true | _ -> false diff --git a/compiler/syntax/src/res_token_debugger.ml b/compiler/syntax/src/res_token_debugger.ml index 93a4b9f5133..e745308dd4f 100644 --- a/compiler/syntax/src/res_token_debugger.ml +++ b/compiler/syntax/src/res_token_debugger.ml @@ -21,6 +21,8 @@ let dump_tokens filename = | Res_token.Open -> "Open" | Res_token.True -> "True" | Res_token.False -> "False" + | Res_token.Break -> "Break" + | Res_token.Continue -> "Continue" | Res_token.Codepoint {original} -> "Codepoint(\"" ^ original ^ "\")" | Res_token.Int {i} -> "Int(\"" ^ i ^ "\")" | Res_token.Float {f} -> "Float(\"" ^ f ^ "\")" diff --git a/packages/@rescript/runtime/Js_re.res b/packages/@rescript/runtime/Js_re.res index 7574f12d4b3..03da41c8cdb 100644 --- a/packages/@rescript/runtime/Js_re.res +++ b/packages/@rescript/runtime/Js_re.res @@ -136,15 +136,15 @@ set. let re = /ab*TODO/g let str = "abbcdefabh" -let break = ref(false) -while !break.contents { +let break_ = ref(false) +while !break_.contents { switch Js.Re.exec_(re, str) { | Some(result) => Js.Nullable.iter(Js.Re.captures(result)[0], match_ => { let next = Belt.Int.toString(Js.Re.lastIndex(re)) Js.log("Found " ++ (match_ ++ (". Next match starts at " ++ next))) }) - | None => break := true + | None => break_ := true } } ``` diff --git a/packages/@rescript/runtime/Stdlib_AsyncIterator.resi b/packages/@rescript/runtime/Stdlib_AsyncIterator.resi index 3a8b5d9ff79..7befd027170 100644 --- a/packages/@rescript/runtime/Stdlib_AsyncIterator.resi +++ b/packages/@rescript/runtime/Stdlib_AsyncIterator.resi @@ -132,14 +132,14 @@ let asyncIterator: AsyncIterator.t<(string, string)> = %raw(` let processMyAsyncIterator = async () => { // ReScript doesn't have `for ... of` loops, but it's easy to mimic using a while loop. - let break = ref(false) + let break_ = ref(false) - while !break.contents { + while !break_.contents { // Await the next iterator value let {value, done} = await asyncIterator->AsyncIterator.next // Exit the while loop if the iterator says it's done - break := done + break_ := done if done { value->Option.isNone == true diff --git a/scripts/format.sh b/scripts/format.sh index dac4f60aad4..daf6fd988ba 100755 --- a/scripts/format.sh +++ b/scripts/format.sh @@ -8,7 +8,7 @@ echo Formatting OCaml code... opam exec -- dune build @fmt --auto-promote echo Formatting ReScript code... -files=$(find packages tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") +files=$(find packages tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/build_tests/super_errors/fixtures/break_keyword_binding.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") ./cli/rescript.js format $files echo Formatting JS code... diff --git a/scripts/format_check.sh b/scripts/format_check.sh index 8206f3a8327..84edccf479e 100755 --- a/scripts/format_check.sh +++ b/scripts/format_check.sh @@ -17,7 +17,7 @@ case "$(uname -s)" in fi echo "Checking ReScript code formatting..." - files=$(find packages tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") + files=$(find packages tests -type f \( -name "*.res" -o -name "*.resi" \) ! -name "syntaxErrors*" ! -name "generated_mocha_test.res" ! -path "tests/build_tests/super_errors/fixtures/break_keyword_binding.res" ! -path "tests/syntax_tests*" ! -path "tests/analysis_tests/tests*" ! -path "*/node_modules/*") if ./cli/rescript.js format --check $files; then printf "${successGreen}✅ ReScript code formatting ok.${reset}\n" else diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt index 9f8e8ec204f..a413c9c60ea 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt +++ b/tests/analysis_tests/tests-reanalyze/deadcode/expected/deadcode.txt @@ -94,12 +94,13 @@ addValueDeclaration +make DeadTest.res:117:4 path:+DeadTest addValueDeclaration +theSideEffectIsLogging DeadTest.res:121:4 path:+DeadTest addValueDeclaration +stringLengthNoSideEffects DeadTest.res:123:4 path:+DeadTest - addValueDeclaration +globallyLive1 DeadTest.res:128:6 path:+DeadTest.GloobLive - addValueDeclaration +globallyLive2 DeadTest.res:129:6 path:+DeadTest.GloobLive - addValueDeclaration +globallyLive3 DeadTest.res:130:6 path:+DeadTest.GloobLive - addValueDeclaration +funWithInnerVars DeadTest.res:145:4 path:+DeadTest - addValueDeclaration +deadIncorrect DeadTest.res:154:4 path:+DeadTest - addValueDeclaration +ira DeadTest.res:160:4 path:+DeadTest + addValueDeclaration +breakChangesControlFlow DeadTest.res:125:4 path:+DeadTest + addValueDeclaration +globallyLive1 DeadTest.res:134:6 path:+DeadTest.GloobLive + addValueDeclaration +globallyLive2 DeadTest.res:135:6 path:+DeadTest.GloobLive + addValueDeclaration +globallyLive3 DeadTest.res:136:6 path:+DeadTest.GloobLive + addValueDeclaration +funWithInnerVars DeadTest.res:151:4 path:+DeadTest + addValueDeclaration +deadIncorrect DeadTest.res:160:4 path:+DeadTest + addValueDeclaration +ira DeadTest.res:166:4 path:+DeadTest addValueReference DeadTest.res:1:15 --> ImmutableArray.resi:9:0 addValueReference DeadTest.res:8:7 --> DeadTest.res:7:4 addValueReference DeadTest.res:11:7 --> DeadTest.res:10:4 @@ -156,37 +157,37 @@ addValueReference DeadTest.res:117:19 --> React.res:7:0 addTypeReference _none_:1:-1 --> DeadTest.res:117:12 addValueReference DeadTest.res:119:16 --> DeadTest.res:117:4 - addVariantCaseDeclaration A DeadTest.res:134:11 path:+DeadTest.WithInclude.t - addVariantCaseDeclaration A DeadTest.res:137:13 path:+DeadTest.WithInclude.T.t - addVariantCaseDeclaration A DeadTest.res:137:13 path:+DeadTest.WithInclude.t - addTypeReference DeadTest.res:142:7 --> DeadTest.res:134:11 - addValueDeclaration +x DeadTest.res:146:6 path:+DeadTest - addValueDeclaration +y DeadTest.res:147:6 path:+DeadTest - addValueReference DeadTest.res:145:4 --> DeadTest.res:146:6 - addValueReference DeadTest.res:145:4 --> DeadTest.res:147:6 - addRecordLabelDeclaration a DeadTest.res:151:11 path:+DeadTest.rc - addValueDeclaration +_ DeadTest.res:156:0 path:+DeadTest - addValueReference DeadTest.res:156:8 --> DeadTest.res:154:4 - addRecordLabelDeclaration IR.a DeadTest.res:158:24 path:+DeadTest.inlineRecord - addRecordLabelDeclaration IR.b DeadTest.res:158:32 path:+DeadTest.inlineRecord - addRecordLabelDeclaration IR.c DeadTest.res:158:40 path:+DeadTest.inlineRecord - addRecordLabelDeclaration IR.d DeadTest.res:158:51 path:+DeadTest.inlineRecord - addRecordLabelDeclaration IR.e DeadTest.res:158:65 path:+DeadTest.inlineRecord - addVariantCaseDeclaration IR DeadTest.res:158:20 path:+DeadTest.inlineRecord - addValueDeclaration +_ DeadTest.res:161:0 path:+DeadTest - addTypeReference DeadTest.res:163:20 --> DeadTest.res:158:20 - addValueReference DeadTest.res:163:27 --> DeadTest.res:160:4 - addTypeReference DeadTest.res:163:35 --> DeadTest.res:158:32 - addValueReference DeadTest.res:163:35 --> DeadTest.res:163:7 - addValueReference DeadTest.res:163:40 --> DeadTest.res:163:8 - addTypeReference DeadTest.res:163:7 --> DeadTest.res:158:40 - addValueReference DeadTest.res:162:9 --> DeadTest.res:161:8 - addRecordLabelDeclaration IR2.a DeadTest.res:167:26 path:+DeadTest.inlineRecord2 - addRecordLabelDeclaration IR2.b DeadTest.res:167:34 path:+DeadTest.inlineRecord2 - addVariantCaseDeclaration IR2 DeadTest.res:167:21 path:+DeadTest.inlineRecord2 - addRecordLabelDeclaration IR3.a DeadTest.res:169:34 path:+DeadTest.inlineRecord3 - addRecordLabelDeclaration IR3.b DeadTest.res:169:42 path:+DeadTest.inlineRecord3 - addVariantCaseDeclaration IR3 DeadTest.res:169:21 path:+DeadTest.inlineRecord3 + addVariantCaseDeclaration A DeadTest.res:140:11 path:+DeadTest.WithInclude.t + addVariantCaseDeclaration A DeadTest.res:143:13 path:+DeadTest.WithInclude.T.t + addVariantCaseDeclaration A DeadTest.res:143:13 path:+DeadTest.WithInclude.t + addTypeReference DeadTest.res:148:7 --> DeadTest.res:140:11 + addValueDeclaration +x DeadTest.res:152:6 path:+DeadTest + addValueDeclaration +y DeadTest.res:153:6 path:+DeadTest + addValueReference DeadTest.res:151:4 --> DeadTest.res:152:6 + addValueReference DeadTest.res:151:4 --> DeadTest.res:153:6 + addRecordLabelDeclaration a DeadTest.res:157:11 path:+DeadTest.rc + addValueDeclaration +_ DeadTest.res:162:0 path:+DeadTest + addValueReference DeadTest.res:162:8 --> DeadTest.res:160:4 + addRecordLabelDeclaration IR.a DeadTest.res:164:24 path:+DeadTest.inlineRecord + addRecordLabelDeclaration IR.b DeadTest.res:164:32 path:+DeadTest.inlineRecord + addRecordLabelDeclaration IR.c DeadTest.res:164:40 path:+DeadTest.inlineRecord + addRecordLabelDeclaration IR.d DeadTest.res:164:51 path:+DeadTest.inlineRecord + addRecordLabelDeclaration IR.e DeadTest.res:164:65 path:+DeadTest.inlineRecord + addVariantCaseDeclaration IR DeadTest.res:164:20 path:+DeadTest.inlineRecord + addValueDeclaration +_ DeadTest.res:167:0 path:+DeadTest + addTypeReference DeadTest.res:169:20 --> DeadTest.res:164:20 + addValueReference DeadTest.res:169:27 --> DeadTest.res:166:4 + addTypeReference DeadTest.res:169:35 --> DeadTest.res:164:32 + addValueReference DeadTest.res:169:35 --> DeadTest.res:169:7 + addValueReference DeadTest.res:169:40 --> DeadTest.res:169:8 + addTypeReference DeadTest.res:169:7 --> DeadTest.res:164:40 + addValueReference DeadTest.res:168:9 --> DeadTest.res:167:8 + addRecordLabelDeclaration IR2.a DeadTest.res:173:26 path:+DeadTest.inlineRecord2 + addRecordLabelDeclaration IR2.b DeadTest.res:173:34 path:+DeadTest.inlineRecord2 + addVariantCaseDeclaration IR2 DeadTest.res:173:21 path:+DeadTest.inlineRecord2 + addRecordLabelDeclaration IR3.a DeadTest.res:175:34 path:+DeadTest.inlineRecord3 + addRecordLabelDeclaration IR3.b DeadTest.res:175:42 path:+DeadTest.inlineRecord3 + addVariantCaseDeclaration IR3 DeadTest.res:175:21 path:+DeadTest.inlineRecord3 addValueReference DeadTest.res:28:2 --> DeadTest.res:31:6 addValueReference DeadTest.res:36:2 --> DeadTest.res:39:6 addValueReference DeadTest.res:60:2 --> DeadTest.res:64:6 @@ -1638,17 +1639,17 @@ addValueDeclaration +group Unison.res:17:4 path:+Unison addValueDeclaration +fits Unison.res:19:8 path:+Unison addValueDeclaration +toString Unison.res:26:8 path:+Unison - addVariantCaseDeclaration IfNeed Unison.res:4:2 path:+Unison.break - addVariantCaseDeclaration Never Unison.res:5:2 path:+Unison.break - addVariantCaseDeclaration Always Unison.res:6:2 path:+Unison.break - addRecordLabelDeclaration break Unison.res:9:2 path:+Unison.t + addVariantCaseDeclaration IfNeed Unison.res:4:2 path:+Unison.break_ + addVariantCaseDeclaration Never Unison.res:5:2 path:+Unison.break_ + addVariantCaseDeclaration Always Unison.res:6:2 path:+Unison.break_ + addRecordLabelDeclaration break_ Unison.res:9:2 path:+Unison.t addRecordLabelDeclaration doc Unison.res:10:2 path:+Unison.t addVariantCaseDeclaration Empty Unison.res:14:2 path:+Unison.stack addVariantCaseDeclaration Cons Unison.res:15:2 path:+Unison.stack - addValueReference Unison.res:17:4 --> Unison.res:17:20 - addTypeReference Unison.res:17:20 --> Unison.res:4:2 + addValueReference Unison.res:17:4 --> Unison.res:17:21 + addTypeReference Unison.res:17:21 --> Unison.res:4:2 addValueReference Unison.res:17:4 --> Unison.res:17:13 - addValueReference Unison.res:17:4 --> Unison.res:17:28 + addValueReference Unison.res:17:4 --> Unison.res:17:29 addValueReference Unison.res:19:8 --> Unison.res:19:16 addValueReference Unison.res:19:8 --> Unison.res:19:16 addValueReference Unison.res:19:8 --> Unison.res:23:10 @@ -1657,18 +1658,18 @@ addTypeReference Unison.res:23:9 --> Unison.res:10:2 addValueReference Unison.res:19:8 --> Unison.res:19:19 addValueReference Unison.res:26:8 --> Unison.res:26:20 - addValueReference Unison.res:26:8 --> Unison.res:28:23 + addValueReference Unison.res:26:8 --> Unison.res:28:24 addValueReference Unison.res:26:8 --> Unison.res:19:8 addValueReference Unison.res:26:8 --> Unison.res:26:20 - addValueReference Unison.res:26:8 --> Unison.res:28:23 + addValueReference Unison.res:26:8 --> Unison.res:28:24 addValueReference Unison.res:26:8 --> Unison.res:26:8 - addValueReference Unison.res:26:8 --> Unison.res:28:17 + addValueReference Unison.res:26:8 --> Unison.res:28:18 addValueReference Unison.res:26:8 --> Unison.res:26:20 - addValueReference Unison.res:26:8 --> Unison.res:28:23 + addValueReference Unison.res:26:8 --> Unison.res:28:24 addValueReference Unison.res:26:8 --> Unison.res:26:8 - addValueReference Unison.res:26:8 --> Unison.res:28:17 + addValueReference Unison.res:26:8 --> Unison.res:28:18 addValueReference Unison.res:26:8 --> Unison.res:26:20 - addValueReference Unison.res:26:8 --> Unison.res:28:23 + addValueReference Unison.res:26:8 --> Unison.res:28:24 addValueReference Unison.res:26:8 --> Unison.res:26:8 addValueReference Unison.res:26:8 --> Unison.res:28:10 addTypeReference Unison.res:28:9 --> Unison.res:9:2 @@ -1677,16 +1678,16 @@ addTypeReference Unison.res:37:20 --> Unison.res:14:2 addValueReference Unison.res:37:0 --> Unison.res:26:8 addTypeReference Unison.res:38:20 --> Unison.res:15:2 - DeadOptionalArgs.addReferences group called with optional argNames:break argNamesMaybe: Unison.res:38:25 - addTypeReference Unison.res:38:38 --> Unison.res:5:2 + DeadOptionalArgs.addReferences group called with optional argNames:break_ argNamesMaybe: Unison.res:38:25 + addTypeReference Unison.res:38:39 --> Unison.res:5:2 addValueReference Unison.res:38:25 --> Unison.res:17:4 - addTypeReference Unison.res:38:53 --> Unison.res:14:2 + addTypeReference Unison.res:38:54 --> Unison.res:14:2 addValueReference Unison.res:38:0 --> Unison.res:26:8 addTypeReference Unison.res:39:20 --> Unison.res:15:2 - DeadOptionalArgs.addReferences group called with optional argNames:break argNamesMaybe: Unison.res:39:25 - addTypeReference Unison.res:39:38 --> Unison.res:6:2 + DeadOptionalArgs.addReferences group called with optional argNames:break_ argNamesMaybe: Unison.res:39:25 + addTypeReference Unison.res:39:39 --> Unison.res:6:2 addValueReference Unison.res:39:25 --> Unison.res:17:4 - addTypeReference Unison.res:39:52 --> Unison.res:14:2 + addTypeReference Unison.res:39:53 --> Unison.res:14:2 addValueReference Unison.res:39:0 --> Unison.res:26:8 Scanning UseImportJsValue.cmt Source:UseImportJsValue.res addValueDeclaration +useGetProp UseImportJsValue.res:2:4 path:+UseImportJsValue @@ -1787,10 +1788,10 @@ addTypeReference DeadTest.res:35:11 --> DeadTest.res:38:11 extendTypeDependencies DeadTest.res:35:11 --> DeadTest.res:38:11 addTypeReference DeadTest.res:38:11 --> DeadTest.res:35:11 - extendTypeDependencies DeadTest.res:137:13 --> DeadTest.res:134:11 - addTypeReference DeadTest.res:134:11 --> DeadTest.res:137:13 - extendTypeDependencies DeadTest.res:134:11 --> DeadTest.res:137:13 - addTypeReference DeadTest.res:137:13 --> DeadTest.res:134:11 + extendTypeDependencies DeadTest.res:143:13 --> DeadTest.res:140:11 + addTypeReference DeadTest.res:140:11 --> DeadTest.res:143:13 + extendTypeDependencies DeadTest.res:140:11 --> DeadTest.res:143:13 + addTypeReference DeadTest.res:143:13 --> DeadTest.res:140:11 extendTypeDependencies DeadRT.res:2:2 --> DeadRT.resi:2:2 addTypeReference DeadRT.resi:2:2 --> DeadRT.res:2:2 extendTypeDependencies DeadRT.resi:2:2 --> DeadRT.res:2:2 @@ -1931,7 +1932,7 @@ Forward Liveness Analysis - decls: 695 + decls: 696 roots(external targets): 134 decl-deps: decls_with_out=407 edges_to_decls=288 @@ -1944,7 +1945,6 @@ Forward Liveness Analysis Root (external ref): Value +CreateErrorHandler2.Error2.+notification Root (annotated): Value +DeadTest.+fortyTwoButExported Root (annotated): Value +ScopedAnnotationsOverride.M.+live1 - Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.b Root (annotated): Value +Docstrings.+twoU Root (external ref): RecordLabel +TypeReexportCrossFileB.reexportedRecord.usedField Root (annotated): Value +NestedModules.Universe.Nested2.+nested2Function @@ -1954,12 +1954,12 @@ Forward Liveness Analysis Root (external ref): VariantCase +DeadTypeTest.deadType.OnlyInImplementation Root (annotated): Value +TestImport.+valueStartingWithUpperCaseLetter Root (external ref): Value +TypeReexport.VariantUseReexported.+value + Root (annotated): RecordLabel +DeadTest.inlineRecord.IR.e Root (external ref): Value +OptionalArgsLiveDead.+liveCaller Root (annotated): RecordLabel +ImportHookDefault.props.renderMe Root (annotated): Value +TypeParams3.+test Root (annotated): Value +Variants.+sunday Root (annotated): Value +NestedModules.Universe.Nested2.Nested3.+nested3Value - Root (annotated): Value +DeadTest.GloobLive.+globallyLive2 Root (annotated): Value +Hooks.+functionWithRenamedArgs Root (external ref): RecordLabel +Unison.t.doc Root (annotated): Value +Tuples.+computeAreaWithIdent @@ -1973,7 +1973,6 @@ Forward Liveness Analysis Root (external ref): Value +Newton.+f Root (external ref): RecordLabel +Records.record.v Root (external ref): VariantCase +DeadTest.VariantUsedOnlyInImplementation.t.A - Root (annotated): Value +DeadTest.GloobLive.+globallyLive3 Root (external ref): Value +Hooks.RenderPropRequiresConversion.+car Root (external ref): RecordLabel +Records.person.address Root (annotated): Value +Variants.+testConvert2 @@ -2008,6 +2007,7 @@ Forward Liveness Analysis Root (external ref): Value OptArg.+bar Root (annotated): Value +Records.+payloadValue Root (external ref): RecordLabel +DeadTest.props.s + Root (external ref): VariantCase +Unison.break_.Never Root (annotated): Value +TestEmitInnerModules.Inner.+y Root (external ref): VariantCase InnerModuleTypes.I.t.Foo Root (annotated): Value +Types.+selfRecursiveConverter @@ -2019,32 +2019,29 @@ Forward Liveness Analysis Root (annotated): Value +TestFirstClassModules.+convertRecord Root (external ref): VariantCase DeadTypeTest.deadType.OnlyInInterface Root (external ref): RecordLabel +Records.myRecBsAs.type_ - Root (external ref): VariantCase +Unison.break.Never Root (annotated): Value +ImportJsValue.+higherOrder Root (annotated): Value +Variants.+restResult3 Root (external ref): RecordLabel +Tuples.person.name Root (external ref): Value +FirstClassModules.M.InnerModule3.+k3 - Root (external ref): VariantCase +Unison.break.Always Root (external ref): RecordLabel +Records.coord.x Root (annotated): RecordLabel +DeadTypeTest.record.y Root (annotated): Value +TestImport.+defaultValue Root (external ref): Value +OptArg.+threeArgs Root (annotated): Value +Types.+setMatch + Root (external ref): Value +DeadTest.+deadIncorrect Root (external ref): RecordLabel +TypeReexport.UseReexported.reexportedType.usedField Root (annotated): Value +Docstrings.+signMessage Root (external ref): Value +DeadExn.+eInside - Root (external ref): VariantCase +DeadTest.inlineRecord.IR Root (external ref): RecordLabel +ComponentAsProp.props.button Root (annotated): Value +ImportJsValue.+returnedFromHigherOrder Root (annotated): Value +TestImport.+innerStuffContents Root (external ref): Value +ModuleExceptionBug.+ddjdj Root (annotated): Value +TransitiveType1.+convert Root (annotated): Value +ImportHooks.+make + Root (external ref): VariantCase +Unison.break_.IfNeed Root (external ref): Value +DeadTest.+make Root (annotated): Value +Records.+testMyRecBsAs - Root (external ref): Value +DeadTest.+ira - Root (external ref): Value +Unison.+toString - Root (external ref): Value +DeadTest.+deadIncorrect + Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.c Root (annotated): Value +Records.+origin Root (annotated): Value +Variants.+onlySunday Root (annotated): Value +Docstrings.+treeU @@ -2057,10 +2054,9 @@ Forward Liveness Analysis Root (annotated): Value +References.+get Root (annotated): Value +ModuleAliases.+testNested Root (external ref): Value +FirstClassModules.SomeFunctor.+ww - Root (external ref): VariantCase +DeadTest.WithInclude.t.A - Root (external ref): Value +Unison.+group Root (annotated): Value +ImportJsValue.+area Root (annotated): Value +Records.+testMyRec + Root (annotated): Value +DeadTest.GloobLive.+globallyLive3 Root (external ref): RecordLabel +Uncurried.auth.login Root (annotated): Value +ImportJsValue.+roundedNumber Root (external ref): RecordLabel +RepeatedLabel.tabState.a @@ -2104,11 +2100,12 @@ Forward Liveness Analysis Root (annotated): Value +Hooks.RenderPropRequiresConversion.+make Root (annotated): Value +LetPrivate.local_1.+x Root (annotated): Value +TestImport.+make - Root (external ref): RecordLabel +Unison.t.break + Root (annotated): Value +DeadTest.GloobLive.+globallyLive1 Root (annotated): Value +Docstrings.+grouped Root (annotated): Value +OcamlWarningSuppressToplevel.M.+suppressed4 Root (annotated): Value +Types.+optFunction Root (annotated): Value +Records.+getPayloadRecordPlusOne + Root (external ref): VariantCase +DeadTest.inlineRecord.IR Root (annotated): Value +Types.+swap Root (annotated): Value +Types.+jsonStringify Root (annotated): RecordLabel +ImportHookDefault.props.person @@ -2124,6 +2121,7 @@ Forward Liveness Analysis Root (annotated): Value +Types.+jsStringT Root (annotated): Value +Variants.+restResult1 Root (external ref): VariantCase +TypeReexport.VariantUseReexported.reexportedType.A + Root (external ref): Value +Unison.+toString Root (annotated): Value +ImportJsValue.+polymorphic Root (annotated): Value +References.+set Root (external ref): Value +DeadTest.MM.+x @@ -2132,7 +2130,7 @@ Forward Liveness Analysis Root (annotated): Value +Docstrings.+useParamU Root (annotated): Value +ImportJsValue.+useColor Root (annotated): Value +Tuples.+changeSecondAge - Root (annotated): RecordLabel +DeadTest.inlineRecord.IR.e + Root (external ref): Value +Unison.+group Root (annotated): Value +Docstrings.+unnamed1U Root (annotated): Value +Records.+recordValue Root (annotated): Value +ImportHookDefault.+make @@ -2146,15 +2144,17 @@ Forward Liveness Analysis Root (annotated): Value +References.+create Root (annotated): Value +Types.+currentTime Root (annotated): Value +Records.+someBusiness2 + Root (external ref): Value +DeadTest.+ira Root (annotated): Value +FirstClassModules.+testConvert - Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.c Root (external ref): RecordLabel +Records.coord.z Root (annotated): Value +Types.+someIntList Root (annotated): Value +Types.+jsString2T + Root (external ref): VariantCase +Unison.stack.Empty Root (annotated): Value +Records.+coord2d Root (external ref): RecordLabel +DynamicallyLoadedComponent.props.s Root (external ref): RecordLabel +Tuples.person.age Root (annotated): Value +NestedModules.Universe.+someString + Root (external ref): VariantCase +Unison.break_.Always Root (annotated): Value +TestFirstClassModules.+convertInterface Root (external ref): RecordLabel +TestPromise.fromPayload.s Root (annotated): Value +Types.+testMarshalFields @@ -2174,6 +2174,7 @@ Forward Liveness Analysis Root (annotated): Value +Records.+computeArea3 Root (annotated): Value +Variants.+fortytwoBAD Root (external ref): Value +DeadTest.+thisIsUsedOnce + Root (external ref): RecordLabel +Unison.t.break_ Root (external ref): Value ImmutableArray.+fromArray Root (external ref): Value +RepeatedLabel.+userData Root (annotated): Value +Variants.+testConvert2to3 @@ -2189,7 +2190,6 @@ Forward Liveness Analysis Root (annotated): Value +Variants.+restResult2 Root (annotated): Value +Docstrings.+useParam Root (annotated): Value +Records.+getPayload - Root (external ref): VariantCase +Unison.break.IfNeed Root (annotated): Value +ScopedAnnotationsOverride.M.NestedInLive.+nestedLive Root (external ref): Value +FirstClassModules.M.+y Root (external ref): RecordLabel +Uncurried.authU.loginU @@ -2203,6 +2203,7 @@ Forward Liveness Analysis Root (annotated): Value +VariantsWithPayload.+printManyPayloads Root (annotated): Value +TestFirstClassModules.+convertFirstClassModuleWithTypeEquations Root (annotated): Value +TransitiveType1.+convertAlias + Root (external ref): VariantCase +Unison.stack.Cons Root (external ref): Exception +DeadExn.Inside.Einside Root (annotated): Value +TestImport.+defaultValue2 Root (external ref): Exception +DeadExn.Etoplevel @@ -2221,11 +2222,11 @@ Forward Liveness Analysis Root (annotated): Value +References.+destroysRefIdentity Root (external ref): Value +FirstClassModules.M.+x Root (external ref): Value +TypeReexportCrossFileB.+recordValue - Root (external ref): VariantCase +Unison.stack.Empty Root (annotated): Value +Records.+computeArea4 Root (annotated): Value +TestModuleAliases.+testInner1Expanded Root (external ref): Value +TypeReexport.OnlyReexportedDead.+value Root (annotated): Value +ImportIndex.+make + Root (external ref): RecordLabel +DeadTest.inlineRecord.IR.b Root (annotated): Value +Unboxed.+testV1 Root (annotated): Value +NestedModules.Universe.+theAnswer Root (annotated): Value +References.+access @@ -2237,11 +2238,11 @@ Forward Liveness Analysis Root (annotated): Value +References.+update Root (annotated): Value +Opaque.+noConversion Root (external ref): RecordLabel +RepeatedLabel.tabState.b + Root (annotated): Value +DeadTest.GloobLive.+globallyLive2 Root (external ref): RecordLabel +TypeReexport.OnlyReexportedDead.reexportedType.usedField Root (annotated): Value +Docstrings.+unitArgWithConversion - Root (annotated): Value +DeadTest.GloobLive.+globallyLive1 Root (external ref): RecordLabel +Records.business.owner - Root (external ref): VariantCase +Unison.stack.Cons + Root (external ref): VariantCase +DeadTest.WithInclude.t.A Root (external ref): VariantCase +DeadTypeTest.deadType.InBoth Root (external ref): RecordLabel +Records.business.address Root (external ref): RecordLabel +VariantsWithPayload.payload.y @@ -2272,9 +2273,7 @@ Forward Liveness Analysis Propagate: InnerModuleTypes.I.t.Foo -> +InnerModuleTypes.I.t.Foo Propagate: DeadTypeTest.deadType.OnlyInInterface -> +DeadTypeTest.deadType.OnlyInInterface Propagate: +TypeReexport.UseReexported.reexportedType.usedField -> +TypeReexport.UseReexported.originalType.usedField - Propagate: +Unison.+toString -> +Unison.+fits Propagate: +References.+get -> +References.R.+get - Propagate: +DeadTest.WithInclude.t.A -> +DeadTest.WithInclude.t.A Propagate: ErrorHandler.Make.+notify -> +ErrorHandler.Make.+notify Propagate: +References.+make -> +References.R.+make Propagate: +ScopedAnnotationsLiveVsDead.LiveScope.+root -> +ScopedAnnotationsLiveVsDead.+middleLive @@ -2286,6 +2285,7 @@ Forward Liveness Analysis Propagate: +OptArg.+wrapOneArg -> +OptArg.+oneArg Propagate: +TestImmutableArray.+testImmutableArrayGet -> ImmutableArray.Array.+get Propagate: +TypeReexport.VariantUseReexported.reexportedType.A -> +TypeReexport.VariantUseReexported.originalType.A + Propagate: +Unison.+toString -> +Unison.+fits Propagate: +References.+set -> +References.R.+set Propagate: +DeadTest.MM.+x -> +DeadTest.MM.+x Propagate: NestedModulesInSignature.Universe.+theAnswer -> +NestedModulesInSignature.Universe.+theAnswer @@ -2297,6 +2297,7 @@ Forward Liveness Analysis Propagate: +ImportJsValue.+useGetAbs -> +ImportJsValue.AbsoluteValue.+getAbs Propagate: +TypeReexport.VariantUseOriginal.reexportedType.A -> +TypeReexport.VariantUseOriginal.originalType.A Propagate: +TypeReexport.OnlyReexportedDead.reexportedType.usedField -> +TypeReexport.OnlyReexportedDead.originalType.usedField + Propagate: +DeadTest.WithInclude.t.A -> +DeadTest.WithInclude.t.A Propagate: +References.R.+get -> +References.R.+get Propagate: +References.R.+make -> +References.R.+make Propagate: +ScopedAnnotationsLiveVsDead.+middleLive -> +ScopedAnnotationsLiveVsDead.+leafLive @@ -2499,6 +2500,7 @@ Forward Liveness Analysis <- +DeadTest.+make (live) Dead Value +DeadTest.+theSideEffectIsLogging Dead Value +DeadTest.+stringLengthNoSideEffects + Dead Value +DeadTest.+breakChangesControlFlow Live (annotated) Value +DeadTest.GloobLive.+globallyLive1 Live (annotated) Value +DeadTest.GloobLive.+globallyLive2 Live (annotated) Value +DeadTest.GloobLive.+globallyLive3 @@ -3868,12 +3870,12 @@ Forward Liveness Analysis Live (annotated) Value +Uncurried.+sumU2 Live (annotated) Value +Uncurried.+sumCurried Live (annotated) Value +Uncurried.+sumLblCurried - Live (external ref) VariantCase +Unison.break.IfNeed + Live (external ref) VariantCase +Unison.break_.IfNeed deps: in=1 (live=1 dead=0) out=0 <- +Unison.+group (live) - Live (external ref) VariantCase +Unison.break.Never - Live (external ref) VariantCase +Unison.break.Always - Live (external ref) RecordLabel +Unison.t.break + Live (external ref) VariantCase +Unison.break_.Never + Live (external ref) VariantCase +Unison.break_.Always + Live (external ref) RecordLabel +Unison.t.break_ deps: in=1 (live=1 dead=0) out=0 <- +Unison.+toString (live) Live (external ref) RecordLabel +Unison.t.doc @@ -3884,7 +3886,7 @@ Forward Liveness Analysis Live (external ref) VariantCase +Unison.stack.Cons Live (external ref) Value +Unison.+group deps: in=0 (live=0 dead=0) out=1 - -> +Unison.break.IfNeed + -> +Unison.break_.IfNeed Live (propagated) Value +Unison.+fits deps: in=2 (live=2 dead=0) out=2 <- +Unison.+fits (live) @@ -3896,7 +3898,7 @@ Forward Liveness Analysis <- +Unison.+toString (live) -> +Unison.+fits -> +Unison.+toString - -> +Unison.t.break + -> +Unison.t.break_ -> ... (1 more) Live (annotated) Value +UseImportJsValue.+useGetProp Live (annotated) Value +UseImportJsValue.+useTypeImportedInOtherModule @@ -3956,7 +3958,7 @@ Forward Liveness Analysis Live (annotated) Value +VariantsWithPayload.+testVariant1Object Incorrect Dead Annotation - DeadTest.res:153:1-28 + DeadTest.res:159:1-28 deadIncorrect is annotated @dead but is live Warning Dead Module @@ -4083,12 +4085,16 @@ Forward Liveness Analysis DeadTest.res:123:1-54 stringLengthNoSideEffects is never used and could have side effects + Warning Dead Value With Side Effects + DeadTest.res:125:1-62 + breakChangesControlFlow is never used and could have side effects + Warning Dead Type - DeadTest.res:151:12-17 + DeadTest.res:157:12-17 rc.a is a record label never used to read a value Warning Dead Type - DeadTest.res:158:25-30 + DeadTest.res:164:25-30 inlineRecord.IR.a is a record label never used to read a value Warning Dead Module @@ -5199,10 +5205,6 @@ Forward Liveness Analysis OptionalArgsLiveDead.res:1:1-33 optional argument fmt of function formatDate is never used - Warning Redundant Optional Argument - Unison.res:17:1-60 - optional argument break of function group is always supplied (2 calls) - Warning Unused Argument OptArg.res:14:1-42 optional argument a of function twoArgs is never used @@ -5219,8 +5221,12 @@ Forward Liveness Analysis OptArg.res:20:1-51 optional argument a of function wrapOneArg is always supplied (1 calls) + Warning Redundant Optional Argument + Unison.res:17:1-55 + optional argument break_ of function group is always supplied (2 calls) + Warning Redundant Optional Argument OptArg.res:26:1-70 optional argument c of function wrapfourArgs is always supplied (2 calls) - Analysis reported 317 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:2, Warning Redundant Optional Argument:6, Warning Unused Argument:12) + 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) diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res index 61b320568b7..1690f4dfef1 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/DeadTest.res @@ -122,6 +122,12 @@ let theSideEffectIsLogging = Js.log(123) let stringLengthNoSideEffects = String.length("sdkdl") +let breakChangesControlFlow = { + while true { + break + } +} + // Trace.infok("", "", ({pf}) => pf("%s", "")) module GloobLive = { diff --git a/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res index 154c25fa766..84d0bf16f28 100644 --- a/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res +++ b/tests/analysis_tests/tests-reanalyze/deadcode/src/Unison.res @@ -1,12 +1,12 @@ // Exmple of several DCE checks operating in unison -type break = +type break_ = | IfNeed | Never | Always type t = { - break: break, + break_: break_, doc: string, } @@ -14,7 +14,7 @@ type rec stack = | Empty | Cons(t, stack) -let group = (~break=IfNeed, doc) => {break: break, doc: doc} +let group = (~break_=IfNeed, doc) => {break_, doc: doc} let rec fits = (w, stack) => switch stack { @@ -25,8 +25,8 @@ let rec fits = (w, stack) => let rec toString = (~width, stack) => switch stack { - | Cons({break, doc}, stack) => - switch break { + | Cons({break_, doc}, stack) => + switch break_ { | IfNeed => (fits(width, stack) ? "fits " : "no ") ++ (stack -> toString(~width=width - 1)) | Never => "never " ++ (doc ++ (stack -> toString(~width=width - 1))) | Always => "always " ++ (doc ++ (stack -> toString(~width=width - 1))) @@ -35,6 +35,5 @@ let rec toString = (~width, stack) => } toString(~width=80, Empty)->ignore -toString(~width=80, Cons(group(~break=Never, "abc"), Empty))->ignore -toString(~width=80, Cons(group(~break=Always, "d"), Empty))->ignore - +toString(~width=80, Cons(group(~break_=Never, "abc"), Empty))->ignore +toString(~width=80, Cons(group(~break_=Always, "d"), Empty))->ignore diff --git a/tests/build_tests/super_errors/expected/break_in_nested_function.res.expected b/tests/build_tests/super_errors/expected/break_in_nested_function.res.expected new file mode 100644 index 00000000000..069ba4cc5d8 --- /dev/null +++ b/tests/build_tests/super_errors/expected/break_in_nested_function.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/break_in_nested_function.res:3:5-9 + + 1 │ while true { + 2 │ let stop = () => { + 3 │ break + 4 │ () + 5 │ } + + `break` can only be used directly inside a loop body. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/break_keyword_binding.res.expected b/tests/build_tests/super_errors/expected/break_keyword_binding.res.expected new file mode 100644 index 00000000000..9cfa61091bf --- /dev/null +++ b/tests/build_tests/super_errors/expected/break_keyword_binding.res.expected @@ -0,0 +1,8 @@ + + Syntax error! + /.../fixtures/break_keyword_binding.res:1:5-9 + + 1 │ let break = 1 + 2 │ + + `break` is a reserved keyword. Keywords need to be escaped: \"break" \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/break_outside_loop.res.expected b/tests/build_tests/super_errors/expected/break_outside_loop.res.expected new file mode 100644 index 00000000000..de704b3ef90 --- /dev/null +++ b/tests/build_tests/super_errors/expected/break_outside_loop.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/break_outside_loop.res:2:3-7 + + 1 │ let _ = { + 2 │ break + 3 │ () + 4 │ } + + `break` can only be used directly inside a loop body. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/continue_in_nested_function.res.expected b/tests/build_tests/super_errors/expected/continue_in_nested_function.res.expected new file mode 100644 index 00000000000..ecaf46577c7 --- /dev/null +++ b/tests/build_tests/super_errors/expected/continue_in_nested_function.res.expected @@ -0,0 +1,11 @@ + + We've found a bug for you! + /.../fixtures/continue_in_nested_function.res:3:5-12 + + 1 │ while true { + 2 │ let skip = () => { + 3 │ continue + 4 │ () + 5 │ } + + `continue` can only be used inside a loop body. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/continue_outside_loop.res.expected b/tests/build_tests/super_errors/expected/continue_outside_loop.res.expected new file mode 100644 index 00000000000..0e4ba9690df --- /dev/null +++ b/tests/build_tests/super_errors/expected/continue_outside_loop.res.expected @@ -0,0 +1,10 @@ + + We've found a bug for you! + /.../fixtures/continue_outside_loop.res:2:3-10 + + 1 │ let _ = { + 2 │ continue + 3 │ () + 4 │ } + + `continue` can only be used inside a loop body. \ No newline at end of file diff --git a/tests/build_tests/super_errors/expected/loop_control_nonreturning_statement.res.expected b/tests/build_tests/super_errors/expected/loop_control_nonreturning_statement.res.expected new file mode 100644 index 00000000000..7165c3350e7 --- /dev/null +++ b/tests/build_tests/super_errors/expected/loop_control_nonreturning_statement.res.expected @@ -0,0 +1,23 @@ + + Warning number 21 + /.../fixtures/loop_control_nonreturning_statement.res:3:5-9 + + 1 │ let breakInWhile = () => { + 2 │ while true { + 3 │ break + 4 │ Console.log("after break") + 5 │ } + + This statement does not continue execution; following code is unreachable. + + + Warning number 21 + /.../fixtures/loop_control_nonreturning_statement.res:10:5-12 + + 8 │ let continueInFor = () => { + 9 │ for i in 0 to 10 { + 10 │ continue + 11 │ Console.log(i) + 12 │ } + + This statement does not continue execution; following code is unreachable. \ No newline at end of file diff --git a/tests/build_tests/super_errors/fixtures/break_in_nested_function.res b/tests/build_tests/super_errors/fixtures/break_in_nested_function.res new file mode 100644 index 00000000000..3ab38615f2a --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/break_in_nested_function.res @@ -0,0 +1,8 @@ +while true { + let stop = () => { + break + () + } + + stop() +} diff --git a/tests/build_tests/super_errors/fixtures/break_keyword_binding.res b/tests/build_tests/super_errors/fixtures/break_keyword_binding.res new file mode 100644 index 00000000000..58017b1282e --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/break_keyword_binding.res @@ -0,0 +1 @@ +let break = 1 diff --git a/tests/build_tests/super_errors/fixtures/break_outside_loop.res b/tests/build_tests/super_errors/fixtures/break_outside_loop.res new file mode 100644 index 00000000000..7c52cbc1370 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/break_outside_loop.res @@ -0,0 +1,4 @@ +let _ = { + break + () +} diff --git a/tests/build_tests/super_errors/fixtures/continue_in_nested_function.res b/tests/build_tests/super_errors/fixtures/continue_in_nested_function.res new file mode 100644 index 00000000000..adc37b2c191 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/continue_in_nested_function.res @@ -0,0 +1,8 @@ +while true { + let skip = () => { + continue + () + } + + skip() +} diff --git a/tests/build_tests/super_errors/fixtures/continue_outside_loop.res b/tests/build_tests/super_errors/fixtures/continue_outside_loop.res new file mode 100644 index 00000000000..7b298df9cc1 --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/continue_outside_loop.res @@ -0,0 +1,4 @@ +let _ = { + continue + () +} diff --git a/tests/build_tests/super_errors/fixtures/loop_control_nonreturning_statement.res b/tests/build_tests/super_errors/fixtures/loop_control_nonreturning_statement.res new file mode 100644 index 00000000000..850d1428cfe --- /dev/null +++ b/tests/build_tests/super_errors/fixtures/loop_control_nonreturning_statement.res @@ -0,0 +1,27 @@ +let breakInWhile = () => { + while true { + break + Console.log("after break") + } +} + +let continueInFor = () => { + for i in 0 to 10 { + continue + Console.log(i) + } +} + +let terminalBreakInWhile = () => { + while true { + break + } +} + +let terminalContinueInFor = () => { + for i in 0 to 10 { + if i > 5 { + continue + } + } +} diff --git a/tests/syntax_benchmarks/data/Napkinscript.res b/tests/syntax_benchmarks/data/Napkinscript.res index a472306af98..22acfa41691 100644 --- a/tests/syntax_benchmarks/data/Napkinscript.res +++ b/tests/syntax_benchmarks/data/Napkinscript.res @@ -204,11 +204,11 @@ module Doc = { | list{} => true | list{(_ind, _mode, Text(txt)), ...rest} => fits(w - String.length(txt), rest) | list{(ind, mode, Indent(doc)), ...rest} => fits(w, list{(ind + 2, mode, doc), ...rest}) - | list{(_ind, Flat, LineBreak(break)), ...rest} => - if break == Hard { + | list{(_ind, Flat, LineBreak(break_)), ...rest} => + if break_ == Hard { true } else { - let w = if break == Classic { + let w = if break_ == Classic { w - 1 } else { w @@ -381,8 +381,8 @@ module Doc = { text(")"), }), ) - | LineBreak(break) => - let breakTxt = switch break { + | LineBreak(break_) => + let breakTxt = switch break_ { | Classic => "Classic" | Soft => "Soft" | Hard => "Hard" @@ -2408,18 +2408,18 @@ module Grammar = { module Reporting = { module TerminalDoc = { - type break = + type break_ = | Never | Always type rec document = | Nil - | Group({break: break, doc: document}) + | Group({break_: break_, doc: document}) | Text(string) | Indent({amount: int, doc: document}) | Append({doc1: document, doc2: document}) - let group = (~break, doc) => Group({break, doc}) + let group = (~break_, doc) => Group({break_, doc}) let text = txt => Text(txt) let indent = (i, d) => Indent({amount: i, doc: d}) let append = (d1, d2) => Append({doc1: d1, doc2: d2}) @@ -2456,9 +2456,9 @@ module Reporting = { let rest = push(rest, doc1) loop(rest, mode, offset) - | Group({break, doc}) => + | Group({break_, doc}) => let rest = push(rest, doc) - switch break { + switch break_ { | Always => loop(rest, Break, offset) | Never => loop(rest, Flat, offset) } @@ -2504,7 +2504,7 @@ module Reporting = { let indent = (@doesNotRaise String.make)(from, ' ') let underline = (@doesNotRaise String.make)(len, '^') let line = highlight(~from=0, ~len, underline) - group(~break=Always, append(text(txt), text(indent ++ line))) + group(~break_=Always, append(text(txt), text(indent ++ line))) } let rec drop = (n, l) => @@ -2597,7 +2597,7 @@ module Reporting = { text(x) } - group(~break=Never, append(append(text(rowNr), text(" │")), indent(2, line))) + group(~break_=Never, append(append(text(rowNr), text(" │")), indent(2, line))) } let reportDoc = ref(TerminalDoc.nil) @@ -2610,7 +2610,7 @@ module Reporting = { reportDoc := { open TerminalDoc let ix = startLine + i - group(~break=Always, append(reportDoc.contents, renderLine(line, ix))) + group(~break_=Always, append(reportDoc.contents, renderLine(line, ix))) } } diff --git a/tests/syntax_benchmarks/data/RedBlackTree.res b/tests/syntax_benchmarks/data/RedBlackTree.res index 75a887c5949..e1dff155df1 100644 --- a/tests/syntax_benchmarks/data/RedBlackTree.res +++ b/tests/syntax_benchmarks/data/RedBlackTree.res @@ -342,15 +342,15 @@ let removeNode = (rbt, node) => { rbt.root = Some(successor) } } else { - let break = ref(false) + let break_ = ref(false) let successorRef = ref(successor) - while !break.contents { + while !break_.contents { let successor = successorRef.contents // Case 1: node is root. Done. switch successor.parent { | None => rbt.root = Some(successor) - break.contents = true + break_.contents = true | Some(successorParent) => // Case 2: sibling red. Flip color of P and S. Left rotate P. let sibling = siblingOf(successor) @@ -393,7 +393,7 @@ let removeNode = (rbt, node) => { siblingNN.color = Red } successorParent.color = Black - break.contents = true + break_.contents = true } else if ( // Case 5: sibling black, sibling left child red, right child black, // node is left child. Rotate right sibling. Swap color of sibling and @@ -420,7 +420,7 @@ let removeNode = (rbt, node) => { (sibling.right->castNotOption).color = Black rotateLeft(rbt, sibling) } - break.contents = true + break_.contents = true } else { // Case 6: sibling black, sibling right child red, node is left child. // Rotate left node parent. Swap color of parent and sibling. Paint diff --git a/tests/syntax_benchmarks/data/RedBlackTreeNoComments.res b/tests/syntax_benchmarks/data/RedBlackTreeNoComments.res index 9842a002b8b..627e6a8d5d2 100644 --- a/tests/syntax_benchmarks/data/RedBlackTreeNoComments.res +++ b/tests/syntax_benchmarks/data/RedBlackTreeNoComments.res @@ -266,15 +266,15 @@ let removeNode = (rbt, node) => { rbt.root = Some(successor) } } else { - let break = ref(false) + let break_ = ref(false) let successorRef = ref(successor) - while !break.contents { + while !break_.contents { let successor = successorRef.contents switch successor.parent { | None => rbt.root = Some(successor) - break.contents = true + break_.contents = true | Some(successorParent) => let sibling = siblingOf(successor) if sibling !== None && (sibling->castNotOption).color === Red { @@ -311,7 +311,7 @@ let removeNode = (rbt, node) => { siblingNN.color = Red } successorParent.color = Black - break.contents = true + break_.contents = true } else if sibling !== None && (sibling->castNotOption).color === Black { let sibling = sibling->castNotOption if ( @@ -333,7 +333,7 @@ let removeNode = (rbt, node) => { (sibling.right->castNotOption).color = Black rotateLeft(rbt, sibling) } - break.contents = true + break_.contents = true } else { let sibling = siblingOf(successor) let sibling = sibling->castNotOption diff --git a/tests/syntax_tests/data/idempotency/genType/src/Converter.res b/tests/syntax_tests/data/idempotency/genType/src/Converter.res index e697563df3f..1e8c7b22de5 100644 --- a/tests/syntax_tests/data/idempotency/genType/src/Converter.res +++ b/tests/syntax_tests/data/idempotency/genType/src/Converter.res @@ -410,7 +410,7 @@ let rec apply = (~config, ~converter, ~indent, ~nameGen, ~toJS, ~variantTables, (" = " ++ (x ++ (";" ++ - (Indent.break(~indent=indent1) ++ + (Indent.break_(~indent=indent1) ++ ("return " ++ (resultName->apply( ~config, @@ -497,7 +497,7 @@ let rec apply = (~config, ~converter, ~indent, ~nameGen, ~toJS, ~variantTables, | list{props} if isHook => let propsName = "$props"->EmitText.name(~nameGen) ( - Indent.break(~indent=indent1) ++ ("const " ++ (propsName ++ (" = " ++ (props ++ ";")))), + Indent.break_(~indent=indent1) ++ ("const " ++ (propsName ++ (" = " ++ (props ++ ";")))), list{value, propsName}, ) @@ -505,7 +505,7 @@ let rec apply = (~config, ~converter, ~indent, ~nameGen, ~toJS, ~variantTables, } declareProps ++ - (Indent.break(~indent=indent1) ++ + (Indent.break_(~indent=indent1) ++ (functionName->EmitText.funCall(~args, ~useCurry)->mkReturn)) } diff --git a/tests/syntax_tests/data/idempotency/genType/src/EmitText.res b/tests/syntax_tests/data/idempotency/genType/src/EmitText.res index 4da990a0ff8..0dbcf2105e7 100644 --- a/tests/syntax_tests/data/idempotency/genType/src/EmitText.res +++ b/tests/syntax_tests/data/idempotency/genType/src/EmitText.res @@ -44,13 +44,13 @@ let funDef = (~bodyArgs, ~functionName, ~funParams, ~indent, ~mkBody, ~typeVars) } ++ (genericsString(~typeVars) ++ ((funParams->parens) ++ - (" {" ++ ((bodyArgs->mkBody) ++ (Indent.break(~indent) ++ "}")))))) + (" {" ++ ((bodyArgs->mkBody) ++ (Indent.break_(~indent) ++ "}")))))) let ifThenElse = (~indent, if_, then_, else_) => { let indent1 = indent->Indent.more if_(~indent=indent1) ++ - (Indent.break(~indent) ++ - ("? " ++ (then_(~indent=indent1) ++ (Indent.break(~indent) ++ (": " ++ else_(~indent=indent1)))))) + (Indent.break_(~indent) ++ + ("? " ++ (then_(~indent=indent1) ++ (Indent.break_(~indent) ++ (": " ++ else_(~indent=indent1)))))) } let newNameGen = () => Hashtbl.create(1) @@ -69,7 +69,7 @@ let \"switch" = (~indent, ~cases, expr) => { } else { expr ++ ("===" ++ - (label ++ (Indent.break(~indent) ++ ("? " ++ (code ++ (Indent.break(~indent) ++ ": ")))))) + (label ++ (Indent.break_(~indent) ++ ("? " ++ (code ++ (Indent.break_(~indent) ++ ": ")))))) } ) ->String.concat("") diff --git a/tests/syntax_tests/data/idempotency/genType/src/EmitType.res b/tests/syntax_tests/data/idempotency/genType/src/EmitType.res index 7e562270ae9..0c55898617c 100644 --- a/tests/syntax_tests/data/idempotency/genType/src/EmitType.res +++ b/tests/syntax_tests/data/idempotency/genType/src/EmitType.res @@ -242,8 +242,8 @@ let rec renderType = (~config, ~indent=None, ~typeNameIsInterface, ~inFunType, t }) let rendered = \"@"(noPayloadsRendered, payloadsRendered) let indent1 = rendered->Indent.heuristicVariants(~indent) - (indent1 == None ? "" : Indent.break(~indent=indent1) ++ " ") ++ - (rendered->String.concat((indent1 == None ? " " : Indent.break(~indent=indent1)) ++ "| ")) + (indent1 == None ? "" : Indent.break_(~indent=indent1) ++ " ") ++ + (rendered->String.concat((indent1 == None ? " " : Indent.break_(~indent=indent1)) ++ "| ")) } and renderField = ( ~config, @@ -254,7 +254,7 @@ and renderField = ( ) => { let optMarker = optional === Optional ? "?" : "" let mutMarker = mutable_ == Immutable ? config.language == Flow ? "+" : "readonly " : "" - Indent.break(~indent) ++ + Indent.break_(~indent) ++ (mutMarker ++ (lbl ++ (optMarker ++ @@ -267,11 +267,11 @@ and renderFields = (~closedFlag, ~config, ~indent, ~inFunType, ~typeNameIsInterf let renderedFields = fields->List.map(renderField(~config, ~indent=indent1, ~typeNameIsInterface, ~inFunType)) let dotdotdot = - config.language == Flow && !exact ? list{Indent.break(~indent=indent1) ++ "..."} : list{} + config.language == Flow && !exact ? list{Indent.break_(~indent=indent1) ++ "..."} : list{} (exact ? "{|" : "{") ++ space ++ (String.concat(config.language == TypeScript ? "; " : ", ", \"@"(renderedFields, dotdotdot)) ++ - (Indent.break(~indent) ++ (space ++ (exact ? "|}" : "}")))) + (Indent.break_(~indent) ++ (space ++ (exact ? "|}" : "}")))) } and renderFunType = ( ~config, @@ -533,12 +533,12 @@ let emitPropTypes = (~config, ~emitters, ~indent, ~name, fields) => { let indent1 = indent->Indent.more prefix("shape") ++ ("({" ++ - (Indent.break(~indent=indent1) ++ + (Indent.break_(~indent=indent1) ++ ((fields ->List.filter(({nameJS}: field) => nameJS != "children") ->List.map(emitField(~indent=indent1)) - ->String.concat("," ++ Indent.break(~indent=indent1))) ++ - (Indent.break(~indent) ++ "})")))) + ->String.concat("," ++ Indent.break_(~indent=indent1))) ++ + (Indent.break_(~indent) ++ "})")))) | Ident(_) | Null(_) @@ -560,11 +560,11 @@ let emitPropTypes = (~config, ~emitters, ~indent, ~name, fields) => { name ++ (".propTypes = " ++ ("{" ++ - (Indent.break(~indent=indent1) ++ + (Indent.break_(~indent=indent1) ++ ((fields ->List.filter(({nameJS}: field) => nameJS != "children") ->List.map(emitField(~indent=indent1)) - ->String.concat("," ++ Indent.break(~indent=indent1))) ++ (Indent.break(~indent) ++ "};"))))) + ->String.concat("," ++ Indent.break_(~indent=indent1))) ++ (Indent.break_(~indent) ++ "};"))))) ->Emitters.\"export"(~emitters) } diff --git a/tests/syntax_tests/data/idempotency/genType/src/Indent.res b/tests/syntax_tests/data/idempotency/genType/src/Indent.res index bd1a63a2135..b13c3f0d1bf 100644 --- a/tests/syntax_tests/data/idempotency/genType/src/Indent.res +++ b/tests/syntax_tests/data/idempotency/genType/src/Indent.res @@ -1,6 +1,6 @@ type t = option -let break = (~indent) => +let break_ = (~indent) => switch indent { | None => "" | Some(s) => "\n" ++ s @@ -19,6 +19,6 @@ let heuristicFields = (~indent, fields) => { let heuristicVariants = (~indent, rendered) => { let threshold = 40 - let break = rendered->String.concat(" ")->String.length > threshold - break && indent == None ? Some(" ") : indent + let break_ = rendered->String.concat(" ")->String.length > threshold + break_ && indent == None ? Some(" ") : indent } 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 new file mode 100644 index 00000000000..f8a0f4036ce --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/loopControl.res.txt @@ -0,0 +1,10 @@ +;;while true do + if done then break; + if skip then continue; + (match state with + | Skip -> continue + | Stop -> break + | KeepGoing -> work ()) + done +;;for i = 0 to 10 do match i with | 3 -> continue | 8 -> break | _ -> work () + done \ No newline at end of file diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res b/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res new file mode 100644 index 00000000000..aa6899bc523 --- /dev/null +++ b/tests/syntax_tests/data/parsing/grammar/expressions/loopControl.res @@ -0,0 +1,23 @@ +while true { + if done { + break + } + + if skip { + continue + } + + switch state { + | Skip => continue + | Stop => break + | KeepGoing => work() + } +} + +for i in 0 to 10 { + switch i { + | 3 => continue + | 8 => break + | _ => work() + } +} diff --git a/tests/syntax_tests/data/parsing/infiniteLoops/equalAfterBinaryExpr.res b/tests/syntax_tests/data/parsing/infiniteLoops/equalAfterBinaryExpr.res index 78eda1d9d81..594d2ea377d 100644 --- a/tests/syntax_tests/data/parsing/infiniteLoops/equalAfterBinaryExpr.res +++ b/tests/syntax_tests/data/parsing/infiniteLoops/equalAfterBinaryExpr.res @@ -62,15 +62,15 @@ let removeNode = (rbt, node) => { rbt->rootSet(Some(successor)) } } else { - let break = ref(false) + let break_ = ref(false) let successorRef = ref(successor) - while !break.contents { + while !break_.contents { let successor = successorRef.contents // Case 1: node is root. Done. switch successor.parent { | None => rbt->rootSet(Some(successor)) - break.contents = true + break_.contents = true | Some(successorParent) => // Case 2: sibling red. Flip color of P and S. Left rotate P. let sibling = siblingOf(successor) @@ -117,7 +117,7 @@ let removeNode = (rbt, node) => { siblingNN.color = Red } successorParent.color = Black - break.contents = true + break_.contents = true } else if // Case 5: sibling black, sibling left child red, right child black, // node is left child. Rotate right sibling. Swap color of sibling and @@ -143,7 +143,7 @@ let removeNode = (rbt, node) => { (sibling.right->castNotOption).color = Black rotateLeft(rbt, sibling) } - break.contents = true + break_.contents = true } else { // Case 6: sibling black, sibling right child red, node is left child. // Rotate left node parent. Swap color of parent and sibling. Paint diff --git a/tests/syntax_tests/data/parsing/infiniteLoops/expected/equalAfterBinaryExpr.res.txt b/tests/syntax_tests/data/parsing/infiniteLoops/expected/equalAfterBinaryExpr.res.txt index fd05b79b8ff..a9275e7059b 100644 --- a/tests/syntax_tests/data/parsing/infiniteLoops/expected/equalAfterBinaryExpr.res.txt +++ b/tests/syntax_tests/data/parsing/infiniteLoops/expected/equalAfterBinaryExpr.res.txt @@ -57,13 +57,13 @@ let removeNode [arity:2]rbt node = (successor.color <- Black; if successor.parent === None then rbt -> (rootSet (Some successor))) else - (let break = ref false in + (let break_ = ref false in let successorRef = ref successor in - while not break.contents do + while not break_.contents do let successor = successorRef.contents in match successor.parent with | None -> - (rbt -> (rootSet (Some successor)); break.contents <- true) + (rbt -> (rootSet (Some successor)); break_.contents <- true) | Some successorParent -> let sibling = siblingOf successor in (if @@ -106,7 +106,7 @@ let removeNode [arity:2]rbt node = then (if (!==) sibling None then siblingNN.color <- Red; successorParent.color <- Black; - break.contents <- true) + break_.contents <- true) else if ((!==) sibling None) && @@ -139,7 +139,7 @@ let removeNode [arity:2]rbt node = (sibling.color <- Red; (sibling.right -> castNotOption).color <- Black; rotateLeft rbt sibling); - break.contents <- true) + break_.contents <- true) else (let sibling = siblingOf successor in let sibling = sibling -> castNotOption in diff --git a/tests/syntax_tests/data/parsing/infiniteLoops/expected/nonRecTypes.res.txt b/tests/syntax_tests/data/parsing/infiniteLoops/expected/nonRecTypes.res.txt index 1d67914ae9a..3028343c9d5 100644 --- a/tests/syntax_tests/data/parsing/infiniteLoops/expected/nonRecTypes.res.txt +++ b/tests/syntax_tests/data/parsing/infiniteLoops/expected/nonRecTypes.res.txt @@ -162,13 +162,13 @@ include if (parentGet successor) === None then rootSet rbt (Some successor)) else - (let break = ref false in + (let break_ = ref false in let successorRef = ref successor in - while not break.contents do + while not break_.contents do let successor = successorRef.contents in match parentGet successor with | None -> - (rootSet rbt (Some successor); break.contents <- true) + (rootSet rbt (Some successor); break_.contents <- true) | Some successorParent -> let sibling = siblingOf successor in (if @@ -215,7 +215,7 @@ include then (if (!==) sibling None then colorSet siblingNN Red; colorSet successorParent Black; - break.contents <- true) + break_.contents <- true) else if ((!==) sibling None) && @@ -254,7 +254,7 @@ include colorSet (castNotOption (rightGet sibling)) Black; rotateLeft rbt sibling); - break.contents <- true) + break_.contents <- true) else (let sibling = siblingOf successor in let sibling = castNotOption sibling in diff --git a/tests/syntax_tests/data/parsing/infiniteLoops/nonRecTypes.res b/tests/syntax_tests/data/parsing/infiniteLoops/nonRecTypes.res index b2a8779a000..75df34426b7 100644 --- a/tests/syntax_tests/data/parsing/infiniteLoops/nonRecTypes.res +++ b/tests/syntax_tests/data/parsing/infiniteLoops/nonRecTypes.res @@ -103,14 +103,14 @@ let removeNode = (rbt, node) => { rootSet(rbt, Some(successor)); }; } else { - let break = ref(false); + let break_ = ref(false); let successorRef = ref(successor); - while (!break.contents) { + while (!break_.contents) { let successor = successorRef.contents; switch (parentGet(successor)) { | None => rootSet(rbt, Some(successor)); - break.contents = true; + break_.contents = true; | Some(successorParent) => let sibling = siblingOf(successor); if (sibling !== None && colorGet(castNotOption(sibling)) === Red) { @@ -164,7 +164,7 @@ let removeNode = (rbt, node) => { colorSet(siblingNN, Red); }; colorSet(successorParent, Black); - break.contents = true; + break_.contents = true; } else if (sibling !== None && colorGet(castNotOption(sibling)) === Black) { let sibling = castNotOption(sibling); @@ -199,7 +199,7 @@ let removeNode = (rbt, node) => { colorSet(castNotOption(rightGet(sibling)), Black); rotateLeft(rbt, sibling); }; - break.contents = true; + break_.contents = true; } else { let sibling = siblingOf(successor); let sibling = castNotOption(sibling); diff --git a/tests/syntax_tests/data/printer/expr/binary.res b/tests/syntax_tests/data/printer/expr/binary.res index 210c67f68ee..3925fafc04c 100644 --- a/tests/syntax_tests/data/printer/expr/binary.res +++ b/tests/syntax_tests/data/printer/expr/binary.res @@ -143,7 +143,7 @@ let () = { sibling.right->castNotOption.color = Black rotateLeft(rbt, sibling) } - break.contents = true + break_.contents = true } else { let sibling = siblingOf(successor) let sibling = sibling->castNotOption diff --git a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt index 0eae8193512..fdea02c9273 100644 --- a/tests/syntax_tests/data/printer/expr/expected/binary.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/binary.res.txt @@ -173,7 +173,7 @@ let () = { sibling.right->castNotOption.color = Black rotateLeft(rbt, sibling) } - break.contents = true + break_.contents = true } else { let sibling = siblingOf(successor) let sibling = sibling->castNotOption diff --git a/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt b/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt new file mode 100644 index 00000000000..aa6899bc523 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/expected/loopControl.res.txt @@ -0,0 +1,23 @@ +while true { + if done { + break + } + + if skip { + continue + } + + switch state { + | Skip => continue + | Stop => break + | KeepGoing => work() + } +} + +for i in 0 to 10 { + switch i { + | 3 => continue + | 8 => break + | _ => work() + } +} diff --git a/tests/syntax_tests/data/printer/expr/expected/while.res.txt b/tests/syntax_tests/data/printer/expr/expected/while.res.txt index 8db23ce4224..b9378321336 100644 --- a/tests/syntax_tests/data/printer/expr/expected/while.res.txt +++ b/tests/syntax_tests/data/printer/expr/expected/while.res.txt @@ -18,5 +18,5 @@ while continuePrefix.contents && aPrefixLen.contents <= aLen && bPrefixLen.conte let x = @attr while true { - break() + break_() } diff --git a/tests/syntax_tests/data/printer/expr/loopControl.res b/tests/syntax_tests/data/printer/expr/loopControl.res new file mode 100644 index 00000000000..aa6899bc523 --- /dev/null +++ b/tests/syntax_tests/data/printer/expr/loopControl.res @@ -0,0 +1,23 @@ +while true { + if done { + break + } + + if skip { + continue + } + + switch state { + | Skip => continue + | Stop => break + | KeepGoing => work() + } +} + +for i in 0 to 10 { + switch i { + | 3 => continue + | 8 => break + | _ => work() + } +} diff --git a/tests/syntax_tests/data/printer/expr/while.res b/tests/syntax_tests/data/printer/expr/while.res index 57298e0bba3..91e1f3d0292 100644 --- a/tests/syntax_tests/data/printer/expr/while.res +++ b/tests/syntax_tests/data/printer/expr/while.res @@ -21,5 +21,5 @@ while ( } let x = @attr while true { - break() + break_() } diff --git a/tests/tests/src/loop_control_test.mjs b/tests/tests/src/loop_control_test.mjs new file mode 100644 index 00000000000..ff80dc15ecf --- /dev/null +++ b/tests/tests/src/loop_control_test.mjs @@ -0,0 +1,179 @@ +// Generated by ReScript, PLEASE EDIT WITH CARE + +import * as Mocha from "mocha"; +import * as Belt_List from "@rescript/runtime/lib/es6/Belt_List.mjs"; +import * as Test_utils from "./test_utils.mjs"; + +Mocha.describe("Loop_control_test", () => { + Mocha.test("while loop break and continue", () => { + let values = /* [] */0; + let i = 0; + while (i < 6) { + i = i + 1 | 0; + if (i === 2) { + continue; + } + if (i === 5) { + break; + + } + values = { + hd: i, + tl: values + }; + }; + Test_utils.eq("File \"loop_control_test.res\", line 23, characters 7-14", [ + 1, + 3, + 4 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("switch inside while targets the loop", () => { + let values = /* [] */0; + let i = 0; + while (i < 6) { + i = i + 1 | 0; + let match = i; + if (match !== 2) { + if (match !== 5) { + values = { + hd: i, + tl: values + }; + } else { + break; + + } + } else { + continue; + } + }; + Test_utils.eq("File \"loop_control_test.res\", line 40, characters 7-14", [ + 1, + 3, + 4 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("string switch inside while targets the loop via JS switch", () => { + let values = /* [] */0; + let i = 0; + loop_0: while (i < 6) { + i = i + 1 | 0; + let match = i; + let state = match !== 2 ? ( + match !== 5 ? "keep" : "stop" + ) : "skip"; + switch (state) { + case "skip" : + continue loop_0; + case "stop" : + break loop_0; + default: + values = { + hd: i, + tl: values + }; + } + }; + Test_utils.eq("File \"loop_control_test.res\", line 64, characters 7-14", [ + 1, + 3, + 4 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("for loop break and continue", () => { + let values = /* [] */0; + for (let i = 0; i <= 5; ++i) { + if (i === 1) { + continue; + } + if (i === 4) { + break; + + } + values = { + hd: i, + tl: values + }; + } + Test_utils.eq("File \"loop_control_test.res\", line 82, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("braced break expression in value position compiles", () => { + let reached = false; + while (true) { + break; + + }; + Test_utils.eq("File \"loop_control_test.res\", line 95, characters 7-14", false, reached); + }); + Mocha.test("braced continue expression in value position compiles", () => { + let values = /* [] */0; + for (let i = 0; i <= 3; ++i) { + if (i === 1) { + continue; + } + values = { + hd: i, + tl: values + }; + } + Test_utils.eq("File \"loop_control_test.res\", line 111, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("switch inside for targets the loop", () => { + let values = /* [] */0; + for (let i = 0; i <= 5; ++i) { + if (i !== 1) { + if (i !== 4) { + values = { + hd: i, + tl: values + }; + } else { + break; + + } + } else { + continue; + } + } + Test_utils.eq("File \"loop_control_test.res\", line 125, characters 7-14", [ + 0, + 2, + 3 + ], Belt_List.toArray(Belt_List.reverse(values))); + }); + Mocha.test("string switch inside for targets the loop via JS switch", () => { + let values = /* [] */0; + loop_1: for (let i = 0; i <= 5; ++i) { + let state = i !== 1 ? ( + i !== 4 ? "keep" : "stop" + ) : "skip"; + switch (state) { + case "skip" : + continue loop_1; + case "stop" : + break loop_1; + default: + values = { + hd: i, + tl: values + }; + } + } + Test_utils.eq("File \"loop_control_test.res\", line 146, 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 new file mode 100644 index 00000000000..f85b78591b5 --- /dev/null +++ b/tests/tests/src/loop_control_test.res @@ -0,0 +1,148 @@ +open Mocha +open Test_utils + +describe(__MODULE__, () => { + test("while loop break and continue", () => { + let values = ref(list{}) + let i = ref(0) + + while i.contents < 6 { + i := i.contents + 1 + + if i.contents == 2 { + continue + } + + if i.contents == 5 { + break + } + + values := list{i.contents, ...values.contents} + } + + eq(__LOC__, [1, 3, 4], values.contents->Belt.List.reverse->Belt.List.toArray) + }) + + test("switch inside while targets the loop", () => { + let values = ref(list{}) + let i = ref(0) + + while i.contents < 6 { + i := i.contents + 1 + + switch i.contents { + | 2 => continue + | 5 => break + | _ => values := list{i.contents, ...values.contents} + } + } + + eq(__LOC__, [1, 3, 4], 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 while targets the loop via JS switch", () => { + let values = ref(list{}) + let i = ref(0) + + while i.contents < 6 { + i := i.contents + 1 + + let state = switch i.contents { + | 2 => "skip" + | 5 => "stop" + | _ => "keep" + } + + switch state { + | "skip" => continue + | "stop" => break + | _ => values := list{i.contents, ...values.contents} + } + } + + eq(__LOC__, [1, 3, 4], values.contents->Belt.List.reverse->Belt.List.toArray) + }) + + test("for loop break and continue", () => { + let values = ref(list{}) + + for i in 0 to 5 { + 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("braced break expression in value position compiles", () => { + let reached = ref(false) + let acceptUnit = (_: unit) => () + + while true { + let x = {break} + acceptUnit(x) + reached := true + } + + eq(__LOC__, false, reached.contents) + }) + + test("braced continue expression in value position compiles", () => { + let values = ref(list{}) + let acceptUnit = (_: unit) => () + + for i in 0 to 3 { + if i == 1 { + let x = {continue} + acceptUnit(x) + } + + values := list{i, ...values.contents} + } + + eq(__LOC__, [0, 2, 3], values.contents->Belt.List.reverse->Belt.List.toArray) + }) + + test("switch inside for targets the loop", () => { + let values = ref(list{}) + + for i in 0 to 5 { + 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 targets the loop via JS switch", () => { + let values = ref(list{}) + + for i in 0 to 5 { + 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) + }) +})