diff --git a/src/filters/exceptions.ml b/src/filters/exceptions.ml index fa4b0182928..d092e890d7d 100644 --- a/src/filters/exceptions.ml +++ b/src/filters/exceptions.ml @@ -480,9 +480,11 @@ let catch_native ctx catches t p = ) (* Haxe-specific wildcard catches should go to if-fest because they need additional handling *) | (v,_) :: _ when is_haxe_wildcard_catch ctx v.v_type -> - (match handle_as_value_exception with - | [] -> + (match handle_as_value_exception, value_exception_catch with + | [], None -> catches_to_ifs ctx catches t p + | [], Some catch -> + catches_to_ifs ctx [catch] t p | _ -> catches_as_value_exception ctx handle_as_value_exception None t p :: catches_to_ifs ctx catches t p diff --git a/src/filters/renameVars.ml b/src/filters/renameVars.ml index b23c61139f9..b7c8b882c3e 100644 --- a/src/filters/renameVars.ml +++ b/src/filters/renameVars.ml @@ -223,6 +223,9 @@ let declare_var rc scope v = let will_be_reserved rc v = rc.rc_no_shadowing || (has_var_flag v VCaptured && rc.rc_hoisting) +let unbound_variable v = + raise (Failure (Printf.sprintf "Unbound variable: %s<%i>" v.v_name v.v_id)) + (** Invoked for each `TLocal v` texr_expr *) @@ -234,7 +237,7 @@ let rec determine_overlaps rc scope v = Overlaps.add v scope.foreign_vars; (match scope.parent with | Some parent -> determine_overlaps rc parent v - | None -> raise (Failure "Failed to locate variable declaration") + | None -> unbound_variable v ) | (d, _) :: _ when d == v -> () @@ -261,7 +264,7 @@ let use_var rc scope v = | Some parent -> loop parent | None -> - raise (Failure "Failed to locate variable declaration") + unbound_variable v end in loop scope diff --git a/src/generators/gencpp.ml b/src/generators/gencpp.ml index 48cbb065e39..e417bbbb718 100644 --- a/src/generators/gencpp.ml +++ b/src/generators/gencpp.ml @@ -393,7 +393,12 @@ let keyword_remap name = | "HX_" | "HXLINE" | "HXDLIN" | "NO" | "YES" | "abstract" | "decltype" | "finally" | "nullptr" | "static_assert" - | "struct" -> "_hx_" ^ name + | "struct" | "_Atomic" + | "constexpr" | "consteval" | "constinit" + | "co_await" | "co_return" | "co_yield" + | "alignas" | "alignof" + | "_Alignas" | "_Alignof" + | "requires" -> "_hx_" ^ name | x -> x ;; diff --git a/src/generators/genhl.ml b/src/generators/genhl.ml index a6de801d3de..7db70613ea8 100644 --- a/src/generators/genhl.ml +++ b/src/generators/genhl.ml @@ -3329,7 +3329,10 @@ let generate_static ctx c f = let gen_content() = op ctx (OThrow (make_string ctx ("Requires compiling with -D hl-ver=" ^ ver ^ ".0 or higher") null_pos)); in - ignore(make_fun ctx ~gen_content (s_type_path c.cl_path,f.cf_name) (alloc_fid ctx c f) (match f.cf_expr with Some { eexpr = TFunction f } -> f | _ -> abort "Missing function body" f.cf_pos) None None) + (match f.cf_expr with + | Some { eexpr = TFunction fn } -> ignore(make_fun ctx ~gen_content (s_type_path c.cl_path,f.cf_name) (alloc_fid ctx c f) fn None None) + | _ -> if not (Meta.has Meta.NoExpr f.cf_meta) then abort "Missing function body" f.cf_pos) + else add_native "std" f.cf_name | (Meta.HlNative,[] ,_ ) :: _ -> @@ -3337,7 +3340,9 @@ let generate_static ctx c f = | (Meta.HlNative,_ ,p) :: _ -> abort "Invalid @:hlNative decl" p | [] -> - ignore(make_fun ctx (s_type_path c.cl_path,f.cf_name) (alloc_fid ctx c f) (match f.cf_expr with Some { eexpr = TFunction f } -> f | _ -> abort "Missing function body" f.cf_pos) None None) + (match f.cf_expr with + | Some { eexpr = TFunction fn } -> ignore(make_fun ctx (s_type_path c.cl_path,f.cf_name) (alloc_fid ctx c f) fn None None) + | _ -> if not (Meta.has Meta.NoExpr f.cf_meta) then abort "Missing function body" f.cf_pos) | _ :: l -> loop l in diff --git a/src/macro/macroApi.ml b/src/macro/macroApi.ml index 1a2d668ab99..e825da0938e 100644 --- a/src/macro/macroApi.ml +++ b/src/macro/macroApi.ml @@ -1664,10 +1664,19 @@ let decode_type_def v = EClass (mk flags fields) | 3, [t] -> ETypedef (mk (if isExtern then [EExtern] else []) (decode_ctype t)) - | 4, [tthis;tfrom;tto] -> - let flags = match opt decode_array tfrom with None -> [] | Some ta -> List.map (fun t -> AbFrom (decode_ctype t)) ta in + | 4, [tthis;tflags;tfrom;tto] -> + let flags = match opt decode_array tflags with + | None -> [] + | Some ta -> List.map (fun f -> match decode_enum f with + | 0, [] -> AbEnum + | 1, [ct] -> AbFrom (decode_ctype ct) + | 2, [ct] -> AbTo (decode_ctype ct) + | _ -> raise Invalid_expr + ) ta in + let flags = match opt decode_array tfrom with None -> flags | Some ta -> List.map (fun t -> AbFrom (decode_ctype t)) ta @ flags in let flags = match opt decode_array tto with None -> flags | Some ta -> (List.map (fun t -> AbTo (decode_ctype t)) ta) @ flags in let flags = match opt decode_ctype tthis with None -> flags | Some t -> (AbOver t) :: flags in + let flags = if isExtern then AbExtern :: flags else flags in EAbstract(mk flags fields) | 5, [fk;al] -> let fk = decode_class_field_kind fk in diff --git a/src/typing/matcher/exprToPattern.ml b/src/typing/matcher/exprToPattern.ml index c8e10d64752..491bd6d9126 100644 --- a/src/typing/matcher/exprToPattern.ml +++ b/src/typing/matcher/exprToPattern.ml @@ -412,6 +412,9 @@ let rec make pctx toplevel t e = restore(); let pat = make pctx toplevel e1.etype e2 in PatExtractor {ex_var = v; ex_expr = e1; ex_pattern = pat} + | EBinop((OpEq | OpNotEq | OpLt | OpLte | OpGt | OpGte | OpBoolAnd | OpBoolOr),_,_) -> + let e_rhs = (EConst (Ident "true"),null_pos) in + loop (EBinop(OpArrow,e,e_rhs),(pos e)) (* Special case for completion on a pattern local: We don't want to add the local to the context while displaying (#7319) *) | EDisplay((EConst (Ident _),_ as e),dk) when pctx.ctx.com.display.dms_kind = DMDefault -> diff --git a/src/typing/nullSafety.ml b/src/typing/nullSafety.ml index d411fcb5a95..b0683bce6d7 100644 --- a/src/typing/nullSafety.ml +++ b/src/typing/nullSafety.ml @@ -1041,6 +1041,8 @@ class expr_checker mode immediate_execution report = | TMeta (_, e) -> self#is_nullable_expr e | TThrow _ -> false | TReturn _ -> false + | TContinue -> false + | TBreak -> false | TBinop ((OpAssign | OpAssignOp _), _, right) -> self#is_nullable_expr right | TBlock exprs -> local_safety#block_declared; diff --git a/src/typing/operators.ml b/src/typing/operators.ml index e399bd42c2a..3c9d92f035d 100644 --- a/src/typing/operators.ml +++ b/src/typing/operators.ml @@ -704,18 +704,15 @@ let type_assign_op ctx op e1 e2 with_type p = let cf_get,tf_get,r_get,ekey = AbstractCast.find_array_read_access ctx a tl ekey p in (* bind complex keys to a variable so they do not make it into the output twice *) let save = save_locals ctx in - let maybe_bind_to_temp e = match Optimizer.make_constant_expression ctx e with - | Some e -> e,None - | None -> - let v = gen_local ctx e.etype p in - let e' = mk (TLocal v) e.etype p in - e', Some (mk (TVar (v,Some e)) ctx.t.tvoid p) + let vr = new value_reference ctx in + let maybe_bind_to_temp name e = match Optimizer.make_constant_expression ctx e with + | Some e -> e + | None -> vr#as_var name e in - let ekey,ekey' = maybe_bind_to_temp ekey in - let ebase,ebase' = maybe_bind_to_temp ebase in + let ebase = maybe_bind_to_temp "base" ebase in + let ekey = maybe_bind_to_temp "key" ekey in let eget = mk_array_get_call ctx (cf_get,tf_get,r_get,ekey) c ebase p in let eget = type_binop2 ctx op eget e2 true WithType.value p in - let vr = new value_reference ctx in let eget = BinopResult.to_texpr vr eget (fun e -> e) in unify ctx eget.etype r_get p; let cf_set,tf_set,r_set,ekey,eget = AbstractCast.find_array_write_access ctx a tl ekey eget p in @@ -727,8 +724,6 @@ let type_assign_op ctx op e1 e2 with_type p = | Some _,Some _ -> let ef_set = mk (TField(et,(FStatic(c,cf_set)))) tf_set p in let el = [make_call ctx ef_set [ebase;ekey;eget] r_set p] in - let el = match ebase' with None -> el | Some ebase -> ebase :: el in - let el = match ekey' with None -> el | Some ekey -> ekey :: el in begin match el with | [e] -> e | el -> mk (TBlock el) r_set p diff --git a/std/cpp/_std/haxe/Exception.hx b/std/cpp/_std/haxe/Exception.hx index 4c244a7e34d..92fbaf553bd 100644 --- a/std/cpp/_std/haxe/Exception.hx +++ b/std/cpp/_std/haxe/Exception.hx @@ -19,7 +19,10 @@ class Exception { if(Std.isOfType(value, Exception)) { return value; } else { - return new ValueException(value, null, value); + var e = new ValueException(value, null, value); + // Undo automatic __shiftStack() + e.__unshiftStack(); + return e; } } @@ -63,6 +66,12 @@ class Exception { __skipStack++; } + @:noCompletion + @:ifFeature("haxe.Exception.get_stack") + inline function __unshiftStack():Void { + __skipStack--; + } + function get_message():String { return __exceptionMessage; } diff --git a/std/eval/_std/haxe/Exception.hx b/std/eval/_std/haxe/Exception.hx index 814370e7ac8..48467a04484 100644 --- a/std/eval/_std/haxe/Exception.hx +++ b/std/eval/_std/haxe/Exception.hx @@ -18,7 +18,10 @@ class Exception { if(Std.isOfType(value, Exception)) { return value; } else { - return new ValueException(value, null, value); + var e = new ValueException(value, null, value); + // Undo automatic __shiftStack() + e.__unshiftStack(); + return e; } } @@ -63,6 +66,12 @@ class Exception { __skipStack++; } + @:noCompletion + @:ifFeature("haxe.Exception.get_stack") + inline function __unshiftStack():Void { + __skipStack--; + } + function get_message():String { return __exceptionMessage; } diff --git a/std/haxe/macro/Expr.hx b/std/haxe/macro/Expr.hx index 778e0473772..30d7f00e3f5 100644 --- a/std/haxe/macro/Expr.hx +++ b/std/haxe/macro/Expr.hx @@ -1004,7 +1004,7 @@ enum TypeDefKind { /** Represents an abstract kind. **/ - TDAbstract(tthis:Null, ?from:Array, ?to:Array); + TDAbstract(tthis:Null, ?flags:Array, ?from:Array, ?to:Array); /** Represents a module-level field. @@ -1013,6 +1013,28 @@ enum TypeDefKind { } +/** + Represents an abstract flag. +**/ +enum AbstractFlag { + /** + Indicates that this abstract is an `enum abstract` + **/ + AbEnum; + + /** + Indicates that this abstract can be assigned from `ct`. + This flag can be added several times to add multiple "from" types. + **/ + AbFrom(ct:ComplexType); + + /** + Indicates that this abstract can be assigned to `ct`. + This flag can be added several times to add multiple "to" types. + **/ + AbTo(ct:ComplexType); +} + /** This error can be used to handle or produce compilation errors in macros. **/ diff --git a/std/haxe/macro/MacroStringTools.hx b/std/haxe/macro/MacroStringTools.hx index 85e12b9b9a9..bfd422e4eb0 100644 --- a/std/haxe/macro/MacroStringTools.hx +++ b/std/haxe/macro/MacroStringTools.hx @@ -94,6 +94,10 @@ class MacroStringTools { static public function toComplex(path:String):ComplexType { var pack = path.split("."); - return TPath({pack: pack, name: pack.pop(), params: []}); + if(pack.length >= 2 && ~/^[A-Z]/.match(pack[pack.length - 2])) { + return TPath({pack: pack, sub: pack.pop(), name: pack.pop(), params: []}); + } else { + return TPath({pack: pack, name: pack.pop(), params: []}); + } } } diff --git a/std/haxe/macro/Printer.hx b/std/haxe/macro/Printer.hx index e931e68cbc5..6766acb3d0f 100644 --- a/std/haxe/macro/Printer.hx +++ b/std/haxe/macro/Printer.hx @@ -385,13 +385,26 @@ class Printer { case _: printComplexType(ct); }) + ";"; - case TDAbstract(tthis, from, to): - "abstract " + case TDAbstract(tthis, tflags, from, to): + var from = from == null ? [] : from.copy(); + var to = to == null ? [] : to.copy(); + var isEnum = false; + + for (flag in tflags) { + switch (flag) { + case AbEnum: isEnum = true; + case AbFrom(ct): from.push(ct); + case AbTo(ct): to.push(ct); + } + } + + (isEnum ? "enum " : "") + + "abstract " + t.name + ((t.params != null && t.params.length > 0) ? "<" + t.params.map(printTypeParamDecl).join(", ") + ">" : "") + (tthis == null ? "" : "(" + printComplexType(tthis) + ")") - + (from == null ? "" : [for (f in from) " from " + printComplexType(f)].join("")) - + (to == null ? "" : [for (t in to) " to " + printComplexType(t)].join("")) + + [for (f in from) " from " + printComplexType(f)].join("") + + [for (f in to) " to " + printComplexType(f)].join("") + " {\n" + [ for (f in t.fields) { diff --git a/std/hl/_std/haxe/Exception.hx b/std/hl/_std/haxe/Exception.hx index 8f3ae5fc734..68734c49586 100644 --- a/std/hl/_std/haxe/Exception.hx +++ b/std/hl/_std/haxe/Exception.hx @@ -18,7 +18,10 @@ class Exception { if(Std.isOfType(value, Exception)) { return value; } else { - return new ValueException(value, null, value); + var e = new ValueException(value, null, value); + // Undo automatic __shiftStack() + e.__unshiftStack(); + return e; } } @@ -62,6 +65,12 @@ class Exception { __skipStack++; } + @:noCompletion + @:ifFeature("haxe.Exception.get_stack") + inline function __unshiftStack():Void { + __skipStack--; + } + function get_message():String { return __exceptionMessage; } diff --git a/std/hl/_std/haxe/NativeStackTrace.hx b/std/hl/_std/haxe/NativeStackTrace.hx index e7cf077a142..eada6faf0be 100644 --- a/std/hl/_std/haxe/NativeStackTrace.hx +++ b/std/hl/_std/haxe/NativeStackTrace.hx @@ -29,7 +29,7 @@ class NativeStackTrace { var count = callStackRaw(null); var arr = new NativeArray(count); callStackRaw(arr); - return arr; + return arr.sub(1, arr.length - 1); } @:hlNative("std", "exception_stack_raw") diff --git a/std/neko/_std/haxe/Exception.hx b/std/neko/_std/haxe/Exception.hx index 1302231c141..0579eb835ab 100644 --- a/std/neko/_std/haxe/Exception.hx +++ b/std/neko/_std/haxe/Exception.hx @@ -18,7 +18,10 @@ class Exception { if(Std.isOfType(value, Exception)) { return value; } else { - return new ValueException(value, null, value); + var e = new ValueException(value, null, value); + // Undo automatic __shiftStack() + e.__unshiftStack(); + return e; } } @@ -63,6 +66,12 @@ class Exception { __skipStack++; } + @:noCompletion + @:ifFeature("haxe.Exception.get_stack") + inline function __unshiftStack():Void { + __skipStack--; + } + function get_message():String { return __exceptionMessage; } diff --git a/tests/misc/hl/projects/Issue11196/Issue11196.hx b/tests/misc/hl/projects/Issue11196/Issue11196.hx new file mode 100644 index 00000000000..9126da39de6 --- /dev/null +++ b/tests/misc/hl/projects/Issue11196/Issue11196.hx @@ -0,0 +1,3 @@ +function main() { + var a:hl.I64 = 5; +} \ No newline at end of file diff --git a/tests/misc/hl/projects/Issue11196/compile.hxml b/tests/misc/hl/projects/Issue11196/compile.hxml new file mode 100644 index 00000000000..49a5d4098e6 --- /dev/null +++ b/tests/misc/hl/projects/Issue11196/compile.hxml @@ -0,0 +1,3 @@ +-m Issue11196 +-hl out.hl +-dce no \ No newline at end of file diff --git a/tests/nullsafety/src/cases/TestLoose.hx b/tests/nullsafety/src/cases/TestLoose.hx index 233f9c778d7..704f6521abd 100644 --- a/tests/nullsafety/src/cases/TestLoose.hx +++ b/tests/nullsafety/src/cases/TestLoose.hx @@ -113,4 +113,11 @@ class TestLoose { } shouldFail(if (foo()) {}); } + + static function nullCoal_continue_shouldPass():Void { + for (i in 0...1) { + var i:String = staticVar ?? continue; + var i2:String = staticVar ?? break; + } + } } diff --git a/tests/unit/src/unit/TestExceptions.hx b/tests/unit/src/unit/TestExceptions.hx index 98fd5e13a48..6fb5580af80 100644 --- a/tests/unit/src/unit/TestExceptions.hx +++ b/tests/unit/src/unit/TestExceptions.hx @@ -1,4 +1,4 @@ -package unit; +package unit; import haxe.Exception; import haxe.exceptions.ArgumentException; @@ -235,17 +235,16 @@ class TestExceptions extends Test { public function testExceptionStack() { var data = [ '_without_ throws' => stacksWithoutThrowLevel1(), - '_with_ throws' => stacksWithThrowLevel1() + '_with_ throws' => stacksWithThrowLevel1(), + #if (eval || hl || neko) + 'auto wrapped' => stacksAutoWrappedLevel1() + #end ]; for(label => stacks in data) { Assert.isTrue(stacks.length > 1, '$label: wrong stacks.length'); var expected = null; var lineShift = 0; for(s in stacks) { - // TODO: fix hl vs other targets difference with callstacks - // See https://github.com/HaxeFoundation/haxe/issues/10926 - #if hl @:privateAccess s.asArray().shift(); #end - if(expected == null) { expected = stackItemData(s[0]); } else { @@ -295,6 +294,24 @@ class TestExceptions extends Test { return result; } + #if (eval || hl || neko) + function stacksAutoWrappedLevel1() { + return stacksAutoWrappedLevel2(); + } + + function stacksAutoWrappedLevel2():Array { + @:pure(false) function wrapNativeError(_) return []; + + var result:Array = []; + // It's critical for `testExceptionStack` test to keep the following lines + // order with no additional code in between. + result.push(try throw new Exception('') catch(e:Exception) e.stack); + result.push(try throw "" catch(e:Exception) e.stack); + result.push(try wrapNativeError((null:String).length) catch(e:Exception) e.stack); + return result; + } + #end + function stackItemData(item:StackItem):ItemData { var result:ItemData = {}; switch item { @@ -321,6 +338,16 @@ class TestExceptions extends Test { eq('haxe.Exception', HelperMacros.typeString(try throw new Exception('') catch(e) e)); } + function testCatchValueException() { + try { + throw ""; + } catch(e:ValueException) { + Assert.pass(); + } catch(e) { + Assert.fail(); + } + } + function testNotImplemented() { try { futureFeature(); @@ -374,4 +401,4 @@ class TestExceptions extends Test { } } #end -} \ No newline at end of file +} diff --git a/tests/unit/src/unit/issues/Issue11157.hx b/tests/unit/src/unit/issues/Issue11157.hx new file mode 100644 index 00000000000..3f2a47cff04 --- /dev/null +++ b/tests/unit/src/unit/issues/Issue11157.hx @@ -0,0 +1,25 @@ +package unit.issues; + +private enum E { + CInt(i:Int); + CString(s:String); +} + +class Issue11157 extends Test { + function process(e:E) { + return switch e { + case CInt(_ > 0 && _ < 12): + "in range"; + case CString(_.toLowerCase() == "foo"): + return "foo"; + case _: + return "something else"; + } + } + + function test() { + eq("in range", (process(CInt(11)))); + eq("something else", (process(CInt(12)))); + eq("foo", (process(CString("FOO")))); + } +} diff --git a/tests/unit/src/unit/issues/Issue11248.hx b/tests/unit/src/unit/issues/Issue11248.hx new file mode 100644 index 00000000000..f198354bdee --- /dev/null +++ b/tests/unit/src/unit/issues/Issue11248.hx @@ -0,0 +1,18 @@ +package unit.issues; + +class Issue11248 extends unit.Test { + public static var BIT_A:UInt = 1; + public static var FLAG_1:Int = 0; + + static final _flags:Map = [0 => 1010]; + public static var flags(get, never):Map; + + public static function get_flags() { + return _flags; + } + + function test() { + flags[FLAG_1] ^= BIT_A; + eq(1011, flags[FLAG_1]); + } +}