Prechádzať zdrojové kódy

deal with OpAssignOp overloads and side-effects (closes #4681)

Simon Krajewski 9 rokov pred
rodič
commit
c1191e32fb
2 zmenil súbory, kde vykonal 150 pridanie a 6 odobranie
  1. 115 0
      tests/unit/src/unit/issues/Issue4681.hx
  2. 35 6
      typer.ml

+ 115 - 0
tests/unit/src/unit/issues/Issue4681.hx

@@ -0,0 +1,115 @@
+package unit.issues;
+
+private abstract A(Int) {
+	public function new(i) {
+		this = i;
+	}
+
+	public function get() {
+		return this;
+	}
+
+	@:op(A + B)
+	static function add(x:A, x2:Int) {
+		return new A(x.get() + x2);
+	}
+
+	@:op(A * B)
+	@:commutative
+	static function multiply(x:A, x2:Int) {
+		return x.get() * x2;
+	}
+}
+
+class Issue4681 extends unit.Test {
+
+	var array:Array<A>;
+	var field:A;
+	var array2:Array<Int>;
+	var field2:Int;
+
+	function getArray() {
+		return array;
+	}
+
+	function getArray2() {
+		return array2;
+	}
+
+	function getThis(i:Int) {
+		return this;
+	}
+
+	function getA() {
+		return new A(0);
+	}
+
+	function setup() {
+		array = [new A(0)];
+		field = new A(0);
+		array2 = [2];
+		field2 = 2;
+	}
+
+	public function testNormal() {
+		setup();
+		var a = new A(0);
+		a += 1;
+		eq(1, a.get());
+	}
+
+	public function testNormal2() {
+		setup();
+		var a = 2;
+		a *= new A(3);
+		eq(6, a);
+	}
+
+	public function testArray() {
+		setup();
+		var x = 0;
+		getArray()[x++] += 1;
+		eq(1, array[0].get());
+		eq(1, x);
+	}
+
+	public function testArray2() {
+		setup();
+		var x = 0;
+		getArray2()[x++] *= new A(3);
+		eq(6, array2[0]);
+		eq(1, x);
+	}
+
+	public function testField() {
+		setup();
+		var x = 0;
+		getThis(x++).field += 1;
+		eq(1, x);
+		eq(1, field.get());
+	}
+
+	public function testField2() {
+		setup();
+		var x = 0;
+		getThis(x++).field2 *= new A(3);
+		eq(1, x);
+		eq(6, field2);
+	}
+
+	public function testNested() {
+		setup();
+		var x = 0;
+		getArray()[(getArray()[x++] += 1).get() - 1] += 1;
+		eq(2, array[0].get());
+		eq(1, x);
+	}
+
+	public function testNested2() {
+		setup();
+		var x = 0;
+		getArray2()[(getArray2()[x++] *= new A(3)) - 6] *= new A(3);
+		eq(18, array2[0]);
+		eq(1, x);
+	}
+}

+ 35 - 6
typer.ml

@@ -1927,7 +1927,12 @@ let rec type_binop ctx op e1 e2 is_assign_op with_type p =
 		(match type_access ctx (fst e1) (snd e1) MSet with
 		| AKNo s -> error ("Cannot access field or identifier " ^ s ^ " for writing") p
 		| AKExpr e ->
+			let save = save_locals ctx in
+			let v = gen_local ctx e.etype in
+			let has_side_effect = Optimizer.has_side_effect e in
+			let e1 = if has_side_effect then (EConst(Ident v.v_name),e.epos) else e1 in
 			let eop = type_binop ctx op e1 e2 true with_type p in
+			save();
 			(match eop.eexpr with
 			| TBinop (_,_,e2) ->
 				unify ctx eop.etype e.etype p;
@@ -1936,7 +1941,31 @@ let rec type_binop ctx op e1 e2 is_assign_op with_type p =
 			| TMeta((Meta.RequiresAssign,_,_),e2) ->
 				unify ctx e2.etype e.etype p;
 				check_assign ctx e;
-				mk (TBinop (OpAssign,e,e2)) e.etype p;
+				begin match e.eexpr with
+					| TArray(ea1,ea2) when has_side_effect ->
+						let v1 = gen_local ctx ea1.etype in
+						let ev1 = mk (TLocal v1) v1.v_type p in
+						let v2 = gen_local ctx ea2.etype in
+						let ev2 = mk (TLocal v2) v2.v_type p in
+						let e = {e with eexpr = TArray(ev1,ev2)} in
+						mk (TBlock [
+							mk (TVar(v1,Some ea1)) ctx.t.tvoid p;
+							mk (TVar(v2,Some ea2)) ctx.t.tvoid p;
+							mk (TVar(v,Some e)) ctx.t.tvoid p;
+							mk (TBinop (OpAssign,e,e2)) e.etype p;
+						]) e.etype p
+					| TField(ea1,fa) when has_side_effect ->
+						let v1 = gen_local ctx ea1.etype in
+						let ev1 = mk (TLocal v1) v1.v_type p in
+						let e = {e with eexpr = TField(ev1,fa)} in
+						mk (TBlock [
+							mk (TVar(v1,Some ea1)) ctx.t.tvoid p;
+							mk (TVar(v,Some e)) ctx.t.tvoid p;
+							mk (TBinop (OpAssign,e,e2)) e.etype p;
+						]) e.etype p
+					| _ ->
+              			mk (TBinop (OpAssign,e,e2)) e.etype p;
+				end
 			| _ ->
 				(* this must be an abstract cast *)
 				check_assign ctx e;
@@ -2265,14 +2294,14 @@ and type_binop2 ctx op (e1 : texpr) (e2 : Ast.expr) is_assign_op wt p =
 		in
 		(* special case for == and !=: if the second type is a monomorph, assume that we want to unify
 		   it with the first type to preserve comparison semantics. *)
-		let is_eq_op = match op with OpEq | OpNotEq -> true | _ -> false in
+	let is_eq_op = match op with OpEq | OpNotEq -> true | _ -> false in
 		if is_eq_op then begin match follow e1.etype,follow e2.etype with
 			| TMono _,_ | _,TMono _ ->
 				Type.unify e1.etype e2.etype
 			| _ ->
 				()
 		end;
- 		let rec loop ol = match ol with
+		let rec loop ol = match ol with
 			| (op_cf,cf) :: ol when op_cf <> op && (not is_assign_op || op_cf <> OpAssignOp(op)) ->
 				loop ol
 			| (op_cf,cf) :: ol ->
@@ -2352,9 +2381,9 @@ and type_binop2 ctx op (e1 : texpr) (e2 : Ast.expr) is_assign_op wt p =
 	with Not_found -> try
 		begin match follow e2.etype with
 			| TAbstract({a_impl = Some c} as a,tl) -> find_overload a c tl false
-			| _ -> raise Not_found
-		end
-	with Not_found ->
+								| _ -> raise Not_found
+							end
+						with Not_found ->
 		make e1 e2
 
 and type_unop ctx op flag e p =