From 982ac87146ce1cffe7750138a3d4cc022c1b545d Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 3 Dec 2021 06:26:23 +0300 Subject: [PATCH 1/9] Null coalescing operator --- src/codegen/gencommon/dynamicOperators.ml | 2 +- src/core/ast.ml | 4 ++- src/core/json/genjson.ml | 3 +- src/generators/gencpp.ml | 3 ++ src/generators/genhl.ml | 2 +- src/generators/genpy.ml | 2 +- src/generators/genswf9.ml | 2 +- src/macro/eval/evalDebugMisc.ml | 4 +-- src/macro/eval/evalMisc.ml | 2 +- src/macro/macroApi.ml | 2 ++ src/optimization/analyzerTexpr.ml | 1 + src/optimization/analyzerTexprTransformer.ml | 4 +-- src/optimization/optimizer.ml | 3 +- src/syntax/lexer.ml | 3 +- src/syntax/parser.ml | 4 +-- src/syntax/reification.ml | 1 + src/typing/operators.ml | 3 +- src/typing/typer.ml | 6 ++++ std/haxe/display/JsonModuleTypes.hx | 1 + std/haxe/macro/Expr.hx | 5 +++ std/haxe/macro/Printer.hx | 1 + tests/unit/src/unit/TestNullCoalescing.hx | 33 ++++++++++++++++++++ 22 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 tests/unit/src/unit/TestNullCoalescing.hx diff --git a/src/codegen/gencommon/dynamicOperators.ml b/src/codegen/gencommon/dynamicOperators.ml index e114db1e4bf..57e356cab16 100644 --- a/src/codegen/gencommon/dynamicOperators.ml +++ b/src/codegen/gencommon/dynamicOperators.ml @@ -119,7 +119,7 @@ let init com handle_strings (should_change:texpr->bool) (equals_handler:texpr->t { e with eexpr = TBinop (op, mk_cast com.basic.tbool (run e1), mk_cast com.basic.tbool (run e2)) } | OpAnd | OpOr | OpXor | OpShl | OpShr | OpUShr -> { e with eexpr = TBinop (op, mk_cast com.basic.tint (run e1), mk_cast com.basic.tint (run e2)) } - | OpAssign | OpAssignOp _ | OpInterval | OpArrow | OpIn -> + | OpAssign | OpAssignOp _ | OpInterval | OpArrow | OpIn | OpNullCoal -> Globals.die "" __LOC__) | TUnop (Increment as op, flag, e1) diff --git a/src/core/ast.ml b/src/core/ast.ml index 8085075440c..08419265bab 100644 --- a/src/core/ast.ml +++ b/src/core/ast.ml @@ -91,6 +91,7 @@ type binop = | OpInterval | OpArrow | OpIn + | OpNullCoal type unop = | Increment @@ -555,6 +556,7 @@ let rec s_binop = function | OpInterval -> "..." | OpArrow -> "=>" | OpIn -> " in " + | OpNullCoal -> "??" let s_unop = function | Increment -> "++" @@ -1233,4 +1235,4 @@ let get_meta_options metas meta = | [] -> [] in - loop metas \ No newline at end of file + loop metas diff --git a/src/core/json/genjson.ml b/src/core/json/genjson.ml index e41b9351fc1..547b822db03 100644 --- a/src/core/json/genjson.ml +++ b/src/core/json/genjson.ml @@ -148,6 +148,7 @@ let rec generate_binop ctx op = | OpInterval -> "OpInterval",None | OpArrow -> "OpArrow",None | OpIn -> "OpIn",None + | OpNullCoal -> "OpNullCoal",None in generate_adt ctx (Some (["haxe";"macro"],"Binop")) name args @@ -732,4 +733,4 @@ let generate types file = let ch = open_out_bin file in Json.write_json (output_string ch) json; close_out ch; - t() \ No newline at end of file + t() diff --git a/src/generators/gencpp.ml b/src/generators/gencpp.ml index cd9a9f5ccc5..999b07b2083 100644 --- a/src/generators/gencpp.ml +++ b/src/generators/gencpp.ml @@ -4218,6 +4218,7 @@ let gen_cpp_ast_expression_tree ctx class_name func_name function_args function_ | OpInterval -> "..." | OpArrow -> "->" | OpIn -> " in " + | OpNullCoal -> "??" | OpAssign | OpAssignOp _ -> abort "Unprocessed OpAssign" pos and gen_closure closure = @@ -7301,6 +7302,7 @@ let cppia_op_info = function | IaBinOp OpInterval -> ("...", 121) | IaBinOp OpArrow -> ("=>", 122) | IaBinOp OpIn -> (" in ", 123) + | IaBinOp OpNullCoal -> ("??", 124) | IaBinOp OpAssignOp OpAdd -> ("+=", 201) | IaBinOp OpAssignOp OpMult -> ("*=", 202) | IaBinOp OpAssignOp OpDiv -> ("/=", 203) @@ -7318,6 +7320,7 @@ let cppia_op_info = function | IaBinOp OpAssignOp OpMod -> ("%=", 220) | IaBinOp OpAssignOp OpIn + | IaBinOp OpAssignOp OpNullCoal | IaBinOp OpAssignOp OpInterval | IaBinOp OpAssignOp OpAssign | IaBinOp OpAssignOp OpEq diff --git a/src/generators/genhl.ml b/src/generators/genhl.ml index cea943633a2..a81f961be39 100644 --- a/src/generators/genhl.ml +++ b/src/generators/genhl.ml @@ -2484,7 +2484,7 @@ and eval_expr ctx e = free ctx r; binop r r b; r)) - | OpInterval | OpArrow | OpIn -> + | OpInterval | OpArrow | OpIn | OpNullCoal -> die "" __LOC__) | TUnop (Not,_,v) -> let tmp = alloc_tmp ctx HBool in diff --git a/src/generators/genpy.ml b/src/generators/genpy.ml index 7f58398cea7..dbafb2fdbbc 100644 --- a/src/generators/genpy.ml +++ b/src/generators/genpy.ml @@ -1081,7 +1081,7 @@ module Printer = struct | OpShr -> ">>" | OpUShr -> ">>" | OpMod -> "%" - | OpInterval | OpArrow | OpIn | OpAssignOp _ -> die "" __LOC__ + | OpInterval | OpArrow | OpIn | OpNullCoal | OpAssignOp _ -> die "" __LOC__ let print_string s = Printf.sprintf "\"%s\"" (StringHelper.s_escape s) diff --git a/src/generators/genswf9.ml b/src/generators/genswf9.ml index 1f144dbbf9a..ae148f27933 100644 --- a/src/generators/genswf9.ml +++ b/src/generators/genswf9.ml @@ -1829,7 +1829,7 @@ and gen_binop ctx retval op e1 e2 t p = gen_op A3OLt | OpLte -> gen_op A3OLte - | OpInterval | OpArrow | OpIn -> + | OpInterval | OpArrow | OpIn | OpNullCoal -> die "" __LOC__ and gen_expr ctx retval e = diff --git a/src/macro/eval/evalDebugMisc.ml b/src/macro/eval/evalDebugMisc.ml index 72da971d601..7a083f66fc5 100644 --- a/src/macro/eval/evalDebugMisc.ml +++ b/src/macro/eval/evalDebugMisc.ml @@ -266,7 +266,7 @@ let rec expr_to_value ctx env e = | OpBoolOr -> if is_true (loop e1) then VTrue else loop e2 - | OpInterval | OpArrow | OpIn -> + | OpInterval | OpArrow | OpIn | OpNullCoal -> raise NoValueExpr | _ -> let v1 = loop e1 in @@ -417,4 +417,4 @@ and write_expr ctx env expr value = let expr_to_value_safe ctx env e = try expr_to_value ctx env e - with NoValueExpr -> VNull \ No newline at end of file + with NoValueExpr -> VNull diff --git a/src/macro/eval/evalMisc.ml b/src/macro/eval/evalMisc.ml index de88f4b5f37..25742ee184b 100644 --- a/src/macro/eval/evalMisc.ml +++ b/src/macro/eval/evalMisc.ml @@ -284,7 +284,7 @@ let get_binop_fun op p = match op with | OpShr -> op_shr p | OpUShr -> op_ushr p | OpMod -> op_mod p - | OpAssign | OpBoolAnd | OpBoolOr | OpAssignOp _ | OpInterval | OpArrow | OpIn -> die ~p "" __LOC__ + | OpAssign | OpBoolAnd | OpBoolOr | OpAssignOp _ | OpInterval | OpArrow | OpIn | OpNullCoal -> die ~p "" __LOC__ let prepare_callback v n = match v with diff --git a/src/macro/macroApi.ml b/src/macro/macroApi.ml index 8f26a4cbd4f..7e9b4cb75d8 100644 --- a/src/macro/macroApi.ml +++ b/src/macro/macroApi.ml @@ -260,6 +260,7 @@ let rec encode_binop op = | OpInterval -> 21, [] | OpArrow -> 22, [] | OpIn -> 23, [] + | OpNullCoal -> 24, [] in encode_enum IBinop tag pl @@ -581,6 +582,7 @@ let rec decode_op op = | 21, [] -> OpInterval | 22,[] -> OpArrow | 23,[] -> OpIn + | 24,[] -> OpNullCoal | _ -> raise Invalid_expr let decode_unop op = diff --git a/src/optimization/analyzerTexpr.ml b/src/optimization/analyzerTexpr.ml index 936034e280b..78764eb420f 100644 --- a/src/optimization/analyzerTexpr.ml +++ b/src/optimization/analyzerTexpr.ml @@ -504,6 +504,7 @@ module Fusion = struct | OpAssignOp _ | OpInterval | OpIn + | OpNullCoal | OpArrow -> false diff --git a/src/optimization/analyzerTexprTransformer.ml b/src/optimization/analyzerTexprTransformer.ml index 833342ecee2..8941f470931 100644 --- a/src/optimization/analyzerTexprTransformer.ml +++ b/src/optimization/analyzerTexprTransformer.ml @@ -747,7 +747,7 @@ and func ctx i = | OpAdd | OpMult | OpDiv | OpSub | OpAnd | OpOr | OpXor | OpShl | OpShr | OpUShr | OpMod -> true - | OpAssignOp _ | OpInterval | OpArrow | OpIn | OpAssign | OpEq + | OpAssignOp _ | OpInterval | OpArrow | OpIn | OpNullCoal | OpAssign | OpEq | OpNotEq | OpGt | OpGte | OpLt | OpLte | OpBoolAnd | OpBoolOr -> false in @@ -777,4 +777,4 @@ and func ctx i = mk (TFunction {tf with tf_expr = e}) t p let to_texpr ctx = - func ctx ctx.entry.bb_id \ No newline at end of file + func ctx ctx.entry.bb_id diff --git a/src/optimization/optimizer.ml b/src/optimization/optimizer.ml index f06b0e84005..e0164fa0035 100644 --- a/src/optimization/optimizer.ml +++ b/src/optimization/optimizer.ml @@ -55,6 +55,7 @@ let standard_precedence op = | OpBoolAnd -> 14, left | OpBoolOr -> 15, left | OpArrow -> 16, left + | OpNullCoal -> 17, right | OpAssignOp OpAssign -> 18, right (* mimics ?: *) | OpAssign | OpAssignOp _ -> 19, right @@ -385,4 +386,4 @@ let rec make_constant_expression ctx ?(concat_strings=false) e = | None -> None | Some e -> make_constant_expression ctx e) with Not_found -> None) *) - | _ -> None \ No newline at end of file + | _ -> None diff --git a/src/syntax/lexer.ml b/src/syntax/lexer.ml index 6a56ecc1b14..b60769c5fbd 100644 --- a/src/syntax/lexer.ml +++ b/src/syntax/lexer.ml @@ -379,6 +379,7 @@ let rec token lexbuf = | "}" -> mk lexbuf BrClose | "(" -> mk lexbuf POpen | ")" -> mk lexbuf PClose + | "??" -> mk lexbuf (Binop OpNullCoal) | "?" -> mk lexbuf Question | "@" -> mk lexbuf At @@ -636,4 +637,4 @@ let lex_xml p lexbuf = try not_xml ctx 0 (name <> "") (* don't allow self-closing fragments *) with Exit -> - error Unterminated_markup p \ No newline at end of file + error Unterminated_markup p diff --git a/src/syntax/parser.ml b/src/syntax/parser.ml index e8076b2c38e..fce248f70dd 100644 --- a/src/syntax/parser.ml +++ b/src/syntax/parser.ml @@ -271,7 +271,7 @@ let precedence op = | OpInterval -> 7, left | OpBoolAnd -> 8, left | OpBoolOr -> 9, left - | OpArrow -> 10, right + | OpArrow | OpNullCoal -> 10, right | OpAssign | OpAssignOp _ -> 11, right let is_higher_than_ternary = function @@ -425,4 +425,4 @@ let convert_abstract_flags flags = let no_keyword what s = match Stream.peek s with | Some (Kwd kwd,p) -> error (Custom ("Keyword " ^ (s_keyword kwd) ^ " cannot be used as " ^ what)) p - | _ -> raise Stream.Failure \ No newline at end of file + | _ -> raise Stream.Failure diff --git a/src/syntax/reification.ml b/src/syntax/reification.ml index 6b6e8cbffa6..f234d3eb200 100644 --- a/src/syntax/reification.ml +++ b/src/syntax/reification.ml @@ -67,6 +67,7 @@ let reify in_macro = | OpInterval -> op "OpInterval" | OpArrow -> op "OpArrow" | OpIn -> op "OpIn" + | OpNullCoal -> op "OpNullCoal" in let to_string s p = let len = String.length s in diff --git a/src/typing/operators.ml b/src/typing/operators.ml index 781f6279f93..1e118d45fec 100644 --- a/src/typing/operators.ml +++ b/src/typing/operators.ml @@ -444,6 +444,7 @@ let make_binop ctx op e1 e2 is_assign_op with_type p = typing_error "Unexpected =>" p | OpIn -> typing_error "Unexpected in" p + | OpNullCoal | OpAssign | OpAssignOp _ -> die "" __LOC__ @@ -952,4 +953,4 @@ let type_unop ctx op flag e with_type p = find_overload_or_make e end | AKUsingField _ | AKResolve _ -> - typing_error "Invalid operation" p \ No newline at end of file + typing_error "Invalid operation" p diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 2303f96a69a..4acd641b6d4 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1675,6 +1675,12 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = type_expr ctx (format_string ctx s p) with_type | EConst c -> Texpr.type_constant ctx.com.basic c p + | EBinop (OpNullCoal,e1,e2) -> + let enull = (EConst (Ident "null"), p) in + let cond = EBinop (OpEq, e1, enull) in + let texpr2 = type_expr ctx (Expr.ensure_block e2) with_type in + let iftype = WithType.WithType(texpr2.etype,None) in + type_expr ctx (EIf ((cond, p),e2,Some e1),p) iftype | EBinop (op,e1,e2) -> type_binop ctx op e1 e2 false with_type p | EBlock [] when (match with_type with diff --git a/std/haxe/display/JsonModuleTypes.hx b/std/haxe/display/JsonModuleTypes.hx index e546162aa43..503e4b40281 100644 --- a/std/haxe/display/JsonModuleTypes.hx +++ b/std/haxe/display/JsonModuleTypes.hx @@ -163,6 +163,7 @@ enum abstract JsonBinopKind(String) { var OpInterval; var OpArrow; var OpIn; + var OpNullCoal; } typedef JsonBinop = { diff --git a/std/haxe/macro/Expr.hx b/std/haxe/macro/Expr.hx index b41112eb0c9..b750f33d48f 100644 --- a/std/haxe/macro/Expr.hx +++ b/std/haxe/macro/Expr.hx @@ -214,6 +214,11 @@ enum Binop { `in` **/ OpIn; + + /** + `??` + **/ + OpNullCoal; } /** diff --git a/std/haxe/macro/Printer.hx b/std/haxe/macro/Printer.hx index 73953dc7f5c..15d8b369138 100644 --- a/std/haxe/macro/Printer.hx +++ b/std/haxe/macro/Printer.hx @@ -75,6 +75,7 @@ class Printer { case OpInterval: "..."; case OpArrow: "=>"; case OpIn: "in"; + case OpNullCoal: "??"; case OpAssignOp(op): printBinop(op) + "="; } diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx new file mode 100644 index 00000000000..dcc9baaf970 --- /dev/null +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -0,0 +1,33 @@ +package unit; + +@:nullSafety(StrictThreaded) +class TestNullCoalescing extends Test { + final nullInt:Null = null; + final nullBool:Null = null; + + function test() { + eq(null ?? nullInt, null); + eq(null ?? nullBool, null); + final a = Std.random(0) + 1; + final b = Std.random(0) + 2; + eq(1 + a + 1 ?? 1 + b + 1, 3); + + final nullableBool:Null = false; + final testBool:Bool = nullBool ?? true; + final testNullBool = null ?? nullableBool; + final s:Int = nullInt == null ? 2 : nullInt; + final s:Int = if (nullInt == null) 2; else nullInt; + final s:Int = nullInt ?? 2; + + // $type(testBool); // Bool + // $type(testNullBool); // Null + // $type(s); // Int + + eq(testBool, true); + eq(testNullBool, false); + eq(s, 2); + + eq(nullInt ?? 2 ?? 3 + 100, 2); + eq(nullInt ?? nullInt ?? 3, 3); + } +} From e2cf41fe191ea18c7b646114b4394fc27715425b Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 3 Dec 2021 06:27:30 +0300 Subject: [PATCH 2/9] use value_reference to avoid duplication --- src/typing/typer.ml | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 4acd641b6d4..81f4a84c74e 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1444,6 +1444,19 @@ and type_cast ctx e t p = let texpr = loop t in mk (TCast (type_expr ctx e WithType.value,Some texpr)) t p +and make_if_then_else ctx e0 e1 e2 with_type p = + let e1,e2,t = match with_type with + | WithType.NoValue -> e1,e2,ctx.t.tvoid + | WithType.Value _ -> e1,e2,unify_min ctx [e1; e2] + | WithType.WithType(t,src) when (match follow t with TMono _ -> true | t -> ExtType.is_void t) -> + e1,e2,unify_min_for_type_source ctx [e1; e2] src + | WithType.WithType(t,_) -> + let e1 = AbstractCast.cast_or_unify ctx t e1 e1.epos in + let e2 = AbstractCast.cast_or_unify ctx t e2 e2.epos in + e1,e2,t + in + mk (TIf (e0,e1,Some e2)) t p + and type_if ctx e e1 e2 with_type is_ternary p = let e = type_expr ctx e WithType.value in if is_ternary then begin match e.eexpr with @@ -1452,22 +1465,12 @@ and type_if ctx e e1 e2 with_type is_ternary p = end; let e = AbstractCast.cast_or_unify ctx ctx.t.tbool e p in let e1 = type_expr ctx (Expr.ensure_block e1) with_type in - (match e2 with + match e2 with | None -> mk (TIf (e,e1,None)) ctx.t.tvoid p | Some e2 -> let e2 = type_expr ctx (Expr.ensure_block e2) with_type in - let e1,e2,t = match with_type with - | WithType.NoValue -> e1,e2,ctx.t.tvoid - | WithType.Value _ -> e1,e2,unify_min ctx [e1; e2] - | WithType.WithType(t,src) when (match follow t with TMono _ -> true | t -> ExtType.is_void t) -> - e1,e2,unify_min_for_type_source ctx [e1; e2] src - | WithType.WithType(t,_) -> - let e1 = AbstractCast.cast_or_unify ctx t e1 e1.epos in - let e2 = AbstractCast.cast_or_unify ctx t e2 e2.epos in - e1,e2,t - in - mk (TIf (e,e1,Some e2)) t p) + make_if_then_else ctx e e1 e2 with_type p and type_meta ?(mode=MGet) ctx m e1 with_type p = if ctx.is_display_file then DisplayEmitter.check_display_metadata ctx [m]; @@ -1676,11 +1679,14 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = | EConst c -> Texpr.type_constant ctx.com.basic c p | EBinop (OpNullCoal,e1,e2) -> - let enull = (EConst (Ident "null"), p) in - let cond = EBinop (OpEq, e1, enull) in - let texpr2 = type_expr ctx (Expr.ensure_block e2) with_type in - let iftype = WithType.WithType(texpr2.etype,None) in - type_expr ctx (EIf ((cond, p),e2,Some e1),p) iftype + 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) with_type in + let e1 = vr#as_var "tmp" e1 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 e_if = make_if_then_else ctx e_cond e1 e2 with_type p in + vr#to_texpr e_if | EBinop (op,e1,e2) -> type_binop ctx op e1 e2 false with_type p | EBlock [] when (match with_type with From 06b446fc3ed4da70737917c860dd519f39d75117 Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 3 Dec 2021 06:28:27 +0300 Subject: [PATCH 3/9] Disable op overloading --- src/typing/typeloadFields.ml | 2 ++ src/typing/typer.ml | 3 +- tests/unit/src/unit/TestNullCoalescing.hx | 40 ++++++++++++++++++++--- 3 files changed, 40 insertions(+), 5 deletions(-) diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index c08ec473398..379ee4a82ca 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -1091,6 +1091,8 @@ let check_abstract (ctx,cctx,fctx) c cf fd t ret p = allow_no_expr(); | (Meta.Op,[EBinop(OpAssign,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Assignment overloading is not supported") p; + | (Meta.Op,[EBinop(OpNullCoal,_,_),_],_) :: _ -> + typing_error (cf.cf_name ^ ": Null coalescing overloading is not supported") p; | (Meta.Op,[ETernary(_,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Ternary overloading is not supported") p; | (Meta.Op,[EBinop(op,_,_),_],_) :: _ -> diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 81f4a84c74e..4b62c781abb 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1685,7 +1685,8 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = let e1 = vr#as_var "tmp" e1 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 e_if = make_if_then_else ctx e_cond e1 e2 with_type p in + let iftype = 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 (op,e1,e2) -> type_binop ctx op e1 e2 false with_type p diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index dcc9baaf970..4a0637e8656 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -5,29 +5,61 @@ class TestNullCoalescing extends Test { final nullInt:Null = null; final nullBool:Null = null; + var count = 0; + function call() { + count++; + return "_"; + } + function test() { + var a = call() ?? "default"; + eq(count, 1); + eq(null ?? nullInt, null); eq(null ?? nullBool, null); - final a = Std.random(0) + 1; + + final a:Dynamic = Std.random(0) + 1; final b = Std.random(0) + 2; eq(1 + a + 1 ?? 1 + b + 1, 3); final nullableBool:Null = false; - final testBool:Bool = nullBool ?? true; + final testBool = nullBool ?? true; final testNullBool = null ?? nullableBool; final s:Int = nullInt == null ? 2 : nullInt; final s:Int = if (nullInt == null) 2; else nullInt; - final s:Int = nullInt ?? 2; + final s = nullInt ?? 2; // $type(testBool); // Bool // $type(testNullBool); // Null // $type(s); // Int + final shouldBeBool:Bool = testBool; + if (testNullBool == null) {} + final shouldBeInt:Int = s; eq(testBool, true); eq(testNullBool, false); eq(s, 2); - eq(nullInt ?? 2 ?? 3 + 100, 2); + eq(nullInt == null ? 2 : nullInt, 2); + eq(nullInt ?? 2, 2); + eq(nullInt ?? (2 : Null) ?? 3 + 100, 2); eq(nullInt ?? nullInt ?? 3, 3); + + final i:Null = 1; + final arr:Array = [i ?? 2]; + arr.push(i ?? 2); + arr.push((1 : Null) ?? 2); + eq(arr[0], 1); + eq(arr[1], 1); + eq(arr[2], 1); + + final arr = [ + nullInt ?? 2, + 2 + ]; + eq(arr[0], arr[1]); + + var a = [0 => nullInt ?? 0 + 100]; + eq(a[0], 100); } } From fc68ea45cc2ee707f351303057892b013425a66d Mon Sep 17 00:00:00 2001 From: RblSb Date: Tue, 16 Nov 2021 09:05:04 +0300 Subject: [PATCH 4/9] Allow ??= --- src/syntax/lexer.ml | 1 + src/typing/typeloadFields.ml | 1 + src/typing/typer.ml | 4 ++++ tests/unit/src/unit/TestNullCoalescing.hx | 26 +++++++++++++++++++++++ 4 files changed, 32 insertions(+) diff --git a/src/syntax/lexer.ml b/src/syntax/lexer.ml index b60769c5fbd..1abb67ef75a 100644 --- a/src/syntax/lexer.ml +++ b/src/syntax/lexer.ml @@ -345,6 +345,7 @@ let rec token lexbuf = | "<<=" -> mk lexbuf (Binop (OpAssignOp OpShl)) | "||=" -> mk lexbuf (Binop (OpAssignOp OpBoolOr)) | "&&=" -> mk lexbuf (Binop (OpAssignOp OpBoolAnd)) + | "??=" -> mk lexbuf (Binop (OpAssignOp OpNullCoal)) (*//| ">>=" -> mk lexbuf (Binop (OpAssignOp OpShr)) *) (*//| ">>>=" -> mk lexbuf (Binop (OpAssignOp OpUShr)) *) | "==" -> mk lexbuf (Binop OpEq) diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index 379ee4a82ca..802e96dfbe7 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -1091,6 +1091,7 @@ let check_abstract (ctx,cctx,fctx) c cf fd t ret p = allow_no_expr(); | (Meta.Op,[EBinop(OpAssign,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Assignment overloading is not supported") p; + | (Meta.Op,[EBinop(OpAssignOp OpNullCoal,_,_),_],_) :: _ | (Meta.Op,[EBinop(OpNullCoal,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Null coalescing overloading is not supported") p; | (Meta.Op,[ETernary(_,_,_),_],_) :: _ -> diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 4b62c781abb..c7ac8f33e9d 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1688,6 +1688,10 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = let iftype = 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) -> + let e_cond = EBinop(OpNotEq,e1,(EConst(Ident "null"), p)) in + let e_if = EIf ((e_cond, p),e1,Some e2) in + type_assign ctx e1 (e_if, p) with_type p | EBinop (op,e1,e2) -> type_binop ctx op e1 e2 false with_type p | EBlock [] when (match with_type with diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index 4a0637e8656..a8cdfb6255c 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -61,5 +61,31 @@ class TestNullCoalescing extends Test { var a = [0 => nullInt ?? 0 + 100]; eq(a[0], 100); + + final di:Null = null; + final di2:Null = null; + final di3:Null = 2; + eq(di ?? di2 ?? di3, 2); + + var a:Null = null; + a ??= 5; + eq(a, 5); + var a:Null = null; + eq(a ??= 5, 5); + eq(a, 5); + + count = 0; + var a = call(); + eq(count, 1); + a ??= call(); + eq(count, 1); + + var a:Null = null; + final b = a ??= call(); + final c = a ??= call(); + eq(count, 2); + eq(a, "_"); + eq(b, "_"); + eq(c, "_"); } } From 14b597b3788380873efe55907df691fb2aeca3bf Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 19 Nov 2021 09:58:35 +0300 Subject: [PATCH 5/9] Option 2: better errors for static targets --- src/typing/typer.ml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/typing/typer.ml b/src/typing/typer.ml index c7ac8f33e9d..b07805eabac 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1682,6 +1682,11 @@ 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) with_type in + if ctx.com.config.pf_static then begin + let t = unify_min ctx [e1; e2] in + if not (is_nullable t) then + typing_error ("On static platforms, type " ^ (s_type (print_context()) t) ^ " can't be compared with null") p; + end; let e1 = vr#as_var "tmp" e1 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 @@ -1689,6 +1694,13 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = 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) -> + if ctx.com.config.pf_static then begin + let e1 = type_expr ctx (Expr.ensure_block e1) with_type in + let e2 = type_expr ctx (Expr.ensure_block e2) with_type in + let t = unify_min ctx [e1; e2] in + if not (is_nullable t) then + typing_error ("On static platforms, type " ^ (s_type (print_context()) t) ^ " can't be compared with null") p; + end; let e_cond = EBinop(OpNotEq,e1,(EConst(Ident "null"), p)) in let e_if = EIf ((e_cond, p),e1,Some e2) in type_assign ctx e1 (e_if, p) with_type p From d2b5851f29318f4add33d1bb3fce180d0a0b87cf Mon Sep 17 00:00:00 2001 From: RblSb Date: Thu, 9 Dec 2021 11:09:48 +0300 Subject: [PATCH 6/9] Type first operand as nullable --- src/typing/typer.ml | 14 +------------- tests/unit/src/unit/TestNullCoalescing.hx | 21 ++++++++++++++++++--- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/typing/typer.ml b/src/typing/typer.ml index b07805eabac..641b6374853 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1682,25 +1682,13 @@ 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) with_type in - if ctx.com.config.pf_static then begin - let t = unify_min ctx [e1; e2] in - if not (is_nullable t) then - typing_error ("On static platforms, type " ^ (s_type (print_context()) t) ^ " can't be compared with null") p; - end; - let e1 = vr#as_var "tmp" e1 in + 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 e_if = make_if_then_else ctx e_cond e1 e2 iftype p in vr#to_texpr e_if | EBinop (OpAssignOp OpNullCoal,e1,e2) -> - if ctx.com.config.pf_static then begin - let e1 = type_expr ctx (Expr.ensure_block e1) with_type in - let e2 = type_expr ctx (Expr.ensure_block e2) with_type in - let t = unify_min ctx [e1; e2] in - if not (is_nullable t) then - typing_error ("On static platforms, type " ^ (s_type (print_context()) t) ^ " can't be compared with null") p; - end; let e_cond = EBinop(OpNotEq,e1,(EConst(Ident "null"), p)) in let e_if = EIf ((e_cond, p),e1,Some e2) in type_assign ctx e1 (e_if, p) with_type p diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index a8cdfb6255c..887b9b6f80d 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -4,6 +4,7 @@ package unit; class TestNullCoalescing extends Test { final nullInt:Null = null; final nullBool:Null = null; + final nullString:Null = null; var count = 0; function call() { @@ -15,8 +16,8 @@ class TestNullCoalescing extends Test { var a = call() ?? "default"; eq(count, 1); - eq(null ?? nullInt, null); - eq(null ?? nullBool, null); + eq(nullInt ?? nullInt, null); + eq(nullBool ?? nullBool, null); final a:Dynamic = Std.random(0) + 1; final b = Std.random(0) + 2; @@ -24,7 +25,7 @@ class TestNullCoalescing extends Test { final nullableBool:Null = false; final testBool = nullBool ?? true; - final testNullBool = null ?? nullableBool; + final testNullBool = nullBool ?? nullableBool; final s:Int = nullInt == null ? 2 : nullInt; final s:Int = if (nullInt == null) 2; else nullInt; final s = nullInt ?? 2; @@ -73,6 +74,8 @@ class TestNullCoalescing extends Test { var a:Null = null; eq(a ??= 5, 5); eq(a, 5); + var a = "default"; + eq(a ??= "5", "default"); count = 0; var a = call(); @@ -87,5 +90,17 @@ class TestNullCoalescing extends Test { eq(a, "_"); eq(b, "_"); eq(c, "_"); + + final a:Null = ({} : Dynamic).x; + eq(a ?? 2, 2); + + final a = nullInt; + eq(a ?? 2, 2); + + final a = nullString; + eq(a ?? "2", "2"); + + eq(1 ?? 2, 1); + eq("1" ?? "2", "1"); } } From 48320b6ed38b8940e9323611c9703b2bce8f5d15 Mon Sep 17 00:00:00 2001 From: RblSb Date: Thu, 16 Dec 2021 17:20:44 +0300 Subject: [PATCH 7/9] Allow (rtl) ?? overloading imagine rtl multiplication op with this, i'm not sure... --- src/context/typecore.ml | 3 +++ src/typing/operators.ml | 10 +++++++++- src/typing/typeloadFields.ml | 5 ++--- src/typing/typer.ml | 13 ++----------- tests/unit/src/unit/TestNullCoalescing.hx | 19 +++++++++++++++++++ 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/context/typecore.ml b/src/context/typecore.ml index 65e6b817616..a75ada4607d 100644 --- a/src/context/typecore.ml +++ b/src/context/typecore.ml @@ -207,6 +207,9 @@ let unify_min_for_type_source_ref : (typer -> texpr list -> WithType.with_type_s let analyzer_run_on_expr_ref : (Common.context -> texpr -> texpr) ref = ref (fun _ _ -> die "" __LOC__) let cast_or_unify_raise_ref : (typer -> ?uctx:unification_context option -> Type.t -> texpr -> pos -> texpr) ref = ref (fun _ ?uctx _ _ _ -> assert false) let type_generic_function_ref : (typer -> field_access -> (unit -> texpr) field_call_candidate -> WithType.t -> pos -> texpr) ref = ref (fun _ _ _ _ _ -> assert false) +let make_if_then_else_ref : (typer -> +Type.texpr -> +Type.texpr -> Type.texpr -> WithType.t -> Common.pos -> Type.texpr) ref = ref (fun _ _ -> die "" __LOC__) let pass_name = function | PBuildModule -> "build-module" diff --git a/src/typing/operators.ml b/src/typing/operators.ml index 1e118d45fec..b26f00e031f 100644 --- a/src/typing/operators.ml +++ b/src/typing/operators.ml @@ -444,7 +444,15 @@ let make_binop ctx op e1 e2 is_assign_op with_type p = typing_error "Unexpected =>" p | OpIn -> typing_error "Unexpected in" p - | OpNullCoal + | OpNullCoal -> + let vr = new value_reference ctx in + 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 e_if = (!make_if_then_else_ref) ctx e_cond e1 e2 iftype p in + let e = vr#to_texpr e_if in + BinopSpecial (e,false) | OpAssign | OpAssignOp _ -> die "" __LOC__ diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index 802e96dfbe7..166ed420639 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -1091,9 +1091,8 @@ let check_abstract (ctx,cctx,fctx) c cf fd t ret p = allow_no_expr(); | (Meta.Op,[EBinop(OpAssign,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Assignment overloading is not supported") p; - | (Meta.Op,[EBinop(OpAssignOp OpNullCoal,_,_),_],_) :: _ - | (Meta.Op,[EBinop(OpNullCoal,_,_),_],_) :: _ -> - typing_error (cf.cf_name ^ ": Null coalescing overloading is not supported") p; + | (Meta.Op,[EBinop(OpAssignOp OpNullCoal,_,_),_],_) :: _ -> + typing_error (cf.cf_name ^ ": Null coalescing assignment overloading is not supported") p; | (Meta.Op,[ETernary(_,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Ternary overloading is not supported") p; | (Meta.Op,[EBinop(op,_,_),_],_) :: _ -> diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 641b6374853..22a757fe540 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1678,16 +1678,6 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = type_expr ctx (format_string ctx s p) with_type | EConst c -> Texpr.type_constant ctx.com.basic c p - | EBinop (OpNullCoal,e1,e2) -> - 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) with_type in - 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 e_if = make_if_then_else ctx e_cond e1 e2 iftype p in - vr#to_texpr e_if | EBinop (OpAssignOp OpNullCoal,e1,e2) -> let e_cond = EBinop(OpNotEq,e1,(EConst(Ident "null"), p)) in let e_if = EIf ((e_cond, p),e1,Some e2) in @@ -2000,4 +1990,5 @@ unify_min_for_type_source_ref := unify_min_for_type_source; make_call_ref := make_call; type_call_target_ref := type_call_target; type_access_ref := type_access; -type_block_ref := type_block +type_block_ref := type_block; +make_if_then_else_ref := make_if_then_else diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index 887b9b6f80d..739b89cec23 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -102,5 +102,24 @@ class TestNullCoalescing extends Test { eq(1 ?? 2, 1); eq("1" ?? "2", "1"); + + var a = new MyAbstract(1); + eq(a ?? 2, 3); + // ?? is right-associative + eq((a ?? 10 * 10) ?? 1 * 10, 111); + + final arr = []; + function item(n) { + arr.push(n); + return n; + } + eq(item(1) ?? item(2) ?? item(3), 1); + eq(arr.length, 1); + for (i => v in [1]) eq(arr[i], v); } } + +abstract MyAbstract(Int) from Int { + public function new(a) this = a; + @:op(a ?? b) function plus(b):MyAbstract return this + b; +} From 2756d97e8b9c22c1f033f131ec4db790b382f5a0 Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 17 Dec 2021 17:32:28 +0300 Subject: [PATCH 8/9] Revert "Allow (rtl) ?? overloading" This reverts commit 48320b6ed38b8940e9323611c9703b2bce8f5d15. --- src/context/typecore.ml | 3 --- src/typing/operators.ml | 10 +--------- src/typing/typeloadFields.ml | 5 +++-- src/typing/typer.ml | 13 +++++++++++-- tests/unit/src/unit/TestNullCoalescing.hx | 19 ------------------- 5 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/context/typecore.ml b/src/context/typecore.ml index a75ada4607d..65e6b817616 100644 --- a/src/context/typecore.ml +++ b/src/context/typecore.ml @@ -207,9 +207,6 @@ let unify_min_for_type_source_ref : (typer -> texpr list -> WithType.with_type_s let analyzer_run_on_expr_ref : (Common.context -> texpr -> texpr) ref = ref (fun _ _ -> die "" __LOC__) let cast_or_unify_raise_ref : (typer -> ?uctx:unification_context option -> Type.t -> texpr -> pos -> texpr) ref = ref (fun _ ?uctx _ _ _ -> assert false) let type_generic_function_ref : (typer -> field_access -> (unit -> texpr) field_call_candidate -> WithType.t -> pos -> texpr) ref = ref (fun _ _ _ _ _ -> assert false) -let make_if_then_else_ref : (typer -> -Type.texpr -> -Type.texpr -> Type.texpr -> WithType.t -> Common.pos -> Type.texpr) ref = ref (fun _ _ -> die "" __LOC__) let pass_name = function | PBuildModule -> "build-module" diff --git a/src/typing/operators.ml b/src/typing/operators.ml index b26f00e031f..1e118d45fec 100644 --- a/src/typing/operators.ml +++ b/src/typing/operators.ml @@ -444,15 +444,7 @@ let make_binop ctx op e1 e2 is_assign_op with_type p = typing_error "Unexpected =>" p | OpIn -> typing_error "Unexpected in" p - | OpNullCoal -> - let vr = new value_reference ctx in - 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 e_if = (!make_if_then_else_ref) ctx e_cond e1 e2 iftype p in - let e = vr#to_texpr e_if in - BinopSpecial (e,false) + | OpNullCoal | OpAssign | OpAssignOp _ -> die "" __LOC__ diff --git a/src/typing/typeloadFields.ml b/src/typing/typeloadFields.ml index 166ed420639..802e96dfbe7 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -1091,8 +1091,9 @@ let check_abstract (ctx,cctx,fctx) c cf fd t ret p = allow_no_expr(); | (Meta.Op,[EBinop(OpAssign,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Assignment overloading is not supported") p; - | (Meta.Op,[EBinop(OpAssignOp OpNullCoal,_,_),_],_) :: _ -> - typing_error (cf.cf_name ^ ": Null coalescing assignment overloading is not supported") p; + | (Meta.Op,[EBinop(OpAssignOp OpNullCoal,_,_),_],_) :: _ + | (Meta.Op,[EBinop(OpNullCoal,_,_),_],_) :: _ -> + typing_error (cf.cf_name ^ ": Null coalescing overloading is not supported") p; | (Meta.Op,[ETernary(_,_,_),_],_) :: _ -> typing_error (cf.cf_name ^ ": Ternary overloading is not supported") p; | (Meta.Op,[EBinop(op,_,_),_],_) :: _ -> diff --git a/src/typing/typer.ml b/src/typing/typer.ml index 22a757fe540..641b6374853 100644 --- a/src/typing/typer.ml +++ b/src/typing/typer.ml @@ -1678,6 +1678,16 @@ and type_expr ?(mode=MGet) ctx (e,p) (with_type:WithType.t) = type_expr ctx (format_string ctx s p) with_type | EConst c -> Texpr.type_constant ctx.com.basic c p + | EBinop (OpNullCoal,e1,e2) -> + 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) with_type in + 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 e_if = make_if_then_else ctx e_cond e1 e2 iftype p in + vr#to_texpr e_if | EBinop (OpAssignOp OpNullCoal,e1,e2) -> let e_cond = EBinop(OpNotEq,e1,(EConst(Ident "null"), p)) in let e_if = EIf ((e_cond, p),e1,Some e2) in @@ -1990,5 +2000,4 @@ unify_min_for_type_source_ref := unify_min_for_type_source; make_call_ref := make_call; type_call_target_ref := type_call_target; type_access_ref := type_access; -type_block_ref := type_block; -make_if_then_else_ref := make_if_then_else +type_block_ref := type_block diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index 739b89cec23..887b9b6f80d 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -102,24 +102,5 @@ class TestNullCoalescing extends Test { eq(1 ?? 2, 1); eq("1" ?? "2", "1"); - - var a = new MyAbstract(1); - eq(a ?? 2, 3); - // ?? is right-associative - eq((a ?? 10 * 10) ?? 1 * 10, 111); - - final arr = []; - function item(n) { - arr.push(n); - return n; - } - eq(item(1) ?? item(2) ?? item(3), 1); - eq(arr.length, 1); - for (i => v in [1]) eq(arr[i], v); } } - -abstract MyAbstract(Int) from Int { - public function new(a) this = a; - @:op(a ?? b) function plus(b):MyAbstract return this + b; -} From a792bdea98399549b1e0108dfde7369a23a8b9c6 Mon Sep 17 00:00:00 2001 From: RblSb Date: Fri, 17 Dec 2021 17:37:29 +0300 Subject: [PATCH 9/9] Evaluation test --- tests/unit/src/unit/TestNullCoalescing.hx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/unit/src/unit/TestNullCoalescing.hx b/tests/unit/src/unit/TestNullCoalescing.hx index 887b9b6f80d..0189e140458 100644 --- a/tests/unit/src/unit/TestNullCoalescing.hx +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -102,5 +102,23 @@ class TestNullCoalescing extends Test { eq(1 ?? 2, 1); eq("1" ?? "2", "1"); + + final arr = []; + function item(n) { + arr.push(n); + return n; + } + eq(item(1) ?? item(2) ?? item(3), 1); + eq(arr.length, 1); + for (i => v in [1]) eq(arr[i], v); + + final arr = []; + function item(n) { + arr.push(n); + return null; + } + eq(item(1) ?? item(2) ?? item(3), null); + eq(arr.length, 3); + for (i => v in [1, 2, 3]) eq(arr[i], v); } }