From e090b424d0c5a7d7320afdd79aa7a3ff2dacc821 Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 6 Sep 2022 03:41:18 +0300 Subject: [PATCH 1/7] Early returns in NullCoal --- src/typing/typer.ml | 23 ++++++++++++++++++++++- tests/unit/src/unit/issues/Issue10744.hx | 17 +++++++++++++---- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 979cea68127..99eb7c8f99a 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1852,7 +1852,28 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = let e1 = vr#as_var "tmp" {e1 with etype = ctx.t.tnull e1.etype} in let e_null = Builder.make_null e1.etype e1.epos in let e_cond = mk (TBinop(OpNotEq,e1,e_null)) ctx.t.tbool e1.epos in - let iftype = WithType.WithType(e2.etype,None) in + let rec is_dead_end e = match e.eexpr with + | TParenthesis e -> is_dead_end e + | TThrow _ -> true + | TReturn _ -> true + | TBreak -> true + | TContinue -> true + | TWhile (_, body, DoWhile) -> is_dead_end body + | TIf (_, if_body, Some else_body) -> is_dead_end if_body && is_dead_end else_body + | TBlock exprs -> List.exists is_dead_end exprs + | TMeta (_, e) -> is_dead_end e + | TCast (e, _) -> is_dead_end e + | _ -> false in + let follow_null_once t = + match t with + | TAbstract({a_path = [],"Null"},[t]) -> t + | _ -> t + in + let iftype = if is_dead_end e2 then + WithType.with_type (follow_null_once e1.etype) + else + WithType.WithType(e2.etype,None) + in let e_if = make_if_then_else ctx e_cond e1 e2 iftype p in vr#to_texpr e_if | EBinop (OpAssignOp OpNullCoal,e1,e2) -> diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 43b330e153e..895c1b1426d 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -5,11 +5,20 @@ import unit.HelperMacros.typeString; class Issue10744 extends Test { function test() { var v:Null = 10; - eq("Null", typeString(v ?? return)); - eq("Null", typeString(v ?? throw true)); + eq("Int", typeString(v ?? return)); + eq("Int", typeString(v ?? throw true)); for (i in 0...1) { - eq("Null", typeString(v ?? break)); - eq("Null", typeString(v ?? continue)); + eq("Int", typeString(v ?? break)); + eq("Int", typeString(v ?? continue)); } + eq("Int", typeString(v ?? { + (throw "nope"); + })); + eq("Null", typeString(v ?? { + if (Std.random(0) == 0) + return; + else + v; + })); } } From 22cfbc27e10c984cd9e7918051db47ee5a76c539 Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 6 Sep 2022 08:23:00 +0300 Subject: [PATCH 2/7] Mental test --- src/typing/typer.ml | 1 + tests/unit/src/unit/issues/Issue10744.hx | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 99eb7c8f99a..7e948ab2d37 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1861,6 +1861,7 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = | TWhile (_, body, DoWhile) -> is_dead_end body | TIf (_, if_body, Some else_body) -> is_dead_end if_body && is_dead_end else_body | TBlock exprs -> List.exists is_dead_end exprs + | TCall (ecall, args) -> List.exists is_dead_end args | TMeta (_, e) -> is_dead_end e | TCast (e, _) -> is_dead_end e | _ -> false in diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 895c1b1426d..3136ac3e9e6 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -20,5 +20,14 @@ class Issue10744 extends Test { else v; })); + eq("Int", typeString(v ?? { + if (Std.random(0) == 0) + return; + else + throw "nope"; + })); + eq("Int", typeString(v ?? { + Std.parseInt(return); + })); } } From a6763469835a66477a50fb22cb9d7c2bdd599db5 Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 13 Sep 2022 15:16:42 +0300 Subject: [PATCH 3/7] Check call expr too --- src/core/tFunctions.ml | 20 +++++++++++++++++++- src/typing/nullSafety.ml | 18 +----------------- src/typing/typer.ml | 16 ++-------------- tests/unit/src/unit/issues/Issue10744.hx | 3 +++ 4 files changed, 25 insertions(+), 32 deletions(-) diff --git a/src/core/tFunctions.ml b/src/core/tFunctions.ml index f9eebecdc56..7953bfcca46 100644 --- a/src/core/tFunctions.ml +++ b/src/core/tFunctions.ml @@ -615,6 +615,24 @@ let rec has_mono t = match t with | TLazy f -> has_mono (lazy_type f) +(* + Checks if execution of provided expression is guaranteed to be terminated with `return`, `throw`, `break` or `continue`. +*) +let rec has_dead_end e = match e.eexpr with + | TParenthesis e -> has_dead_end e + | TThrow _ -> true + | TReturn _ -> true + | TBreak -> true + | TContinue -> true + | TWhile (_, body, DoWhile) -> has_dead_end body + | TIf (_, if_body, Some else_body) -> has_dead_end if_body && has_dead_end else_body + | TBlock exprs -> List.exists has_dead_end exprs + | TCall (ecall, args) -> has_dead_end ecall || List.exists has_dead_end args + (* | TArrayDecl els -> List.exists has_dead_end els *) + | TMeta (_, e) -> has_dead_end e + | TCast (e, _) -> has_dead_end e + | _ -> false + let concat e1 e2 = let e = (match e1.eexpr, e2.eexpr with | TBlock el1, TBlock el2 -> TBlock (el1@el2) @@ -850,4 +868,4 @@ let type_has_meta t m = let var_extra params e = { v_params = params; v_expr = e; -} \ No newline at end of file +} diff --git a/src/typing/nullSafety.ml b/src/typing/nullSafety.ml index e0d150feb38..ea06fe1e758 100644 --- a/src/typing/nullSafety.ml +++ b/src/typing/nullSafety.ml @@ -320,22 +320,6 @@ class unificator = traverse a_args b_args end -(** - Checks if execution of provided expression is guaranteed to be terminated with `return`, `throw`, `break` or `continue`. -*) -let rec is_dead_end e = - match e.eexpr with - | TThrow _ -> true - | TReturn _ -> true - | TBreak -> true - | TContinue -> true - | TWhile (_, body, DoWhile) -> is_dead_end body - | TIf (_, if_body, Some else_body) -> is_dead_end if_body && is_dead_end else_body - | TBlock exprs -> List.exists is_dead_end exprs - | TMeta (_, e) -> is_dead_end e - | TCast (e, _) -> is_dead_end e - | _ -> false - (** Check if `expr` is a `trace` (not a call, but identifier itself) *) @@ -923,7 +907,7 @@ class local_safety (mode:safety_mode) = self#get_current_scope#reset_to initial_safe; (** execute `else_body` with known not-null variables *) let handle_dead_end body safe_vars = - if is_dead_end body then + if TFunctions.has_dead_end body then List.iter self#get_current_scope#add_to_safety safe_vars in (match else_body with diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 7e948ab2d37..8e7fd3e2b2c 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1852,25 +1852,13 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = let e1 = vr#as_var "tmp" {e1 with etype = ctx.t.tnull e1.etype} in let e_null = Builder.make_null e1.etype e1.epos in let e_cond = mk (TBinop(OpNotEq,e1,e_null)) ctx.t.tbool e1.epos in - let rec is_dead_end e = match e.eexpr with - | TParenthesis e -> is_dead_end e - | TThrow _ -> true - | TReturn _ -> true - | TBreak -> true - | TContinue -> true - | TWhile (_, body, DoWhile) -> is_dead_end body - | TIf (_, if_body, Some else_body) -> is_dead_end if_body && is_dead_end else_body - | TBlock exprs -> List.exists is_dead_end exprs - | TCall (ecall, args) -> List.exists is_dead_end args - | TMeta (_, e) -> is_dead_end e - | TCast (e, _) -> is_dead_end e - | _ -> false in + let follow_null_once t = match t with | TAbstract({a_path = [],"Null"},[t]) -> t | _ -> t in - let iftype = if is_dead_end e2 then + let iftype = if TFunctions.has_dead_end e2 then WithType.with_type (follow_null_once e1.etype) else WithType.WithType(e2.etype,None) diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 3136ac3e9e6..bc4d87c2a20 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -29,5 +29,8 @@ class Issue10744 extends Test { eq("Int", typeString(v ?? { Std.parseInt(return); })); + eq("Int", typeString(v ?? { + (return)(); + })); } } From 2dc11558de45c02b11cc1a3dedae2b0a22179b2a Mon Sep 17 00:00:00 2001 From: RblSb Date: Wed, 9 Nov 2022 07:48:38 +0300 Subject: [PATCH 4/7] More exprs --- src/core/tFunctions.ml | 8 +++- tests/unit/src/unit/issues/Issue10744.hx | 55 ++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/src/core/tFunctions.ml b/src/core/tFunctions.ml index 7953bfcca46..5a628b550b2 100644 --- a/src/core/tFunctions.ml +++ b/src/core/tFunctions.ml @@ -624,11 +624,15 @@ let rec has_dead_end e = match e.eexpr with | TReturn _ -> true | TBreak -> true | TContinue -> true - | TWhile (_, body, DoWhile) -> has_dead_end body + | TArray (target, index) -> has_dead_end index + | TBinop (OpBoolAnd, e1, e2) -> has_dead_end e1 + | TBinop (_, e1, e2) -> has_dead_end e1 || has_dead_end e2 + | TVar (_, e) -> (match e with | Some e -> has_dead_end e | None -> false) | TIf (_, if_body, Some else_body) -> has_dead_end if_body && has_dead_end else_body | TBlock exprs -> List.exists has_dead_end exprs | TCall (ecall, args) -> has_dead_end ecall || List.exists has_dead_end args - (* | TArrayDecl els -> List.exists has_dead_end els *) + | TNew (_, _, args) -> List.exists has_dead_end args + | TArrayDecl els -> List.exists has_dead_end els | TMeta (_, e) -> has_dead_end e | TCast (e, _) -> has_dead_end e | _ -> false diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index bc4d87c2a20..5c29291bbd7 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -20,6 +20,11 @@ class Issue10744 extends Test { else v; })); + eq("Null", typeString(v ?? { + function foo() + return; + v; + })); eq("Int", typeString(v ?? { if (Std.random(0) == 0) return; @@ -32,5 +37,55 @@ class Issue10744 extends Test { eq("Int", typeString(v ?? { (return)(); })); + eq("Int", typeString(v ?? { + v + return; + v; + })); + eq("Null", typeString(v ?? { + false && return ; + v; + })); + eq("Int", typeString(v ?? { + final a = return; + v; + })); + eq("Int", typeString(v ?? { + [0, return, 2]; + v; + })); + eq("Int", typeString(v ?? { + switch (null) { + case _: return; + } + v; + })); + eq("Int", typeString(v ?? { + switch (return) { + case _: null; + } + v; + })); + eq("Int", typeString(v ?? { + final arr = []; + arr[return]; + v; + })); + eq("Int", typeString(v ?? { + new EReg("", return); + v; + })); + eq("Null", typeString(v ?? { + do { + break; + return; + } while (true); + v; + })); + eq("Null", typeString(v ?? { + try { + throw null; + } catch (e) {} + v; + })); } } From 2f8211b03e3774c818d985e3573ff35eb698e1ba Mon Sep 17 00:00:00 2001 From: Simon Krajewski Date: Thu, 10 Nov 2022 09:27:42 +0100 Subject: [PATCH 5/7] get has_dead_end under control --- src/core/tFunctions.ml | 22 ------ src/core/texpr.ml | 77 ++++++++++++++++++++ src/optimization/analyzerTexpr.ml | 5 -- src/optimization/analyzerTexprTransformer.ml | 2 +- src/optimization/inline.ml | 2 +- src/optimization/optimizerTexpr.ml | 6 -- src/typing/nullSafety.ml | 2 +- src/typing/typer.ml | 2 +- tests/unit/src/unit/issues/Issue10744.hx | 76 +++++++++++++++++++ 9 files changed, 157 insertions(+), 37 deletions(-) diff --git a/src/core/tFunctions.ml b/src/core/tFunctions.ml index 5a628b550b2..36c01dcef45 100644 --- a/src/core/tFunctions.ml +++ b/src/core/tFunctions.ml @@ -615,28 +615,6 @@ let rec has_mono t = match t with | TLazy f -> has_mono (lazy_type f) -(* - Checks if execution of provided expression is guaranteed to be terminated with `return`, `throw`, `break` or `continue`. -*) -let rec has_dead_end e = match e.eexpr with - | TParenthesis e -> has_dead_end e - | TThrow _ -> true - | TReturn _ -> true - | TBreak -> true - | TContinue -> true - | TArray (target, index) -> has_dead_end index - | TBinop (OpBoolAnd, e1, e2) -> has_dead_end e1 - | TBinop (_, e1, e2) -> has_dead_end e1 || has_dead_end e2 - | TVar (_, e) -> (match e with | Some e -> has_dead_end e | None -> false) - | TIf (_, if_body, Some else_body) -> has_dead_end if_body && has_dead_end else_body - | TBlock exprs -> List.exists has_dead_end exprs - | TCall (ecall, args) -> has_dead_end ecall || List.exists has_dead_end args - | TNew (_, _, args) -> List.exists has_dead_end args - | TArrayDecl els -> List.exists has_dead_end els - | TMeta (_, e) -> has_dead_end e - | TCast (e, _) -> has_dead_end e - | _ -> false - let concat e1 e2 = let e = (match e1.eexpr, e2.eexpr with | TBlock el1, TBlock el2 -> TBlock (el1@el2) diff --git a/src/core/texpr.ml b/src/core/texpr.ml index 2f2d852c9f1..bf79d6641d8 100644 --- a/src/core/texpr.ml +++ b/src/core/texpr.ml @@ -842,3 +842,80 @@ let punion_el default_pos el = default_pos else punion first last + +let is_exhaustive e1 def = + let rec loop e1 = match e1.eexpr with + | TMeta((Meta.Exhaustive,_,_),_) -> true + | TMeta(_, e1) | TParenthesis e1 -> loop e1 + | _ -> false + in + def <> None || loop e1 + +let rec is_true_expr e1 = match e1.eexpr with + | TConst(TBool true) -> true + | TParenthesis e1 -> is_true_expr e1 + | _ -> false + +let rec is_false_expr e1 = match e1.eexpr with + | TConst(TBool false) -> true + | TParenthesis e1 -> is_false_expr e1 + | _ -> false + +module DeadEnd = struct + exception BreakOrContinue + + (* + Checks if execution of provided expression is guaranteed to be terminated with `return`, `throw`, `break` or `continue`. + *) + let has_dead_end e = + let rec loop e = + let in_loop e = + try + loop e + with BreakOrContinue -> + false + in + match e.eexpr with + | TContinue | TBreak -> + raise BreakOrContinue + | TThrow e1 -> + loop e1 || true + | TReturn (Some e1) -> + loop e1 || true (* recurse first, could be `return continue` *) + | TReturn None -> + true + | TFunction _ -> + false (* This isn't executed, so don't recurse *) + | TIf (cond, if_body, Some else_body) -> + loop cond || loop if_body && loop else_body + | TSwitch(e1, cases, def) -> + let check_exhaustive () = + (is_exhaustive e1 def) && List.for_all (fun (el,e) -> + List.exists loop el || + loop e + ) cases && + Option.map_default (loop ) true def (* true because we know it's exhaustive *) + in + loop e1 || check_exhaustive () + | TFor(_, e1, _) -> + loop e1 (* I think? *) + | TBinop(OpBoolAnd, e1, e2) -> + loop e1 || is_true_expr e1 && loop e2 + | TBinop(OpBoolOr, e1, e2) -> + loop e1 || is_false_expr e1 && loop e2 + | TWhile(cond, body, flag) -> + loop cond || ((flag = DoWhile || is_true_expr cond) && in_loop body) + | TTry(e1,[]) -> + loop e1 + | TTry(_,catches) -> + (* The try expression is irrelevant because we have to conservatively assume that + anything could throw control flow into the catch expressions. *) + List.for_all (fun (_,e) -> loop e) catches + | _ -> + check_expr loop e + in + try + loop e + with BreakOrContinue -> + true +end \ No newline at end of file diff --git a/src/optimization/analyzerTexpr.ml b/src/optimization/analyzerTexpr.ml index 6cd112ca6f8..f44cef2cfbc 100644 --- a/src/optimization/analyzerTexpr.ml +++ b/src/optimization/analyzerTexpr.ml @@ -25,11 +25,6 @@ open Globals let s_expr_pretty e = s_expr_pretty false "" false (s_type (print_context())) e -let rec is_true_expr e1 = match e1.eexpr with - | TConst(TBool true) -> true - | TParenthesis e1 -> is_true_expr e1 - | _ -> false - let is_stack_allocated c = Meta.has Meta.StructAccess c.cl_meta let map_values ?(allow_control_flow=true) f e = diff --git a/src/optimization/analyzerTexprTransformer.ml b/src/optimization/analyzerTexprTransformer.ml index 8941f470931..98d5f8b0c9d 100644 --- a/src/optimization/analyzerTexprTransformer.ml +++ b/src/optimization/analyzerTexprTransformer.ml @@ -432,7 +432,7 @@ let rec func ctx bb tf t p = end end | TSwitch(e1,cases,edef) -> - let is_exhaustive = edef <> None || is_exhaustive e1 in + let is_exhaustive = is_exhaustive e1 edef in let bb,e1 = bind_to_temp bb false e1 in bb.bb_terminator <- TermCondBranch e1; let reachable = ref [] in diff --git a/src/optimization/inline.ml b/src/optimization/inline.ml index ee580004f40..3880f2aadab 100644 --- a/src/optimization/inline.ml +++ b/src/optimization/inline.ml @@ -723,7 +723,7 @@ let rec type_inline ctx cf f ethis params tret config p ?(self_calling_closure=f in_loop := old; { e with eexpr = TWhile (cond,eloop,flag) } | TSwitch (e1,cases,def) when term -> - let term = term && (def <> None || is_exhaustive e1) in + let term = term && (is_exhaustive e1 def) in let cases = List.map (fun (el,e) -> let el = List.map (map false false) el in el, map term false e diff --git a/src/optimization/optimizerTexpr.ml b/src/optimization/optimizerTexpr.ml index d30e808a79e..28e457c95bf 100644 --- a/src/optimization/optimizerTexpr.ml +++ b/src/optimization/optimizerTexpr.ml @@ -21,12 +21,6 @@ let has_side_effect e = with Exit -> true -let rec is_exhaustive e1 = match e1.eexpr with - | TMeta((Meta.Exhaustive,_,_),_) -> true - | TMeta(_, e1) | TParenthesis e1 -> is_exhaustive e1 - | _ -> false - - let is_read_only_field_access e fa = match fa with | FEnum _ -> true diff --git a/src/typing/nullSafety.ml b/src/typing/nullSafety.ml index ea06fe1e758..be3da1f5913 100644 --- a/src/typing/nullSafety.ml +++ b/src/typing/nullSafety.ml @@ -907,7 +907,7 @@ class local_safety (mode:safety_mode) = self#get_current_scope#reset_to initial_safe; (** execute `else_body` with known not-null variables *) let handle_dead_end body safe_vars = - if TFunctions.has_dead_end body then + if DeadEnd.has_dead_end body then List.iter self#get_current_scope#add_to_safety safe_vars in (match else_body with diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 8e7fd3e2b2c..6abfbc01062 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1858,7 +1858,7 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = | TAbstract({a_path = [],"Null"},[t]) -> t | _ -> t in - let iftype = if TFunctions.has_dead_end e2 then + let iftype = if DeadEnd.has_dead_end e2 then WithType.with_type (follow_null_once e1.etype) else WithType.WithType(e2.etype,None) diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 5c29291bbd7..2a4c7517cf0 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -45,6 +45,14 @@ class Issue10744 extends Test { false && return ; v; })); + eq("Int", typeString(v ?? { + true && return ; + v; + })); + eq("Int", typeString(v ?? { + false || return ; + v; + })); eq("Int", typeString(v ?? { final a = return; v; @@ -81,11 +89,79 @@ class Issue10744 extends Test { } while (true); v; })); + eq("Int", typeString(v ?? { + do { + break; // die + return; + } while (true); // resurrect + return; // die again + v; + })); eq("Null", typeString(v ?? { try { throw null; } catch (e) {} v; })); + eq("Int", typeString(v ?? { + try { + throw null; + } catch (e) { + return; + } + v; + })); + eq("Null", typeString(v ?? { + try { + throw null; + } catch (e:String) { + // fall through + } catch (e) { + return; + } + v; + })); + eq("Null", typeString(v ?? { + try { + return; + } catch (e:String) { + // fall through + } catch (e) { + return; + } + v; + })); + eq("Int", typeString(v ?? { + try { + return; + } catch (e:String) { + return; + } catch (e) { + return; + } + v; + })); + eq("Null", typeString(v ?? { + try { + // something here COULD throw and end up in the fall through case + } catch (e:String) { + // fall through + } catch (e) { + return; + } + v; + })); + eq("Int", typeString(v ?? { + try { + return; + } + v; + })); + eq("Null", typeString(v ?? { + try { + // fall through + } + v; + })); } } From 433318efeb498859d4c95b5249095e39718cc42d Mon Sep 17 00:00:00 2001 From: RblSb Date: Sun, 13 Nov 2022 03:47:15 +0300 Subject: [PATCH 6/7] Ignore tfor --- src/core/texpr.ml | 4 ++-- tests/unit/src/unit/issues/Issue10744.hx | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/core/texpr.ml b/src/core/texpr.ml index bf79d6641d8..cfe48238964 100644 --- a/src/core/texpr.ml +++ b/src/core/texpr.ml @@ -898,7 +898,7 @@ module DeadEnd = struct in loop e1 || check_exhaustive () | TFor(_, e1, _) -> - loop e1 (* I think? *) + false | TBinop(OpBoolAnd, e1, e2) -> loop e1 || is_true_expr e1 && loop e2 | TBinop(OpBoolOr, e1, e2) -> @@ -918,4 +918,4 @@ module DeadEnd = struct loop e with BreakOrContinue -> true -end \ No newline at end of file +end diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 2a4c7517cf0..1fa83910f59 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -61,6 +61,12 @@ class Issue10744 extends Test { [0, return, 2]; v; })); + eq("Null", typeString(v ?? { + for (i in 0...Std.random(1)) { + return; + } + v; + })); eq("Int", typeString(v ?? { switch (null) { case _: return; From 22de9dbce28616e64192a4f674223cd4bc537c04 Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 15 Nov 2022 02:31:18 +0300 Subject: [PATCH 7/7] tfor returns --- src/core/texpr.ml | 2 +- tests/unit/src/unit/issues/Issue10744.hx | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/texpr.ml b/src/core/texpr.ml index cfe48238964..9f91437d639 100644 --- a/src/core/texpr.ml +++ b/src/core/texpr.ml @@ -898,7 +898,7 @@ module DeadEnd = struct in loop e1 || check_exhaustive () | TFor(_, e1, _) -> - false + loop e1 | TBinop(OpBoolAnd, e1, e2) -> loop e1 || is_true_expr e1 && loop e2 | TBinop(OpBoolOr, e1, e2) -> diff --git a/tests/unit/src/unit/issues/Issue10744.hx b/tests/unit/src/unit/issues/Issue10744.hx index 1fa83910f59..37abbea96db 100644 --- a/tests/unit/src/unit/issues/Issue10744.hx +++ b/tests/unit/src/unit/issues/Issue10744.hx @@ -67,6 +67,12 @@ class Issue10744 extends Test { } v; })); + eq("Int", typeString(v ?? { + for (i in [0, return, 2]) { + break; + } + v; + })); eq("Int", typeString(v ?? { switch (null) { case _: return;