Selaa lähdekoodia

Fix `??` inference and precedence (#11252)

RblSb 2 vuotta sitten
vanhempi
commit
39960a1ec5

+ 7 - 6
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

+ 10 - 5
src/typing/typer.ml

@@ -1892,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

+ 3 - 1
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(),

+ 31 - 9
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<Int> = null;
+	final nullFloat:Null<Float> = null;
 	final nullBool:Null<Bool> = null;
 	final nullString:Null<String> = 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<Bool>
-		// $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<Float>) ?? throw "";
+		t(HelperMacros.isNullable(nullF));
+		t(HelperMacros.isNullable(nullF2));
+		f(HelperMacros.isNullable(notNullF));
+		f(HelperMacros.isNullable(notNullF2));
 	}
 }