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..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) @@ -379,6 +380,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 +638,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/typeloadFields.ml b/src/typing/typeloadFields.ml index c08ec473398..802e96dfbe7 100644 --- a/src/typing/typeloadFields.ml +++ b/src/typing/typeloadFields.ml @@ -1091,6 +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,_,_),_],_) :: _ + | (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 2303f96a69a..641b6374853 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]; @@ -1675,6 +1678,20 @@ 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 + 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/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..0189e140458 --- /dev/null +++ b/tests/unit/src/unit/TestNullCoalescing.hx @@ -0,0 +1,124 @@ +package unit; + +@:nullSafety(StrictThreaded) +class TestNullCoalescing extends Test { + final nullInt:Null = null; + final nullBool:Null = null; + final nullString:Null = null; + + var count = 0; + function call() { + count++; + return "_"; + } + + function test() { + var a = call() ?? "default"; + eq(count, 1); + + eq(nullInt ?? nullInt, null); + eq(nullBool ?? nullBool, null); + + 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 = nullBool ?? true; + final testNullBool = nullBool ?? nullableBool; + final s:Int = nullInt == null ? 2 : nullInt; + final s:Int = if (nullInt == null) 2; else nullInt; + 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 == 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); + + 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); + var a = "default"; + eq(a ??= "5", "default"); + + 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, "_"); + + 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"); + + 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); + } +}