diff --git a/extra/CHANGES.txt b/extra/CHANGES.txt index ce8d634a7f1..3eb4d6f20d0 100644 --- a/extra/CHANGES.txt +++ b/extra/CHANGES.txt @@ -1,3 +1,30 @@ +2023-09-01 4.3.2 + + General improvements: + + all : do not raise error on no-op reification outside macro + + Bugfixes: + + all : don't infer Null if it already is Null (#11286) + all : fix ?? inference and precedence (#11252) + all : bring back forced inline (#11217) + all : allow non constant "inline" var init with -D no-inline (#11192) + all : improve @:enum abstract deprecation warning handling (#11302) + all : fix some stack overflow with pretty errors + display : fix go to definition with final (#11173) + display : fix completion requests with @:forwardStatics (#11294) + eval : fix MainLoop.add not repeating (#11202) + hl/eval/neko : fix exception stack when wrapping native exceptions (#11249) + macro : map `this` when restoring typed expressions (#11212) + macro : safe navigation fix for ExprTools.map (#11204) + macro : safe navigation fix for haxe.macro.Printer (#11206) + macro : macro generated EVars position fixes (#11163) + macro : fix abstract casts for local statics (#11301) + macro : add flags to TDAbstract to be able to construct enum abstracts (#11230) + nullsafety : make break/continue expressions not-nullable (#11269) + nullsafety : handle return in assignment (#11114) + 2023-04-28 4.3.1 Breaking changes: diff --git a/extra/github-actions/build-mac.yml b/extra/github-actions/build-mac.yml index 003d5232eeb..7e4184c2a2d 100644 --- a/extra/github-actions/build-mac.yml +++ b/extra/github-actions/build-mac.yml @@ -1,7 +1,7 @@ - name: Install dependencies env: # For compatibility with macOS 10.13 - ZLIB_VERSION: 1.2.13 + ZLIB_VERSION: 1.3 MBEDTLS_VERSION: 2.25.0 PCRE2_VERSION: 10.42 run: | diff --git a/extra/github-actions/install-nsis.yml b/extra/github-actions/install-nsis.yml index 13d67d0241e..752016d080e 100644 --- a/extra/github-actions/install-nsis.yml +++ b/extra/github-actions/install-nsis.yml @@ -1,5 +1,5 @@ - name: choco install nsis - uses: nick-invision/retry@v1 + uses: nick-invision/retry@v2 with: timeout_minutes: 10 max_attempts: 10 diff --git a/extra/github-actions/workflows/main.yml b/extra/github-actions/workflows/main.yml index ccd546ad57c..ad44b06144c 100644 --- a/extra/github-actions/workflows/main.yml +++ b/extra/github-actions/workflows/main.yml @@ -119,7 +119,7 @@ jobs: FORCE_COLOR: 1 steps: - name: Login to GitHub Container Registry - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: registry: ghcr.io username: ${{ github.actor }} @@ -130,7 +130,7 @@ jobs: - name: Set up QEMU id: qemu - uses: docker/setup-qemu-action@v1 + uses: docker/setup-qemu-action@v2 with: image: tonistiigi/binfmt:latest platforms: all diff --git a/haxe.opam b/haxe.opam index bf19e490d45..60e1671fe72 100644 --- a/haxe.opam +++ b/haxe.opam @@ -1,6 +1,6 @@ opam-version: "2.0" name: "haxe" -version: "4.3.1" +version: "4.3.2" synopsis: "Multi-target universal programming language" description: """ Haxe is an open source toolkit based on a modern, diff --git a/src-json/warning.json b/src-json/warning.json index 07173b0712d..38a6474deee 100644 --- a/src-json/warning.json +++ b/src-json/warning.json @@ -63,6 +63,11 @@ "doc": "This define is deprecated and should no longer be used", "parent": "WDeprecated" }, + { + "name": "WDeprecatedEnumAbstract", + "doc": "`@:enum abstract` is deprecated, `enum abstract` should be used instead", + "parent": "WDeprecated" + }, { "name": "WVarInit", "doc": "A local variable might be used before being assigned a value", diff --git a/src/compiler/server.ml b/src/compiler/server.ml index f3d5fb3f6ee..ec815960c33 100644 --- a/src/compiler/server.ml +++ b/src/compiler/server.ml @@ -121,39 +121,36 @@ module Communication = struct end in - try - let read_char line = match input_char_or_done ch line with - | '\n' -> inc 1 line - | '\r' -> - ignore(input_char_or_done ch line); - inc 2 line - | c -> begin - let line = ref (line ^ (String.make 1 c)) in - let rec skip n = - if n > 0 then begin - let c = input_char_or_done ch !line in - line := !line ^ (String.make 1 c); - skip (n - 1) - end - in + let read_char line = match input_char_or_done ch line with + | '\n' -> inc 1 line + | '\r' -> + ignore(input_char_or_done ch line); + inc 2 line + | c -> begin + let line = ref (line ^ (String.make 1 c)) in + let rec skip n = + if n > 0 then begin + let c = input_char_or_done ch !line in + line := !line ^ (String.make 1 c); + skip (n - 1) + end + in - let code = int_of_char c in - if code < 0xC0 then () - else if code < 0xE0 then skip 1 - else if code < 0xF0 then skip 2 - else skip 3; + let code = int_of_char c in + if code < 0xC0 then () + else if code < 0xE0 then skip 1 + else if code < 0xF0 then skip 2 + else skip 3; - (1, !line) - end - in + (1, !line) + end + in - let (delta, line) = read_char line in - loop (p + delta) line - with End_of_file -> - close_in ch; + let (delta, line) = read_char line in + loop (p + delta) line in - loop 0 ""; + try loop 0 ""; with End_of_file -> close_in ch; List.rev !lines let resolve_file ctx f = diff --git a/src/context/display/displayFields.ml b/src/context/display/displayFields.ml index 989517734f3..7fc5eeeea1f 100644 --- a/src/context/display/displayFields.ml +++ b/src/context/display/displayFields.ml @@ -228,21 +228,25 @@ let collect ctx e_ast e dk with_type p = | TAnon an -> (* @:forwardStatics *) let items = match !(an.a_status) with - | Statics { cl_kind = KAbstractImpl { a_meta = meta; a_this = TInst (c,_) }} when Meta.has Meta.ForwardStatics meta -> - let items = List.fold_left (fun acc cf -> - if should_access c cf true && is_new_item acc cf.cf_name then begin - let origin = Self(TClassDecl c) in - let item = make_class_field origin cf in - PMap.add cf.cf_name item acc - end else - acc - ) items c.cl_ordered_statics in - PMap.foldi (fun name item acc -> - if is_new_item acc name then - PMap.add name item acc - else - acc - ) PMap.empty items + | Statics { cl_kind = KAbstractImpl { a_meta = meta; a_this}} when Meta.has Meta.ForwardStatics meta -> + begin match follow a_this with + | TInst (c,_) -> + let items = List.fold_left (fun acc cf -> + if should_access c cf true && is_new_item acc cf.cf_name then begin + let origin = Self(TClassDecl c) in + let item = make_class_field origin cf in + PMap.add cf.cf_name item acc + end else + acc + ) items c.cl_ordered_statics in + PMap.foldi (fun name item acc -> + if is_new_item acc name then + PMap.add name item acc + else + acc + ) PMap.empty items + | _ -> items + end | _ -> items in (* Anon own fields *) diff --git a/src/context/typecore.ml b/src/context/typecore.ml index be053100f73..22ccc8a0052 100644 --- a/src/context/typecore.ml +++ b/src/context/typecore.ml @@ -455,6 +455,16 @@ let is_removable_field com f = | _ -> false) ) +let is_forced_inline c cf = + match c with + | Some { cl_kind = KAbstractImpl _ } -> true + | Some c when has_class_flag c CExtern -> true + | _ when has_class_field_flag cf CfExtern -> true + | _ -> false + +let needs_inline ctx c cf = + cf.cf_kind = Method MethInline && ctx.allow_inline && (ctx.g.doinline || is_forced_inline c cf) + (** checks if we can access to a given class field using current context *) let rec can_access ctx c cf stat = if (has_class_field_flag cf CfPublic) then @@ -703,10 +713,6 @@ let get_next_stored_typed_expr_id = let uid = ref 0 in (fun() -> incr uid; !uid) -let get_stored_typed_expr com id = - let e = com.stored_typed_exprs#find id in - Texpr.duplicate_tvars e - let store_typed_expr com te p = let id = get_next_stored_typed_expr_id() in com.stored_typed_exprs#add id te; diff --git a/src/core/globals.ml b/src/core/globals.ml index ae4652b7838..1b81264206f 100644 --- a/src/core/globals.ml +++ b/src/core/globals.ml @@ -27,7 +27,7 @@ type platform = | Hl | Eval -let version = 4301 +let version = 4302 let version_major = version / 1000 let version_minor = (version mod 1000) / 100 let version_revision = (version mod 100) diff --git a/src/core/texpr.ml b/src/core/texpr.ml index 37a72d128c8..71826899260 100644 --- a/src/core/texpr.ml +++ b/src/core/texpr.ml @@ -299,7 +299,9 @@ let rec equal e1 e2 = match e1.eexpr,e2.eexpr with | TEnumParameter(e1,ef1,i1),TEnumParameter(e2,ef2,i2) -> equal e1 e2 && ef1 == ef2 && i1 = i2 | _ -> false -let duplicate_tvars e = +let e_identity e = e + +let duplicate_tvars f_this e = let vars = Hashtbl.create 0 in let copy_var v = let v2 = alloc_var v.v_kind v.v_name v.v_type v.v_pos in @@ -336,6 +338,8 @@ let duplicate_tvars e = {e with eexpr = TLocal v2} with _ -> e) + | TConst TThis -> + f_this e | _ -> map_expr build_expr e in diff --git a/src/filters/exceptions.ml b/src/filters/exceptions.ml index 349b5178498..c3318b4c347 100644 --- a/src/filters/exceptions.ml +++ b/src/filters/exceptions.ml @@ -482,9 +482,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/macro/macroApi.ml b/src/macro/macroApi.ml index d106b1551ed..c44b80491b2 100644 --- a/src/macro/macroApi.ml +++ b/src/macro/macroApi.ml @@ -544,7 +544,7 @@ and encode_expr e = 10, [encode_array (List.map (fun v -> encode_obj [ "name",encode_placed_name v.ev_name; - "name_pos",encode_pos (pos v.ev_name); + "namePos",encode_pos (pos v.ev_name); "isFinal",vbool v.ev_final; "isStatic",vbool v.ev_static; "type",null encode_ctype v.ev_type; @@ -907,7 +907,9 @@ and decode_expr v = let static = if vstatic == vnull then false else decode_bool vstatic in let vmeta = field v "meta" in let meta = if vmeta == vnull then [] else decode_meta_content vmeta in - let name = (decode_placed_name (field v "name_pos") (field v "name")) + let name_pos = maybe_decode_pos (field v "namePos") in + let name_pos = if name_pos = null_pos then p else name_pos in + let name = ((decode_string (field v "name")), name_pos) and t = opt decode_ctype (field v "type") and eo = opt loop (field v "expr") in mk_evar ~final ~static ?t ?eo ~meta name @@ -1621,10 +1623,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/optimization/analyzerTexpr.ml b/src/optimization/analyzerTexpr.ml index abdb85f18f5..153797217e9 100644 --- a/src/optimization/analyzerTexpr.ml +++ b/src/optimization/analyzerTexpr.ml @@ -236,7 +236,7 @@ module TexprFilter = struct let e_if eo = mk (TIf(e_not,e_break,eo)) com.basic.tvoid p in let rec map_continue e = match e.eexpr with | TContinue -> - Texpr.duplicate_tvars (e_if (Some e)) + Texpr.duplicate_tvars e_identity (e_if (Some e)) | TWhile _ | TFor _ -> e | _ -> diff --git a/src/optimization/inline.ml b/src/optimization/inline.ml index 2115fa29c90..098a1ff7af0 100644 --- a/src/optimization/inline.ml +++ b/src/optimization/inline.ml @@ -6,10 +6,6 @@ open Common open Typecore open Error -let needs_inline ctx is_extern_class cf = - cf.cf_kind = Method MethInline && ctx.allow_inline - && (ctx.g.doinline || is_extern_class || has_class_field_flag cf CfExtern) - let mk_untyped_call name p params = { eexpr = TCall({ eexpr = TIdent name; etype = t_dynamic; epos = p }, params); @@ -510,14 +506,14 @@ class inline_state ctx ethis params cf f p = object(self) | VIInline -> begin match e'.eexpr with (* If we inline a function expression, we have to duplicate its locals. *) - | TFunction _ -> Texpr.duplicate_tvars e' + | TFunction _ -> Texpr.duplicate_tvars e_identity e' | TCast(e1,None) when in_assignment -> e1 | _ -> e' end | VIInlineIfCalled when in_call -> (* We allow inlining function expressions into call-places. However, we have to substitute their locals to avoid duplicate declarations. *) - Texpr.duplicate_tvars e' + Texpr.duplicate_tvars e_identity e' | _ -> e end with Not_found -> diff --git a/src/optimization/inlineConstructors.ml b/src/optimization/inlineConstructors.ml index 76ba820eb32..8df1b5d7916 100644 --- a/src/optimization/inlineConstructors.ml +++ b/src/optimization/inlineConstructors.ml @@ -212,7 +212,7 @@ let inline_constructors ctx original_e = | TNew _, true -> true, false | TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some ({eexpr = TFunction _})} as cf)} as c,_,_), _ -> - Inline.needs_inline ctx (has_class_flag c CExtern) cf, false + needs_inline ctx (Some c) cf, false | _ -> false, false in is_ctor || Type.check_expr (check_for_ctors ~force_inline:is_meta_inline) e @@ -237,7 +237,7 @@ let inline_constructors ctx original_e = | TNew _, true -> mark() | TNew({ cl_constructor = Some ({cf_kind = Method MethInline; cf_expr = Some ({eexpr = TFunction _})} as cf)} as c,_,_), _ -> - if Inline.needs_inline ctx (has_class_flag c CExtern) cf then mark() + if needs_inline ctx (Some c) cf then mark() else e | _ -> e in @@ -285,7 +285,7 @@ let inline_constructors ctx original_e = let f = PMap.find fname ctor.ioc_class.cl_fields in begin match f.cf_params, f.cf_kind, f.cf_expr with | [], Method MethInline, Some({eexpr = TFunction tf}) -> - if Inline.needs_inline ctx (has_class_flag ctor.ioc_class CExtern) f then + if needs_inline ctx (Some ctor.ioc_class) f then Some (ctor.ioc_class, ctor.ioc_tparams, f, tf) else None diff --git a/src/optimization/optimizer.ml b/src/optimization/optimizer.ml index cda13f04fa9..e518dc45963 100644 --- a/src/optimization/optimizer.ml +++ b/src/optimization/optimizer.ml @@ -349,7 +349,7 @@ let rec reduce_loop ctx e = (match inl with | None -> reduce_expr ctx e | Some e -> reduce_loop ctx e) - | {eexpr = TField(ef,(FStatic(cl,cf) | FInstance(cl,_,cf)))} when needs_inline ctx (has_class_flag cl CExtern) cf && not (rec_stack_memq cf inline_stack) -> + | {eexpr = TField(ef,(FStatic(cl,cf) | FInstance(cl,_,cf)))} when needs_inline ctx (Some cl) cf && not (rec_stack_memq cf inline_stack) -> begin match cf.cf_expr with | Some {eexpr = TFunction tf} -> let config = inline_config (Some cl) cf el e.etype in diff --git a/src/syntax/parser.ml b/src/syntax/parser.ml index e806d038c15..dc039324ace 100644 --- a/src/syntax/parser.ml +++ b/src/syntax/parser.ml @@ -267,12 +267,13 @@ let precedence op = | OpAdd | OpSub -> 3, left | OpShl | OpShr | OpUShr -> 4, left | OpOr | OpAnd | OpXor -> 5, left - | OpEq | OpNotEq | OpGt | OpLt | OpGte | OpLte -> 6, left - | OpInterval -> 7, left - | OpBoolAnd -> 8, left - | OpBoolOr | OpNullCoal -> 9, left - | OpArrow -> 10, right - | OpAssign | OpAssignOp _ -> 11, right + | OpNullCoal -> 6, left + | OpEq | OpNotEq | OpGt | OpLt | OpGte | OpLte -> 7, left + | OpInterval -> 8, left + | OpBoolAnd -> 9, left + | OpBoolOr -> 10, left + | OpArrow -> 11, right + | OpAssign | OpAssignOp _ -> 12, right let is_higher_than_ternary = function | OpAssign | OpAssignOp _ | OpArrow -> false diff --git a/src/syntax/reification.ml b/src/syntax/reification.ml index 963c9133eb1..a3dd1c1b692 100644 --- a/src/syntax/reification.ml +++ b/src/syntax/reification.ml @@ -292,6 +292,7 @@ let reify in_macro = expr "EVars" [to_array (fun v p -> let fields = [ "name", to_string (fst v.ev_name) (snd v.ev_name); + "namePos", to_pos (snd v.ev_name); "type", to_opt to_type_hint v.ev_type p; "expr", to_opt to_expr v.ev_expr p; "isFinal",to_bool v.ev_final p; diff --git a/src/typing/callUnification.ml b/src/typing/callUnification.ml index 190f6113d54..5accc16d384 100644 --- a/src/typing/callUnification.ml +++ b/src/typing/callUnification.ml @@ -6,13 +6,6 @@ open Typecore open Error open FieldAccess -let is_forced_inline c cf = - match c with - | Some { cl_kind = KAbstractImpl _ } -> true - | Some c when has_class_flag c CExtern -> true - | _ when has_class_field_flag cf CfExtern -> true - | _ -> false - let rec unify_call_args ctx el args r callp inline force_inline in_overload = let call_error err p = raise (Error (Call_error err,p,0)) diff --git a/src/typing/calls.ml b/src/typing/calls.ml index 6c6739a6e8b..eb9eeb3cf45 100644 --- a/src/typing/calls.ml +++ b/src/typing/calls.ml @@ -34,8 +34,7 @@ let make_call ctx e params t ?(force_inline=false) p = raise Exit in if not force_inline then begin - let is_extern_class = match cl with Some c -> (has_class_flag c CExtern) | _ -> false in - if not (Inline.needs_inline ctx is_extern_class f) then raise Exit; + if not (needs_inline ctx cl f) then raise Exit; end else begin match cl with | None -> @@ -191,7 +190,10 @@ let rec acc_get ctx g = in let dispatcher p = new call_dispatcher ctx MGet WithType.value p in match g with - | AKNo(_,p) -> typing_error ("This expression cannot be accessed for reading") p + | AKNo(acc,p) -> + if not (Common.ignore_error ctx.com) then + typing_error ("This expression cannot be accessed for reading") p + else acc_get ctx acc; | AKExpr e -> e | AKSafeNav sn -> (* generate null-check branching for the safe navigation chain *) diff --git a/src/typing/forLoop.ml b/src/typing/forLoop.ml index 56d528ce5d7..fcbbfcbad6c 100644 --- a/src/typing/forLoop.ml +++ b/src/typing/forLoop.ml @@ -327,7 +327,7 @@ module IterationKind = struct | _ -> map_expr loop e in let e2 = loop e2 in - Texpr.duplicate_tvars e2 + Texpr.duplicate_tvars e_identity e2 ) in mk (TBlock el) t_void p | IteratorIntConst(a,b,ascending) -> @@ -368,7 +368,7 @@ module IterationKind = struct let el = List.map (fun e -> let ev = mk (TVar(v,Some e)) t_void e.epos in let e = concat ev e2 in - Texpr.duplicate_tvars e + Texpr.duplicate_tvars e_identity e ) el in mk (TBlock el) t_void p | IteratorArray | IteratorArrayAccess -> diff --git a/src/typing/macroContext.ml b/src/typing/macroContext.ml index 73feb882a2a..64844182211 100644 --- a/src/typing/macroContext.ml +++ b/src/typing/macroContext.ml @@ -474,9 +474,9 @@ and flush_macro_context mint ctx = mctx.com.Common.modules <- modules; (* we should maybe ensure that all filters in Main are applied. Not urgent atm *) let expr_filters = [ + "handle_abstract_casts",AbstractCast.handle_abstract_casts mctx; "local_statics",Filters.LocalStatic.run mctx; "VarLazifier",VarLazifier.apply mctx.com; - "handle_abstract_casts",AbstractCast.handle_abstract_casts mctx; "Exceptions",Exceptions.filter mctx; "captured_vars",CapturedVars.captured_vars mctx.com; ] in @@ -885,7 +885,7 @@ let setup() = let type_stored_expr ctx e1 = let id = match e1 with (EConst (Int (s, _)),_) -> int_of_string s | _ -> die "" __LOC__ in - get_stored_typed_expr ctx.com id + TyperBase.get_stored_typed_expr ctx id ;; load_macro_ref := load_macro; diff --git a/src/typing/matcher.ml b/src/typing/matcher.ml index 0caa8ae1cf2..72f2a5e3a2e 100644 --- a/src/typing/matcher.ml +++ b/src/typing/matcher.ml @@ -1662,7 +1662,7 @@ module TexprConverter = struct | None -> typing_error "Unmatched patterns: _" p; | Some e -> - Texpr.duplicate_tvars e + Texpr.duplicate_tvars e_identity e end module Match = struct diff --git a/src/typing/nullSafety.ml b/src/typing/nullSafety.ml index be3da1f5913..a583e4dab65 100644 --- a/src/typing/nullSafety.ml +++ b/src/typing/nullSafety.ml @@ -1040,7 +1040,9 @@ class expr_checker mode immediate_execution report = | TMeta (m, _) when contains_unsafe_meta [m] -> false | TMeta (_, e) -> self#is_nullable_expr e | TThrow _ -> false - | TReturn (Some e) -> self#is_nullable_expr e + | 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 67b699b13f1..90740b56d33 100644 --- a/src/typing/operators.ml +++ b/src/typing/operators.ml @@ -570,35 +570,39 @@ let type_assign ctx e1 e2 with_type p = | _ , _ -> ()); mk (TBinop (OpAssign,e1,e2)) e1.etype p in - match e1 with - | AKNo(_,p) -> - typing_error "This expression cannot be accessed for writing" p - | AKUsingField _ | AKSafeNav _ -> - typing_error "Invalid operation" p - | AKExpr { eexpr = TLocal { v_kind = VUser TVOLocalFunction; v_name = name } } -> - typing_error ("Cannot access function " ^ name ^ " for writing") p - | AKField fa -> - let ef = FieldAccess.get_field_expr fa FWrite in - assign_to ef - | AKExpr e1 -> - assign_to e1 - | AKAccessor fa -> - let dispatcher = new call_dispatcher ctx (MSet (Some e2)) with_type p in - dispatcher#accessor_call fa [] [e2] - | AKAccess(a,tl,c,ebase,ekey) -> - let e2 = type_rhs WithType.value in - mk_array_set_call ctx (AbstractCast.find_array_write_access ctx a tl ekey e2 p) c ebase p - | AKResolve(sea,name) -> - let eparam = sea.se_this in - let e_name = Texpr.Builder.make_string ctx.t name null_pos in - (new call_dispatcher ctx (MCall [e2]) with_type p)#field_call sea.se_access [eparam;e_name] [e2] - | AKUsingAccessor sea -> - let fa_set = match FieldAccess.resolve_accessor sea.se_access (MSet (Some e2)) with - | AccessorFound fa -> fa - | _ -> typing_error "Could not resolve accessor" p - in - let dispatcher = new call_dispatcher ctx (MCall [e2]) with_type p in - dispatcher#field_call fa_set [sea.se_this] [e2] + let rec check_acc e1 = match e1 with + | AKNo(acc,p) -> + if not (Common.ignore_error ctx.com) then + typing_error "This expression cannot be accessed for writing" p + else check_acc acc + | AKUsingField _ | AKSafeNav _ -> + typing_error "Invalid operation" p + | AKExpr { eexpr = TLocal { v_kind = VUser TVOLocalFunction; v_name = name } } -> + typing_error ("Cannot access function " ^ name ^ " for writing") p + | AKField fa -> + let ef = FieldAccess.get_field_expr fa FWrite in + assign_to ef + | AKExpr e1 -> + assign_to e1 + | AKAccessor fa -> + let dispatcher = new call_dispatcher ctx (MSet (Some e2)) with_type p in + dispatcher#accessor_call fa [] [e2] + | AKAccess(a,tl,c,ebase,ekey) -> + let e2 = type_rhs WithType.value in + mk_array_set_call ctx (AbstractCast.find_array_write_access ctx a tl ekey e2 p) c ebase p + | AKResolve(sea,name) -> + let eparam = sea.se_this in + let e_name = Texpr.Builder.make_string ctx.t name null_pos in + (new call_dispatcher ctx (MCall [e2]) with_type p)#field_call sea.se_access [eparam;e_name] [e2] + | AKUsingAccessor sea -> + let fa_set = match FieldAccess.resolve_accessor sea.se_access (MSet (Some e2)) with + | AccessorFound fa -> fa + | _ -> typing_error "Could not resolve accessor" p + in + let dispatcher = new call_dispatcher ctx (MCall [e2]) with_type p in + dispatcher#field_call fa_set [sea.se_this] [e2] + in + check_acc e1 let type_non_assign_op ctx op e1 e2 is_assign_op abstract_overload_only with_type p = (* If the with_type is an abstract which has exactly one applicable @:op method, we can promote it diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index 2e154165d30..dd47ae44a9f 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -917,7 +917,7 @@ module TypeBinding = struct e end in e - | Var v when v.v_read = AccInline -> + | Var v when v.v_read = AccInline && (ctx.g.doinline || is_forced_inline (Some c) cf) -> let e = require_constant_expression e "Inline variable initialization must be a constant value" in begin match c.cl_kind with | KAbstractImpl a when has_class_field_flag cf CfEnum && a.a_enum -> diff --git a/src/typing/typeloadModule.ml b/src/typing/typeloadModule.ml index af8c1f9d4a9..5fba914782f 100644 --- a/src/typing/typeloadModule.ml +++ b/src/typing/typeloadModule.ml @@ -204,7 +204,9 @@ module ModuleLevel = struct begin match p_enum_meta with | None when a.a_enum -> a.a_meta <- (Meta.Enum,[],null_pos) :: a.a_meta; (* HAXE5: remove *) | None -> () - | Some p -> warning ctx WDeprecated "`@:enum abstract` is deprecated in favor of `enum abstract`" p + | Some p -> + let options = Warning.from_meta d.d_meta in + ctx.com.warning WDeprecatedEnumAbstract options "`@:enum abstract` is deprecated in favor of `enum abstract`" p end; decls := (TAbstractDecl a, decl) :: !decls; match d.d_data with diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 3cb5bcf5f56..63a1f1b4c8d 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -330,8 +330,9 @@ let rec type_ident_raise ctx i p mode with_type = let t = match with_type with | WithType.WithType(t,_) -> begin match follow t with - | TMono r -> - (* If our expected type is a monomorph, bind it to Null. *) + | TMono r when not (is_nullable t) -> + (* If our expected type is a monomorph, bind it to Null. The is_nullable check is here because + the expected type could already be Null, in which case we don't want to double-wrap (issue #11286). *) Monomorph.do_bind r (tnull()) | _ -> (* Otherwise there's no need to create a monomorph, we can just type the null literal @@ -1891,19 +1892,24 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = let vr = new value_reference ctx in let e1 = type_expr ctx (Expr.ensure_block e1) with_type in let e2 = type_expr ctx (Expr.ensure_block e2) (WithType.with_type e1.etype) in - let e1 = vr#as_var "tmp" {e1 with etype = ctx.t.tnull e1.etype} in + let tmin = unify_min ctx [e1; e2] in + let e1 = vr#as_var "tmp" {e1 with etype = ctx.t.tnull tmin} 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 follow_null_once t = + let rec follow_null t = match t with - | TAbstract({a_path = [],"Null"},[t]) -> t + | TAbstract({a_path = [],"Null"},[t]) -> follow_null t | _ -> t in let iftype = if DeadEnd.has_dead_end e2 then - WithType.with_type (follow_null_once e1.etype) + WithType.with_type (follow_null e1.etype) else - WithType.WithType(e2.etype,None) + let t = match e2.etype with + | TAbstract({a_path = [],"Null"},[t]) -> tmin + | _ -> follow_null tmin + in + WithType.with_type t in let e_if = make_if_then_else ctx e_cond e1 e2 iftype p in vr#to_texpr e_if diff --git a/src/typing/typerBase.ml b/src/typing/typerBase.ml index e80395c08fe..656d2d660c6 100644 --- a/src/typing/typerBase.ml +++ b/src/typing/typerBase.ml @@ -173,6 +173,10 @@ let get_this ctx p = | FunConstructor | FunMember -> mk (TConst TThis) ctx.tthis p +let get_stored_typed_expr ctx id = + let e = ctx.com.stored_typed_exprs#find id in + Texpr.duplicate_tvars (fun e -> get_this ctx e.epos) e + let assign_to_this_is_allowed ctx = match ctx.curclass.cl_kind with | KAbstractImpl _ -> 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/eval/_std/sys/thread/EventLoop.hx b/std/eval/_std/sys/thread/EventLoop.hx index d5ef5e97c89..5b25958b499 100644 --- a/std/eval/_std/sys/thread/EventLoop.hx +++ b/std/eval/_std/sys/thread/EventLoop.hx @@ -196,11 +196,8 @@ class EventLoop { if (started && isMainThread) { var next = @:privateAccess MainLoop.tick(); - if (haxe.MainLoop.hasEvents()) { - wakeup.send(); - } else { - refUnref(); - } + if (haxe.MainLoop.hasEvents()) wakeup.send(); + refUnref(); } } } diff --git a/std/haxe/macro/Expr.hx b/std/haxe/macro/Expr.hx index c2bce319ff6..f9e06dcc6b8 100644 --- a/std/haxe/macro/Expr.hx +++ b/std/haxe/macro/Expr.hx @@ -316,6 +316,11 @@ typedef Var = { **/ var name:String; + /** + The position of the variable name. + **/ + var ?namePos:Position; + /** The type-hint of the variable, if available. **/ @@ -994,7 +999,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. @@ -1002,6 +1007,28 @@ enum TypeDefKind { TDField(kind:FieldType, ?access:Array); // ignore TypeDefinition.fields } +/** + 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/ExprTools.hx b/std/haxe/macro/ExprTools.hx index 33208ae27e5..4eac236318e 100644 --- a/std/haxe/macro/ExprTools.hx +++ b/std/haxe/macro/ExprTools.hx @@ -144,7 +144,7 @@ class ExprTools { case EConst(_): e.expr; case EArray(e1, e2): EArray(f(e1), f(e2)); case EBinop(op, e1, e2): EBinop(op, f(e1), f(e2)); - case EField(e, field): EField(f(e), field); + case EField(e, field, kind): EField(f(e), field, kind); case EParenthesis(e): EParenthesis(f(e)); case EObjectDecl(fields): var ret = []; diff --git a/std/haxe/macro/Printer.hx b/std/haxe/macro/Printer.hx index 306b8787c46..075f7400354 100644 --- a/std/haxe/macro/Printer.hx +++ b/std/haxe/macro/Printer.hx @@ -382,13 +382,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) { @@ -448,8 +461,9 @@ class Printer { add("EBinop " + printBinop(op)); loopI(e1); loopI(e2); - case EField(e, field): - add("EField " + field); + case EField(e, field, kind): + if (kind == null) kind = Normal; + add('EField $field (${kind.getName()})'); loopI(e); case EParenthesis(e): add("EParenthesis"); 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/display/src/cases/Issue11173.hx b/tests/display/src/cases/Issue11173.hx new file mode 100644 index 00000000000..74f53a85d18 --- /dev/null +++ b/tests/display/src/cases/Issue11173.hx @@ -0,0 +1,48 @@ +package cases; + +class Foo { + public final field = 0; + + public function new() {} +} + +class Issue11173 extends DisplayTestCase { + /** + class Main { + static final {-1-}field{-2-} = 0; + static function main() { + {-3-}field{-4-} = 5; + + final foo = new Foo(); + foo.{-5-}field{-6-} = "ho${-9-}la"; + } + } + class Foo { + public final {-7-}field{-8-} = "hi"; + public function new() {} + } + **/ + function test() { + arrayEq([ + { + kind: DKCompilerError, + severity: Error, + range: diagnosticsRange(pos(3), pos(4)), + relatedInformation: [], + args: "This expression cannot be accessed for writing" + }, + { + kind: DKCompilerError, + severity: Error, + range: diagnosticsRange(pos(5), pos(6)), + relatedInformation: [], + args: "This expression cannot be accessed for writing" + } + ], diagnostics()); + eq("Int", type(pos(4))); + eq("String", type(pos(6))); + eq(range(1, 2), position(pos(4))); + eq(range(7, 8), position(pos(6))); + eq("String", type(pos(9))); + } +} diff --git a/tests/misc/projects/Issue11005/Main.hx b/tests/misc/projects/Issue11005/Main.hx new file mode 100644 index 00000000000..4a16ee7b5cd --- /dev/null +++ b/tests/misc/projects/Issue11005/Main.hx @@ -0,0 +1,8 @@ +function main() {} + +abstract Foo(String) { + public inline function new(str:String) + this = str; +} + +inline final foo = new Foo("foo"); diff --git a/tests/misc/projects/Issue11005/compile.hxml b/tests/misc/projects/Issue11005/compile.hxml new file mode 100644 index 00000000000..0e77692281d --- /dev/null +++ b/tests/misc/projects/Issue11005/compile.hxml @@ -0,0 +1,2 @@ +-main Main +-D no-inline diff --git a/tests/misc/projects/Issue11102/Main.hx b/tests/misc/projects/Issue11102/Main.hx new file mode 100644 index 00000000000..c2adaa6f5b9 --- /dev/null +++ b/tests/misc/projects/Issue11102/Main.hx @@ -0,0 +1,12 @@ +function main() { + var foo:Foo = 42; + + switch (foo) { + case LCTRL: + case _: + } +} + +enum abstract Foo(Int) from Int to Int { + var LCTRL:Foo = 224 | (1<<30); +} diff --git a/tests/misc/projects/Issue11102/compile.hxml b/tests/misc/projects/Issue11102/compile.hxml new file mode 100644 index 00000000000..0e77692281d --- /dev/null +++ b/tests/misc/projects/Issue11102/compile.hxml @@ -0,0 +1,2 @@ +-main Main +-D no-inline diff --git a/tests/misc/projects/Issue11162/Macro.macro.hx b/tests/misc/projects/Issue11162/Macro.macro.hx new file mode 100644 index 00000000000..8a4658ab619 --- /dev/null +++ b/tests/misc/projects/Issue11162/Macro.macro.hx @@ -0,0 +1,7 @@ +class Macro { + public static function build() { + return haxe.macro.Context.getBuildFields().concat((macro class A { + function bar() var a = main(); + }).fields); + } +} diff --git a/tests/misc/projects/Issue11162/Macro2.macro.hx b/tests/misc/projects/Issue11162/Macro2.macro.hx new file mode 100644 index 00000000000..7ddb0a8c19d --- /dev/null +++ b/tests/misc/projects/Issue11162/Macro2.macro.hx @@ -0,0 +1,15 @@ +import haxe.macro.Expr; + +class Macro2 { + public static function build() { + var pos = (macro 0).pos; + var e = {expr: EBlock([ + {expr: EVars([{name: "a", type: null, expr: null}]), pos: pos}, + {expr: EBinop(OpAssign, {expr: EConst(CIdent("a")), pos: pos}, macro main()), pos: pos} + ]), pos: pos}; + + return haxe.macro.Context.getBuildFields().concat((macro class A { + function bar() $e; + }).fields); + } +} diff --git a/tests/misc/projects/Issue11162/Main.hx b/tests/misc/projects/Issue11162/Main.hx new file mode 100644 index 00000000000..f7b235f0261 --- /dev/null +++ b/tests/misc/projects/Issue11162/Main.hx @@ -0,0 +1,4 @@ +@:build(Macro.build()) +class Main { + static function main() {} +} diff --git a/tests/misc/projects/Issue11162/Main2.hx b/tests/misc/projects/Issue11162/Main2.hx new file mode 100644 index 00000000000..f2aebdb40d1 --- /dev/null +++ b/tests/misc/projects/Issue11162/Main2.hx @@ -0,0 +1,4 @@ +@:build(Macro2.build()) +class Main { + static function main() {} +} diff --git a/tests/misc/projects/Issue11162/compile-fail.hxml b/tests/misc/projects/Issue11162/compile-fail.hxml new file mode 100644 index 00000000000..b66ea29f280 --- /dev/null +++ b/tests/misc/projects/Issue11162/compile-fail.hxml @@ -0,0 +1,3 @@ +-main Main +-D message.reporting=pretty +-D message.no-color diff --git a/tests/misc/projects/Issue11162/compile-fail.hxml.stderr b/tests/misc/projects/Issue11162/compile-fail.hxml.stderr new file mode 100644 index 00000000000..b7d9fb86546 --- /dev/null +++ b/tests/misc/projects/Issue11162/compile-fail.hxml.stderr @@ -0,0 +1,6 @@ +[ERROR] Macro.macro.hx:4: characters 22-23 + + 4 | function bar() var a = main(); + | ^ + | Variables of type Void are not allowed + diff --git a/tests/misc/projects/Issue11162/compile2-fail.hxml b/tests/misc/projects/Issue11162/compile2-fail.hxml new file mode 100644 index 00000000000..04c12f8cb8b --- /dev/null +++ b/tests/misc/projects/Issue11162/compile2-fail.hxml @@ -0,0 +1,3 @@ +-main Main2 +-D message.reporting=pretty +-D message.no-color diff --git a/tests/misc/projects/Issue11162/compile2-fail.hxml.stderr b/tests/misc/projects/Issue11162/compile2-fail.hxml.stderr new file mode 100644 index 00000000000..a86a66e37c1 --- /dev/null +++ b/tests/misc/projects/Issue11162/compile2-fail.hxml.stderr @@ -0,0 +1,6 @@ +[ERROR] Macro2.macro.hx:5: characters 20-21 + + 5 | var pos = (macro 0).pos; + | ^ + | Variables of type Void are not allowed + diff --git a/tests/misc/projects/Issue11193/Macro.hx b/tests/misc/projects/Issue11193/Macro.hx new file mode 100644 index 00000000000..d2ed9e0f66c --- /dev/null +++ b/tests/misc/projects/Issue11193/Macro.hx @@ -0,0 +1,6 @@ +class Macro { + static function test() { + static var m:Map = []; + trace(m); + } +} diff --git a/tests/misc/projects/Issue11193/compile.hxml b/tests/misc/projects/Issue11193/compile.hxml new file mode 100644 index 00000000000..660593079f7 --- /dev/null +++ b/tests/misc/projects/Issue11193/compile.hxml @@ -0,0 +1 @@ +--macro Macro.test() diff --git a/tests/misc/projects/Issue11286/Main.hx b/tests/misc/projects/Issue11286/Main.hx new file mode 100644 index 00000000000..fb474cfdc19 --- /dev/null +++ b/tests/misc/projects/Issue11286/Main.hx @@ -0,0 +1,14 @@ +function main() { + var a = null; + if (a == null) a = 10; + $type(a); // Null> + + var b = null; + $type(b); // Null> + b = null; + $type(b); // Null>> + b = null; + $type(b); // Null>>> + b = 10; + $type(b); // Null>> +} \ No newline at end of file diff --git a/tests/misc/projects/Issue11286/compile.hxml b/tests/misc/projects/Issue11286/compile.hxml new file mode 100644 index 00000000000..fab0aeecc3d --- /dev/null +++ b/tests/misc/projects/Issue11286/compile.hxml @@ -0,0 +1 @@ +--main Main \ No newline at end of file diff --git a/tests/misc/projects/Issue11286/compile.hxml.stderr b/tests/misc/projects/Issue11286/compile.hxml.stderr new file mode 100644 index 00000000000..b7a89f0e169 --- /dev/null +++ b/tests/misc/projects/Issue11286/compile.hxml.stderr @@ -0,0 +1,5 @@ +Main.hx:4: characters 8-9 : Warning : Null +Main.hx:7: characters 8-9 : Warning : Null> +Main.hx:9: characters 8-9 : Warning : Null> +Main.hx:11: characters 8-9 : Warning : Null> +Main.hx:13: characters 8-9 : Warning : Null \ No newline at end of file diff --git a/tests/nullsafety/src/cases/TestLoose.hx b/tests/nullsafety/src/cases/TestLoose.hx index 2be8f7cbaa3..704f6521abd 100644 --- a/tests/nullsafety/src/cases/TestLoose.hx +++ b/tests/nullsafety/src/cases/TestLoose.hx @@ -99,4 +99,25 @@ class TestLoose { } } } -} \ No newline at end of file + + static function nullCoal_returnNull_shouldPass(token:{children:Array}):Null { + final children = token.children ?? return null; + var i = children.length; + return null; + } + + static function localFunc_returnNullCoal_shouldFail():Void { + function foo() { + final x = (null : Null) ?? return null; + return x; + } + 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/TestMain.hx b/tests/unit/src/unit/TestMain.hx index 6bc80f35305..0552c7f6dab 100644 --- a/tests/unit/src/unit/TestMain.hx +++ b/tests/unit/src/unit/TestMain.hx @@ -75,6 +75,7 @@ function main() { new TestCasts(), new TestSyntaxModule(), new TestNull(), + new TestNullCoalescing(), new TestNumericCasts(), new TestHashMap(), new TestRest(), @@ -107,7 +108,8 @@ function main() { new TestOverloadsForEveryone(), new TestInterface(), new TestNaN(), - #if ((dce == "full") && !interp) new TestDCE(), + #if ((dce == "full") && !interp) + new TestDCE(), #end new TestMapComprehension(), new TestMacro(), diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index 0189e140458..76eb4895fbc 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -1,18 +1,25 @@ package unit; +private class A {} +private class B extends A {} +private class C extends A {} + @:nullSafety(StrictThreaded) class TestNullCoalescing extends Test { final nullInt:Null = null; + final nullFloat:Null = null; final nullBool:Null = null; final nullString:Null = null; var count = 0; + function call() { count++; return "_"; } function test() { + eq(true, 0 != 1 ?? 2); var a = call() ?? "default"; eq(count, 1); @@ -30,9 +37,10 @@ class TestNullCoalescing extends Test { final s:Int = if (nullInt == null) 2; else nullInt; final s = nullInt ?? 2; - // $type(testBool); // Bool - // $type(testNullBool); // Null - // $type(s); // Int + f(HelperMacros.isNullable(testBool)); + t(HelperMacros.isNullable(testNullBool)); + f(HelperMacros.isNullable(s)); + // nullsafety filter test: final shouldBeBool:Bool = testBool; if (testNullBool == null) {} final shouldBeInt:Int = s; @@ -54,10 +62,7 @@ class TestNullCoalescing extends Test { eq(arr[1], 1); eq(arr[2], 1); - final arr = [ - nullInt ?? 2, - 2 - ]; + final arr = [nullInt ?? 2, 2]; eq(arr[0], arr[1]); var a = [0 => nullInt ?? 0 + 100]; @@ -110,7 +115,8 @@ class TestNullCoalescing extends Test { } eq(item(1) ?? item(2) ?? item(3), 1); eq(arr.length, 1); - for (i => v in [1]) eq(arr[i], v); + for (i => v in [1]) + eq(arr[i], v); final arr = []; function item(n) { @@ -119,6 +125,22 @@ class TestNullCoalescing extends Test { } eq(item(1) ?? item(2) ?? item(3), null); eq(arr.length, 3); - for (i => v in [1, 2, 3]) eq(arr[i], v); + for (i => v in [1, 2, 3]) + eq(arr[i], v); + + var b:B = cast null; + var c:C = cast null; + var a = if (b != null) b else c; + var a = b ?? c; + eq("unit._TestNullCoalescing.A", HelperMacros.typeString(a)); + + var nullF = false ? nullFloat : 0; + var nullF2 = nullFloat ?? nullInt; + var notNullF = nullFloat ?? 0; + var notNullF2 = (1 : Null) ?? throw ""; + t(HelperMacros.isNullable(nullF)); + t(HelperMacros.isNullable(nullF2)); + f(HelperMacros.isNullable(notNullF)); + f(HelperMacros.isNullable(notNullF2)); } } diff --git a/tests/unit/src/unit/issues/Issue11212.hx b/tests/unit/src/unit/issues/Issue11212.hx new file mode 100644 index 00000000000..7a81c2eb95f --- /dev/null +++ b/tests/unit/src/unit/issues/Issue11212.hx @@ -0,0 +1,19 @@ +package unit.issues; + +class Issue11212 extends Test { + var value = 123; + + function test() { + #if !macro + var foo:Foo = value; // on JS this generates `let foo = function() { return this.value; };` on JS + eq(123, foo()); + #end + } +} + +@:callable +private abstract Foo(() -> Int) from () -> Int { + @:from macro static function ofExpr(e) { + return macro @:pos(e.pos) (function():Int return $e : Foo); + } +}