Преглед на файлове

[typer] align null coalescing top down inference with normal if/else

closes #11425
Simon Krajewski преди 1 година
родител
ревизия
39642c94d8
променени са 2 файла, в които са добавени 78 реда и са изтрити 17 реда
  1. 14 17
      src/typing/typer.ml
  2. 64 0
      tests/unit/src/unit/issues/Issue11425.hx

+ 14 - 17
src/typing/typer.ml

@@ -1562,8 +1562,7 @@ 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
+and get_if_then_else_operands ctx e1 e2 with_type = 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) ->
@@ -1572,7 +1571,9 @@ and make_if_then_else ctx e0 e1 e2 with_type p =
 		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
+
+and make_if_then_else ctx e0 e1 e2 with_type p =
+	let e1,e2,t = get_if_then_else_operands ctx e1 e2 with_type in
 	mk (TIf (e0,e1,Some e2)) t p
 
 and type_if ctx e e1 e2 with_type is_ternary p =
@@ -1847,26 +1848,22 @@ 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 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 e1,e2,tmin = get_if_then_else_operands ctx e1 e2 with_type in
 		let rec follow_null t =
 			match t with
 			| TAbstract({a_path = [],"Null"},[t]) -> follow_null t
 			| _ -> t
 		in
 		let iftype = if DeadEnd.has_dead_end e2 then
-			WithType.with_type (follow_null e1.etype)
-		else
-			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
+			follow_null e1.etype
+		else match e2.etype with
+			| TAbstract({a_path = [],"Null"},[t]) -> tmin
+			| _ -> follow_null tmin
+		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 e_if = mk (TIf(e_cond,e1,Some 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

+ 64 - 0
tests/unit/src/unit/issues/Issue11425.hx

@@ -0,0 +1,64 @@
+package unit.issues;
+
+private enum VariantType {
+	VT_String(s:String);
+	VT_Float(s:Float);
+}
+
+@:transitive
+private abstract Variant(VariantType) from VariantType {
+	@:from static function fromString(s:String):Variant {
+		return VT_String(s);
+	}
+
+	@:to public function toString():String {
+		if (this == null) {
+			return null;
+		}
+		return switch (this) {
+			case VT_String(s): s;
+			case VT_Float(s): Std.string(s);
+		}
+	}
+
+	@:from static function fromFloat(s:Float):Variant {
+		return VT_Float(s);
+	}
+
+	@:to public function toFloat():Null<Float> {
+		if (this == null) {
+			return null;
+		}
+		return switch (this) {
+			case VT_Float(s): s;
+			default: throw "Variant Type Error (" + this + ")";
+		}
+	}
+}
+
+class Issue11425 extends Test {
+	function test() {
+		var variant1:Variant = 4.0;
+
+		var testValue1:Float = variant1; // Works fine.
+		// discrepency between "... != ... ? ... : ..." and "... ?? ... "
+		var testValue2:Float = (variant1 != null) ? variant1 : 1.0; // Works fine.
+		var testValue3:Float = variant1 ?? 1.0; // ERROR: Float should be Variant
+
+		// type hint testValue6 as Variant
+		var testValue6:Variant = (variant1 != null) ? variant1 : 1.0; // Works fine.
+		// type hint testValue7 as Variant
+		var testValue7:Variant = variant1 ?? 1.0; // ERROR: Float should be Variant
+
+		// using "cast" to get around compilation and see generated output:
+		// though presumably cast is influencing the output also
+		var testValue8:Float = (variant1 != null) ? variant1 : 1.0; // Works fine.
+		// generated: variant1 != null ? Variant.toFloat(variant1) : 1.0
+		var testValue9:Float = variant1 ?? cast 1.0; // Works fine.
+		// generated: Variant.toFloat(variant1 != null ? variant1 : 1.0)
+		var testValue10 = variant1 ?? cast 1.0; // Works fine.
+		// generated: variant1 != null ? variant1 : 1.0
+
+		utest.Assert.pass();
+	}
+}